diff --git a/Cargo.toml b/Cargo.toml index c4a3979f..fdb31be0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ edition = "2018" # and -accelerated suffix means that this resolver will be the default used by the Builder. [features] default = ["default-resolver"] -default-resolver = ["aes-gcm", "chacha20poly1305", "blake2", "sha2", "x25519-dalek", "rand"] +default-resolver = ["aes-gcm", "chacha20poly1305", "blake2", "sha2", "x25519-dalek", "x448","rand"] nightly = ["blake2/simd_opt", "x25519-dalek/nightly", "subtle/nightly"] ring-resolver = ["ring"] ring-accelerated = ["ring-resolver", "default-resolver"] @@ -47,6 +47,7 @@ blake2 = { version = "0.9", optional = true } rand = { version = "0.7", optional = true } sha2 = { version = "0.9", optional = true } x25519-dalek = { version = "0.6", optional = true } +x448 = { version = "0.6", optional = true } pqcrypto-kyber = { version = "0.6", optional = true } pqcrypto-traits = { version = "0.3", optional = true } diff --git a/README.md b/README.md index e49fa23a..da7aa3cb 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ specification. |-----------:|:-------:|:----:|:---------:| | CSPRNG | ✔ | ✔ | ✔ | | 25519 | ✔ | ✔ | ✔ | -| 448 | | | | +| 448 | ✔ | | | | AESGCM | ✔ | ✔ | | | ChaChaPoly | ✔ | ✔ | ✔ | | SHA256 | ✔ | ✔ | ✔ | diff --git a/src/params/mod.rs b/src/params/mod.rs index 416061d5..36d25d3c 100644 --- a/src/params/mod.rs +++ b/src/params/mod.rs @@ -35,7 +35,7 @@ impl FromStr for BaseChoice { #[derive(PartialEq, Copy, Clone, Debug)] pub enum DHChoice { Curve25519, - Ed448, + Curve448, } impl FromStr for DHChoice { @@ -45,7 +45,7 @@ impl FromStr for DHChoice { use self::DHChoice::*; match s { "25519" => Ok(Curve25519), - "448" => Ok(Ed448), + "448" => Ok(Curve448), _ => bail!(PatternProblem::UnsupportedDhType), } } diff --git a/src/resolvers/default.rs b/src/resolvers/default.rs index a8a518c7..3f32159f 100644 --- a/src/resolvers/default.rs +++ b/src/resolvers/default.rs @@ -13,6 +13,7 @@ use pqcrypto_traits::kem::{Ciphertext, PublicKey, SecretKey, SharedSecret}; use rand::rngs::OsRng; use sha2::{Digest, Sha256, Sha512}; use x25519_dalek as x25519; +use x448; use super::CryptoResolver; #[cfg(feature = "pqclean_kyber1024")] @@ -39,7 +40,7 @@ impl CryptoResolver for DefaultResolver { fn resolve_dh(&self, choice: &DHChoice) -> Option> { match *choice { DHChoice::Curve25519 => Some(Box::new(Dh25519::default())), - _ => None, + DHChoice::Curve448 => Some(Box::new(Dh448::default())), } } @@ -75,6 +76,16 @@ struct Dh25519 { privkey: [u8; 32], pubkey: [u8; 32], } +/// Wraps x448 [crate-crypto]. +struct Dh448 { + privkey: [u8; 56], + pubkey: [u8; 56], +} +impl Default for Dh448 { + fn default() -> Dh448 { + Dh448 { privkey: [0u8; 56], pubkey: [0u8; 56] } + } +} /// Wraps `aes-gcm`'s AES256-GCM implementation. #[derive(Default)] @@ -162,6 +173,45 @@ impl Dh for Dh25519 { } } +impl Dh for Dh448 { + fn name(&self) -> &'static str { + "448" + } + + fn pub_len(&self) -> usize { + 56 + } + + fn priv_len(&self) -> usize { + 56 + } + + fn set(&mut self, privkey: &[u8]) { + copy_slices!(privkey, &mut self.privkey); + self.pubkey = x448::x448_unchecked(self.privkey, x448::X448_BASEPOINT_BYTES); + } + + fn generate(&mut self, rng: &mut dyn Random) { + rng.fill_bytes(&mut self.privkey); + self.pubkey = x448::x448_unchecked(self.privkey, x448::X448_BASEPOINT_BYTES); + } + + fn pubkey(&self) -> &[u8] { + &self.pubkey + } + + fn privkey(&self) -> &[u8] { + &self.privkey + } + + fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), ()> { + let pub_key = x448::PublicKey::from_bytes_unchecked(pubkey).unwrap(); + let result = x448::x448_unchecked(self.privkey, *pub_key.as_bytes()); + copy_slices!(&result, out); + Ok(()) + } +} + impl Cipher for CipherAesGcm { fn name(&self) -> &'static str { "AESGCM" @@ -527,6 +577,7 @@ mod tests { use super::*; use hex; + #[test] fn test_sha256() { let mut output = [0u8; 32]; @@ -611,6 +662,24 @@ mod tests { == "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552" ); } + #[test] + fn test_curve448() { + // Curve448 test - draft-curves-10 + let mut keypair: Dh448 = Default::default(); + let scalar = + Vec::::from_hex("3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3") + .unwrap(); + copy_slices!(&scalar, &mut keypair.privkey); + let public = + Vec::::from_hex("06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086") + .unwrap(); + let mut output = [0u8; 56]; + keypair.dh(&public, &mut output).unwrap(); + assert_eq!( + hex::encode(&output[..]), + "ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f" + ); + } #[test] fn test_aesgcm() { diff --git a/tests/vectors.rs b/tests/vectors.rs index 1af19974..5bbbf210 100644 --- a/tests/vectors.rs +++ b/tests/vectors.rs @@ -259,16 +259,10 @@ fn test_vectors_from_json(json: &str) { let mut passes = 0; let mut fails = 0; - let mut ignored = 0; for vector in test_vectors.vectors { let params: NoiseParams = vector.protocol_name.parse().unwrap(); - if params.dh == DHChoice::Ed448 { - ignored += 1; - continue; - } - let (init, resp) = match build_session_pair(&vector) { Ok((init, resp)) => (init, resp), Err(s) => { @@ -299,7 +293,6 @@ fn test_vectors_from_json(json: &str) { } println!("\n{}/{} passed", passes, passes + fails); - println!("* ignored {} unsupported variants", ignored); if fails > 0 { panic!("at least one vector failed."); }