diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-04-10 00:25:30 +0200 |
---|---|---|
committer | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-06-22 11:57:06 +0200 |
commit | f30cc7f367084fa295bd542cf9eb9a3398657fce (patch) | |
tree | e064aae01066e303b4320fbf45a3d2e75bbb9109 | |
parent | df5eb44e8e442c5bc4d958d914121075258393ec (diff) |
openpgp: Move Nettle ECDH implementation to the backend module
-rw-r--r-- | openpgp/src/crypto/backend/nettle.rs | 1 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/nettle/ecdh.rs | 656 | ||||
-rw-r--r-- | openpgp/src/crypto/ecdh.rs | 668 |
3 files changed, 663 insertions, 662 deletions
diff --git a/openpgp/src/crypto/backend/nettle.rs b/openpgp/src/crypto/backend/nettle.rs index e7614445..7bbea8ca 100644 --- a/openpgp/src/crypto/backend/nettle.rs +++ b/openpgp/src/crypto/backend/nettle.rs @@ -3,6 +3,7 @@ use nettle::random::{Random, Yarrow}; pub mod asymmetric; +pub mod ecdh; /// Fills the given buffer with random data. pub fn random<B: AsMut<[u8]>>(mut buf: B) { diff --git a/openpgp/src/crypto/backend/nettle/ecdh.rs b/openpgp/src/crypto/backend/nettle/ecdh.rs new file mode 100644 index 00000000..400c66d1 --- /dev/null +++ b/openpgp/src/crypto/backend/nettle/ecdh.rs @@ -0,0 +1,656 @@ +//! Elliptic Curve Diffie-Hellman. + +use nettle::{cipher, curve25519, mode, mode::Mode, ecc, ecdh, random::Yarrow}; + +use crate::{Error, Result}; +use crate::crypto::SessionKey; +use crate::crypto::ecdh::{kdf, pkcs5_pad, pkcs5_unpad}; +use crate::crypto::mem::Protected; +use crate::crypto::mpi::{MPI, PublicKey, SecretKeyMaterial, Ciphertext}; +use crate::packet::{key, Key}; +use crate::utils::{write_be_u64, read_be_u64}; +use crate::types::{Curve, HashAlgorithm, SymmetricAlgorithm, PublicKeyAlgorithm}; + +/// Wraps a session key using Elliptic Curve Diffie-Hellman. +#[allow(non_snake_case)] +pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, + session_key: &SessionKey) + -> Result<Ciphertext> + where R: key::KeyRole +{ + let mut rng = Yarrow::default(); + + if let &PublicKey::ECDH { + ref curve, ref q,.. + } = recipient.mpis() { + match curve { + Curve::Cv25519 => { + // Obtain the authenticated recipient public key R + let R = q.decode_point(curve)?.0; + + // Generate an ephemeral key pair {v, V=vG} + let v: Protected = + curve25519::private_key(&mut rng).into(); + + // Compute the public key. We need to add an encoding + // octet in front of the key. + let mut VB = [0x40; 1 + curve25519::CURVE25519_SIZE]; + curve25519::mul_g(&mut VB[1..], &v) + .expect("buffers are of the wrong size"); + let VB = MPI::new(&VB); + + // Compute the shared point S = vR; + let mut S: Protected = + vec![0; curve25519::CURVE25519_SIZE].into(); + curve25519::mul(&mut S, &v, R) + .expect("buffers are of the wrong size"); + + encrypt_shared(recipient, session_key, VB, &S) + } + Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { + // Obtain the authenticated recipient public key R and + // generate an ephemeral private key v. + + // Note: ecc::Point and ecc::Scalar are cleaned up by + // nettle. + let (Rx, Ry) = q.decode_point(curve)?; + let (R, v, field_sz) = match curve { + Curve::NistP256 => { + let R = ecc::Point::new::<ecc::Secp256r1>(Rx, Ry)?; + let v = + ecc::Scalar::new_random::<ecc::Secp256r1, _>(&mut rng); + let field_sz = 256; + + (R, v, field_sz) + } + Curve::NistP384 => { + let R = ecc::Point::new::<ecc::Secp384r1>(Rx, Ry)?; + let v = + ecc::Scalar::new_random::<ecc::Secp384r1, _>(&mut rng); + let field_sz = 384; + + (R, v, field_sz) + } + Curve::NistP521 => { + let R = ecc::Point::new::<ecc::Secp521r1>(Rx, Ry)?; + let v = + ecc::Scalar::new_random::<ecc::Secp521r1, _>(&mut rng); + let field_sz = 521; + + (R, v, field_sz) + } + _ => unreachable!(), + }; + + // Compute the public key. + let VB = ecdh::point_mul_g(&v); + let (VBx, VBy) = VB.as_bytes(); + let VB = MPI::new_weierstrass(&VBx, &VBy, field_sz); + + // Compute the shared point S = vR; + let S = ecdh::point_mul(&v, &R)?; + + // Get the X coordinate, safely dispose of Y. + let (Sx, Sy) = S.as_bytes(); + Protected::from(Sy); // Just a precaution. + + // Zero-pad to the size of the underlying field, + // rounded to the next byte. + let mut Sx = Vec::from(Sx); + while Sx.len() < (field_sz + 7) / 8 { + Sx.insert(0, 0); + } + + encrypt_shared(recipient, session_key, VB, &Sx.into()) + } + + // Not implemented in Nettle + Curve::BrainpoolP256 | Curve::BrainpoolP512 => + Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + + // N/A + Curve::Unknown(_) | Curve::Ed25519 => + Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + + Curve::__Nonexhaustive => unreachable!(), + } + } else { + Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()) + } +} + +/// Wraps a session key. +/// +/// After using Elliptic Curve Diffie-Hellman to compute a shared +/// secret, this function deterministically encrypts the given session +/// key. +/// +/// `VB` is the ephemeral public key (with 0x40 prefix), `S` is the +/// shared Diffie-Hellman secret. +#[allow(non_snake_case)] +pub fn encrypt_shared<R>(recipient: &Key<key::PublicParts, R>, + session_key: &SessionKey, VB: MPI, + S: &Protected) + -> Result<Ciphertext> + where R: key::KeyRole +{ + match recipient.mpis() { + &PublicKey::ECDH{ ref curve, ref hash, ref sym,.. } => { + // m = sym_alg_ID || session key || checksum || pkcs5_padding; + let mut m = Vec::with_capacity(40); + m.extend_from_slice(session_key); + let m = pkcs5_pad(m.into(), 40); + // Note: We always pad up to 40 bytes to obfuscate the + // length of the symmetric key. + + // Compute KDF input. + let param = make_param(recipient, curve, hash, sym); + + // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap + // Compute Z = KDF( S, Z_len, Param ); + #[allow(non_snake_case)] + let Z = kdf(S, sym.key_size()?, *hash, ¶m)?; + + // Compute C = AESKeyWrap( Z, m ) as per [RFC3394] + #[allow(non_snake_case)] + let C = aes_key_wrap(*sym, &Z, &m)?; + + // Output (MPI(VB) || len(C) || C). + Ok(Ciphertext::ECDH { + e: VB, + key: C.into_boxed_slice(), + }) + } + + _ => + Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), + } +} + +/// Unwraps a session key using Elliptic Curve Diffie-Hellman. +#[allow(non_snake_case)] +pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, + recipient_sec: &SecretKeyMaterial, + ciphertext: &Ciphertext) + -> Result<SessionKey> + where R: key::KeyRole +{ + match (recipient.mpis(), recipient_sec, ciphertext) { + (PublicKey::ECDH { ref curve, ..}, + SecretKeyMaterial::ECDH { ref scalar, }, + Ciphertext::ECDH { ref e, .. }) => + { + let S: Protected = match curve { + Curve::Cv25519 => { + // Get the public part V of the ephemeral key. + let V = e.decode_point(curve)?.0; + + // Nettle expects the private key to be exactly + // CURVE25519_SIZE bytes long but OpenPGP allows leading + // zeros to be stripped. + // Padding has to be unconditional; otherwise we have a + // secret-dependent branch. + // + // Reverse the scalar. See + // https://lists.gnupg.org/pipermail/gnupg-devel/2018-February/033437.html. + let missing = curve25519::CURVE25519_SIZE + .saturating_sub(scalar.value().len()); + let mut r = [0u8; curve25519::CURVE25519_SIZE]; + + r[missing..].copy_from_slice(scalar.value()); + r.reverse(); + + // Compute the shared point S = rV = rvG, where (r, R) + // is the recipient's key pair. + let mut S: Protected = + vec![0; curve25519::CURVE25519_SIZE].into(); + let res = curve25519::mul(&mut S, &r[..], V); + + unsafe { + memsec::memzero(r.as_mut_ptr(), + curve25519::CURVE25519_SIZE); + } + res.expect("buffers are of the wrong size"); + S + } + + Curve::NistP256 | Curve::NistP384 | Curve::NistP521 => { + // Get the public part V of the ephemeral key and + // compute the shared point S = rV = rvG, where (r, R) + // is the recipient's key pair. + let (Vx, Vy) = e.decode_point(curve)?; + let (V, r, field_sz) = match curve { + Curve::NistP256 => { + let V = + ecc::Point::new::<ecc::Secp256r1>(&Vx, &Vy)?; + let r = + ecc::Scalar::new::<ecc::Secp256r1>(scalar.value())?; + + (V, r, 256) + } + Curve::NistP384 => { + let V = + ecc::Point::new::<ecc::Secp384r1>(&Vx, &Vy)?; + let r = + ecc::Scalar::new::<ecc::Secp384r1>(scalar.value())?; + + (V, r, 384) + } + Curve::NistP521 => { + let V = + ecc::Point::new::<ecc::Secp521r1>(&Vx, &Vy)?; + let r = + ecc::Scalar::new::<ecc::Secp521r1>(scalar.value())?; + + (V, r, 521) + } + _ => unreachable!(), + }; + let S = ecdh::point_mul(&r, &V)?; + + // Get the X coordinate, safely dispose of Y. + let (Sx, Sy) = S.as_bytes(); + Protected::from(Sy); // Just a precaution. + + // Zero-pad to the size of the underlying field, + // rounded to the next byte. + let mut Sx = Vec::from(Sx); + while Sx.len() < (field_sz + 7) / 8 { + Sx.insert(0, 0); + } + + Sx.into() + } + + _ => { + return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); + } + }; + + decrypt_shared(recipient, &S, ciphertext) + } + + _ => + Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), + } +} + +/// Unwraps a session key. +/// +/// After using Elliptic Curve Diffie-Hellman to compute the shared +/// secret, this function decrypts the given encrypted session key. +/// +/// `recipient` is the message receiver's public key, `S` is the +/// shared Diffie-Hellman secret used to encrypt `ciphertext`. +#[allow(non_snake_case)] +pub fn decrypt_shared<R>(recipient: &Key<key::PublicParts, R>, + S: &Protected, + ciphertext: &Ciphertext) + -> Result<SessionKey> + where R: key::KeyRole +{ + match (recipient.mpis(), ciphertext) { + (PublicKey::ECDH { ref curve, ref hash, ref sym, ..}, + Ciphertext::ECDH { ref key, .. }) => { + // Compute KDF input. + let param = make_param(recipient, curve, hash, sym); + + // Z_len = the key size for the KEK_alg_ID used with AESKeyWrap + // Compute Z = KDF( S, Z_len, Param ); + #[allow(non_snake_case)] + let Z = kdf(&S, sym.key_size()?, *hash, ¶m)?; + + // Compute m = AESKeyUnwrap( Z, C ) as per [RFC3394] + let m = aes_key_unwrap(*sym, &Z, key)?; + let cipher = SymmetricAlgorithm::from(m[0]); + let m = pkcs5_unpad(m, 1 + cipher.key_size()? + 2)?; + + Ok(m.into()) + }, + + _ => + Err(Error::InvalidArgument( + "Expected an ECDH key and ciphertext".into()).into()), + } +} + +fn make_param<P, R>(recipient: &Key<P, R>, + curve: &Curve, hash: &HashAlgorithm, + sym: &SymmetricAlgorithm) + -> Vec<u8> + where P: key::KeyParts, + R: key::KeyRole +{ + // Param = curve_OID_len || curve_OID || + // public_key_alg_ID || 03 || 01 || KDF_hash_ID || + // KEK_alg_ID for AESKeyWrap || "Anonymous Sender " || + // recipient_fingerprint; + let fp = recipient.fingerprint(); + + let mut param = Vec::with_capacity( + 1 + curve.oid().len() // Length and Curve OID, + + 1 // Public key algorithm ID, + + 4 // KDF parameters, + + 20 // "Anonymous Sender ", + + fp.as_bytes().len()); // Recipients key fingerprint. + + param.push(curve.oid().len() as u8); + param.extend_from_slice(curve.oid()); + param.push(PublicKeyAlgorithm::ECDH.into()); + param.push(3); + param.push(1); + param.push((*hash).into()); + param.push((*sym).into()); + param.extend_from_slice(b"Anonymous Sender "); + param.extend_from_slice(fp.as_bytes()); + assert_eq!(param.len(), + 1 + curve.oid().len() // Length and Curve OID, + + 1 // Public key algorithm ID, + + 4 // KDF parameters, + + 20 // "Anonymous Sender ", + + fp.as_bytes().len()); // Recipients key fingerprint. + + param +} + +/// Wraps a key using the AES Key Wrap Algorithm. +/// +/// See [RFC 3394]. +/// +/// [RFC 3394]: https://tools.ietf.org/html/rfc3394 +pub fn aes_key_wrap(algo: SymmetricAlgorithm, key: &Protected, + plaintext: &Protected) + -> Result<Vec<u8>> { + use crate::SymmetricAlgorithm::*; + + if plaintext.len() % 8 != 0 { + return Err(Error::InvalidArgument( + "Plaintext must be a multiple of 8".into()).into()); + } + + if key.len() != algo.key_size()? { + return Err(Error::InvalidArgument("Bad key size".into()).into()); + } + + // We need ECB for the algorithm. However, there is no + // nettle::Mode:ECB, and we need nettle::Mode for polymorphism (we + // cannot have Box<nettle::Cipher>). To work around this, we use + // CBC, and always use an all-zero IV. + let mut cipher: Box<dyn Mode> = match algo { + AES128 => Box::new( + mode::Cbc::<cipher::Aes128>::with_encrypt_key(key)?), + AES192 => Box::new( + mode::Cbc::<cipher::Aes192>::with_encrypt_key(key)?), + AES256 => Box::new( + mode::Cbc::<cipher::Aes256>::with_encrypt_key(key)?), + _ => return Err(Error::UnsupportedSymmetricAlgorithm(algo).into()), + }; + + // Inputs: Plaintext, n 64-bit values {P1, P2, ..., Pn}, and + // Key, K (the KEK). + // Outputs: Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn}. + let n = plaintext.len() / 8; + let mut ciphertext = vec![0; 8 + plaintext.len()]; + + // 1) Initialize variables. + // + // Set A = IV, an initial value (see 2.2.3) + let mut a = AES_KEY_WRAP_IV; + + { + // For i = 1 to n + // R[i] = P[i] + let r = &mut ciphertext[8..]; + r.copy_from_slice(plaintext); + + let mut b = [0; 16]; + let mut tmp = [0; 16]; + let mut iv: Protected = vec![0; cipher.block_size()].into(); + + // 2) Calculate intermediate values. + + // For j = 0 to 5 + for j in 0..6 { + // For i=1 to n + for i in 0..n { + // B = AES(K, A | R[i]) + write_be_u64(&mut tmp[..8], a); + &mut tmp[8..].copy_from_slice(&r[8 * i..8 * (i + 1)]); + iv.iter_mut().for_each(|p| *p = 0); // Turn CBC into ECB. + cipher.encrypt(&mut iv, &mut b, &tmp)?; + + // A = MSB(64, B) ^ t where t = (n*j)+i + a = read_be_u64(&b[..8]) ^ ((n * j) + i + 1) as u64; + // (Note that our i runs from 0 to n-1 instead of 1 to + // n, hence the index shift. + + // R[i] = LSB(64, B) + &mut r[8 * i..8 * (i + 1)].copy_from_slice(&b[8..]); + } + } + } + + // 3) Output the results. + // + // Set C[0] = A + // For i = 1 to n + // C[i] = R[i] + write_be_u64(&mut ciphertext[..8], a); + Ok(ciphertext) +} + +/// Unwraps an encrypted key using the AES Key Wrap Algorithm. +/// +/// See [RFC 3394]. +/// +/// [RFC 3394]: https://tools.ietf.org/html/rfc3394 +pub fn aes_key_unwrap(algo: SymmetricAlgorithm, key: &Protected, + ciphertext: &[u8]) + -> Result<Protected> { + use crate::SymmetricAlgorithm::*; + + if ciphertext.len() % 8 != 0 { + return Err(Error::InvalidArgument( + "Ciphertext must be a multiple of 8".into()).into()); + } + + if key.len() != algo.key_size()? { + return Err(Error::InvalidArgument("Bad key size".into()).into()); + } + + // We need ECB for the algorithm. However, there is no + // nettle::Mode:ECB, and we need nettle::Mode for polymorphism (we + // cannot have Box<nettle::Cipher>). To work around this, we use + // CBC, and always use an all-zero IV. + let mut cipher: Box<dyn Mode> = match algo { + AES128 => Box::new( + mode::Cbc::<cipher::Aes128>::with_decrypt_key(key)?), + AES192 => Box::new( + mode::Cbc::<cipher::Aes192>::with_decrypt_key(key)?), + AES256 => Box::new( + mode::Cbc::<cipher::Aes256>::with_decrypt_key(key)?), + _ => return Err(Error::UnsupportedSymmetricAlgorithm(algo).into()), + }; + + // Inputs: Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn}, and + // Key, K (the KEK). + // Outputs: Plaintext, n 64-bit values {P1, P2, ..., Pn}. + let n = ciphertext.len() / 8 - 1; + let mut plaintext = Vec::with_capacity(ciphertext.len() - 8); + + // 1) Initialize variables. + // + // Set A = C[0] + // For i = 1 to n + // R[i] = C[i] + let mut a = read_be_u64(&ciphertext[..8]); + plaintext.extend_from_slice(&ciphertext[8..]); + let mut plaintext: Protected = plaintext.into(); + + // 2) Calculate intermediate values. + { + let r = &mut plaintext; + + let mut b = [0; 16]; + let mut tmp = [0; 16]; + let mut iv: Protected = vec![0; cipher.block_size()].into(); + + // For j = 5 to 0 + for j in (0..=5).rev() { + // For i = n to 1 + for i in (0..=n-1).rev() { + // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i + write_be_u64(&mut tmp[..8], a ^ ((n * j) + i + 1) as u64); + &mut tmp[8..].copy_from_slice(&r[8 * i..8 * (i + 1)]); + // (Note that our i runs from n-1 to 0 instead of n to + // 1, hence the index shift. + iv.iter_mut().for_each(|p| *p = 0); // Turn CBC into ECB. + cipher.decrypt(&mut iv, &mut b, &tmp)?; + + // A = MSB(64, B) + a = read_be_u64(&b[..8]); + + // R[i] = LSB(64, B) + &mut r[8 * i..8 * (i + 1)].copy_from_slice(&b[8..]); + } + } + } + + // 3) Output results. + // + // If A is an appropriate initial value (see 2.2.3), + // Then + // For i = 1 to n + // P[i] = R[i] + // Else + // Return an error + if a == AES_KEY_WRAP_IV { + Ok(plaintext) + } else { + Err(Error::InvalidArgument("Bad key".into()).into()) + } +} + +const AES_KEY_WRAP_IV: u64 = 0xa6a6a6a6a6a6a6a6; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pkcs5_padding() { + let v = pkcs5_pad(vec![0, 0, 0].into(), 8); + assert_eq!(&v, &Protected::from(&[0, 0, 0, 5, 5, 5, 5, 5][..])); + let v = pkcs5_unpad(v, 3).unwrap(); + assert_eq!(&v, &Protected::from(&[0, 0, 0][..])); + + let v = pkcs5_pad(vec![].into(), 8); + assert_eq!(&v, &Protected::from(&[8, 8, 8, 8, 8, 8, 8, 8][..])); + let v = pkcs5_unpad(v, 0).unwrap(); + assert_eq!(&v, &Protected::from(&[][..])); + } + + #[test] + fn aes_wrapping() { + struct Test { + algo: SymmetricAlgorithm, + kek: &'static [u8], + key_data: &'static [u8], + ciphertext: &'static [u8], + } + + // These are the test vectors from RFC3394. + const TESTS: &[Test] = &[ + Test { + algo: SymmetricAlgorithm::AES128, + kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], + key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], + ciphertext: &[0x1F, 0xA6, 0x8B, 0x0A, 0x81, 0x12, 0xB4, 0x47, + 0xAE, 0xF3, 0x4B, 0xD8, 0xFB, 0x5A, 0x7B, 0x82, + 0x9D, 0x3E, 0x86, 0x23, 0x71, 0xD2, 0xCF, 0xE5], + }, + Test { + algo: SymmetricAlgorithm::AES192, + kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17], + key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], + ciphertext: &[0x96, 0x77, 0x8B, 0x25, 0xAE, 0x6C, 0xA4, 0x35, + 0xF9, 0x2B, 0x5B, 0x97, 0xC0, 0x50, 0xAE, 0xD2, + 0x46, 0x8A, 0xB8, 0xA1, 0x7A, 0xD8, 0x4E, 0x5D], + }, + Test { + algo: SymmetricAlgorithm::AES256, + kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F], + key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], + ciphertext: &[0x64, 0xE8, 0xC3, 0xF9, 0xCE, 0x0F, 0x5B, 0xA2, + 0x63, 0xE9, 0x77, 0x79, 0x05, 0x81, 0x8A, 0x2A, + 0x93, 0xC8, 0x19, 0x1E, 0x7D, 0x6E, 0x8A, 0xE7], + }, + Test { + algo: SymmetricAlgorithm::AES192, + kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17], + key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], + ciphertext: &[0x03, 0x1D, 0x33, 0x26, 0x4E, 0x15, 0xD3, 0x32, + 0x68, 0xF2, 0x4E, 0xC2, 0x60, 0x74, 0x3E, 0xDC, + 0xE1, 0xC6, 0xC7, 0xDD, 0xEE, 0x72, 0x5A, 0x93, + 0x6B, 0xA8, 0x14, 0x91, 0x5C, 0x67, 0x62, 0xD2], + }, + Test { + algo: SymmetricAlgorithm::AES256, + kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F], + key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], + ciphertext: &[0xA8, 0xF9, 0xBC, 0x16, 0x12, 0xC6, 0x8B, 0x3F, + 0xF6, 0xE6, 0xF4, 0xFB, 0xE3, 0x0E, 0x71, 0xE4, + 0x76, 0x9C, 0x8B, 0x80, 0xA3, 0x2C, 0xB8, 0x95, + 0x8C, 0xD5, 0xD1, 0x7D, 0x6B, 0x25, 0x4D, 0xA1], + }, + Test { + algo: SymmetricAlgorithm::AES256, + kek: &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F], + key_data: &[0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], + ciphertext: &[0x28, 0xC9, 0xF4, 0x04, 0xC4, 0xB8, 0x10, 0xF4, + 0xCB, 0xCC, 0xB3, 0x5C, 0xFB, 0x87, 0xF8, 0x26, + 0x3F, 0x57, 0x86, 0xE2, 0xD8, 0x0E, 0xD3, 0x26, + 0xCB, 0xC7, 0xF0, 0xE7, 0x1A, 0x99, 0xF4, 0x3B, + 0xFB, 0x98, 0x8B, 0x9B, 0x7A, 0x02, 0xDD, 0x21], + }, + ]; + + for test in TESTS { + let ciphertext = aes_key_wrap(test.algo, + &test.kek.into(), + &test.key_data.into()) + .unwrap(); + assert_eq!(test.ciphertext, &ciphertext[..]); + + let key_data = aes_key_unwrap(test.algo, + &test.kek.into(), + &ciphertext[..]) + .unwrap(); + assert_eq!(&Protected::from(test.key_data), &key_data); + } + } +} diff --git a/openpgp/src/crypto/ecdh.rs b/openpgp/src/crypto/ecdh.rs index e95ecd58..d88510a3 100644 --- a/openpgp/src/crypto/ecdh.rs +++ b/openpgp/src/crypto/ecdh.rs @@ -1,368 +1,14 @@ //! Elliptic Curve Diffie-Hellman. use crate::vec_truncate; -use crate::Error; -use crate::packet::{ - Key, - key, -}; -use crate::Result; -use crate::types::{ - Curve, - HashAlgorithm, - SymmetricAlgorithm, - PublicKeyAlgorithm, -}; -use crate::utils::{ - write_be_u64, - read_be_u64, -}; -use crate::crypto::SessionKey; -use crate::crypto::mem::Protected; -use crate::crypto::mpi::{MPI, PublicKey, SecretKeyMaterial, Ciphertext}; -use nettle::{cipher, curve25519, mode, mode::Mode, ecc, ecdh, random::Yarrow}; - -/// Wraps a session key using Elliptic Curve Diffie-Hellman. -#[allow(non_snake_case)] -pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, - session_key: &SessionKey) - -> Result<Ciphertext> - where R: key::KeyRole -{ - let mut rng = Yarrow::default(); - - if let &PublicKey::ECDH { - ref curve, ref q,.. - } = recipient.mpis() { - match curve { - Curve::Cv25519 => { - // Obtain the authenticated recipient public key R - let R = q.decode_point(curve)?.0; - - // Generate an ephemeral key pair {v, V=vG} - let v: Protected = - curve25519::private_key(&mut rng).into(); - - // Compute the public key. We need to add an encoding - // octet in front of the key. - let mut VB = [0x40; 1 + curve25519::CURVE25519_SIZE]; - curve25519::mul_g(&mut VB[1..], &v) - .expect("buffers a |