summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2024-04-16 15:29:43 +0200
committerJustus Winter <justus@sequoia-pgp.org>2024-04-16 15:29:43 +0200
commit83860faa021ad1bdc3ebb1a8b0deec651c0b5e46 (patch)
tree51e00803a2ade8de9a524695261d843f60f2e999
parentf650e183e645d22873247939fedf522736863f16 (diff)
openpgp: Support NistP521 using the RustCrypto backend.
-rw-r--r--Cargo.lock15
-rw-r--r--openpgp/Cargo.toml3
-rw-r--r--openpgp/NEWS2
-rw-r--r--openpgp/src/crypto/backend/rust/asymmetric.rs93
-rw-r--r--openpgp/src/crypto/backend/rust/ecdh.rs38
5 files changed, 147 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7b61006f..70a19fea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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());
},