Skip to main content

jj_core/
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::num::NonZeroUsize;
21use std::sync::Mutex;
22
23use clru::CLruCache;
24use thiserror::Error;
25
26use crate::backend::CommitId;
27
28// TODO: This is a duplication of `jj_lib::store::COMMIT_CACHE_CAPACITY`. Use
29// the respective constant when we lower `Store`.
30const COMMIT_CACHE_CAPACITY: NonZeroUsize = NonZeroUsize::new(100).unwrap();
31
32/// A status of the signature, part of the [Verification] type.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum SigStatus {
35    /// Valid signature that matches the data.
36    Good,
37    /// Valid signature that could not be verified (e.g. due to an unknown key).
38    Unknown,
39    /// Valid signature that does not match the signed data.
40    Bad,
41}
42
43impl Display for SigStatus {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let s = match self {
46            Self::Good => "good",
47            Self::Unknown => "unknown",
48            Self::Bad => "bad",
49        };
50        write!(f, "{s}")
51    }
52}
53
54/// The result of a signature verification.
55/// Key and display are optional additional info that backends can or can not
56/// provide to add additional information for the templater to potentially show.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct Verification {
59    /// The status of the signature.
60    pub status: SigStatus,
61    /// The key id representation, if available. For GPG, this will be the key
62    /// fingerprint.
63    pub key: Option<String>,
64    /// A display string, if available. For GPG, this will be formatted primary
65    /// user ID.
66    pub display: Option<String>,
67}
68
69impl Verification {
70    /// A shortcut to create an `Unknown` verification with no additional
71    /// metadata.
72    pub fn unknown() -> Self {
73        Self {
74            status: SigStatus::Unknown,
75            key: None,
76            display: None,
77        }
78    }
79
80    /// Create a new verification
81    pub fn new(status: SigStatus, key: Option<String>, display: Option<String>) -> Self {
82        Self {
83            status,
84            key,
85            display,
86        }
87    }
88}
89
90/// The backend for signing and verifying cryptographic signatures.
91///
92/// This allows using different signers, such as GPG or SSH, or different
93/// versions of them.
94pub trait SigningBackend: Debug + Send + Sync {
95    /// Name of the backend, used in the config and for display.
96    fn name(&self) -> &str;
97
98    /// Check if the signature can be read and verified by this backend.
99    ///
100    /// Should check the signature format, usually just looks at the prefix.
101    fn can_read(&self, signature: &[u8]) -> bool;
102
103    /// Create a signature for arbitrary data.
104    ///
105    /// The `key` parameter is what `jj sign` receives as key argument, or what
106    /// is configured in the `signing.key` config.
107    fn sign(&self, data: &[u8], key: Option<&str>) -> SignResult<Vec<u8>>;
108
109    /// Verify a signature. Should be reflexive with `sign`:
110    /// ```rust,ignore
111    /// verify(data, sign(data)?)?.status == SigStatus::Good
112    /// ```
113    fn verify(&self, data: &[u8], signature: &[u8]) -> SignResult<Verification>;
114}
115
116/// An error type for the signing/verifying operations
117#[derive(Debug, Error)]
118pub enum SignError {
119    /// The verification failed because the signature *format* was invalid.
120    #[error("Invalid signature")]
121    InvalidSignatureFormat,
122    /// A generic error from the backend impl.
123    #[error("Signing error")]
124    Backend(#[source] Box<dyn std::error::Error + Send + Sync>),
125}
126
127/// A result type for the signing/verifying operations
128pub type SignResult<T> = Result<T, SignError>;
129
130/// Wraps low-level signing backends and adds caching, similar to `Store`.
131#[derive(Debug)]
132pub struct Signer {
133    /// The backend that is used for signing commits.
134    /// Optional because signing might not be configured.
135    main_backend: Option<Box<dyn SigningBackend>>,
136    /// All known backends without the main one - used for verification.
137    /// Main backend is also used for verification, but it's not in this list
138    /// for ownership reasons.
139    backends: Vec<Box<dyn SigningBackend>>,
140    cache: Mutex<CLruCache<CommitId, Verification>>,
141}
142
143impl Signer {
144    /// Creates a signer with the given backends.
145    pub fn new(
146        main_backend: Option<Box<dyn SigningBackend>>,
147        other_backends: Vec<Box<dyn SigningBackend>>,
148    ) -> Self {
149        Self {
150            main_backend,
151            backends: other_backends,
152            cache: Mutex::new(CLruCache::new(COMMIT_CACHE_CAPACITY)),
153        }
154    }
155
156    /// Checks if the signer can sign, i.e. if a main backend is configured.
157    pub fn can_sign(&self) -> bool {
158        self.main_backend.is_some()
159    }
160
161    /// This is just a pass-through to the main backend that unconditionally
162    /// creates a signature.
163    pub fn sign(&self, data: &[u8], key: Option<&str>) -> SignResult<Vec<u8>> {
164        self.main_backend
165            .as_ref()
166            .expect("tried to sign without checking can_sign first")
167            .sign(data, key)
168    }
169
170    /// Looks for backend that can verify the signature and returns the result
171    /// of its verification.
172    pub fn verify(
173        &self,
174        commit_id: &CommitId,
175        data: &[u8],
176        signature: &[u8],
177    ) -> SignResult<Verification> {
178        let cached = self.cache.lock().unwrap().get(commit_id).cloned();
179        if let Some(check) = cached {
180            return Ok(check);
181        }
182
183        let verification = self
184            .main_backend
185            .iter()
186            .chain(self.backends.iter())
187            .filter(|b| b.can_read(signature))
188            // skip unknown and invalid sigs to allow other backends that can read to try
189            // for example, we might have gpg and sq, both of which could read a PGP signature
190            .find_map(|backend| match backend.verify(data, signature) {
191                Ok(check) if check.status == SigStatus::Unknown => None,
192                Err(SignError::InvalidSignatureFormat) => None,
193                e => Some(e),
194            })
195            .transpose()?;
196
197        if let Some(verification) = verification {
198            // a key might get imported before next call?.
199            // realistically this is unlikely, but technically
200            // it's correct to not cache unknowns here
201            if verification.status != SigStatus::Unknown {
202                self.cache
203                    .lock()
204                    .unwrap()
205                    .put(commit_id.clone(), verification.clone());
206            }
207            Ok(verification)
208        } else {
209            // now here it's correct to cache unknowns, as we don't
210            // have a backend that knows how to handle this signature
211            //
212            // not sure about how much of an optimization this is
213            self.cache
214                .lock()
215                .unwrap()
216                .put(commit_id.clone(), Verification::unknown());
217            Ok(Verification::unknown())
218        }
219    }
220}