Skip to main content

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            #[allow(deprecated)]
230            match (self.public().pk_algo(), self.public().mpis(), secret) {
231                (PublicKeyAlgorithm::Ed25519,
232                 mpi::PublicKey::Ed25519 { a },
233                 mpi::SecretKeyMaterial::Ed25519 { x }) => {
234                    Ok(mpi::Signature::Ed25519 {
235                        s: Box::new(Backend::ed25519_sign(x, a, digest)?),
236                    })
237                },
238
239                (PublicKeyAlgorithm::Ed448,
240                 mpi::PublicKey::Ed448 { a },
241                 mpi::SecretKeyMaterial::Ed448 { x }) => {
242                    Ok(mpi::Signature::Ed448 {
243                        s: Box::new(Backend::ed448_sign(x, a, digest)?),
244                    })
245                },
246
247                (PublicKeyAlgorithm::EdDSA,
248                 mpi::PublicKey::EdDSA { curve, q },
249                 mpi::SecretKeyMaterial::EdDSA { scalar }) => match curve {
250                    Curve::Ed25519 => {
251                        let public = q.decode_point(&Curve::Ed25519)?.0
252                            .try_into()?;
253                        let secret = scalar.value_padded(32);
254                        let sig =
255                            Backend::ed25519_sign(&secret, &public, digest)?;
256                        Ok(mpi::Signature::EdDSA {
257                            r: mpi::MPI::new(&sig[..32]),
258                            s: mpi::MPI::new(&sig[32..]),
259                        })
260                    },
261                    _ => Err(
262                        Error::UnsupportedEllipticCurve(curve.clone()).into()),
263                },
264
265                (PublicKeyAlgorithm::DSA,
266                 mpi::PublicKey::DSA { p, q, g, y },
267                 mpi::SecretKeyMaterial::DSA { x }) => {
268                    let (r, s) = Backend::dsa_sign(x, p, q, g, y, digest)?;
269                    Ok(mpi::Signature::DSA { r, s })
270                },
271
272                (_algo, _public, secret) =>
273                    self.sign_backend(secret, hash_algo, digest),
274            }
275        })
276    }
277}
278
279impl Decryptor for KeyPair {
280    fn public(&self) -> &Key<key::PublicParts, key::UnspecifiedRole> {
281        KeyPair::public(self)
282    }
283
284    fn decrypt(&mut self,
285               ciphertext: &mpi::Ciphertext,
286               plaintext_len: Option<usize>)
287               -> Result<SessionKey>
288    {
289        use crate::crypto::ecdh::aes_key_unwrap;
290        use crate::crypto::backend::{Backend, interface::{Asymmetric, Kdf}};
291
292        self.secret().map(|secret| {
293            #[allow(non_snake_case)]
294            match (self.public().mpis(), secret, ciphertext) {
295                (mpi::PublicKey::X25519 { u: U },
296                 mpi::SecretKeyMaterial::X25519 { x },
297                 mpi::Ciphertext::X25519 { e: E, key }) => {
298                    // Compute the shared point S = xE;
299                    let S = Backend::x25519_shared_point(x, E)?;
300
301                    // Compute the wrap key.
302                    let wrap_algo = SymmetricAlgorithm::AES128;
303                    let mut ikm: SessionKey = vec![0; 32 + 32 + 32].into();
304
305                    // Yes clippy, this operation will always return
306                    // zero.  This is the intended outcome.  Chill.
307                    #[allow(clippy::erasing_op)]
308                    ikm[0 * 32..1 * 32].copy_from_slice(&E[..]);
309                    ikm[1 * 32..2 * 32].copy_from_slice(&U[..]);
310                    ikm[2 * 32..3 * 32].copy_from_slice(&S[..]);
311                    let mut kek = vec![0; wrap_algo.key_size()?].into();
312                    Backend::hkdf_sha256(&ikm, None, b"OpenPGP X25519",
313                                         &mut kek)?;
314
315                    Ok(aes_key_unwrap(wrap_algo, kek.as_protected(),
316                                      key)?.into())
317                },
318
319                (mpi::PublicKey::X448 { u: U },
320                 mpi::SecretKeyMaterial::X448 { x },
321                 mpi::Ciphertext::X448 { e: E, key }) => {
322                    // Compute the shared point S = xE;
323                    let S = Backend::x448_shared_point(x, E)?;
324
325                    // Compute the wrap key.
326                    let wrap_algo = SymmetricAlgorithm::AES256;
327                    let mut ikm: SessionKey = vec![0; 56 + 56 + 56].into();
328
329                    // Yes clippy, this operation will always return
330                    // zero.  This is the intended outcome.  Chill.
331                    #[allow(clippy::erasing_op)]
332                    ikm[0 * 56..1 * 56].copy_from_slice(&E[..]);
333                    ikm[1 * 56..2 * 56].copy_from_slice(&U[..]);
334                    ikm[2 * 56..3 * 56].copy_from_slice(&S[..]);
335                    let mut kek = vec![0; wrap_algo.key_size()?].into();
336                    Backend::hkdf_sha512(&ikm, None, b"OpenPGP X448",
337                                         &mut kek)?;
338
339                    Ok(aes_key_unwrap(wrap_algo, kek.as_protected(),
340                                      key)?.into())
341                },
342
343                (mpi::PublicKey::ECDH { curve: Curve::Cv25519, .. },
344                 mpi::SecretKeyMaterial::ECDH { scalar, },
345                 mpi::Ciphertext::ECDH { e, .. }) =>
346                {
347                    // Get the public part V of the ephemeral key.
348                    let V = e.decode_point(&Curve::Cv25519)?.0;
349
350                    // X25519 expects the private key to be exactly 32
351                    // bytes long but OpenPGP allows leading zeros to
352                    // be stripped.  Padding has to be unconditional;
353                    // otherwise we have a secret-dependent branch.
354                    let mut r = scalar.value_padded(32);
355
356                    // Reverse the scalar.  See
357                    // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html
358                    r.reverse();
359
360                    // Compute the shared point S = rV = rvG, where
361                    // (r, R) is the recipient's key pair.
362                    let S = Backend::x25519_shared_point(&r, &V.try_into()?)?;
363
364                    crate::crypto::ecdh::decrypt_unwrap(
365                        self.public(), &S, ciphertext, plaintext_len)
366                },
367
368                (_public, secret, _ciphertext) =>
369                    self.decrypt_backend(secret, ciphertext, plaintext_len),
370            }
371        })
372    }
373}