summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2024-04-16 15:03:19 +0200
committerJustus Winter <justus@sequoia-pgp.org>2024-04-16 15:03:19 +0200
commitf650e183e645d22873247939fedf522736863f16 (patch)
tree8d477ada9814619aa60625fcb7f70fabd37d8747
parentcc699ad1f6c25f10d76176a233cf91683a23c398 (diff)
openpgp: Support NistP384 using the RustCrypto backend.
-rw-r--r--Cargo.lock13
-rw-r--r--openpgp/Cargo.toml2
-rw-r--r--openpgp/NEWS5
-rw-r--r--openpgp/src/crypto/backend/rust/asymmetric.rs95
-rw-r--r--openpgp/src/crypto/backend/rust/ecdh.rs40
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
@@ -1976,6 +1976,18 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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<P: key::KeyParts, R: key::KeyRole> Key<P, R> {
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::<p384::NistP384>::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<R> Key4<SecretParts, R>
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<R>(recipient: &Key<key::PublicParts, R>,
(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<R>(recipient: &Key<key::PublicParts, R>,
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());
},