jj_lib/
signing.rs

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