1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
// Copyright 2023 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Generic APIs to work with cryptographic signatures created and verified by
//! various backends.
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::RwLock;
use thiserror::Error;
use crate::backend::CommitId;
use crate::settings::UserSettings;
/// A status of the signature, part of the [Verification] type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SigStatus {
/// Valid signature that matches the data.
Good,
/// Valid signature that could not be verified (e.g. due to an unknown key).
Unknown,
/// Valid signature that does not match the signed data.
Bad,
}
/// The result of a signature verification.
/// Key and display are optional additional info that backends can or can not
/// provide to add additional information for the templater to potentially show.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Verification {
/// The status of the signature.
pub status: SigStatus,
/// The key id representation, if available. For GPG, this will be the key
/// fingerprint.
pub key: Option<String>,
/// A display string, if available. For GPG, this will be formatted primary
/// user ID.
pub display: Option<String>,
}
impl Verification {
/// A shortcut to create an `Unknown` verification with no additional
/// metadata.
pub fn unknown() -> Self {
Self {
status: SigStatus::Unknown,
key: None,
display: None,
}
}
}
/// The backend for signing and verifying cryptographic signatures.
///
/// This allows using different signers, such as GPG or SSH, or different
/// versions of them.
pub trait SigningBackend: Debug + Send + Sync {
/// Name of the backend, used in the config and for display.
fn name(&self) -> &str;
/// Check if the signature can be read and verified by this backend.
///
/// Should check the signature format, usually just looks at the prefix.
fn can_read(&self, signature: &[u8]) -> bool;
/// Create a signature for arbitrary data.
///
/// The `key` parameter is what `jj sign` receives as key argument, or what
/// is configured in the `signing.key` config.
fn sign(&self, data: &[u8], key: Option<&str>) -> SignResult<Vec<u8>>;
/// Verify a signature. Should be reflexive with `sign`:
/// ```rust,ignore
/// verify(data, sign(data)?)?.status == SigStatus::Good
/// ```
fn verify(&self, data: &[u8], signature: &[u8]) -> SignResult<Verification>;
}
/// An error type for the signing/verifying operations
#[derive(Debug, Error)]
pub enum SignError {
/// The verification failed because the signature *format* was invalid.
#[error("Invalid signature")]
InvalidSignatureFormat,
/// A generic error from the backend impl.
#[error("Signing error: {0}")]
Backend(Box<dyn std::error::Error + Send + Sync>),
}
/// A result type for the signing/verifying operations
pub type SignResult<T> = Result<T, SignError>;
/// An error type for the signing backend initialization.
#[derive(Debug, Error)]
pub enum SignInitError {
/// If the backend name specified in the config is not known.
#[error("Unknown signing backend configured: {0}")]
UnknownBackend(String),
/// A generic error from the backend impl.
#[error("Failed to initialize signing: {0}")]
Backend(Box<dyn std::error::Error + Send + Sync>),
}
/// A enum that describes if a created/rewritten commit should be signed or not.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SignBehavior {
/// Drop existing signatures.
/// This is what jj did before signing support or does now when a signing
/// backend is not configured.
#[default]
Drop,
/// Only sign commits that were authored by self and already signed,
/// "preserving" the signature across rewrites.
/// This is what jj does when a signing backend is configured.
Keep,
/// Sign/re-sign commits that were authored by self and drop them for
/// others. This is what jj does when configured to always sign.
Own,
/// Always sign commits, regardless of who authored or signed them before.
/// This is what jj does on `jj sign -f`.
Force,
}
/// Wraps low-level signing backends and adds caching, similar to `Store`.
#[derive(Debug, Default)]
pub struct Signer {
/// The backend that is used for signing commits.
/// Optional because signing might not be configured.
main_backend: Option<Box<dyn SigningBackend>>,
/// All known backends without the main one - used for verification.
/// Main backend is also used for verification, but it's not in this list
/// for ownership reasons.
backends: Vec<Box<dyn SigningBackend>>,
cache: RwLock<HashMap<CommitId, Verification>>,
}
impl Signer {
/// Creates a signer based on user settings. Uses all known backends, and
/// chooses one of them to be used for signing depending on the config.
pub fn from_settings(settings: &UserSettings) -> Result<Self, SignInitError> {
let mut backends: Vec<Box<dyn SigningBackend>> = vec![
// Box::new(GpgBackend::from_settings(settings)?),
// Box::new(SshBackend::from_settings(settings)?),
// Box::new(X509Backend::from_settings(settings)?),
];
let main_backend = settings
.signing_backend()
.map(|backend| {
backends
.iter()
.position(|b| b.name() == backend)
.map(|i| backends.remove(i))
.ok_or(SignInitError::UnknownBackend(backend))
})
.transpose()?;
Ok(Self::new(main_backend, backends))
}
/// Creates a signer with the given backends.
pub fn new(
main_backend: Option<Box<dyn SigningBackend>>,
other_backends: Vec<Box<dyn SigningBackend>>,
) -> Self {
Self {
main_backend,
backends: other_backends,
cache: Default::default(),
}
}
/// Checks if the signer can sign, i.e. if a main backend is configured.
pub fn can_sign(&self) -> bool {
self.main_backend.is_some()
}
/// This is just a pass-through to the main backend that unconditionally
/// creates a signature.
pub fn sign(&self, data: &[u8], key: Option<&str>) -> SignResult<Vec<u8>> {
self.main_backend
.as_ref()
.expect("tried to sign without checking can_sign first")
.sign(data, key)
}
/// Looks for backend that can verify the signature and returns the result
/// of its verification.
pub fn verify(
&self,
commit_id: &CommitId,
data: &[u8],
signature: &[u8],
) -> SignResult<Verification> {
let cached = self.cache.read().unwrap().get(commit_id).cloned();
if let Some(check) = cached {
return Ok(check);
}
let verification = self
.main_backend
.iter()
.chain(self.backends.iter())
.filter(|b| b.can_read(signature))
// skip unknown and invalid sigs to allow other backends that can read to try
// for example, we might have gpg and sq, both of which could read a PGP signature
.find_map(|backend| match backend.verify(data, signature) {
Ok(check) if check.status == SigStatus::Unknown => None,
Err(SignError::InvalidSignatureFormat) => None,
e => Some(e),
})
.transpose()?;
if let Some(verification) = verification {
// a key might get imported before next call?.
// realistically this is unlikely, but technically
// it's correct to not cache unknowns here
if verification.status != SigStatus::Unknown {
self.cache
.write()
.unwrap()
.insert(commit_id.clone(), verification.clone());
}
Ok(verification)
} else {
// now here it's correct to cache unknowns, as we don't
// have a backend that knows how to handle this signature
//
// not sure about how much of an optimization this is
self.cache
.write()
.unwrap()
.insert(commit_id.clone(), Verification::unknown());
Ok(Verification::unknown())
}
}
}