From f650e183e645d22873247939fedf522736863f16 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 16 Apr 2024 15:03:19 +0200 Subject: openpgp: Support NistP384 using the RustCrypto backend. --- Cargo.lock | 13 ++++ openpgp/Cargo.toml | 2 + openpgp/NEWS | 5 ++ openpgp/src/crypto/backend/rust/asymmetric.rs | 95 ++++++++++++++++++++++++++- openpgp/src/crypto/backend/rust/ecdh.rs | 40 +++++++++++ 5 files changed, 153 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70429e70..7b61006f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1975,6 +1975,18 @@ dependencies = [ "sha2", ] +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2646,6 +2658,7 @@ dependencies = [ "openssl", "openssl-sys", "p256", + "p384", "quickcheck", "rand", "rand_core", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index d3ef4626..efdb2e68 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -81,6 +81,7 @@ idea = { version = "0.5", optional = true, features = ["zeroize"] } md-5 = { version = "0.10", features = ["oid"], optional = true } num-bigint-dig = { version = "0.8", default-features = false, optional = true } p256 = { version = "0.13", optional = true, features = ["ecdh", "ecdsa"] } +p384 = { version = "0.13", optional = true, features = ["ecdh", "ecdsa"] } rand = { version = "0.8", optional = true, default-features = false } rand_core = { version = "0.6", optional = true } ripemd = { version = "0.1", features = ["oid"], optional = true } @@ -118,6 +119,7 @@ crypto-rust = [ "dep:md-5", "dep:num-bigint-dig", "dep:ripemd", "dep:rsa", "dep:sha2", "sha1collisiondetection/digest-trait", "sha1collisiondetection/oid", "dep:twofish", "dep:typenum", "dep:x25519-dalek", "dep:p256", + "dep:p384", "dep:rand", "rand?/getrandom", "dep:rand_core", "rand_core?/getrandom", "dep:ecdsa", "dep:aes-gcm", "dep:dsa" ] diff --git a/openpgp/NEWS b/openpgp/NEWS index 95d629e4..0e338f0c 100644 --- a/openpgp/NEWS +++ b/openpgp/NEWS @@ -3,6 +3,11 @@ #+TITLE: sequoia-openpgp NEWS – history of user-visible changes #+STARTUP: content hidestars + +* Changes in 1.21.0 +** New functionality + - The RustCrypto backend now supports ECDH and ECDSA over the NIST + curve P-384. * Changes in 1.20.0 ** New functionality - S2K::Implicit diff --git a/openpgp/src/crypto/backend/rust/asymmetric.rs b/openpgp/src/crypto/backend/rust/asymmetric.rs index a07a760c..d8adf44f 100644 --- a/openpgp/src/crypto/backend/rust/asymmetric.rs +++ b/openpgp/src/crypto/backend/rust/asymmetric.rs @@ -67,9 +67,9 @@ impl Asymmetric for super::Backend { fn supports_curve(curve: &Curve) -> bool { use self::Curve::*; match curve { - NistP256 + NistP256 | NistP384 => true, - NistP384 | NistP521 + NistP521 => false, Ed25519 | Cv25519 => true, @@ -297,6 +297,32 @@ impl KeyPair { s: MPI::new(&sig.s().to_bytes()), }) }, + + Curve::NistP384 => { + use p384::Scalar; + const LEN: usize = 48; + + let key = scalar.value_padded(LEN); + let key = Scalar::reduce_bytes(GA::try_from_slice(&key)?); + let dig = pad_truncating(digest, LEN); + let dig = GA::try_from_slice(&dig)?; + + let sig = loop { + let mut k: Protected = vec![0; LEN].into(); + crate::crypto::random(&mut k); + let k = Scalar::reduce_bytes( + GA::try_from_slice(&k)?); + if let Ok(s) = key.try_sign_prehashed(k, &dig) { + break s.0; + } + }; + + Ok(mpi::Signature::ECDSA { + r: MPI::new(&sig.r().to_bytes()), + s: MPI::new(&sig.s().to_bytes()), + }) + }, + _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, @@ -453,6 +479,31 @@ impl Key { let dig = GA::try_from_slice(&dig)?; key.verify_prehashed(&dig, &sig).map_err(bad) }, + + Curve::NistP384 => { + use p384::{AffinePoint, ecdsa::Signature}; + const LEN: usize = 48; + + let key = AffinePoint::from_encoded_point( + &EncodedPoint::::from_bytes(q.value())?); + let key = if key.is_some().into() { + key.unwrap() + } else { + return Err(Error::InvalidKey( + "Point is not on the curve".into()).into()); + }; + + let sig = Signature::from_scalars( + GA::try_clone_from_slice( + &r.value_padded(LEN).map_err(bad)?)?, + GA::try_clone_from_slice( + &s.value_padded(LEN).map_err(bad)?)?) + .map_err(bad)?; + let dig = pad_truncating(digest, LEN); + let dig = GA::try_from_slice(&dig)?; + key.verify_prehashed(&dig, &sig).map_err(bad) + }, + _ => Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }, _ => Err(Error::MalformedPacket(format!( @@ -597,6 +648,46 @@ impl Key4 Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) }, + (Curve::NistP384, true) => { + use p384::{EncodedPoint, SecretKey}; + + let secret = SecretKey::random( + &mut p384::elliptic_curve::rand_core::OsRng); + let public = EncodedPoint::from(secret.public_key()); + + let public_mpis = mpi::PublicKey::ECDSA { + curve, + q: MPI::new(public.as_bytes()), + }; + let private_mpis = mpi::SecretKeyMaterial::ECDSA { + scalar: Vec::from(secret.to_bytes().as_slice()).into(), + }; + + Ok((PublicKeyAlgorithm::ECDSA, public_mpis, private_mpis)) + }, + + (Curve::NistP384, false) => { + use p384::{EncodedPoint, SecretKey}; + + let secret = SecretKey::random( + &mut p384::elliptic_curve::rand_core::OsRng); + let public = EncodedPoint::from(secret.public_key()); + + let public_mpis = mpi::PublicKey::ECDH { + q: MPI::new(public.as_bytes()), + hash: + crate::crypto::ecdh::default_ecdh_kdf_hash(&curve), + sym: + crate::crypto::ecdh::default_ecdh_kek_cipher(&curve), + curve, + }; + let private_mpis = mpi::SecretKeyMaterial::ECDH { + scalar: Vec::from(secret.to_bytes().as_slice()).into(), + }; + + Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) + }, + _ => Err(Error::UnsupportedEllipticCurve(curve).into()), } } diff --git a/openpgp/src/crypto/backend/rust/ecdh.rs b/openpgp/src/crypto/backend/rust/ecdh.rs index 56e4357b..eed2f827 100644 --- a/openpgp/src/crypto/backend/rust/ecdh.rs +++ b/openpgp/src/crypto/backend/rust/ecdh.rs @@ -81,6 +81,29 @@ pub fn encrypt(recipient: &Key, (VB, shared) }, + + Curve::NistP384 => { + use p384::{EncodedPoint, PublicKey, ecdh::EphemeralSecret}; + + // Decode the recipient's public key. + let recipient_key = PublicKey::from_sec1_bytes(q.value())?; + + // Generate a keypair and perform Diffie-Hellman. + let secret = EphemeralSecret::random( + &mut p384::elliptic_curve::rand_core::OsRng); + let public = EncodedPoint::from(PublicKey::from(&secret)); + let shared = secret.diffie_hellman(&recipient_key); + + // Encode our public key. + let VB = MPI::new(public.as_bytes()); + + // Encode the shared secret. + let shared: &[u8] = shared.raw_secret_bytes(); + let shared = Protected::from(shared); + + (VB, shared) + }, + _ => return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()), }; @@ -135,6 +158,23 @@ pub fn decrypt(recipient: &Key, let secret = diffie_hellman(r.to_nonzero_scalar(), V.as_affine()); Vec::from(secret.raw_secret_bytes().as_slice()).into() }, + + Curve::NistP384 => { + use p384::{SecretKey, PublicKey}; + const NISTP384_SIZE: usize = 48; + + // Get the public part V of the ephemeral key. + let V = PublicKey::from_sec1_bytes(e.value())?; + + let scalar: [u8; NISTP384_SIZE] = + scalar.value_padded(NISTP384_SIZE).as_ref().try_into()?; + let scalar = GA::try_from_slice(&scalar)?; + let r = SecretKey::from_bytes(&scalar)?; + + let secret = diffie_hellman(r.to_nonzero_scalar(), V.as_affine()); + Vec::from(secret.raw_secret_bytes().as_slice()).into() + }, + _ => { return Err(Error::UnsupportedEllipticCurve(curve.clone()).into()); }, -- cgit v1.2.3