diff options
author | Kai Michaelis <kai@sequoia-pgp.org> | 2019-02-13 19:21:16 +0100 |
---|---|---|
committer | Kai Michaelis <kai@sequoia-pgp.org> | 2019-02-15 19:44:52 +0100 |
commit | d93ecf46c7371893300e9036904f2b13ec47143d (patch) | |
tree | f02d46973d999ab2b15957a23736fb9c06fb64ca | |
parent | 18a89541e107b650390ea9c943bb2b8eef59684f (diff) |
openpgp: implement ECDH for NIST P-{256,384,521}
Extends ecdh::(un)wrap_session_key to handle keys on NIST P-256, P-384
and P-521.
-rw-r--r-- | openpgp/src/crypto/ecdh.rs | 274 | ||||
-rw-r--r-- | openpgp/src/crypto/mpis.rs | 20 | ||||
-rw-r--r-- | openpgp/src/packet/key.rs | 4 |
3 files changed, 189 insertions, 109 deletions
diff --git a/openpgp/src/crypto/ecdh.rs b/openpgp/src/crypto/ecdh.rs index b3116485..c0b6f932 100644 --- a/openpgp/src/crypto/ecdh.rs +++ b/openpgp/src/crypto/ecdh.rs @@ -17,6 +17,7 @@ use crypto::mpis::{MPI, PublicKey, SecretKey, Ciphertext}; use nettle::{cipher, curve25519, mode, Mode, ecc, ecdh, Yarrow}; /// Wraps a session key using Elliptic Curve Diffie-Hellman. +#[allow(non_snake_case)] pub fn wrap_session_key(recipient: &Key, session_key: &[u8]) -> Result<Ciphertext> { @@ -28,7 +29,6 @@ pub fn wrap_session_key(recipient: &Key, session_key: &[u8]) match curve { Curve::Cv25519 => { // Obtain the authenticated recipient public key R - #[allow(non_snake_case)] let R = q.decode_point(curve)?.0; // Generate an ephemeral key pair {v, V=vG} @@ -37,20 +37,70 @@ pub fn wrap_session_key(recipient: &Key, session_key: &[u8]) // Compute the public key. We need to add an encoding // octet in front of the key. - #[allow(non_snake_case)] 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; - #[allow(non_snake_case)] let mut S = [0; curve25519::CURVE25519_SIZE]; curve25519::mul(&mut S, &v, R) .expect("buffers are of the wrong size"); - wrap_session_key_deterministic(recipient, session_key, &VB, &S) + wrap_session_key_deterministic(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. + println!("q: {:?}",q); + 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. We need to add an encoding + // octet in front of the 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)?; + let (Sx,_) = S.as_bytes(); + + wrap_session_key_deterministic(recipient, session_key, VB, &Sx) + } + + // 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()), } } else { @@ -58,118 +108,150 @@ pub fn wrap_session_key(recipient: &Key, session_key: &[u8]) } } -// VB: Ephemeral public key, +// VB: Ephemeral public key (with 0x40 prefix), // S: Shared DH secret. #[allow(non_snake_case)] pub(crate) fn wrap_session_key_deterministic(recipient: &Key, session_key: &[u8], - VB: &[u8; 33], S: &[u8; 32]) -> Result<Ciphertext> + VB: MPI, S: &[u8]) -> Result<Ciphertext> { - if let &PublicKey::ECDH { - ref curve, ref hash, ref sym,.. - } = recipient.mpis() { - match curve { - Curve::Cv25519 => { - // m = symm_alg_ID || session key || checksum || pkcs5_padding; - let mut m = Vec::with_capacity(40); - m.extend_from_slice(session_key); - pkcs5_pad(&mut m, 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: MPI::new(VB), - key: C.into_boxed_slice(), - }) - }, - - _ => - Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + match recipient.mpis() { + &PublicKey::ECDH{ ref curve, ref hash, ref sym,.. } => { + // m = symm_alg_ID || session key || checksum || pkcs5_padding; + let mut m = Vec::with_capacity(40); + m.extend_from_slice(session_key); + pkcs5_pad(&mut m, 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(), + }) } - } else { - Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()) + + _ => + Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), } } /// Unwraps a session key using Elliptic Curve Diffie-Hellman. +#[allow(non_snake_case)] pub fn unwrap_session_key(recipient: &Key, recipient_sec: &SecretKey, ciphertext: &Ciphertext) -> Result<Box<[u8]>> { use memsec; - if let (&PublicKey::ECDH { - ref curve, ref hash, ref sym, .. - }, SecretKey::ECDH { - ref scalar, - }, Ciphertext::ECDH { - ref e, ref key, - }) = (recipient.mpis(), recipient_sec, ciphertext) { - match curve { - Curve::Cv25519 => { - // Get the public part V of the ephemeral key. - #[allow(non_snake_case)] - 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 unconditionaly, otherwise we have a - // secret-dependant 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. - #[allow(non_snake_case)] - let mut S = [0; curve25519::CURVE25519_SIZE]; - let res = curve25519::mul(&mut S, &r[..], V); - - unsafe { - memsec::memzero(r.as_mut_ptr(), - curve25519::CURVE25519_SIZE); + match (recipient.mpis(), recipient_sec, ciphertext) { + (&PublicKey::ECDH { ref curve, ref hash, ref sym, ..}, + SecretKey::ECDH { ref scalar, }, + Ciphertext::ECDH { ref e, ref key, }) => + { + let S: Box<[u8]> = 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 unconditionaly, otherwise we have a + // secret-dependant 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 = [0; curve25519::CURVE25519_SIZE]; + 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"); + Box::new(S) } - res.expect("buffers are of the wrong size"); - // Compute KDF input. - let param = make_param(recipient, curve, hash, sym); + 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) = match curve { + Curve::NistP256 => { + let V = + ecc::Point::new::<ecc::Secp256r1>(&Vx, &Vy)?; + let r = + ecc::Scalar::new::<ecc::Secp256r1>(&scalar.value[..])?; + + (V, r) + } + Curve::NistP384 => { + let V = + ecc::Point::new::<ecc::Secp384r1>(&Vx, &Vy)?; + let r = + ecc::Scalar::new::<ecc::Secp384r1>(&scalar.value[..])?; + + (V, r) + } + Curve::NistP521 => { + let V = + ecc::Point::new::<ecc::Secp521r1>(&Vx, &Vy)?; + let r = + ecc::Scalar::new::<ecc::Secp521r1>(&scalar.value[..])?; + + (V, r) + } + _ => unreachable!(), + }; + let S = ecdh::point_mul(&r, &V)?; + let (Sx, _) = S.as_bytes(); + + Sx + } - // 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)?; + _ => { + return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); + } + }; + // Compute KDF input. + let param = make_param(recipient, curve, hash, sym); - // Compute m = AESKeyUnwrap( Z, C ) as per [RFC3394] - let mut m = aes_key_unwrap(*sym, &Z, key)?; - let cipher = SymmetricAlgorithm::from(m[0]); - pkcs5_unpad(&mut m, 1 + cipher.key_size()? + 2)?; + // 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)?; - Ok(m.into_boxed_slice()) - }, + // Compute m = AESKeyUnwrap( Z, C ) as per [RFC3394] + let mut m = aes_key_unwrap(*sym, &Z, key)?; + let cipher = SymmetricAlgorithm::from(m[0]); + pkcs5_unpad(&mut m, 1 + cipher.key_size()? + 2)?; - _ => - Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), + Ok(m.into_boxed_slice()) } - } else { - Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()) + + _ => + Err(Error::InvalidArgument("Expected an ECDHPublicKey".into()).into()), } } diff --git a/openpgp/src/crypto/mpis.rs b/openpgp/src/crypto/mpis.rs index 64ce231f..2a7cd678 100644 --- a/openpgp/src/crypto/mpis.rs +++ b/openpgp/src/crypto/mpis.rs @@ -50,21 +50,19 @@ impl MPI { } /// Creates new MPI for EC point. - pub fn new_weierstrass(x: &[u8], y: &[u8]) -> Self { - use std::cmp::max; + pub fn new_weierstrass(x: &[u8], y: &[u8], field_bits: usize) -> Self { + let field_sz = if field_bits % 8 > 0 { 1 } else { 0 } + field_bits / 8; + let mut val = vec![0x0u8; 1 + 2 * field_sz]; + let x_missing = field_sz - x.len(); + let y_missing = field_sz - y.len(); - let coord_len = max(x.len(), y.len()); - let mut val = vec![0x0u8; 1 + 2 * coord_len]; - let x_missing = coord_len - x.len(); - let y_missing = coord_len - y.len(); - - val[0] = 0x40; - val[1 + x_missing..1 + coord_len].copy_from_slice(x); - val[1 + coord_len + y_missing..].copy_from_slice(y); + val[0] = 0x4; + val[1 + x_missing..1 + field_sz].copy_from_slice(x); + val[1 + field_sz + y_missing..].copy_from_slice(y); MPI{ value: val.into_boxed_slice(), - bits: 6 + 16 * coord_len, + bits: 3 + 16 * field_sz, } } diff --git a/openpgp/src/packet/key.rs b/openpgp/src/packet/key.rs index 2c805af6..42d0b013 100644 --- a/openpgp/src/packet/key.rs +++ b/openpgp/src/packet/key.rs @@ -740,9 +740,9 @@ mod tests { let key = Key::import_public_cv25519(&public[..], HashAlgorithm::SHA256, SymmetricAlgorithm::AES128, ctime).unwrap(); // PKESK - let eph_pubkey: &[u8; 33] = b"\x40\xda\x1c\x69\xc4\xe3\xb6\x9c\x6e\xd4\xc6\x69\x6c\x89\xc7\x09\xe9\xf8\x6a\xf1\xe3\x8d\xb6\xaa\xb5\xf7\x29\xae\xa6\xe7\xdd\xfe\x38"; + let eph_pubkey = MPI::new(&b"\x40\xda\x1c\x69\xc4\xe3\xb6\x9c\x6e\xd4\xc6\x69\x6c\x89\xc7\x09\xe9\xf8\x6a\xf1\xe3\x8d\xb6\xaa\xb5\xf7\x29\xae\xa6\xe7\xdd\xfe\x38"[..]); let ciphertext = Ciphertext::ECDH{ - e: MPI::new(&eph_pubkey[..]), + e: eph_pubkey.clone(), key: Vec::from(&b"\x45\x8b\xd8\x4d\x88\xb3\xd2\x16\xb6\xc2\x3b\x99\x33\xd1\x23\x4b\x10\x15\x8e\x04\x16\xc5\x7c\x94\x88\xf6\x63\xf2\x68\x37\x08\x66\xfd\x5a\x7b\x40\x58\x21\x6b\x2c\xc0\xf4\xdc\x91\xd3\x48\xed\xc1"[..]).into_boxed_slice() }; let shared_sec: &[u8; 32] = b"\x44\x0C\x99\x27\xF7\xD6\x1E\xAD\xD1\x1E\x9E\xC8\x22\x2C\x5D\x43\xCE\xB0\xE5\x45\x94\xEC\xAF\x67\xD9\x35\x1D\xA1\xA3\xA8\x10\x0B"; |