diff --git a/Cargo.toml b/Cargo.toml index 46060dc..1b0d297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ vector-tests = [] hfs = [] pqclean_kyber1024 = ["pqcrypto-kyber", "pqcrypto-traits", "hfs", "default-resolver"] xchachapoly = ["chacha20poly1305", "default-resolver"] +p256 = ["dep:p256", "default-resolver"] risky-raw-split = [] [[bench]] @@ -45,7 +46,8 @@ aes-gcm = { version = "0.10", optional = true } chacha20poly1305 = { version = "0.10", optional = true } blake2 = { version = "0.10", optional = true } sha2 = { version = "0.10", optional = true } -curve25519-dalek = { version = "4", optional = true } +curve25519-dalek = { version = "4.1.3", optional = true } +p256 = { version = "0.13.2", features = ["ecdh"], optional = true } pqcrypto-kyber = { version = "0.8", optional = true } pqcrypto-traits = { version = "0.3", optional = true } diff --git a/README.md b/README.md index 6e2226c..76bd19d 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ crypto implementations when available. | CSPRNG | ✔ | ✔ | | 25519 | ✔ | ✔ | | 448 | | | +| P-256 | ✔ | | | AESGCM | ✔ | ✔ | | ChaChaPoly | ✔ | ✔ | | SHA256 | ✔ | ✔ | diff --git a/src/constants.rs b/src/constants.rs index f6d650e..2334d07 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,7 +4,7 @@ pub const TAGLEN: usize = 16; pub const MAXHASHLEN: usize = 64; pub const MAXBLOCKLEN: usize = 128; -pub const MAXDHLEN: usize = 56; +pub const MAXDHLEN: usize = 65; pub const MAXMSGLEN: usize = 65535; #[cfg(feature = "hfs")] diff --git a/src/handshakestate.rs b/src/handshakestate.rs index 671b55d..bd8a73d 100644 --- a/src/handshakestate.rs +++ b/src/handshakestate.rs @@ -78,7 +78,7 @@ impl HandshakeState { symmetricstate.initialize(¶ms.name); symmetricstate.mix_hash(prologue); - let dh_len = s.pub_len(); + let pub_len = s.pub_len(); if initiator { for token in tokens.premsg_pattern_i { symmetricstate.mix_hash( @@ -100,7 +100,7 @@ impl HandshakeState { _ => unreachable!(), } .get() - .ok_or(StateProblem::MissingKeyMaterial)?[..dh_len], + .ok_or(StateProblem::MissingKeyMaterial)?[..pub_len], ); } } else { @@ -112,7 +112,7 @@ impl HandshakeState { _ => unreachable!(), } .get() - .ok_or(StateProblem::MissingKeyMaterial)?[..dh_len], + .ok_or(StateProblem::MissingKeyMaterial)?[..pub_len], ); } for token in tokens.premsg_pattern_r { @@ -152,7 +152,7 @@ impl HandshakeState { } pub(crate) fn dh_len(&self) -> usize { - self.s.pub_len() + self.s.dh_len() } #[cfg(feature = "hfs")] @@ -356,39 +356,39 @@ impl HandshakeState { } let last = self.pattern_position == (self.message_patterns.len() - 1); - let dh_len = self.dh_len(); + let pub_len = self.e.pub_len(); let mut ptr = message; for token in &self.message_patterns[self.pattern_position] { match *token { Token::E => { - if ptr.len() < dh_len { + if ptr.len() < pub_len { return Err(Error::Input); } - self.re[..dh_len].copy_from_slice(&ptr[..dh_len]); - ptr = &ptr[dh_len..]; - self.symmetricstate.mix_hash(&self.re[..dh_len]); + self.re[..pub_len].copy_from_slice(&ptr[..pub_len]); + ptr = &ptr[pub_len..]; + self.symmetricstate.mix_hash(&self.re[..pub_len]); if self.params.handshake.is_psk() { - self.symmetricstate.mix_key(&self.re[..dh_len]); + self.symmetricstate.mix_key(&self.re[..pub_len]); } self.re.enable(); }, Token::S => { let data = if self.symmetricstate.has_key() { - if ptr.len() < dh_len + TAGLEN { + if ptr.len() < pub_len + TAGLEN { return Err(Error::Input); } - let temp = &ptr[..dh_len + TAGLEN]; - ptr = &ptr[dh_len + TAGLEN..]; + let temp = &ptr[..pub_len + TAGLEN]; + ptr = &ptr[pub_len + TAGLEN..]; temp } else { - if ptr.len() < dh_len { + if ptr.len() < pub_len { return Err(Error::Input); } - let temp = &ptr[..dh_len]; - ptr = &ptr[dh_len..]; + let temp = &ptr[..pub_len]; + ptr = &ptr[pub_len..]; temp }; - self.symmetricstate.decrypt_and_mix_hash(data, &mut self.rs[..dh_len])?; + self.symmetricstate.decrypt_and_mix_hash(data, &mut self.rs[..pub_len])?; self.rs.enable(); }, Token::Psk(n) => match self.psks[n as usize] { @@ -472,7 +472,7 @@ impl HandshakeState { /// pattern, for example). #[must_use] pub fn get_remote_static(&self) -> Option<&[u8]> { - self.rs.get().map(|rs| &rs[..self.dh_len()]) + self.rs.get().map(|rs| &rs[..self.s.pub_len()]) } /// Get the handshake hash. diff --git a/src/params/mod.rs b/src/params/mod.rs index f8d7463..c9f65b9 100644 --- a/src/params/mod.rs +++ b/src/params/mod.rs @@ -37,10 +37,13 @@ impl FromStr for BaseChoice { /// Which Diffie-Hellman primitive to use. One of `25519` or `448`, per the spec. #[derive(PartialEq, Copy, Clone, Debug)] pub enum DHChoice { - /// The Curve25519 ellpitic curve. + /// The Curve25519 elliptic curve. Curve25519, /// The Curve448 elliptic curve. Curve448, + #[cfg(feature = "p256")] + /// The P-256 elliptic curve. + P256, } impl FromStr for DHChoice { @@ -51,6 +54,8 @@ impl FromStr for DHChoice { match s { "25519" => Ok(Curve25519), "448" => Ok(Curve448), + #[cfg(feature = "p256")] + "P256" => Ok(P256), _ => Err(PatternProblem::UnsupportedDhType.into()), } } @@ -270,6 +275,13 @@ mod tests { assert!(p.handshake.modifiers.list.is_empty()); } + #[test] + #[cfg(feature = "p256")] + fn test_p256() { + let p: NoiseParams = "Noise_XX_P256_AESGCM_SHA256".parse().unwrap(); + assert_eq!(p.dh, DHChoice::P256); + } + #[test] fn test_basic_deferred() { let p: NoiseParams = "Noise_X1X1_25519_AESGCM_SHA256".parse().unwrap(); diff --git a/src/resolvers/default.rs b/src/resolvers/default.rs index 93ed5b6..5f60f61 100644 --- a/src/resolvers/default.rs +++ b/src/resolvers/default.rs @@ -3,6 +3,8 @@ use blake2::{Blake2b, Blake2b512, Blake2s, Blake2s256}; use chacha20poly1305::XChaCha20Poly1305; use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit}; use curve25519_dalek::montgomery::MontgomeryPoint; +#[cfg(feature = "p256")] +use p256::{self, elliptic_curve::sec1::ToEncodedPoint, EncodedPoint}; #[cfg(feature = "pqclean_kyber1024")] use pqcrypto_kyber::kyber1024; #[cfg(feature = "pqclean_kyber1024")] @@ -38,6 +40,8 @@ impl CryptoResolver for DefaultResolver { match *choice { DHChoice::Curve25519 => Some(Box::::default()), DHChoice::Curve448 => None, + #[cfg(feature = "p256")] + DHChoice::P256 => Some(Box::::default()), } } @@ -74,6 +78,14 @@ struct Dh25519 { pubkey: [u8; 32], } +/// Wraps p256 +#[cfg(feature = "p256")] +#[derive(Default)] +struct P256 { + privkey: [u8; 32], + pubkey: EncodedPoint, +} + /// Wraps `aes-gcm`'s AES256-GCM implementation. #[derive(Default)] struct CipherAesGcm { @@ -175,6 +187,67 @@ impl Dh for Dh25519 { } } +#[cfg(feature = "p256")] +impl P256 { + fn derive_pubkey(&mut self) { + let secret_key = p256::SecretKey::from_bytes(&self.privkey.into()).unwrap(); + let public_key = secret_key.public_key(); + let encoded_pub = public_key.to_encoded_point(false); + self.pubkey = encoded_pub; + } +} + +#[cfg(feature = "p256")] +impl Dh for P256 { + fn name(&self) -> &'static str { + "P256" + } + + fn pub_len(&self) -> usize { + 65 // Uncompressed SEC-1 encoding + } + + fn priv_len(&self) -> usize { + 32 // Scalar + } + + fn dh_len(&self) -> usize { + 32 + } + + fn set(&mut self, privkey: &[u8]) { + let mut bytes = [0u8; 32]; + copy_slices!(privkey, bytes); + self.privkey = bytes; + self.derive_pubkey(); + } + + fn generate(&mut self, rng: &mut dyn Random) { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + self.privkey = bytes; + self.derive_pubkey(); + } + + fn pubkey(&self) -> &[u8] { + self.pubkey.as_bytes() + } + + fn privkey(&self) -> &[u8] { + &self.privkey + } + + fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), Error> { + let secret_key = p256::SecretKey::from_bytes(&self.privkey.into()).or(Err(Error::Dh))?; + let secret_key_scalar = secret_key.to_nonzero_scalar(); + let pub_key: p256::elliptic_curve::PublicKey = + p256::PublicKey::from_sec1_bytes(&pubkey).or(Err(Error::Dh))?; + let dh_output = p256::ecdh::diffie_hellman(secret_key_scalar, pub_key.as_affine()); + copy_slices!(dh_output.raw_secret_bytes(), out); + Ok(()) + } +} + impl Cipher for CipherAesGcm { fn name(&self) -> &'static str { "AESGCM" @@ -613,6 +686,27 @@ mod tests { ); } + #[test] + #[cfg(feature = "p256")] + fn test_p256() { + let mut keypair = P256::default(); + let scalar = + Vec::::from_hex("58c77a30bb0fa177286346d18f59678ac1b8d3637ee65f1bd88a8f52e49ef189") + .unwrap(); + keypair.set(&scalar); + let public = Vec::::from_hex( + "042009fddefeed3b342696b11683b423db8ede2ef5cd66af9b7db2772f7deaf3d\ + f1a69a4648d990ae2a4e5928f156f32e15fa08ba4465df8cb17838dc2afb719d2", + ) + .unwrap(); + let mut output = [0u8; 32]; + keypair.dh(&public, &mut output).unwrap(); + assert_eq!( + hex::encode(output), + "07505b308650d07e6ead11dd36bbf6f24bf99fc6479649fadd3939faa33ddeb3" + ); + } + #[test] fn test_aesgcm() { // AES256-GCM tests - gcm-spec.pdf diff --git a/src/types.rs b/src/types.rs index e98c258..4e8522d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -37,6 +37,11 @@ pub trait Dh: Send + Sync { /// # Errors /// Returns `Error::Dh` in the event that the Diffie-Hellman failed. fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), Error>; + + /// The lenght in bytes of of the DH key exchange. Defaults to the public key. + fn dh_len(&self) -> usize { + self.pub_len() + } } /// Cipher operations