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}