sequoia_openpgp/crypto/
asymmetric.rs

1//! Asymmetric crypto operations.
2
3use crate::packet::{self, key, Key};
4use crate::crypto::SessionKey;
5use crate::crypto::mpi;
6use crate::types::{
7    Curve,
8    HashAlgorithm,
9    PublicKeyAlgorithm,
10    SymmetricAlgorithm,
11};
12
13use crate::{Error, Result};
14
15/// Creates a signature.
16///
17/// Used in the streaming [`Signer`], the methods binding components
18/// to certificates (e.g. [`UserID::bind`]), [`SignatureBuilder`]'s
19/// signing functions (e.g. [`SignatureBuilder::sign_standalone`]),
20/// and likely many more places.
21///
22///   [`Signer`]: crate::serialize::stream::Signer
23///   [`UserID::bind`]: crate::packet::UserID::bind()
24///   [`SignatureBuilder`]: crate::packet::signature::SignatureBuilder
25///   [`SignatureBuilder::sign_standalone`]: crate::packet::signature::SignatureBuilder::sign_standalone()
26///
27/// This is a low-level mechanism to produce an arbitrary OpenPGP
28/// signature.  Using this trait allows Sequoia to perform all
29/// operations involving signing to use a variety of secret key
30/// storage mechanisms (e.g. smart cards).
31///
32/// A signer consists of the public key and a way of creating a
33/// signature.  This crate implements `Signer` for [`KeyPair`], which
34/// is a tuple containing the public and unencrypted secret key in
35/// memory.  Other crates may provide their own implementations of
36/// `Signer` to utilize keys stored in various places.  Currently, the
37/// following implementations exist:
38///
39///   - [`KeyPair`]: In-memory keys.
40///   - [`sequoia_rpc::gnupg::KeyPair`]: Connects to the `gpg-agent`.
41///
42///   [`sequoia_rpc::gnupg::KeyPair`]: https://docs.sequoia-pgp.org/sequoia_ipc/gnupg/struct.KeyPair.html
43pub trait Signer {
44    /// Returns a reference to the public key.
45    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>;
46
47    /// Returns a list of hashes that this signer accepts.
48    ///
49    /// Some cryptographic libraries or hardware modules support signing digests
50    /// produced with only a limited set of hashing algorithms. This function
51    /// indicates to callers which algorithm digests are supported by this signer.
52    ///
53    /// The default implementation of this function allows all hash algorithms to
54    /// be used. Provide an explicit implementation only when a smaller subset
55    /// of hashing algorithms is valid for this `Signer` implementation.
56    fn acceptable_hashes(&self) -> &[HashAlgorithm] {
57        crate::crypto::hash::default_hashes_sorted()
58    }
59
60    /// Creates a signature over the `digest` produced by `hash_algo`.
61    fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
62            -> Result<mpi::Signature>;
63}
64
65impl Signer for Box<dyn Signer> {
66    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
67        self.as_ref().public()
68    }
69
70    fn acceptable_hashes(&self) -> &[HashAlgorithm] {
71        self.as_ref().acceptable_hashes()
72    }
73
74    fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
75            -> Result<mpi::Signature> {
76        self.as_mut().sign(hash_algo, digest)
77    }
78}
79
80impl Signer for Box<dyn Signer + Send + Sync> {
81    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
82        self.as_ref().public()
83    }
84
85    fn acceptable_hashes(&self) -> &[HashAlgorithm] {
86        self.as_ref().acceptable_hashes()
87    }
88
89    fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
90            -> Result<mpi::Signature> {
91        self.as_mut().sign(hash_algo, digest)
92    }
93}
94
95/// Decrypts a message.
96///
97/// Used by [`PKESK::decrypt`] to decrypt session keys.
98///
99///   [`PKESK::decrypt`]: crate::packet::PKESK#method.decrypt
100///
101/// This is a low-level mechanism to decrypt an arbitrary OpenPGP
102/// ciphertext.  Using this trait allows Sequoia to perform all
103/// operations involving decryption to use a variety of secret key
104/// storage mechanisms (e.g. smart cards).
105///
106/// A decryptor consists of the public key and a way of decrypting a
107/// session key.  This crate implements `Decryptor` for [`KeyPair`],
108/// which is a tuple containing the public and unencrypted secret key
109/// in memory.  Other crates may provide their own implementations of
110/// `Decryptor` to utilize keys stored in various places.  Currently, the
111/// following implementations exist:
112///
113///   - [`KeyPair`]: In-memory keys.
114///   - [`sequoia_rpc::gnupg::KeyPair`]: Connects to the `gpg-agent`.
115///
116///   [`sequoia_rpc::gnupg::KeyPair`]: https://docs.sequoia-pgp.org/sequoia_ipc/gnupg/struct.KeyPair.html
117pub trait Decryptor {
118    /// Returns a reference to the public key.
119    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole>;
120
121    /// Decrypts `ciphertext`, returning the plain session key.
122    fn decrypt(&mut self, ciphertext: &mpi::Ciphertext,
123               plaintext_len: Option<usize>)
124               -> Result<SessionKey>;
125}
126
127impl Decryptor for Box<dyn Decryptor> {
128    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
129        self.as_ref().public()
130    }
131
132    fn decrypt(&mut self, ciphertext: &mpi::Ciphertext,
133               plaintext_len: Option<usize>)
134               -> Result<SessionKey> {
135        self.as_mut().decrypt(ciphertext, plaintext_len)
136    }
137}
138
139impl Decryptor for Box<dyn Decryptor + Send + Sync> {
140    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
141        self.as_ref().public()
142    }
143
144    fn decrypt(&mut self, ciphertext: &mpi::Ciphertext,
145               plaintext_len: Option<usize>)
146               -> Result<SessionKey> {
147        self.as_mut().decrypt(ciphertext, plaintext_len)
148    }
149}
150
151/// A cryptographic key pair.
152///
153/// A `KeyPair` is a combination of public and secret key.  If both
154/// are available in memory, a `KeyPair` is a convenient
155/// implementation of [`Signer`] and [`Decryptor`].
156///
157///
158/// # Examples
159///
160/// ```
161/// # fn main() -> sequoia_openpgp::Result<()> {
162/// use sequoia_openpgp as openpgp;
163/// use openpgp::types::Curve;
164/// use openpgp::cert::prelude::*;
165/// use openpgp::packet::prelude::*;
166///
167/// // Conveniently create a KeyPair from a bare key:
168/// let keypair =
169///     Key4::<_, key::UnspecifiedRole>::generate_ecc(false, Curve::Cv25519)?
170///         .into_keypair()?;
171///
172/// // Or from a query over a certificate:
173/// let (cert, _) =
174///     CertBuilder::general_purpose(Some("alice@example.org"))
175///         .generate()?;
176/// let keypair =
177///     cert.keys().unencrypted_secret().nth(0).unwrap().key().clone()
178///         .into_keypair()?;
179/// # Ok(()) }
180/// ```
181#[derive(Clone)]
182pub struct KeyPair {
183    public: Key<key::PublicParts, key::UnspecifiedRole>,
184    secret: packet::key::Unencrypted,
185}
186assert_send_and_sync!(KeyPair);
187
188impl KeyPair {
189    /// Creates a new key pair.
190    pub fn new(public: Key<key::PublicParts, key::UnspecifiedRole>,
191               secret: packet::key::Unencrypted)
192        -> Result<Self>
193    {
194        Ok(Self {
195            public,
196            secret,
197        })
198    }
199
200    /// Returns a reference to the public key.
201    pub fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
202        &self.public
203    }
204
205    /// Returns a reference to the secret key.
206    pub fn secret(&self) -> &packet::key::Unencrypted {
207        &self.secret
208    }
209}
210
211impl From<KeyPair> for Key<key::SecretParts, key::UnspecifiedRole> {
212    fn from(p: KeyPair) -> Self {
213        let (key, secret) = (p.public, p.secret);
214        key.add_secret(secret.into()).0
215    }
216}
217
218impl Signer for KeyPair {
219    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
220        KeyPair::public(self)
221    }
222
223    fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
224            -> Result<mpi::Signature>
225    {
226        use crate::crypto::backend::{Backend, interface::Asymmetric};
227
228        self.secret().map(|secret| {
229            match (self.public().pk_algo(), self.public().mpis(), secret) {
230                (PublicKeyAlgorithm::Ed25519,
231                 mpi::PublicKey::Ed25519 { a },
232                 mpi::SecretKeyMaterial::Ed25519 { x }) => {
233                    Ok(mpi::Signature::Ed25519 {
234                        s: Box::new(Backend::ed25519_sign(x, a, digest)?),
235                    })
236                },
237
238                (PublicKeyAlgorithm::Ed448,
239                 mpi::PublicKey::Ed448 { a },
240                 mpi::SecretKeyMaterial::Ed448 { x }) => {
241                    Ok(mpi::Signature::Ed448 {
242                        s: Box::new(Backend::ed448_sign(x, a, digest)?),
243                    })
244                },
245
246                (PublicKeyAlgorithm::EdDSA,
247                 mpi::PublicKey::EdDSA { curve, q },
248                 mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve {
249                    Curve::Ed25519 => {
250                        let public = q.decode_point(&Curve::Ed25519)?.0
251                            .try_into()?;
252                        let secret = scalar.value_padded(32);
253                        let sig =
254                            Backend::ed25519_sign(&secret, &public, digest)?;
255                        Ok(mpi::Signature::EdDSA {
256                            r: mpi::MPI::new(&sig[..32]),
257                            s: mpi::MPI::new(&sig[32..]),
258                        })
259                    },
260                    _ => Err(
261                        Error::UnsupportedEllipticCurve(curve.clone()).into()),
262                },
263
264                (_algo, _public, secret) =>
265                    self.sign_backend(secret, hash_algo, digest),
266            }
267        })
268    }
269}
270
271impl Decryptor for KeyPair {
272    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
273        KeyPair::public(self)
274    }
275
276    fn decrypt(&mut self,
277               ciphertext: &mpi::Ciphertext,
278               plaintext_len: Option<usize>)
279               -> Result<SessionKey>
280    {
281        use crate::crypto::ecdh::aes_key_unwrap;
282        use crate::crypto::backend::{Backend, interface::{Asymmetric, Kdf}};
283
284        self.secret().map(|secret| {
285            #[allow(non_snake_case)]
286            match (self.public().mpis(), secret, ciphertext) {
287                (mpi::PublicKey::X25519 { u: U },
288                 mpi::SecretKeyMaterial::X25519 { x },
289                 mpi::Ciphertext::X25519 { e: E, key }) => {
290                    // Compute the shared point S = xE;
291                    let S = Backend::x25519_shared_point(x, E)?;
292
293                    // Compute the wrap key.
294                    let wrap_algo = SymmetricAlgorithm::AES128;
295                    let mut ikm: SessionKey = vec![0; 32 + 32 + 32].into();
296
297                    // Yes clippy, this operation will always return
298                    // zero.  This is the intended outcome.  Chill.
299                    #[allow(clippy::erasing_op)]
300                    ikm[0 * 32..1 * 32].copy_from_slice(&E[..]);
301                    ikm[1 * 32..2 * 32].copy_from_slice(&U[..]);
302                    ikm[2 * 32..3 * 32].copy_from_slice(&S[..]);
303                    let mut kek = vec![0; wrap_algo.key_size()?].into();
304                    Backend::hkdf_sha256(&ikm, None, b"OpenPGP X25519",
305                                         &mut kek)?;
306
307                    Ok(aes_key_unwrap(wrap_algo, kek.as_protected(),
308                                      key)?.into())
309                },
310
311                (mpi::PublicKey::X448 { u: U },
312                 mpi::SecretKeyMaterial::X448 { x },
313                 mpi::Ciphertext::X448 { e: E, key }) => {
314                    // Compute the shared point S = xE;
315                    let S = Backend::x448_shared_point(x, E)?;
316
317                    // Compute the wrap key.
318                    let wrap_algo = SymmetricAlgorithm::AES256;
319                    let mut ikm: SessionKey = vec![0; 56 + 56 + 56].into();
320
321                    // Yes clippy, this operation will always return
322                    // zero.  This is the intended outcome.  Chill.
323                    #[allow(clippy::erasing_op)]
324                    ikm[0 * 56..1 * 56].copy_from_slice(&E[..]);
325                    ikm[1 * 56..2 * 56].copy_from_slice(&U[..]);
326                    ikm[2 * 56..3 * 56].copy_from_slice(&S[..]);
327                    let mut kek = vec![0; wrap_algo.key_size()?].into();
328                    Backend::hkdf_sha512(&ikm, None, b"OpenPGP X448",
329                                         &mut kek)?;
330
331                    Ok(aes_key_unwrap(wrap_algo, kek.as_protected(),
332                                      key)?.into())
333                },
334
335                (mpi::PublicKey::ECDH { curve: Curve::Cv25519, .. },
336                 mpi::SecretKeyMaterial::ECDH { scalar, },
337                 mpi::Ciphertext::ECDH { e, .. }) =>
338                {
339                    // Get the public part V of the ephemeral key.
340                    let V = e.decode_point(&Curve::Cv25519)?.0;
341
342                    // X25519 expects the private key to be exactly 32
343                    // bytes long but OpenPGP allows leading zeros to
344                    // be stripped.  Padding has to be unconditional;
345                    // otherwise we have a secret-dependent branch.
346                    let mut r = scalar.value_padded(32);
347
348                    // Reverse the scalar.  See
349                    // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html
350                    r.reverse();
351
352                    // Compute the shared point S = rV = rvG, where
353                    // (r, R) is the recipient's key pair.
354                    let S = Backend::x25519_shared_point(&r, &V.try_into()?)?;
355
356                    crate::crypto::ecdh::decrypt_unwrap(
357                        self.public(), &S, ciphertext, plaintext_len)
358                },
359
360                (_public, secret, _ciphertext) =>
361                    self.decrypt_backend(secret, ciphertext, plaintext_len),
362            }
363        })
364    }
365}