diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2024-04-16 15:29:43 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2024-04-16 15:29:43 +0200 |
commit | 83860faa021ad1bdc3ebb1a8b0deec651c0b5e46 (patch) | |
tree | 51e00803a2ade8de9a524695261d843f60f2e999 | |
parent | f650e183e645d22873247939fedf522736863f16 (diff) |
openpgp: Support NistP521 using the RustCrypto backend.
-rw-r--r-- | Cargo.lock | 15 | ||||
-rw-r--r-- | openpgp/Cargo.toml | 3 | ||||
-rw-r--r-- | openpgp/NEWS | 2 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/asymmetric.rs | 93 | ||||
-rw-r--r-- | openpgp/src/crypto/backend/rust/ecdh.rs | 38 |
5 files changed, 147 insertions, 4 deletions
@@ -1988,6 +1988,20 @@ dependencies = [ ] [[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core", + "sha2", +] + +[[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2659,6 +2673,7 @@ dependencies = [ "openssl-sys", "p256", "p384", + "p521", "quickcheck", "rand", "rand_core", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index efdb2e68..e3c79da9 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -82,6 +82,7 @@ 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"] } +p521 = { 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 } @@ -119,7 +120,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:p384", "dep:p521", "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 0e338f0c..da2b53b3 100644 --- a/openpgp/NEWS +++ b/openpgp/NEWS @@ -8,6 +8,8 @@ ** New functionality - The RustCrypto backend now supports ECDH and ECDSA over the NIST curve P-384. + - The RustCrypto backend now supports ECDH and ECDSA over the NIST + curve P-521. * 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 d8adf44f..a5a434f8 100644 --- a/openpgp/src/crypto/backend/rust/asymmetric.rs +++ b/openpgp/src/crypto/backend/rust/asymmetric.rs @@ -67,10 +67,8 @@ impl Asymmetric for super::Backend { fn supports_curve(curve: &Curve) -> bool { use self::Curve::*; match curve { - NistP256 | NistP384 + NistP256 | NistP384 | NistP521 => true, - NistP521 - => false, Ed25519 | Cv25519 => true, BrainpoolP256 | BrainpoolP512 | Unknown(_) @@ -323,6 +321,31 @@ impl KeyPair { }) }, + Curve::NistP521 => { + use p521::Scalar; + const LEN: usize = 66; + + 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()), }, @@ -504,6 +527,30 @@ impl<P: key::KeyParts, R: key::KeyRole> Key<P, R> { key.verify_prehashed(&dig, &sig).map_err(bad) }, + Curve::NistP521 => { + use p521::{AffinePoint, ecdsa::Signature}; + const LEN: usize = 66; + + let key = AffinePoint::from_encoded_point( + &EncodedPoint::<p521::NistP521>::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!( @@ -688,6 +735,46 @@ impl<R> Key4<SecretParts, R> Ok((PublicKeyAlgorithm::ECDH, public_mpis, private_mpis)) }, + (Curve::NistP521, true) => { + use p521::{EncodedPoint, SecretKey}; + + let secret = SecretKey::random( + &mut p521::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::NistP521, false) => { + use p521::{EncodedPoint, SecretKey}; + + let secret = SecretKey::random( + &mut p521::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 eed2f827..77fd7d23 100644 --- a/openpgp/src/crypto/backend/rust/ecdh.rs +++ b/openpgp/src/crypto/backend/rust/ecdh.rs @@ -104,6 +104,28 @@ pub fn encrypt<R>(recipient: &Key<key::PublicParts, R>, (VB, shared) }, + Curve::NistP521 => { + use p521::{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 p521::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()), }; @@ -175,6 +197,22 @@ pub fn decrypt<R>(recipient: &Key<key::PublicParts, R>, Vec::from(secret.raw_secret_bytes().as_slice()).into() }, + Curve::NistP521 => { + use p521::{SecretKey, PublicKey}; + const NISTP521_SIZE: usize = 66; + + // Get the public part V of the ephemeral key. + let V = PublicKey::from_sec1_bytes(e.value())?; + + let scalar: [u8; NISTP521_SIZE] = + scalar.value_padded(NISTP521_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()); }, |