summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKai Michaelis <kai@sequoia-pgp.org>2019-02-13 19:21:16 +0100
committerKai Michaelis <kai@sequoia-pgp.org>2019-02-15 19:44:52 +0100
commitd93ecf46c7371893300e9036904f2b13ec47143d (patch)
treef02d46973d999ab2b15957a23736fb9c06fb64ca
parent18a89541e107b650390ea9c943bb2b8eef59684f (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.rs274
-rw-r--r--openpgp/src/crypto/mpis.rs20
-rw-r--r--openpgp/src/packet/key.rs4
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, &param)?;
-
- // 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, &param)?;
+
+ // 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, &param)?;
+ _ => {
+ 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, &param)?;
- 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";