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}