From 09e9f46523175df8fbc797455ec4144960130de4 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 8 Apr 2020 11:47:35 -0700 Subject: [PATCH 1/4] add method to derive x25519 from a private key; add nacl-compatible key creation Signed-off-by: Andrew Whitehead --- libursa/src/signatures/ed25519.rs | 62 ++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/libursa/src/signatures/ed25519.rs b/libursa/src/signatures/ed25519.rs index 2803bc99..13a05bb3 100644 --- a/libursa/src/signatures/ed25519.rs +++ b/libursa/src/signatures/ed25519.rs @@ -1,7 +1,7 @@ pub const ALGORITHM_NAME: &str = "ED25519_SHA2_512"; use super::{KeyGenOption, SignatureScheme}; -use ed25519_dalek::{Keypair, PublicKey as PK, Signature}; +use ed25519_dalek::{Keypair, PublicKey as PK, SecretKey as SK, Signature}; pub use ed25519_dalek::{ EXPANDED_SECRET_KEY_LENGTH as PRIVATE_KEY_SIZE, PUBLIC_KEY_LENGTH as PUBLIC_KEY_SIZE, SIGNATURE_LENGTH as SIGNATURE_SIZE, @@ -34,6 +34,34 @@ impl Ed25519Sha512 { ))), } } + + pub fn sign_key_to_key_exchange(sk: &PrivateKey) -> Result { + // Length is normally 64 but we only need the secret from the first half + if sk.len() < 32 { + return Err(CryptoError::ParseError(format!( + "Invalid private key provided" + ))); + } + // hash secret + let hash = sha2::Sha512::digest(&sk[..32]); + let mut output = [0u8; 32]; + output.copy_from_slice(&hash[..32]); + // clamp result + let secret = x25519_dalek::StaticSecret::from(output); + Ok(PrivateKey(secret.to_bytes().to_vec())) + } + + pub fn keypair_from_secret(seed: &[u8]) -> Result<(PublicKey, PrivateKey), CryptoError> { + if seed.len() < 32 { + return Err(CryptoError::ParseError(format!("Invalid secret provided"))); + } + let mut private = vec![0u8; 64]; + private[0..32].copy_from_slice(seed); + let sk = SK::from_bytes(&private[..32]).unwrap(); + let pk = PK::from(&sk).to_bytes().to_vec(); + private[32..].copy_from_slice(pk.as_ref()); + Ok((PublicKey(pk), PrivateKey(private))) + } } impl SignatureScheme for Ed25519Sha512 { @@ -106,7 +134,11 @@ mod test { const MESSAGE_1: &[u8] = b"This is a dummy message for use with tests"; const SIGNATURE_1: &str = "451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808"; const PRIVATE_KEY: &str = "1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"; + const PRIVATE_KEY_X25519: &str = + "08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f"; const PUBLIC_KEY: &str = "27c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"; + const PUBLIC_KEY_X25519: &str = + "9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d"; #[test] #[ignore] @@ -202,11 +234,37 @@ mod test { #[cfg(any(feature = "x25519", feature = "x25519_asm"))] #[test] - fn ed25519_to_x25519() { + fn ed25519_to_x25519_default() { let scheme = Ed25519Sha512::new(); let (p, _) = scheme.keypair(None).unwrap(); let res = Ed25519Sha512::ver_key_to_key_exchange(&p); assert!(res.is_ok()); } + + #[cfg(any(feature = "x25519", feature = "x25519_asm"))] + #[test] + fn ed25519_to_x25519_verify() { + let sk = PrivateKey(hex::decode(PRIVATE_KEY).unwrap()); + let pk = PublicKey(hex::decode(PUBLIC_KEY).unwrap()); + let scheme = Ed25519Sha512::new(); + + let x_pk = Ed25519Sha512::ver_key_to_key_exchange(&pk).unwrap(); + assert_eq!(hex::encode(&x_pk), PUBLIC_KEY_X25519); + + let x_sk = Ed25519Sha512::sign_key_to_key_exchange(&sk).unwrap(); + assert_eq!(hex::encode(&x_sk), PRIVATE_KEY_X25519); + } + + #[cfg(any(feature = "x25519", feature = "x25519_asm"))] + #[test] + fn nacl_derive_from_seed() { + let seed = b"000000000000000000000000Trustee1"; + let test_sk = hex::decode("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097").unwrap(); + let test_pk = &test_sk[32..]; + + let (pk, sk) = Ed25519Sha512::keypair_from_secret(seed).unwrap(); + assert_eq!(pk.0, test_pk); + assert_eq!(sk.0, test_sk); + } } From b04f69adf5b96665da5ffa244dc53a9537089c21 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 8 Apr 2020 12:05:00 -0700 Subject: [PATCH 2/4] adjust variable usage for clarity; handle longer seed Signed-off-by: Andrew Whitehead --- libursa/src/signatures/ed25519.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libursa/src/signatures/ed25519.rs b/libursa/src/signatures/ed25519.rs index 13a05bb3..59f2de4e 100644 --- a/libursa/src/signatures/ed25519.rs +++ b/libursa/src/signatures/ed25519.rs @@ -56,8 +56,8 @@ impl Ed25519Sha512 { return Err(CryptoError::ParseError(format!("Invalid secret provided"))); } let mut private = vec![0u8; 64]; - private[0..32].copy_from_slice(seed); - let sk = SK::from_bytes(&private[..32]).unwrap(); + private[..32].copy_from_slice(&seed[..32]); + let sk = SK::from_bytes(&seed[..32]).unwrap(); let pk = PK::from(&sk).to_bytes().to_vec(); private[32..].copy_from_slice(pk.as_ref()); Ok((PublicKey(pk), PrivateKey(private))) From 107d22d0578555618716e8a20e3097747fe69ef7 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 24 Apr 2020 14:20:13 -0700 Subject: [PATCH 3/4] rename keypair_from_secret to expand_keypair; fix warning Signed-off-by: Andrew Whitehead --- libursa/src/signatures/ed25519.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libursa/src/signatures/ed25519.rs b/libursa/src/signatures/ed25519.rs index 59f2de4e..0e68a97a 100644 --- a/libursa/src/signatures/ed25519.rs +++ b/libursa/src/signatures/ed25519.rs @@ -51,13 +51,15 @@ impl Ed25519Sha512 { Ok(PrivateKey(secret.to_bytes().to_vec())) } - pub fn keypair_from_secret(seed: &[u8]) -> Result<(PublicKey, PrivateKey), CryptoError> { - if seed.len() < 32 { - return Err(CryptoError::ParseError(format!("Invalid secret provided"))); + pub fn expand_keypair(ikm: &[u8]) -> Result<(PublicKey, PrivateKey), CryptoError> { + if ikm.len() < 32 { + return Err(CryptoError::ParseError(format!( + "Invalid key material provided" + ))); } let mut private = vec![0u8; 64]; - private[..32].copy_from_slice(&seed[..32]); - let sk = SK::from_bytes(&seed[..32]).unwrap(); + private[..32].copy_from_slice(&ikm[..32]); + let sk = SK::from_bytes(&ikm[..32]).unwrap(); let pk = PK::from(&sk).to_bytes().to_vec(); private[32..].copy_from_slice(pk.as_ref()); Ok((PublicKey(pk), PrivateKey(private))) @@ -247,7 +249,6 @@ mod test { fn ed25519_to_x25519_verify() { let sk = PrivateKey(hex::decode(PRIVATE_KEY).unwrap()); let pk = PublicKey(hex::decode(PUBLIC_KEY).unwrap()); - let scheme = Ed25519Sha512::new(); let x_pk = Ed25519Sha512::ver_key_to_key_exchange(&pk).unwrap(); assert_eq!(hex::encode(&x_pk), PUBLIC_KEY_X25519); @@ -263,7 +264,7 @@ mod test { let test_sk = hex::decode("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097").unwrap(); let test_pk = &test_sk[32..]; - let (pk, sk) = Ed25519Sha512::keypair_from_secret(seed).unwrap(); + let (pk, sk) = Ed25519Sha512::expand_keypair(seed).unwrap(); assert_eq!(pk.0, test_pk); assert_eq!(sk.0, test_sk); } From 6d8904fae9e244cf3f0885d31ec1d2c731695097 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 29 Apr 2020 09:38:36 -0700 Subject: [PATCH 4/4] add docstrings, restrict includes to x25519 features Signed-off-by: Andrew Whitehead --- libursa/src/signatures/ed25519.rs | 45 +++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/libursa/src/signatures/ed25519.rs b/libursa/src/signatures/ed25519.rs index 0e68a97a..ee4018a2 100644 --- a/libursa/src/signatures/ed25519.rs +++ b/libursa/src/signatures/ed25519.rs @@ -1,7 +1,9 @@ pub const ALGORITHM_NAME: &str = "ED25519_SHA2_512"; use super::{KeyGenOption, SignatureScheme}; -use ed25519_dalek::{Keypair, PublicKey as PK, SecretKey as SK, Signature}; +#[cfg(any(feature = "x25519", feature = "x25519_asm"))] +use ed25519_dalek::SecretKey as SK; +use ed25519_dalek::{Keypair, PublicKey as PK, Signature}; pub use ed25519_dalek::{ EXPANDED_SECRET_KEY_LENGTH as PRIVATE_KEY_SIZE, PUBLIC_KEY_LENGTH as PUBLIC_KEY_SIZE, SIGNATURE_LENGTH as SIGNATURE_SIZE, @@ -19,6 +21,19 @@ pub struct Ed25519Sha512; #[cfg(any(feature = "x25519", feature = "x25519_asm"))] impl Ed25519Sha512 { + /// Creates a curve25519 key from an ed25519 public key. + /// + /// Used to derive the public key for DH key exchange. + /// + /// # Example + /// ``` + /// use ursa::signatures::ed25519::Ed25519Sha512; + /// use ursa::signatures::SignatureScheme; + /// + /// let (pk, sk) = Ed25519Sha512::new().keypair(None).unwrap(); + /// let curve_pk = Ed25519Sha512::ver_key_to_key_exchange(&pk).unwrap(); + /// let curve_sk = Ed25519Sha512::sign_key_to_key_exchange(&sk).unwrap(); + /// ``` pub fn ver_key_to_key_exchange(pk: &PublicKey) -> Result { use curve25519_dalek::edwards::CompressedEdwardsY; @@ -35,6 +50,19 @@ impl Ed25519Sha512 { } } + /// Creates a curve25519 key from an ed25519 private key. + /// + /// Used to derive the private key for DH key exchange. + /// + /// # Example + /// ``` + /// use ursa::signatures::ed25519::Ed25519Sha512; + /// use ursa::signatures::SignatureScheme; + /// + /// let (pk, sk) = Ed25519Sha512::new().keypair(None).unwrap(); + /// let curve_pk = Ed25519Sha512::ver_key_to_key_exchange(&pk).unwrap(); + /// let curve_sk = Ed25519Sha512::sign_key_to_key_exchange(&sk).unwrap(); + /// ``` pub fn sign_key_to_key_exchange(sk: &PrivateKey) -> Result { // Length is normally 64 but we only need the secret from the first half if sk.len() < 32 { @@ -51,6 +79,17 @@ impl Ed25519Sha512 { Ok(PrivateKey(secret.to_bytes().to_vec())) } + /// Expand an ed25519 keypair from the input key material. + /// + /// Used to derive a complete keypair from a predetermined secret. + /// + /// # Example + /// ``` + /// use ursa::signatures::ed25519::Ed25519Sha512; + /// + /// let ikm = b"000000000000000000000000000Test1"; + /// let (pk, sk) = Ed25519Sha512::expand_keypair(ikm).unwrap(); + /// ``` pub fn expand_keypair(ikm: &[u8]) -> Result<(PublicKey, PrivateKey), CryptoError> { if ikm.len() < 32 { return Err(CryptoError::ParseError(format!( @@ -136,9 +175,11 @@ mod test { const MESSAGE_1: &[u8] = b"This is a dummy message for use with tests"; const SIGNATURE_1: &str = "451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808"; const PRIVATE_KEY: &str = "1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"; + const PUBLIC_KEY: &str = "27c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"; + #[cfg(any(feature = "x25519", feature = "x25519_asm"))] const PRIVATE_KEY_X25519: &str = "08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f"; - const PUBLIC_KEY: &str = "27c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"; + #[cfg(any(feature = "x25519", feature = "x25519_asm"))] const PUBLIC_KEY_X25519: &str = "9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d";