From 48fa27649f1cfeb27a1beffd2d28b8a746b4c558 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 6 May 2017 01:25:23 -0700 Subject: initial sketches, help from hdevalence --- .gitignore | 3 +++ Cargo.toml | 8 ++++++++ src/lib.rs | 22 ++++++++++++++++++++++ src/spake2.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/spake2.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4308d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f470646 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "spake2" +version = "0.1.0" +authors = ["Brian Warner "] + +[dependencies] +#rust-crypto = "^0.2" +curve25519-dalek = "0.2.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..abab8b1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,22 @@ + +pub mod spake2; +//use spake2::*; + +#[cfg(test)] +mod tests { + use spake2; + #[test] + fn test_foo() { + assert_eq!(spake2::foo(), 1); + } + + #[test] + fn it_works() { + } + + #[test] + #[should_panic(expected = "nope")] + fn it_panics() { + assert!(false, "nope"); + } +} diff --git a/src/spake2.rs b/src/spake2.rs new file mode 100644 index 0000000..7d813d9 --- /dev/null +++ b/src/spake2.rs @@ -0,0 +1,44 @@ + +pub fn foo() -> u8 { + 1 +} + + +trait Group { + type Scalar; + type Element; + pub fn scalarmult(s: Scalar) -> Element; + pub fn scalar_from_integer(u8) -> Scalar; +} + + +struct SPAKE2 { + x: G::Scalar, + password: Vec, + idA: Vec, + idB: Vec, + pw: G::Scalar, +} + +impl for SPAKE2 { + pub fn new(password: &[u8], idA: &[u8], idB: &[u8]) -> SPAKE2 { + let pw: G::Scalar = hash_to_scalar::(password); + let x: G::Scalar = random_scalar::; + + let M1 G::Element = MAGIC(); + let msg1 = ... + let mut pv = Vec::new(); + pv.extend_from_slice(password); + (SPAKE2 {x: x, password: pv, ... }, msg1) + } + + pub fn finish(self, msg2: &[u8]) -> Result { + } +} + + +{ + let (mut s, msg1) = SPAKE2::(&password, &idA, &idB); + //let msg1 = s.msg1; + let key = s.finish(msg2); +} -- cgit v1.2.3 From 57a38426b5e4716f6f14e639e5b3b923ca7e3319 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 17 May 2017 13:30:42 -0700 Subject: more sketches, help from manishearth --- Cargo.toml | 3 ++- src/lib.rs | 3 +++ src/spake2.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f470646..c08f92f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ authors = ["Brian Warner "] [dependencies] #rust-crypto = "^0.2" -curve25519-dalek = "0.2.0" +curve25519-dalek = "0.6.0" +rand = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs index abab8b1..b972f4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ +extern crate rand; +extern crate curve25519_dalek; + pub mod spake2; //use spake2::*; diff --git a/src/spake2.rs b/src/spake2.rs index 7d813d9..ab9ee86 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,44 +1,88 @@ -pub fn foo() -> u8 { - 1 -} - +use curve25519_dalek::scalar::Scalar as c2_Scalar; +use curve25519_dalek::curve::ExtendedPoint as c2_Element; +use curve25519_dalek::curve::BasepointMult; +use curve25519_dalek::curve::ScalarMult; +use rand::OsRng; trait Group { type Scalar; type Element; - pub fn scalarmult(s: Scalar) -> Element; - pub fn scalar_from_integer(u8) -> Scalar; + // const element_length: usize; // in unstable, or u8 + //type ElementBytes : Index+IndexMut; // later + fn random_scalar() -> Self::Scalar; + fn basepoint_mult(s: &Self::Scalar) -> Self::Element; + fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element; + fn add(a: &Self::Element, b: &Self::Element) -> Self::Element; +} + +struct Ed25519Group; + +impl Group for Ed25519Group { + type Scalar = c2_Scalar; + type Element = c2_Element; + //type ElementBytes = Vec; + //type ElementBytes = [u8; 32]; + //type ScalarBytes + + fn random_scalar() -> c2_Scalar { + let mut cspring: OsRng = OsRng::new().unwrap(); + c2_Scalar::random(&mut cspring) + } + fn basepoint_mult(s: &c2_Scalar) -> c2_Element { + c2_Element::basepoint_mult(s) + } + fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element { + e.scalar_mult(s) + } + fn add(a: &c2_Element, b: &c2_Element) -> c2_Element { + a.add(b) + } } +/* "session type pattern" */ + struct SPAKE2 { x: G::Scalar, password: Vec, idA: Vec, idB: Vec, + msg1: Vec, pw: G::Scalar, } -impl for SPAKE2 { - pub fn new(password: &[u8], idA: &[u8], idB: &[u8]) -> SPAKE2 { +impl SPAKE2 { + pub fn new(password: &[u8], idA: &[u8], idB: &[u8]) -> (SPAKE2, Vec) { let pw: G::Scalar = hash_to_scalar::(password); let x: G::Scalar = random_scalar::; - let M1 G::Element = MAGIC(); - let msg1 = ... + let M1: G::Element = unimplemented!(); + let msg1 = unimplemented!(); // M1 to bytes let mut pv = Vec::new(); pv.extend_from_slice(password); - (SPAKE2 {x: x, password: pv, ... }, msg1) + let mut idA_copy = Vec::new(); + idA_copy.extend_from_slice(idA); + let mut idB_copy = Vec::new(); + idB_copy.extend_from_slice(idB); + (SPAKE2 {x: x, + password: pv, + idA: idA_copy, + idB: idB_copy, + msg1: msg1.clone(), + pw: unimplemented!(), + }, msg1) } - - pub fn finish(self, msg2: &[u8]) -> Result { + + pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { } } +/* { let (mut s, msg1) = SPAKE2::(&password, &idA, &idB); //let msg1 = s.msg1; let key = s.finish(msg2); } +*/ -- cgit v1.2.3 From 441f2da853aac8663970043055a28a32aec0f6eb Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 22 May 2017 18:04:17 -0700 Subject: more --- src/spake2.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/spake2.rs b/src/spake2.rs index ab9ee86..3dc2631 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -10,6 +10,7 @@ trait Group { type Element; // const element_length: usize; // in unstable, or u8 //type ElementBytes : Index+IndexMut; // later + fn hash_to_scalar(s: &[u8]) -> Self::Scalar; fn random_scalar() -> Self::Scalar; fn basepoint_mult(s: &Self::Scalar) -> Self::Element; fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element; @@ -25,6 +26,9 @@ impl Group for Ed25519Group { //type ElementBytes = [u8; 32]; //type ScalarBytes + fn hash_to_scalar(s: &[u8]) -> c2_Scalar { + c2_Scalar::hash_from_bytes(&s) + } fn random_scalar() -> c2_Scalar { let mut cspring: OsRng = OsRng::new().unwrap(); c2_Scalar::random(&mut cspring) @@ -54,7 +58,8 @@ struct SPAKE2 { impl SPAKE2 { pub fn new(password: &[u8], idA: &[u8], idB: &[u8]) -> (SPAKE2, Vec) { - let pw: G::Scalar = hash_to_scalar::(password); + //let pw: G::Scalar = hash_to_scalar::(password); + let pw: G::Scalar = G::hash_to_scalar(password); let x: G::Scalar = random_scalar::; let M1: G::Element = unimplemented!(); -- cgit v1.2.3 From 63c7a7217954329006b72e2a41af330ee9a9524b Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 23 May 2017 20:17:27 -0700 Subject: update, make it compile (but not work, of course) --- Cargo.toml | 5 ++++- src/lib.rs | 1 + src/spake2.rs | 46 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c08f92f..bdf1e87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,8 @@ authors = ["Brian Warner "] [dependencies] #rust-crypto = "^0.2" -curve25519-dalek = "0.6.0" +curve25519-dalek = "0.8.0" rand = "0.3.0" +sha2 = "0.4.0" + + diff --git a/src/lib.rs b/src/lib.rs index b972f4b..9d7204a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ extern crate rand; extern crate curve25519_dalek; +extern crate sha2; pub mod spake2; //use spake2::*; diff --git a/src/spake2.rs b/src/spake2.rs index 3dc2631..ac872ad 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,15 +1,22 @@ use curve25519_dalek::scalar::Scalar as c2_Scalar; use curve25519_dalek::curve::ExtendedPoint as c2_Element; -use curve25519_dalek::curve::BasepointMult; -use curve25519_dalek::curve::ScalarMult; +use curve25519_dalek::constants::ED25519_BASEPOINT; +//use curve25519_dalek::curve::BasepointMult; +//use curve25519_dalek::curve::ScalarMult; use rand::OsRng; +use sha2::Sha512; +//use std::ops::{Add, Mul}; trait Group { type Scalar; type Element; + //type Element: Add + // + Mul; // const element_length: usize; // in unstable, or u8 //type ElementBytes : Index+IndexMut; // later + fn const_M() -> Self::Element; + fn const_N() -> Self::Element; fn hash_to_scalar(s: &[u8]) -> Self::Scalar; fn random_scalar() -> Self::Scalar; fn basepoint_mult(s: &Self::Scalar) -> Self::Element; @@ -26,22 +33,36 @@ impl Group for Ed25519Group { //type ElementBytes = [u8; 32]; //type ScalarBytes + fn const_M() -> c2_Element { + // there's a specific value to return here, not this + ED25519_BASEPOINT + } + + fn const_N() -> c2_Element { + // there's a specific value to return here, not this + ED25519_BASEPOINT + } + fn hash_to_scalar(s: &[u8]) -> c2_Scalar { - c2_Scalar::hash_from_bytes(&s) + c2_Scalar::hash_from_bytes::(&s) } fn random_scalar() -> c2_Scalar { let mut cspring: OsRng = OsRng::new().unwrap(); c2_Scalar::random(&mut cspring) } fn basepoint_mult(s: &c2_Scalar) -> c2_Element { - c2_Element::basepoint_mult(s) + //c2_Element::basepoint_mult(s) + &ED25519_BASEPOINT * s } fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element { - e.scalar_mult(s) + e * s + //e.scalar_mult(s) } fn add(a: &c2_Element, b: &c2_Element) -> c2_Element { - a.add(b) + a + b + //a.add(b) } + } @@ -60,10 +81,13 @@ impl SPAKE2 { pub fn new(password: &[u8], idA: &[u8], idB: &[u8]) -> (SPAKE2, Vec) { //let pw: G::Scalar = hash_to_scalar::(password); let pw: G::Scalar = G::hash_to_scalar(password); - let x: G::Scalar = random_scalar::; + let x: G::Scalar = G::random_scalar(); - let M1: G::Element = unimplemented!(); - let msg1 = unimplemented!(); // M1 to bytes + // M1 = B*x + M*pw + let M1: G::Element = G::add(&G::basepoint_mult(&x), + &G::scalarmult(&G::const_M(), &pw)); + //let M1: G::Element = &G::basepoint_mult(&x) + &(&G::const_M() * &pw); + let msg1: Vec = unimplemented!(); // M1 to bytes let mut pv = Vec::new(); pv.extend_from_slice(password); let mut idA_copy = Vec::new(); @@ -79,8 +103,8 @@ impl SPAKE2 { }, msg1) } - pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { - } + /*pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { + }*/ } -- cgit v1.2.3 From e0c310857ccd557d0c0466c9912ae5a46305746e Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 23 May 2017 22:54:32 -0700 Subject: more progress, make some dummy implementations pass tests --- src/lib.rs | 19 +++++++--- src/spake2.rs | 112 +++++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9d7204a..27e26f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,18 +2,27 @@ extern crate rand; extern crate curve25519_dalek; extern crate sha2; +extern crate core; -pub mod spake2; -//use spake2::*; +mod spake2; +pub use spake2::*; #[cfg(test)] mod tests { - use spake2; + use spake2::{SPAKE2, Ed25519Group}; + #[test] - fn test_foo() { - assert_eq!(spake2::foo(), 1); + fn test_one() { + let (s1, msg1) = SPAKE2::::new(b"password", + b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::new(b"password", + b"idA", b"idB"); + let key1 = s1.finish(msg2.as_slice()).unwrap(); + let key2 = s2.finish(msg1.as_slice()).unwrap(); + assert_eq!(key1, key2); } + #[test] fn it_works() { } diff --git a/src/spake2.rs b/src/spake2.rs index ac872ad..98a1118 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,30 +1,35 @@ +#![allow(dead_code)] use curve25519_dalek::scalar::Scalar as c2_Scalar; use curve25519_dalek::curve::ExtendedPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT; -//use curve25519_dalek::curve::BasepointMult; -//use curve25519_dalek::curve::ScalarMult; -use rand::OsRng; +use curve25519_dalek::curve::CompressedEdwardsY; +use rand::{Rng, OsRng}; use sha2::Sha512; -//use std::ops::{Add, Mul}; -trait Group { +#[derive(Debug)] +pub struct SPAKEErr; + +pub trait Group { type Scalar; type Element; //type Element: Add // + Mul; // const element_length: usize; // in unstable, or u8 //type ElementBytes : Index+IndexMut; // later - fn const_M() -> Self::Element; - fn const_N() -> Self::Element; + fn const_m() -> Self::Element; + fn const_n() -> Self::Element; fn hash_to_scalar(s: &[u8]) -> Self::Scalar; - fn random_scalar() -> Self::Scalar; + fn random_scalar(cspring: &mut T) -> Self::Scalar; + fn scalar_neg(s: &Self::Scalar) -> Self::Scalar; + fn element_to_bytes(e: &Self::Element) -> Vec; + fn bytes_to_element(b: &[u8]) -> Option; fn basepoint_mult(s: &Self::Scalar) -> Self::Element; fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element; fn add(a: &Self::Element, b: &Self::Element) -> Self::Element; } -struct Ed25519Group; +pub struct Ed25519Group; impl Group for Ed25519Group { type Scalar = c2_Scalar; @@ -33,12 +38,12 @@ impl Group for Ed25519Group { //type ElementBytes = [u8; 32]; //type ScalarBytes - fn const_M() -> c2_Element { + fn const_m() -> c2_Element { // there's a specific value to return here, not this ED25519_BASEPOINT } - fn const_N() -> c2_Element { + fn const_n() -> c2_Element { // there's a specific value to return here, not this ED25519_BASEPOINT } @@ -46,10 +51,25 @@ impl Group for Ed25519Group { fn hash_to_scalar(s: &[u8]) -> c2_Scalar { c2_Scalar::hash_from_bytes::(&s) } - fn random_scalar() -> c2_Scalar { - let mut cspring: OsRng = OsRng::new().unwrap(); - c2_Scalar::random(&mut cspring) + fn random_scalar(cspring: &mut T) -> c2_Scalar { + c2_Scalar::random(cspring) + } + fn scalar_neg(s: &c2_Scalar) -> c2_Scalar { + -s + } + fn element_to_bytes(s: &c2_Element) -> Vec { + s.compress_edwards().as_bytes().to_vec() + } + fn bytes_to_element(b: &[u8]) -> Option { + if b.len() != 32 { return None; } + //let mut bytes: [u8; 32] = + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(b); + let cey = CompressedEdwardsY(bytes); + // CompressedEdwardsY::new(b) + cey.decompress() } + fn basepoint_mult(s: &c2_Scalar) -> c2_Element { //c2_Element::basepoint_mult(s) &ED25519_BASEPOINT * s @@ -68,50 +88,62 @@ impl Group for Ed25519Group { /* "session type pattern" */ -struct SPAKE2 { +pub struct SPAKE2 { //where &G::Scalar: Neg { x: G::Scalar, password: Vec, - idA: Vec, - idB: Vec, + id_a: Vec, + id_b: Vec, msg1: Vec, pw: G::Scalar, } impl SPAKE2 { - pub fn new(password: &[u8], idA: &[u8], idB: &[u8]) -> (SPAKE2, Vec) { + pub fn new(password: &[u8], id_a: &[u8], id_b: &[u8]) + -> (SPAKE2, Vec) { + let mut cspring: OsRng = OsRng::new().unwrap(); + Self::new_internal(password, id_a, id_b, &mut cspring) + } + fn new_internal(password: &[u8], id_a: &[u8], id_b: &[u8], + rng: &mut T) + -> (SPAKE2, Vec) { //let pw: G::Scalar = hash_to_scalar::(password); let pw: G::Scalar = G::hash_to_scalar(password); - let x: G::Scalar = G::random_scalar(); + let x: G::Scalar = G::random_scalar(rng); - // M1 = B*x + M*pw - let M1: G::Element = G::add(&G::basepoint_mult(&x), - &G::scalarmult(&G::const_M(), &pw)); - //let M1: G::Element = &G::basepoint_mult(&x) + &(&G::const_M() * &pw); - let msg1: Vec = unimplemented!(); // M1 to bytes + // m1 = B*x + M*pw + let m1: G::Element = G::add(&G::basepoint_mult(&x), + &G::scalarmult(&G::const_m(), &pw)); + //let m1: G::Element = &G::basepoint_mult(&x) + &(&G::const_m() * &pw); + let msg1: Vec = G::element_to_bytes(&m1); let mut pv = Vec::new(); pv.extend_from_slice(password); - let mut idA_copy = Vec::new(); - idA_copy.extend_from_slice(idA); - let mut idB_copy = Vec::new(); - idB_copy.extend_from_slice(idB); + let mut id_a_copy = Vec::new(); + id_a_copy.extend_from_slice(id_a); + let mut id_b_copy = Vec::new(); + id_b_copy.extend_from_slice(id_b); (SPAKE2 {x: x, - password: pv, - idA: idA_copy, - idB: idB_copy, + password: pv, // string + id_a: id_a_copy, + id_b: id_b_copy, msg1: msg1.clone(), - pw: unimplemented!(), + pw: pw, // scalar }, msg1) } - /*pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { - }*/ + pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { + #![allow(unused_variables)] + // KA = scalarmult(Y* + scalarmult(N, -int(pw)), x) + // key = H(H(pw) + H(idA) + H(idB) + X* + Y* + KA) + let y = G::bytes_to_element(msg2); + let foo = &G::scalarmult(&G::const_n(), &G::scalar_neg(&self.pw)); + + //"nope".to_vec() + //unimplemented!() + Ok(Vec::new()) + } } -/* -{ - let (mut s, msg1) = SPAKE2::(&password, &idA, &idB); - //let msg1 = s.msg1; - let key = s.finish(msg2); +#[cfg(test)] +mod test { } -*/ -- cgit v1.2.3 From 3542becebbba19b16d9fc7f71bf8865570c8bea1 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 00:39:32 -0700 Subject: fix blinding factors, implement full algorithm --- src/lib.rs | 10 ++-- src/spake2.rs | 148 ++++++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 113 insertions(+), 45 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 27e26f0..fd72f6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,11 +12,11 @@ mod tests { use spake2::{SPAKE2, Ed25519Group}; #[test] - fn test_one() { - let (s1, msg1) = SPAKE2::::new(b"password", - b"idA", b"idB"); - let (s2, msg2) = SPAKE2::::new(b"password", - b"idA", b"idB"); + fn test_basic() { + let (s1, msg1) = SPAKE2::::start_a(b"password", + b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::start_b(b"password", + b"idA", b"idB"); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); diff --git a/src/spake2.rs b/src/spake2.rs index 98a1118..4c24258 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -5,7 +5,7 @@ use curve25519_dalek::curve::ExtendedPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT; use curve25519_dalek::curve::CompressedEdwardsY; use rand::{Rng, OsRng}; -use sha2::Sha512; +use sha2::{Sha256, Sha512, Digest}; #[derive(Debug)] pub struct SPAKEErr; @@ -17,6 +17,7 @@ pub trait Group { // + Mul; // const element_length: usize; // in unstable, or u8 //type ElementBytes : Index+IndexMut; // later + type TranscriptHash; fn const_m() -> Self::Element; fn const_n() -> Self::Element; fn hash_to_scalar(s: &[u8]) -> Self::Scalar; @@ -37,15 +38,27 @@ impl Group for Ed25519Group { //type ElementBytes = Vec; //type ElementBytes = [u8; 32]; //type ScalarBytes + type TranscriptHash = Sha256; fn const_m() -> c2_Element { - // there's a specific value to return here, not this - ED25519_BASEPOINT + // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.M.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))" + // 15cfd18e385952982b6a8f8c7854963b58e34388c8e6dae891db756481a02312 + CompressedEdwardsY([ + 0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, + 0x8c, 0x78, 0x54, 0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, + 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64, 0x81, 0xa0, 0x23, 0x12, + ]).decompress().unwrap() } fn const_n() -> c2_Element { - // there's a specific value to return here, not this - ED25519_BASEPOINT + // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.N.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))" + // f04f2e7eb734b2a8f8b472eaf9c3c632576ac64aea650b496a8a20ff00e583c3 + CompressedEdwardsY([ + 0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, + 0xea, 0xf9, 0xc3, 0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, + 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff, 0x00, 0xe5, 0x83, 0xc3, + ]).decompress().unwrap() + } fn hash_to_scalar(s: &[u8]) -> c2_Scalar { @@ -82,64 +95,119 @@ impl Group for Ed25519Group { a + b //a.add(b) } - } /* "session type pattern" */ pub struct SPAKE2 { //where &G::Scalar: Neg { - x: G::Scalar, - password: Vec, + i_am_a: bool, + xy_scalar: G::Scalar, + password_vec: Vec, id_a: Vec, id_b: Vec, msg1: Vec, - pw: G::Scalar, + password_scalar: G::Scalar, } impl SPAKE2 { - pub fn new(password: &[u8], id_a: &[u8], id_b: &[u8]) - -> (SPAKE2, Vec) { - let mut cspring: OsRng = OsRng::new().unwrap(); - Self::new_internal(password, id_a, id_b, &mut cspring) - } - fn new_internal(password: &[u8], id_a: &[u8], id_b: &[u8], + fn start_internal(i_am_a: bool, + password: &[u8], id_a: &[u8], id_b: &[u8], rng: &mut T) -> (SPAKE2, Vec) { - //let pw: G::Scalar = hash_to_scalar::(password); - let pw: G::Scalar = G::hash_to_scalar(password); - let x: G::Scalar = G::random_scalar(rng); - - // m1 = B*x + M*pw - let m1: G::Element = G::add(&G::basepoint_mult(&x), - &G::scalarmult(&G::const_m(), &pw)); - //let m1: G::Element = &G::basepoint_mult(&x) + &(&G::const_m() * &pw); + //let password_scalar: G::Scalar = hash_to_scalar::(password); + let password_scalar: G::Scalar = G::hash_to_scalar(password); + let xy: G::Scalar = G::random_scalar(rng); + + // a: X = B*x + M*pw + // b: Y = B*y + N*pw + let blinding = match i_am_a { + true => G::const_m(), + false => G::const_n(), + }; + let m1: G::Element = G::add(&G::basepoint_mult(&xy), + &G::scalarmult(&blinding, &password_scalar)); + //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar); let msg1: Vec = G::element_to_bytes(&m1); - let mut pv = Vec::new(); - pv.extend_from_slice(password); + let mut password_vec = Vec::new(); + password_vec.extend_from_slice(password); let mut id_a_copy = Vec::new(); id_a_copy.extend_from_slice(id_a); let mut id_b_copy = Vec::new(); id_b_copy.extend_from_slice(id_b); - (SPAKE2 {x: x, - password: pv, // string - id_a: id_a_copy, - id_b: id_b_copy, - msg1: msg1.clone(), - pw: pw, // scalar + (SPAKE2 { + i_am_a: i_am_a, + xy_scalar: xy, + password_vec: password_vec, // string + id_a: id_a_copy, + id_b: id_b_copy, + msg1: msg1.clone(), + password_scalar: password_scalar, // scalar }, msg1) } + pub fn start_a(password: &[u8], id_a: &[u8], id_b: &[u8]) + -> (SPAKE2, Vec) { + let mut cspring: OsRng = OsRng::new().unwrap(); + Self::start_internal(true, password, id_a, id_b, &mut cspring) + } + + pub fn start_b(password: &[u8], id_a: &[u8], id_b: &[u8]) + -> (SPAKE2, Vec) { + let mut cspring: OsRng = OsRng::new().unwrap(); + Self::start_internal(false, password, id_a, id_b, &mut cspring) + } + pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { - #![allow(unused_variables)] - // KA = scalarmult(Y* + scalarmult(N, -int(pw)), x) - // key = H(H(pw) + H(idA) + H(idB) + X* + Y* + KA) - let y = G::bytes_to_element(msg2); - let foo = &G::scalarmult(&G::const_n(), &G::scalar_neg(&self.pw)); - - //"nope".to_vec() - //unimplemented!() - Ok(Vec::new()) + // a: K = (Y+N*(-pw))*x + // b: K = (X+M*(-pw))*y + let msg2_element = G::bytes_to_element(msg2).unwrap(); + let unblinding = match self.i_am_a { + true => G::const_n(), + false => G::const_m(), + }; + let tmp1 = G::scalarmult(&unblinding, + &G::scalar_neg(&self.password_scalar)); + let tmp2 = G::add(&msg2_element, &tmp1); + let key_element = G::scalarmult(&tmp2, &self.xy_scalar); + + // key = H(H(pw) + H(idA) + H(idB) + X + Y + K) + //transcript = b"".join([sha256(pw).digest(), + // sha256(idA).digest(), sha256(idB).digest(), + // X_msg, Y_msg, K_bytes]) + //key = sha256(transcript).digest() + // note that both sides must use the same order + let mut transcript = Vec::::new(); + + let mut pw_hash = Sha256::new(); + pw_hash.input(&self.password_vec); + transcript.extend_from_slice(pw_hash.result().as_slice()); + + let mut ida_hash = Sha256::new(); + ida_hash.input(&self.id_a); + transcript.extend_from_slice(ida_hash.result().as_slice()); + + let mut idb_hash = Sha256::new(); + idb_hash.input(&self.id_b); + transcript.extend_from_slice(idb_hash.result().as_slice()); + + transcript.extend_from_slice(match self.i_am_a { + true => self.msg1.as_slice(), + false => msg2, + }); + transcript.extend_from_slice(match self.i_am_a { + true => msg2, + false => self.msg1.as_slice(), + }); + + let k_bytes = G::element_to_bytes(&key_element); + transcript.extend_from_slice(k_bytes.as_slice()); + + //let mut hash = G::TranscriptHash::default(); + let mut hash = Sha256::default(); + hash.input(transcript.as_slice()); + + Ok(hash.result().to_vec()) } } -- cgit v1.2.3 From 4c9c23bd64f8f8ce831f41aad0ba21157ef372ca Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 00:44:56 -0700 Subject: add github-provided boilerplate: LICENSE, README, .gitignore --- .gitignore | 11 +++++++++-- LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore index 4308d82..50281a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ -target/ -**/*.rs.bk +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5953125 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Brian Warner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..35e16d1 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# spake2.rs +the SPAKE2 password-authenticated key-exchange algorithm, in Rust -- cgit v1.2.3 From 241652bf95a30394c2f255e8516a77c41c69351c Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 00:48:43 -0700 Subject: README: warn people away from thinking this is ready for use --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 35e16d1..b4bfea3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # spake2.rs the SPAKE2 password-authenticated key-exchange algorithm, in Rust + +NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number and has been published to crates.io. -- cgit v1.2.3 From c7c16d751629123fac607c4ecdeb25764bdf9115 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 01:39:25 -0700 Subject: add support for symmetric mode --- src/lib.rs | 11 ++++++ src/spake2.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 107 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fd72f6b..98758da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,17 @@ mod tests { assert_eq!(key1, key2); } + #[test] + fn test_basic_symmetric() { + let (s1, msg1) = SPAKE2::::start_symmetric(b"password", + b"idS"); + let (s2, msg2) = SPAKE2::::start_symmetric(b"password", + b"idS"); + let key1 = s1.finish(msg2.as_slice()).unwrap(); + let key2 = s2.finish(msg1.as_slice()).unwrap(); + assert_eq!(key1, key2); + } + #[test] fn it_works() { diff --git a/src/spake2.rs b/src/spake2.rs index 4c24258..fe0fe4f 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -20,6 +20,7 @@ pub trait Group { type TranscriptHash; fn const_m() -> Self::Element; fn const_n() -> Self::Element; + fn const_s() -> Self::Element; fn hash_to_scalar(s: &[u8]) -> Self::Scalar; fn random_scalar(cspring: &mut T) -> Self::Scalar; fn scalar_neg(s: &Self::Scalar) -> Self::Scalar; @@ -61,6 +62,17 @@ impl Group for Ed25519Group { } + fn const_s() -> c2_Element { + // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.S.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))" + // 6f00dae87c1be1a73b5922ef431cd8f57879569c222d22b1cd71e8546ab8e6f1 + CompressedEdwardsY([ + 0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, + 0xef, 0x43, 0x1c, 0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, + 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54, 0x6a, 0xb8, 0xe6, 0xf1, + ]).decompress().unwrap() + + } + fn hash_to_scalar(s: &[u8]) -> c2_Scalar { c2_Scalar::hash_from_bytes::(&s) } @@ -100,30 +112,39 @@ impl Group for Ed25519Group { /* "session type pattern" */ +enum Side { + A, + B, + Symmetric, +} pub struct SPAKE2 { //where &G::Scalar: Neg { - i_am_a: bool, + side: Side, xy_scalar: G::Scalar, password_vec: Vec, id_a: Vec, id_b: Vec, + id_s: Vec, msg1: Vec, password_scalar: G::Scalar, } impl SPAKE2 { - fn start_internal(i_am_a: bool, - password: &[u8], id_a: &[u8], id_b: &[u8], - rng: &mut T) - -> (SPAKE2, Vec) { + fn start_internal(side: Side, + password: &[u8], + id_a: &[u8], id_b: &[u8], id_s: &[u8], + rng: &mut T) + -> (SPAKE2, Vec) { //let password_scalar: G::Scalar = hash_to_scalar::(password); let password_scalar: G::Scalar = G::hash_to_scalar(password); let xy: G::Scalar = G::random_scalar(rng); // a: X = B*x + M*pw // b: Y = B*y + N*pw - let blinding = match i_am_a { - true => G::const_m(), - false => G::const_n(), + // sym: X = B*x * S*pw + let blinding = match side { + Side::A => G::const_m(), + Side::B => G::const_n(), + Side::Symmetric => G::const_s(), }; let m1: G::Element = G::add(&G::basepoint_mult(&xy), &G::scalarmult(&blinding, &password_scalar)); @@ -135,12 +156,16 @@ impl SPAKE2 { id_a_copy.extend_from_slice(id_a); let mut id_b_copy = Vec::new(); id_b_copy.extend_from_slice(id_b); + let mut id_s_copy = Vec::new(); + id_s_copy.extend_from_slice(id_s); + (SPAKE2 { - i_am_a: i_am_a, + side: side, xy_scalar: xy, password_vec: password_vec, // string id_a: id_a_copy, id_b: id_b_copy, + id_s: id_s_copy, msg1: msg1.clone(), password_scalar: password_scalar, // scalar }, msg1) @@ -149,22 +174,33 @@ impl SPAKE2 { pub fn start_a(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); - Self::start_internal(true, password, id_a, id_b, &mut cspring) + Self::start_internal(Side::A, + password, id_a, id_b, b"", &mut cspring) } pub fn start_b(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); - Self::start_internal(false, password, id_a, id_b, &mut cspring) + Self::start_internal(Side::B, + password, id_a, id_b, b"", &mut cspring) + } + + + pub fn start_symmetric(password: &[u8], id_s: &[u8]) + -> (SPAKE2, Vec) { + let mut cspring: OsRng = OsRng::new().unwrap(); + Self::start_internal(Side::Symmetric, + password, b"", b"", id_s, &mut cspring) } pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { // a: K = (Y+N*(-pw))*x // b: K = (X+M*(-pw))*y let msg2_element = G::bytes_to_element(msg2).unwrap(); - let unblinding = match self.i_am_a { - true => G::const_n(), - false => G::const_m(), + let unblinding = match self.side { + Side::A => G::const_n(), + Side::B => G::const_m(), + Side::Symmetric => G::const_s(), }; let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar)); @@ -177,6 +213,16 @@ impl SPAKE2 { // X_msg, Y_msg, K_bytes]) //key = sha256(transcript).digest() // note that both sides must use the same order + + Ok(match self.side { + Side::A => self.hash_ab(&self.msg1.as_slice(), msg2, &key_element), + Side::B => self.hash_ab(msg2, &self.msg1.as_slice(), &key_element), + Side::Symmetric => self.hash_symmetric(msg2, &key_element), + }) + } + + fn hash_ab(&self, first_msg: &[u8], second_msg: &[u8], + key_element: &G::Element) -> Vec { let mut transcript = Vec::::new(); let mut pw_hash = Sha256::new(); @@ -191,14 +237,8 @@ impl SPAKE2 { idb_hash.input(&self.id_b); transcript.extend_from_slice(idb_hash.result().as_slice()); - transcript.extend_from_slice(match self.i_am_a { - true => self.msg1.as_slice(), - false => msg2, - }); - transcript.extend_from_slice(match self.i_am_a { - true => msg2, - false => self.msg1.as_slice(), - }); + transcript.extend_from_slice(first_msg); + transcript.extend_from_slice(second_msg); let k_bytes = G::element_to_bytes(&key_element); transcript.extend_from_slice(k_bytes.as_slice()); @@ -206,8 +246,41 @@ impl SPAKE2 { //let mut hash = G::TranscriptHash::default(); let mut hash = Sha256::default(); hash.input(transcript.as_slice()); + hash.result().to_vec() + } + + fn hash_symmetric(&self, msg2: &[u8], key_element: &G::Element) -> Vec { + // # since we don't know which side is which, we must sort the messages + // first_msg, second_msg = sorted([msg1, msg2]) + // transcript = b"".join([sha256(pw).digest(), + // sha256(idSymmetric).digest(), + // first_msg, second_msg, K_bytes]) + let mut transcript = Vec::::new(); + + let mut pw_hash = Sha256::new(); + pw_hash.input(&self.password_vec); + transcript.extend_from_slice(pw_hash.result().as_slice()); - Ok(hash.result().to_vec()) + let mut ids_hash = Sha256::new(); + ids_hash.input(&self.id_s); + transcript.extend_from_slice(ids_hash.result().as_slice()); + + let msg_u = self.msg1.as_slice(); + let msg_v = msg2; + if msg_u < msg_v { + transcript.extend_from_slice(&msg_u); + transcript.extend_from_slice(msg_v); + } else { + transcript.extend_from_slice(msg_v); + transcript.extend_from_slice(&msg_u); + } + + let k_bytes = G::element_to_bytes(&key_element); + transcript.extend_from_slice(k_bytes.as_slice()); + + let mut hash = Sha256::default(); + hash.input(transcript.as_slice()); + hash.result().to_vec() } } -- cgit v1.2.3 From 609ec7fcbd68dc3ba7e2c194991540f17ab9b2d7 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 01:40:33 -0700 Subject: add travis-CI --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..57ec1af --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: rust + +rust: + - stable + +script: + - cargo test -- cgit v1.2.3 From ae521873c62786958051c2ae8aba5eda3e4c8114 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 01:45:41 -0700 Subject: README: add travis badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b4bfea3..228e303 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # spake2.rs the SPAKE2 password-authenticated key-exchange algorithm, in Rust +[![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) + NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number and has been published to crates.io. -- cgit v1.2.3 From 2c0792059c573cf12870f2aa24c9df993deef983 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 18:05:27 -0700 Subject: improvements for tests, which of course fail --- Cargo.toml | 4 +- src/lib.rs | 6 +- src/spake2.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 163 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bdf1e87..bf860d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,6 @@ curve25519-dalek = "0.8.0" rand = "0.3.0" sha2 = "0.4.0" - +[dev-dependencies] +num-bigint = "0.1" +hex = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 98758da..4342a74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,15 @@ extern crate rand; extern crate curve25519_dalek; extern crate sha2; -extern crate core; mod spake2; pub use spake2::*; +#[cfg(test)] +extern crate num_bigint; +#[cfg(test)] +extern crate hex; + #[cfg(test)] mod tests { use spake2::{SPAKE2, Ed25519Group}; diff --git a/src/spake2.rs b/src/spake2.rs index fe0fe4f..e3efa2a 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] use curve25519_dalek::scalar::Scalar as c2_Scalar; use curve25519_dalek::curve::ExtendedPoint as c2_Element; @@ -26,6 +25,7 @@ pub trait Group { fn scalar_neg(s: &Self::Scalar) -> Self::Scalar; fn element_to_bytes(e: &Self::Element) -> Vec; fn bytes_to_element(b: &[u8]) -> Option; + fn element_length() -> usize; fn basepoint_mult(s: &Self::Scalar) -> Self::Element; fn scalarmult(e: &Self::Element, s: &Self::Scalar) -> Self::Element; fn add(a: &Self::Element, b: &Self::Element) -> Self::Element; @@ -85,6 +85,9 @@ impl Group for Ed25519Group { fn element_to_bytes(s: &c2_Element) -> Vec { s.compress_edwards().as_bytes().to_vec() } + fn element_length() -> usize { + 32 + } fn bytes_to_element(b: &[u8]) -> Option { if b.len() != 32 { return None; } //let mut bytes: [u8; 32] = @@ -129,14 +132,12 @@ pub struct SPAKE2 { //where &G::Scalar: Neg { } impl SPAKE2 { - fn start_internal(side: Side, - password: &[u8], - id_a: &[u8], id_b: &[u8], id_s: &[u8], - rng: &mut T) - -> (SPAKE2, Vec) { + fn start_internal(side: Side, + password: &[u8], + id_a: &[u8], id_b: &[u8], id_s: &[u8], + xy_scalar: G::Scalar) -> (SPAKE2, Vec) { //let password_scalar: G::Scalar = hash_to_scalar::(password); let password_scalar: G::Scalar = G::hash_to_scalar(password); - let xy: G::Scalar = G::random_scalar(rng); // a: X = B*x + M*pw // b: Y = B*y + N*pw @@ -146,7 +147,7 @@ impl SPAKE2 { Side::B => G::const_n(), Side::Symmetric => G::const_s(), }; - let m1: G::Element = G::add(&G::basepoint_mult(&xy), + let m1: G::Element = G::add(&G::basepoint_mult(&xy_scalar), &G::scalarmult(&blinding, &password_scalar)); //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar); let msg1: Vec = G::element_to_bytes(&m1); @@ -159,44 +160,75 @@ impl SPAKE2 { let mut id_s_copy = Vec::new(); id_s_copy.extend_from_slice(id_s); + let mut msg_and_side = Vec::new(); + msg_and_side.push(match side { + Side::A => 0x41, // 'A' + Side::B => 0x42, // 'B' + Side::Symmetric => 0x53, // 'S' + }); + msg_and_side.extend_from_slice(&msg1); + (SPAKE2 { side: side, - xy_scalar: xy, + xy_scalar: xy_scalar, password_vec: password_vec, // string id_a: id_a_copy, id_b: id_b_copy, id_s: id_s_copy, msg1: msg1.clone(), password_scalar: password_scalar, // scalar - }, msg1) + }, msg_and_side) + } + + fn start_a_internal(password: &[u8], id_a: &[u8], id_b: &[u8], + xy_scalar: G::Scalar) -> (SPAKE2, Vec) { + Self::start_internal(Side::A, + password, id_a, id_b, b"", xy_scalar) + } + + fn start_b_internal(password: &[u8], id_a: &[u8], id_b: &[u8], + xy_scalar: G::Scalar) -> (SPAKE2, Vec) { + Self::start_internal(Side::B, + password, id_a, id_b, b"", xy_scalar) + } + + fn start_symmetric_internal(password: &[u8], id_s: &[u8], + xy_scalar: G::Scalar) -> (SPAKE2, Vec) { + Self::start_internal(Side::Symmetric, + password, b"", b"", id_s, xy_scalar) } + pub fn start_a(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); - Self::start_internal(Side::A, - password, id_a, id_b, b"", &mut cspring) + let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); + Self::start_a_internal(password, id_a, id_b, xy_scalar) } pub fn start_b(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); - Self::start_internal(Side::B, - password, id_a, id_b, b"", &mut cspring) + let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); + Self::start_b_internal(password, id_a, id_b, xy_scalar) } - pub fn start_symmetric(password: &[u8], id_s: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); - Self::start_internal(Side::Symmetric, - password, b"", b"", id_s, &mut cspring) + let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); + Self::start_symmetric_internal(password, id_s, xy_scalar) } pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { + if msg2.len() != 1 + G::element_length() { + return Err(SPAKEErr); //("inbound message is the wrong length")); + } + let msg_side = msg2[0]; + + let msg2_element = G::bytes_to_element(&msg2[1..]).unwrap(); // a: K = (Y+N*(-pw))*x // b: K = (X+M*(-pw))*y - let msg2_element = G::bytes_to_element(msg2).unwrap(); let unblinding = match self.side { Side::A => G::const_n(), Side::B => G::const_m(), @@ -287,4 +319,109 @@ impl SPAKE2 { #[cfg(test)] mod test { + /* This compares results against the python compatibility tests: + spake2.test.test_compat.SPAKE2.test_asymmetric . The python test passes a + deterministic RNG (used only for tests, of course) into the per-Group + "random_scalar()" function, which results in some particular scalar. + */ + use num_bigint::BigUint; + use curve25519_dalek::scalar::Scalar; + use spake2::{SPAKE2, Ed25519Group}; + + // the python tests show the long-integer form of scalars. the rust code + // wants an array of bytes (little-endian). Make sure the way we convert + // things works correctly. + + fn decimal_to_scalar(d: &[u8]) -> Scalar { + let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le(); + assert_eq!(bytes.len(), 32); + let mut s = Scalar([0u8; 32]); + s.0.copy_from_slice(&bytes); + s + } + + #[test] + fn test_convert() { + let t1_decimal = b"2238329342913194256032495932344128051776374960164957527413114840482143558222"; + let t1_scalar = decimal_to_scalar(t1_decimal); + let expected: Scalar = Scalar( + [0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, + 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, 0x7d, 0x52, + 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, + 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9, 0xf2, 0x04]); + assert_eq!(t1_scalar, expected); + //println!("t1_scalar is {:?}", t1_scalar); + } + + use hex::ToHex; + use curve25519_dalek::constants::ED25519_BASEPOINT; + #[test] + fn test_serialize_basepoint() { + // make sure elements are serialized same as the python library + let exp = "5866666666666666666666666666666666666666666666666666666666666666"; + let base_vec = ED25519_BASEPOINT.compress_edwards().as_bytes().to_vec(); + let base_hex = base_vec.to_hex(); + println!("exp: {:?}", exp); + println!("got: {:?}", base_hex); + assert_eq!(exp, base_hex); + } + + #[test] + fn test_sizes() { + let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", + b"idB"); + assert_eq!(msg1.len(), 1+32); + let (s2, msg2) = SPAKE2::::start_b(b"password", b"idA", + b"idB"); + assert_eq!(msg2.len(), 1+32); + let key1 = s1.finish(&msg2).unwrap(); + let key2 = s2.finish(&msg1).unwrap(); + assert_eq!(key1.len(), 32); + assert_eq!(key2.len(), 32); + + let (s1, msg1) = SPAKE2::::start_symmetric(b"password", + b"idS"); + assert_eq!(msg1.len(), 1+32); + let (s2, msg2) = SPAKE2::::start_symmetric(b"password", + b"idS"); + assert_eq!(msg2.len(), 1+32); + let key1 = s1.finish(&msg2).unwrap(); + let key2 = s2.finish(&msg1).unwrap(); + assert_eq!(key1.len(), 32); + assert_eq!(key2.len(), 32); + } + + #[test] + fn test_asymmetric() { + let scalar_a = decimal_to_scalar(b"2611694063369306139794446498317402240796898290761098242657700742213257926693"); + let scalar_b = decimal_to_scalar(b"7002393159576182977806091886122272758628412261510164356026361256515836884383"); + let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839"); + + println!("scalar_a is {}", scalar_a.as_bytes().to_hex()); + + let (s1, msg1) = SPAKE2::::start_a_internal( + b"password", b"idA", b"idB", scalar_a); + let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9"; + + println!("msg1: {:?}", msg1.to_hex()); + println!("exp : {:?}", expected_msg1); + + assert_eq!(expected_pw_scalar.as_bytes().to_hex(), + s1.xy_scalar.as_bytes().to_hex()); + assert_eq!(msg1.to_hex(), expected_msg1); + + let (s2, msg2) = SPAKE2::::start_b_internal( + b"password", b"idA", b"idB", scalar_b); + assert_eq!(expected_pw_scalar, s2.xy_scalar); + assert_eq!(msg2.to_hex(), + "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"); + + let key1 = s1.finish(&msg2).unwrap(); + let key2 = s2.finish(&msg1).unwrap(); + assert_eq!(key1, key2); + assert_eq!(key1.to_hex(), + "a480bca13fa04464bb644f10e340125e96c9494f7399fef7c2bda67eb0fdf06d"); + } + + } -- cgit v1.2.3 From f490a799829247aa0026f73d70178773b43adec7 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 24 May 2017 18:21:06 -0700 Subject: change the nature of the failure. still wrong, but better wrong! --- src/spake2.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index e3efa2a..d46c92e 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -7,7 +7,7 @@ use rand::{Rng, OsRng}; use sha2::{Sha256, Sha512, Digest}; #[derive(Debug)] -pub struct SPAKEErr; +pub struct SPAKEErr ( String ); pub trait Group { type Scalar; @@ -222,11 +222,15 @@ impl SPAKE2 { pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { if msg2.len() != 1 + G::element_length() { - return Err(SPAKEErr); //("inbound message is the wrong length")); + return Err(SPAKEErr(String::from("inbound message is the wrong length"))) } let msg_side = msg2[0]; - let msg2_element = G::bytes_to_element(&msg2[1..]).unwrap(); + let msg2_element = match G::bytes_to_element(&msg2[1..]) { + Some(x) => x, + None => {return Err(SPAKEErr(String::from("message corrupted")))}, + }; + // a: K = (Y+N*(-pw))*x // b: K = (X+M*(-pw))*y let unblinding = match self.side { -- cgit v1.2.3 From 6d76b86c3215b12a06541416f4f155ae3ccf75e4 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 25 May 2017 14:32:19 -0700 Subject: slow progress, got password-to-scalar working * ported spake2.py password-to-scalar function, since dalek's built-in one does it differently * added "side" byte in messages: emit in start(), strip+check in input() * rewrote transcript-hashing (since transcript is fixed-size) This adds a lot of debug prints, and moves a bunch of test-only code into the top level, all of which will need to be undone eventually. --- Cargo.toml | 6 ++- src/lib.rs | 12 +++-- src/spake2.rs | 147 ++++++++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 124 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf860d3..06e3c4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,10 @@ authors = ["Brian Warner "] #rust-crypto = "^0.2" curve25519-dalek = "0.8.0" rand = "0.3.0" -sha2 = "0.4.0" +#sha2 = "0.4.0" +rust-crypto = "0.2" +num-bigint = "0.1" +hex = "0.2" [dev-dependencies] -num-bigint = "0.1" hex = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 4342a74..9083473 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,17 @@ extern crate rand; extern crate curve25519_dalek; -extern crate sha2; +//extern crate sha2; +extern crate crypto; +extern crate num_bigint; + +extern crate hex; mod spake2; pub use spake2::*; -#[cfg(test)] -extern crate num_bigint; -#[cfg(test)] -extern crate hex; +//#[cfg(test)] +//extern crate hex; #[cfg(test)] mod tests { diff --git a/src/spake2.rs b/src/spake2.rs index d46c92e..43b81dc 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,10 +1,17 @@ +#![allow(dead_code)] use curve25519_dalek::scalar::Scalar as c2_Scalar; use curve25519_dalek::curve::ExtendedPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT; use curve25519_dalek::curve::CompressedEdwardsY; use rand::{Rng, OsRng}; -use sha2::{Sha256, Sha512, Digest}; +//use sha2::{Sha256, Sha512, Digest}; +use crypto::sha2::Sha256; +use crypto::digest::Digest; +use crypto::hkdf; +use num_bigint::BigUint; + +use hex::ToHex; #[derive(Debug)] pub struct SPAKEErr ( String ); @@ -74,7 +81,29 @@ impl Group for Ed25519Group { } fn hash_to_scalar(s: &[u8]) -> c2_Scalar { - c2_Scalar::hash_from_bytes::(&s) + //c2_Scalar::hash_from_bytes::(&s) + // spake2.py does: + // h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16) + // i = int(h, 16) + // i % q + + let mut prk = [0u8; 32]; + let digest = Sha256::new(); + hkdf::hkdf_extract(digest, b"", s, &mut prk); + let mut okm = [0u8; 32+16]; + hkdf::hkdf_expand(digest, &prk, b"SPAKE2 pw", &mut okm); + //okm[32+16-2] = 1; + println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok + + let mut reducible = [0u8; 64]; // little-endian + for i in 0..32+16 { + reducible[32+16-1-i] = okm[i]; + } + println!("reducible: {}", reducible.iter().to_hex()); + let reduced = c2_Scalar::reduce(&reducible); + println!("reduced: {}", reduced.as_bytes().to_hex()); + println!("done"); + reduced } fn random_scalar(cspring: &mut T) -> c2_Scalar { c2_Scalar::random(cspring) @@ -112,6 +141,14 @@ impl Group for Ed25519Group { } } +fn decimal_to_scalar(d: &[u8]) -> c2_Scalar { + let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le(); + assert_eq!(bytes.len(), 32); + let mut s = c2_Scalar([0u8; 32]); + s.0.copy_from_slice(&bytes); + s +} + /* "session type pattern" */ @@ -226,6 +263,21 @@ impl SPAKE2 { } let msg_side = msg2[0]; + match self.side { + Side::A => match msg_side { + 0x42 => (), // 'B' + _ => return Err(SPAKEErr(String::from("bad side"))), + }, + Side::B => match msg_side { + 0x41 => (), // 'A' + _ => return Err(SPAKEErr(String::from("bad side"))), + }, + Side::Symmetric => match msg_side { + 0x53 => (), // 'S' + _ => return Err(SPAKEErr(String::from("bad side"))), + }, + } + let msg2_element = match G::bytes_to_element(&msg2[1..]) { Some(x) => x, None => {return Err(SPAKEErr(String::from("message corrupted")))}, @@ -259,30 +311,39 @@ impl SPAKE2 { fn hash_ab(&self, first_msg: &[u8], second_msg: &[u8], key_element: &G::Element) -> Vec { - let mut transcript = Vec::::new(); + // the transcript is fixed-length, made up of 6 32-byte values: + // byte 0-31 : sha256(pw) + // byte 32-63 : sha256(idA) + // byte 64-95 : sha256(idB) + // byte 96-127 : X_msg + // byte 128-159: Y_msg + // byte 160-191: K_bytes + let mut transcript = [0u8; 6*32]; let mut pw_hash = Sha256::new(); pw_hash.input(&self.password_vec); - transcript.extend_from_slice(pw_hash.result().as_slice()); + pw_hash.result(&mut transcript[0..32]); let mut ida_hash = Sha256::new(); ida_hash.input(&self.id_a); - transcript.extend_from_slice(ida_hash.result().as_slice()); + ida_hash.result(&mut transcript[32..64]); let mut idb_hash = Sha256::new(); idb_hash.input(&self.id_b); - transcript.extend_from_slice(idb_hash.result().as_slice()); + idb_hash.result(&mut transcript[64..96]); - transcript.extend_from_slice(first_msg); - transcript.extend_from_slice(second_msg); + transcript[96..128].copy_from_slice(first_msg); + transcript[128..160].copy_from_slice(second_msg); let k_bytes = G::element_to_bytes(&key_element); - transcript.extend_from_slice(k_bytes.as_slice()); + transcript[160..192].copy_from_slice(k_bytes.as_slice()); //let mut hash = G::TranscriptHash::default(); - let mut hash = Sha256::default(); - hash.input(transcript.as_slice()); - hash.result().to_vec() + let mut hash = Sha256::new(); + hash.input(&transcript); + let mut out = [0u8; 32]; + hash.result(&mut out); + out.to_vec() } fn hash_symmetric(&self, msg2: &[u8], key_element: &G::Element) -> Vec { @@ -291,32 +352,41 @@ impl SPAKE2 { // transcript = b"".join([sha256(pw).digest(), // sha256(idSymmetric).digest(), // first_msg, second_msg, K_bytes]) - let mut transcript = Vec::::new(); + + // the transcript is fixed-length, made up of 5 32-byte values: + // byte 0-31 : sha256(pw) + // byte 32-63 : sha256(idSymmetric) + // byte 64-95 : X_msg + // byte 96-127 : Y_msg + // byte 128-159: K_bytes + let mut transcript = [0u8; 5*32]; let mut pw_hash = Sha256::new(); pw_hash.input(&self.password_vec); - transcript.extend_from_slice(pw_hash.result().as_slice()); + pw_hash.result(&mut transcript[0..32]); let mut ids_hash = Sha256::new(); ids_hash.input(&self.id_s); - transcript.extend_from_slice(ids_hash.result().as_slice()); + ids_hash.result(&mut transcript[32..64]); let msg_u = self.msg1.as_slice(); let msg_v = msg2; if msg_u < msg_v { - transcript.extend_from_slice(&msg_u); - transcript.extend_from_slice(msg_v); + transcript[64..96].copy_from_slice(&msg_u); + transcript[96..128].copy_from_slice(msg_v); } else { - transcript.extend_from_slice(msg_v); - transcript.extend_from_slice(&msg_u); + transcript[64..96].copy_from_slice(msg_v); + transcript[96..128].copy_from_slice(&msg_u); } let k_bytes = G::element_to_bytes(&key_element); - transcript.extend_from_slice(k_bytes.as_slice()); + transcript[128..160].copy_from_slice(k_bytes.as_slice()); - let mut hash = Sha256::default(); - hash.input(transcript.as_slice()); - hash.result().to_vec() + let mut hash = Sha256::new(); + hash.input(&transcript); + let mut out = [0u8; 32]; + hash.result(&mut out); + out.to_vec() } } @@ -328,22 +398,16 @@ mod test { deterministic RNG (used only for tests, of course) into the per-Group "random_scalar()" function, which results in some particular scalar. */ - use num_bigint::BigUint; use curve25519_dalek::scalar::Scalar; + use curve25519_dalek::constants::ED25519_BASEPOINT; use spake2::{SPAKE2, Ed25519Group}; + use hex::ToHex; + use super::*; // the python tests show the long-integer form of scalars. the rust code // wants an array of bytes (little-endian). Make sure the way we convert // things works correctly. - fn decimal_to_scalar(d: &[u8]) -> Scalar { - let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le(); - assert_eq!(bytes.len(), 32); - let mut s = Scalar([0u8; 32]); - s.0.copy_from_slice(&bytes); - s - } - #[test] fn test_convert() { let t1_decimal = b"2238329342913194256032495932344128051776374960164957527413114840482143558222"; @@ -357,8 +421,6 @@ mod test { //println!("t1_scalar is {:?}", t1_scalar); } - use hex::ToHex; - use curve25519_dalek::constants::ED25519_BASEPOINT; #[test] fn test_serialize_basepoint() { // make sure elements are serialized same as the python library @@ -370,6 +432,16 @@ mod test { assert_eq!(exp, base_hex); } + #[test] + fn test_password_to_scalar() { + let password = b"password"; + let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839"); + let pw_scalar = Ed25519Group::hash_to_scalar(password); + println!("exp: {:?}", expected_pw_scalar.as_bytes().to_hex()); + println!("got: {:?}", pw_scalar.as_bytes().to_hex()); + assert_eq!(&pw_scalar, &expected_pw_scalar); + } + #[test] fn test_sizes() { let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", @@ -407,8 +479,15 @@ mod test { b"password", b"idA", b"idB", scalar_a); let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9"; + println!(); + println!("xys1: {:?}", s1.xy_scalar.as_bytes().to_hex()); + println!(); + println!("pws1: {:?}", s1.password_scalar.as_bytes().to_hex()); + println!("exp : {:?}", expected_pw_scalar.as_bytes().to_hex()); + println!(); println!("msg1: {:?}", msg1.to_hex()); println!("exp : {:?}", expected_msg1); + println!(); assert_eq!(expected_pw_scalar.as_bytes().to_hex(), s1.xy_scalar.as_bytes().to_hex()); -- cgit v1.2.3 From c87a7cafb9215ef56a3db764417b5ec778427b81 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 25 May 2017 14:39:45 -0700 Subject: fix test_sizes: needed to strip side-byte before transcript hash --- src/spake2.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index 43b81dc..a21d662 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -303,14 +303,16 @@ impl SPAKE2 { // note that both sides must use the same order Ok(match self.side { - Side::A => self.hash_ab(&self.msg1.as_slice(), msg2, &key_element), - Side::B => self.hash_ab(msg2, &self.msg1.as_slice(), &key_element), - Side::Symmetric => self.hash_symmetric(msg2, &key_element), + Side::A => self.hash_ab(self.msg1.as_slice(), &msg2[1..], &key_element), + Side::B => self.hash_ab(&msg2[1..], self.msg1.as_slice(), &key_element), + Side::Symmetric => self.hash_symmetric(&msg2[1..], &key_element), }) } fn hash_ab(&self, first_msg: &[u8], second_msg: &[u8], key_element: &G::Element) -> Vec { + assert_eq!(first_msg.len(), 32); + assert_eq!(second_msg.len(), 32); // the transcript is fixed-length, made up of 6 32-byte values: // byte 0-31 : sha256(pw) // byte 32-63 : sha256(idA) @@ -347,6 +349,7 @@ impl SPAKE2 { } fn hash_symmetric(&self, msg2: &[u8], key_element: &G::Element) -> Vec { + assert_eq!(msg2.len(), 32); // # since we don't know which side is which, we must sort the messages // first_msg, second_msg = sorted([msg1, msg2]) // transcript = b"".join([sha256(pw).digest(), -- cgit v1.2.3 From 161f8227e70f0e58b8ff92c37e81bf0a28642c55 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 25 May 2017 14:41:39 -0700 Subject: fix incorrect tests the messages now match what I expect, but the final key does not. Next step is to compare transcripts. --- src/spake2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index a21d662..11f4f6f 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -493,12 +493,12 @@ mod test { println!(); assert_eq!(expected_pw_scalar.as_bytes().to_hex(), - s1.xy_scalar.as_bytes().to_hex()); + s1.password_scalar.as_bytes().to_hex()); assert_eq!(msg1.to_hex(), expected_msg1); let (s2, msg2) = SPAKE2::::start_b_internal( b"password", b"idA", b"idB", scalar_b); - assert_eq!(expected_pw_scalar, s2.xy_scalar); + assert_eq!(expected_pw_scalar, s2.password_scalar); assert_eq!(msg2.to_hex(), "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"); -- cgit v1.2.3 From 33522ac59896b357c50d9471d09612f922a4a47b Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 30 May 2017 23:40:20 +0100 Subject: fix all tests, refactor some code for easier testing --- Cargo.toml | 2 +- src/spake2.rs | 256 +++++++++++++++++++++++++++++++++------------------------- 2 files changed, 147 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 06e3c4e..313c7be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Brian Warner "] [dependencies] #rust-crypto = "^0.2" -curve25519-dalek = "0.8.0" +curve25519-dalek = "0.9.0" rand = "0.3.0" #sha2 = "0.4.0" rust-crypto = "0.2" diff --git a/src/spake2.rs b/src/spake2.rs index 11f4f6f..d77ea68 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -81,29 +81,7 @@ impl Group for Ed25519Group { } fn hash_to_scalar(s: &[u8]) -> c2_Scalar { - //c2_Scalar::hash_from_bytes::(&s) - // spake2.py does: - // h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16) - // i = int(h, 16) - // i % q - - let mut prk = [0u8; 32]; - let digest = Sha256::new(); - hkdf::hkdf_extract(digest, b"", s, &mut prk); - let mut okm = [0u8; 32+16]; - hkdf::hkdf_expand(digest, &prk, b"SPAKE2 pw", &mut okm); - //okm[32+16-2] = 1; - println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok - - let mut reducible = [0u8; 64]; // little-endian - for i in 0..32+16 { - reducible[32+16-1-i] = okm[i]; - } - println!("reducible: {}", reducible.iter().to_hex()); - let reduced = c2_Scalar::reduce(&reducible); - println!("reduced: {}", reduced.as_bytes().to_hex()); - println!("done"); - reduced + ed25519_hash_to_scalar(s) } fn random_scalar(cspring: &mut T) -> c2_Scalar { c2_Scalar::random(cspring) @@ -149,6 +127,114 @@ fn decimal_to_scalar(d: &[u8]) -> c2_Scalar { s } +fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { + //c2_Scalar::hash_from_bytes::(&s) + // spake2.py does: + // h = HKDF(salt=b"", ikm=s, hash=SHA256, info=b"SPAKE2 pw", len=32+16) + // i = int(h, 16) + // i % q + + let mut prk = [0u8; 32]; + let digest = Sha256::new(); + hkdf::hkdf_extract(digest, b"", s, &mut prk); + let mut okm = [0u8; 32+16]; + hkdf::hkdf_expand(digest, &prk, b"SPAKE2 pw", &mut okm); + //okm[32+16-2] = 1; + println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok + + let mut reducible = [0u8; 64]; // little-endian + for i in 0..32+16 { + reducible[32+16-1-i] = okm[i]; + } + println!("reducible: {}", reducible.iter().to_hex()); + let reduced = c2_Scalar::reduce(&reducible); + println!("reduced: {}", reduced.as_bytes().to_hex()); + println!("done"); + reduced +} + +fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], + first_msg: &[u8], second_msg: &[u8], key_bytes: &[u8]) + -> Vec { + assert_eq!(first_msg.len(), 32); + assert_eq!(second_msg.len(), 32); + // the transcript is fixed-length, made up of 6 32-byte values: + // byte 0-31 : sha256(pw) + // byte 32-63 : sha256(idA) + // byte 64-95 : sha256(idB) + // byte 96-127 : X_msg + // byte 128-159: Y_msg + // byte 160-191: K_bytes + let mut transcript = [0u8; 6*32]; + + let mut pw_hash = Sha256::new(); + pw_hash.input(&password_vec); + pw_hash.result(&mut transcript[0..32]); + + let mut ida_hash = Sha256::new(); + ida_hash.input(&id_a); + ida_hash.result(&mut transcript[32..64]); + + let mut idb_hash = Sha256::new(); + idb_hash.input(&id_b); + idb_hash.result(&mut transcript[64..96]); + + transcript[96..128].copy_from_slice(first_msg); + transcript[128..160].copy_from_slice(second_msg); + transcript[160..192].copy_from_slice(key_bytes); + + println!("transcript: {:?}", transcript.iter().to_hex()); + + //let mut hash = G::TranscriptHash::default(); + let mut hash = Sha256::new(); + hash.input(&transcript); + let mut out = [0u8; 32]; + hash.result(&mut out); + out.to_vec() +} + +fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], + msg_u: &[u8], msg_v: &[u8], key_bytes: &[u8]) + -> Vec { + assert_eq!(msg_u.len(), 32); + assert_eq!(msg_v.len(), 32); + // # since we don't know which side is which, we must sort the messages + // first_msg, second_msg = sorted([msg1, msg2]) + // transcript = b"".join([sha256(pw).digest(), + // sha256(idSymmetric).digest(), + // first_msg, second_msg, K_bytes]) + + // the transcript is fixed-length, made up of 5 32-byte values: + // byte 0-31 : sha256(pw) + // byte 32-63 : sha256(idSymmetric) + // byte 64-95 : X_msg + // byte 96-127 : Y_msg + // byte 128-159: K_bytes + let mut transcript = [0u8; 5*32]; + + let mut pw_hash = Sha256::new(); + pw_hash.input(&password_vec); + pw_hash.result(&mut transcript[0..32]); + + let mut ids_hash = Sha256::new(); + ids_hash.input(&id_s); + ids_hash.result(&mut transcript[32..64]); + + if msg_u < msg_v { + transcript[64..96].copy_from_slice(&msg_u); + transcript[96..128].copy_from_slice(msg_v); + } else { + transcript[64..96].copy_from_slice(msg_v); + transcript[96..128].copy_from_slice(&msg_u); + } + transcript[128..160].copy_from_slice(key_bytes); + + let mut hash = Sha256::new(); + hash.input(&transcript); + let mut out = [0u8; 32]; + hash.result(&mut out); + out.to_vec() +} /* "session type pattern" */ @@ -294,6 +380,7 @@ impl SPAKE2 { &G::scalar_neg(&self.password_scalar)); let tmp2 = G::add(&msg2_element, &tmp1); let key_element = G::scalarmult(&tmp2, &self.xy_scalar); + let key_bytes = G::element_to_bytes(&key_element); // key = H(H(pw) + H(idA) + H(idB) + X + Y + K) //transcript = b"".join([sha256(pw).digest(), @@ -303,94 +390,20 @@ impl SPAKE2 { // note that both sides must use the same order Ok(match self.side { - Side::A => self.hash_ab(self.msg1.as_slice(), &msg2[1..], &key_element), - Side::B => self.hash_ab(&msg2[1..], self.msg1.as_slice(), &key_element), - Side::Symmetric => self.hash_symmetric(&msg2[1..], &key_element), + Side::A => ed25519_hash_ab(&self.password_vec, + &self.id_a, &self.id_b, + self.msg1.as_slice(), &msg2[1..], + &key_bytes), + Side::B => ed25519_hash_ab(&self.password_vec, + &self.id_a, &self.id_b, + &msg2[1..], self.msg1.as_slice(), + &key_bytes), + Side::Symmetric => ed25519_hash_symmetric(&self.password_vec, + &self.id_s, + &self.msg1, &msg2[1..], + &key_bytes), }) } - - fn hash_ab(&self, first_msg: &[u8], second_msg: &[u8], - key_element: &G::Element) -> Vec { - assert_eq!(first_msg.len(), 32); - assert_eq!(second_msg.len(), 32); - // the transcript is fixed-length, made up of 6 32-byte values: - // byte 0-31 : sha256(pw) - // byte 32-63 : sha256(idA) - // byte 64-95 : sha256(idB) - // byte 96-127 : X_msg - // byte 128-159: Y_msg - // byte 160-191: K_bytes - let mut transcript = [0u8; 6*32]; - - let mut pw_hash = Sha256::new(); - pw_hash.input(&self.password_vec); - pw_hash.result(&mut transcript[0..32]); - - let mut ida_hash = Sha256::new(); - ida_hash.input(&self.id_a); - ida_hash.result(&mut transcript[32..64]); - - let mut idb_hash = Sha256::new(); - idb_hash.input(&self.id_b); - idb_hash.result(&mut transcript[64..96]); - - transcript[96..128].copy_from_slice(first_msg); - transcript[128..160].copy_from_slice(second_msg); - - let k_bytes = G::element_to_bytes(&key_element); - transcript[160..192].copy_from_slice(k_bytes.as_slice()); - - //let mut hash = G::TranscriptHash::default(); - let mut hash = Sha256::new(); - hash.input(&transcript); - let mut out = [0u8; 32]; - hash.result(&mut out); - out.to_vec() - } - - fn hash_symmetric(&self, msg2: &[u8], key_element: &G::Element) -> Vec { - assert_eq!(msg2.len(), 32); - // # since we don't know which side is which, we must sort the messages - // first_msg, second_msg = sorted([msg1, msg2]) - // transcript = b"".join([sha256(pw).digest(), - // sha256(idSymmetric).digest(), - // first_msg, second_msg, K_bytes]) - - // the transcript is fixed-length, made up of 5 32-byte values: - // byte 0-31 : sha256(pw) - // byte 32-63 : sha256(idSymmetric) - // byte 64-95 : X_msg - // byte 96-127 : Y_msg - // byte 128-159: K_bytes - let mut transcript = [0u8; 5*32]; - - let mut pw_hash = Sha256::new(); - pw_hash.input(&self.password_vec); - pw_hash.result(&mut transcript[0..32]); - - let mut ids_hash = Sha256::new(); - ids_hash.input(&self.id_s); - ids_hash.result(&mut transcript[32..64]); - - let msg_u = self.msg1.as_slice(); - let msg_v = msg2; - if msg_u < msg_v { - transcript[64..96].copy_from_slice(&msg_u); - transcript[96..128].copy_from_slice(msg_v); - } else { - transcript[64..96].copy_from_slice(msg_v); - transcript[96..128].copy_from_slice(&msg_u); - } - - let k_bytes = G::element_to_bytes(&key_element); - transcript[128..160].copy_from_slice(k_bytes.as_slice()); - - let mut hash = Sha256::new(); - hash.input(&transcript); - let mut out = [0u8; 32]; - hash.result(&mut out); - out.to_vec() - } } @@ -470,6 +483,29 @@ mod test { assert_eq!(key2.len(), 32); } + #[test] + fn test_hash_ab() { + let key = ed25519_hash_ab( + b"pw", + b"idA", b"idB", + b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // len=32 + b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"); + let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd"; + assert_eq!(key.to_hex(), expected_key); + } + + #[test] + fn test_hash_symmetric() { + let key = ed25519_hash_symmetric( + b"pw", b"idSymmetric", + b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"); + let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b"; + assert_eq!(key.to_hex(), expected_key); + } + #[test] fn test_asymmetric() { let scalar_a = decimal_to_scalar(b"2611694063369306139794446498317402240796898290761098242657700742213257926693"); @@ -506,7 +542,7 @@ mod test { let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1, key2); assert_eq!(key1.to_hex(), - "a480bca13fa04464bb644f10e340125e96c9494f7399fef7c2bda67eb0fdf06d"); + "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"); } -- cgit v1.2.3 From 25820c4f3f4049fa1c99e5e2914f0af000ffdd9d Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 31 Jul 2017 17:51:00 -0700 Subject: prep for preliminary 0.0.1 release * add Cargo.toml metadata * add travis badge * exclude .gitignore --- Cargo.toml | 19 +++++++++++++++---- README.md | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 313c7be..cb614a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,23 @@ [package] name = "spake2" -version = "0.1.0" +version = "0.0.1" authors = ["Brian Warner "] +description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." +documentation = "https://github.com/warner/spake2.rs" +homepage = "https://github.com/warner/spake2.rs" +repository = "https://github.com/warner/spake2.rs" +license = "MIT" +categories = ["cryptography"] +exclude = [ + ".gitignore" +] + +[badges] +travis-ci = { repository = "https://github.com/warner/spake2.rs" } [dependencies] -#rust-crypto = "^0.2" -curve25519-dalek = "0.9.0" -rand = "0.3.0" +curve25519-dalek = "0.9" +rand = "0.3" #sha2 = "0.4.0" rust-crypto = "0.2" num-bigint = "0.1" diff --git a/README.md b/README.md index 228e303..c9e7054 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # spake2.rs -the SPAKE2 password-authenticated key-exchange algorithm, in Rust +The SPAKE2 password-authenticated key-exchange algorithm, in Rust. [![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) -NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number and has been published to crates.io. +NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number (at least 0.1.something) and has been published to crates.io. -- cgit v1.2.3 From 90824a5acba2f7dd8c70cc0465f6c08582537410 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 31 Jul 2017 18:10:50 -0700 Subject: Cargo.toml: fix the travis badge URL --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cb614a2..7516f7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ exclude = [ ] [badges] -travis-ci = { repository = "https://github.com/warner/spake2.rs" } +travis-ci = { repository = "warner/spake2.rs" } [dependencies] curve25519-dalek = "0.9" -- cgit v1.2.3 From 663f4c86dbfe5d6e41d7adcea6f45f4e80151b96 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 1 Aug 2017 03:29:37 +0000 Subject: Link to https://docs.rs/spake2 for doc builds. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7516f7c..5344171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "spake2" version = "0.0.1" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." -documentation = "https://github.com/warner/spake2.rs" +documentation = "https://docs.rs/spake2" homepage = "https://github.com/warner/spake2.rs" repository = "https://github.com/warner/spake2.rs" license = "MIT" -- cgit v1.2.3 From c1a89909cc8588f071a1397023b4c93b8a1835f9 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Tue, 1 Aug 2017 03:30:11 +0000 Subject: Add badges for crates.io and docs.rs to the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9e7054..23ae232 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # spake2.rs The SPAKE2 password-authenticated key-exchange algorithm, in Rust. -[![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) +[![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) [![Crates.io](https://img.shields.io/crates/v/spake2.svg)](https://crates.io/crates/spake2) [![Docs.rs](https://docs.rs/spake2/badge.svg)](https://docs.rs/spake2) NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number (at least 0.1.something) and has been published to crates.io. -- cgit v1.2.3 From a0f653c95ab7799734ba8525120e130b052dca87 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 4 Sep 2017 12:32:43 -0700 Subject: fix several clippy lints --- src/spake2.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index d77ea68..0c6b4c4 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -168,15 +168,15 @@ fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], let mut transcript = [0u8; 6*32]; let mut pw_hash = Sha256::new(); - pw_hash.input(&password_vec); + pw_hash.input(password_vec); pw_hash.result(&mut transcript[0..32]); let mut ida_hash = Sha256::new(); - ida_hash.input(&id_a); + ida_hash.input(id_a); ida_hash.result(&mut transcript[32..64]); let mut idb_hash = Sha256::new(); - idb_hash.input(&id_b); + idb_hash.input(id_b); idb_hash.result(&mut transcript[64..96]); transcript[96..128].copy_from_slice(first_msg); @@ -213,19 +213,19 @@ fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], let mut transcript = [0u8; 5*32]; let mut pw_hash = Sha256::new(); - pw_hash.input(&password_vec); + pw_hash.input(password_vec); pw_hash.result(&mut transcript[0..32]); let mut ids_hash = Sha256::new(); - ids_hash.input(&id_s); + ids_hash.input(id_s); ids_hash.result(&mut transcript[32..64]); if msg_u < msg_v { - transcript[64..96].copy_from_slice(&msg_u); + transcript[64..96].copy_from_slice(msg_u); transcript[96..128].copy_from_slice(msg_v); } else { transcript[64..96].copy_from_slice(msg_v); - transcript[96..128].copy_from_slice(&msg_u); + transcript[96..128].copy_from_slice(msg_u); } transcript[128..160].copy_from_slice(key_bytes); -- cgit v1.2.3 From 332e28b02bcad93144614400c5624ea75ed653b3 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 4 Sep 2017 12:57:26 -0700 Subject: hush remaining lints, thanks clippy for the suggestions --- src/spake2.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index 0c6b4c4..4a9f470 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -143,8 +143,8 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian - for i in 0..32+16 { - reducible[32+16-1-i] = okm[i]; + for (i, x) in okm.iter().enumerate().take(32+16) { + reducible[32+16-1-i] = *x; } println!("reducible: {}", reducible.iter().to_hex()); let reduced = c2_Scalar::reduce(&reducible); -- cgit v1.2.3 From 861ece4475b823005c310d42725fe59e7b4876ea Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 2 Aug 2017 12:55:35 -0700 Subject: move from (unmaintained) rust-crypto to RustCrypto ('sha2' crate) Also upgrade to curve25519-dalek 0.11, hkdf-0.2, refine error types, add more tests. --- Cargo.toml | 6 +++--- src/lib.rs | 35 ++++++++++++++++++++++++++++--- src/spake2.rs | 66 +++++++++++++++++++++++++++++------------------------------ 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5344171..f2a286f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.9" +curve25519-dalek = "0.11" rand = "0.3" -#sha2 = "0.4.0" -rust-crypto = "0.2" +sha2 = "0.6" +hkdf = "0.2" num-bigint = "0.1" hex = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 9083473..beffd19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ extern crate rand; extern crate curve25519_dalek; -//extern crate sha2; -extern crate crypto; +extern crate sha2; +extern crate hkdf; extern crate num_bigint; extern crate hex; @@ -15,7 +15,7 @@ pub use spake2::*; #[cfg(test)] mod tests { - use spake2::{SPAKE2, Ed25519Group}; + use spake2::{SPAKE2, SPAKEErr, Ed25519Group, ErrorType}; #[test] fn test_basic() { @@ -28,6 +28,35 @@ mod tests { assert_eq!(key1, key2); } + #[test] + fn test_mismatch() { + let (s1, msg1) = SPAKE2::::start_a(b"password", + b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::start_b(b"password2", + b"idA", b"idB"); + let key1 = s1.finish(msg2.as_slice()).unwrap(); + let key2 = s2.finish(msg1.as_slice()).unwrap(); + assert_ne!(key1, key2); + } + + #[test] + fn test_reflected_message() { + let (s1, msg1) = SPAKE2::::start_a(b"password", + b"idA", b"idB"); + let r = s1.finish(msg1.as_slice()); + assert_eq!(r.unwrap_err(), SPAKEErr{kind: ErrorType::BadSide}); + } + + #[test] + fn test_bad_length() { + let (s1, msg1) = SPAKE2::::start_a(b"password", + b"idA", b"idB"); + let mut msg2 = Vec::::with_capacity(msg1.len()+1); + msg2.resize(msg1.len()+1, 0u8); + let r = s1.finish(&msg2); + assert_eq!(r.unwrap_err(), SPAKEErr{kind: ErrorType::WrongLength}); + } + #[test] fn test_basic_symmetric() { let (s1, msg1) = SPAKE2::::start_symmetric(b"password", diff --git a/src/spake2.rs b/src/spake2.rs index 4a9f470..abd0373 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,20 +1,27 @@ #![allow(dead_code)] use curve25519_dalek::scalar::Scalar as c2_Scalar; -use curve25519_dalek::curve::ExtendedPoint as c2_Element; -use curve25519_dalek::constants::ED25519_BASEPOINT; -use curve25519_dalek::curve::CompressedEdwardsY; +use curve25519_dalek::edwards::ExtendedPoint as c2_Element; +use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; +use curve25519_dalek::edwards::CompressedEdwardsY; use rand::{Rng, OsRng}; -//use sha2::{Sha256, Sha512, Digest}; -use crypto::sha2::Sha256; -use crypto::digest::Digest; -use crypto::hkdf; +use sha2::{Sha256, Digest}; +use hkdf::Hkdf; use num_bigint::BigUint; use hex::ToHex; -#[derive(Debug)] -pub struct SPAKEErr ( String ); +#[derive(Debug, PartialEq)] +pub enum ErrorType { + BadSide, + WrongLength, + CorruptMessage, +} + +#[derive(Debug, PartialEq)] +pub struct SPAKEErr { + pub kind: ErrorType, +} pub trait Group { type Scalar; @@ -107,7 +114,7 @@ impl Group for Ed25519Group { fn basepoint_mult(s: &c2_Scalar) -> c2_Element { //c2_Element::basepoint_mult(s) - &ED25519_BASEPOINT * s + &ED25519_BASEPOINT_POINT * s } fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element { e * s @@ -134,12 +141,7 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i = int(h, 16) // i % q - let mut prk = [0u8; 32]; - let digest = Sha256::new(); - hkdf::hkdf_extract(digest, b"", s, &mut prk); - let mut okm = [0u8; 32+16]; - hkdf::hkdf_expand(digest, &prk, b"SPAKE2 pw", &mut okm); - //okm[32+16-2] = 1; + let okm = Hkdf::::new(s, b"").derive(b"SPAKE2 pw", 32+16); println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian @@ -169,15 +171,15 @@ fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], let mut pw_hash = Sha256::new(); pw_hash.input(password_vec); - pw_hash.result(&mut transcript[0..32]); + transcript[0..32].copy_from_slice(&pw_hash.result()); let mut ida_hash = Sha256::new(); ida_hash.input(id_a); - ida_hash.result(&mut transcript[32..64]); + transcript[32..64].copy_from_slice(&ida_hash.result()); let mut idb_hash = Sha256::new(); idb_hash.input(id_b); - idb_hash.result(&mut transcript[64..96]); + transcript[64..96].copy_from_slice(&idb_hash.result()); transcript[96..128].copy_from_slice(first_msg); transcript[128..160].copy_from_slice(second_msg); @@ -188,9 +190,7 @@ fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], //let mut hash = G::TranscriptHash::default(); let mut hash = Sha256::new(); hash.input(&transcript); - let mut out = [0u8; 32]; - hash.result(&mut out); - out.to_vec() + hash.result().to_vec() } fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], @@ -214,11 +214,11 @@ fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], let mut pw_hash = Sha256::new(); pw_hash.input(password_vec); - pw_hash.result(&mut transcript[0..32]); + transcript[0..32].copy_from_slice(&pw_hash.result()); let mut ids_hash = Sha256::new(); ids_hash.input(id_s); - ids_hash.result(&mut transcript[32..64]); + transcript[32..64].copy_from_slice(&ids_hash.result()); if msg_u < msg_v { transcript[64..96].copy_from_slice(msg_u); @@ -231,9 +231,7 @@ fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], let mut hash = Sha256::new(); hash.input(&transcript); - let mut out = [0u8; 32]; - hash.result(&mut out); - out.to_vec() + hash.result().to_vec() } /* "session type pattern" */ @@ -345,28 +343,28 @@ impl SPAKE2 { pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { if msg2.len() != 1 + G::element_length() { - return Err(SPAKEErr(String::from("inbound message is the wrong length"))) + return Err(SPAKEErr{kind: ErrorType::WrongLength}); } let msg_side = msg2[0]; match self.side { Side::A => match msg_side { 0x42 => (), // 'B' - _ => return Err(SPAKEErr(String::from("bad side"))), + _ => return Err(SPAKEErr{kind: ErrorType::BadSide}), }, Side::B => match msg_side { 0x41 => (), // 'A' - _ => return Err(SPAKEErr(String::from("bad side"))), + _ => return Err(SPAKEErr{kind: ErrorType::BadSide}), }, Side::Symmetric => match msg_side { 0x53 => (), // 'S' - _ => return Err(SPAKEErr(String::from("bad side"))), + _ => return Err(SPAKEErr{kind: ErrorType::BadSide}), }, } let msg2_element = match G::bytes_to_element(&msg2[1..]) { Some(x) => x, - None => {return Err(SPAKEErr(String::from("message corrupted")))}, + None => {return Err(SPAKEErr{kind: ErrorType::CorruptMessage})}, }; // a: K = (Y+N*(-pw))*x @@ -415,7 +413,7 @@ mod test { "random_scalar()" function, which results in some particular scalar. */ use curve25519_dalek::scalar::Scalar; - use curve25519_dalek::constants::ED25519_BASEPOINT; + use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use spake2::{SPAKE2, Ed25519Group}; use hex::ToHex; use super::*; @@ -441,7 +439,7 @@ mod test { fn test_serialize_basepoint() { // make sure elements are serialized same as the python library let exp = "5866666666666666666666666666666666666666666666666666666666666666"; - let base_vec = ED25519_BASEPOINT.compress_edwards().as_bytes().to_vec(); + let base_vec = ED25519_BASEPOINT_POINT.compress_edwards().as_bytes().to_vec(); let base_hex = base_vec.to_hex(); println!("exp: {:?}", exp); println!("got: {:?}", base_hex); -- cgit v1.2.3 From df09f9209950c529d9c16b64a3b2daad3b3ed8f3 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 21 Sep 2017 13:44:28 -0700 Subject: comment out debug messages --- src/lib.rs | 6 +++--- src/spake2.rs | 17 +++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index beffd19..7a7bdbd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,13 +5,13 @@ extern crate sha2; extern crate hkdf; extern crate num_bigint; -extern crate hex; +//extern crate hex; mod spake2; pub use spake2::*; -//#[cfg(test)] -//extern crate hex; +#[cfg(test)] +extern crate hex; #[cfg(test)] mod tests { diff --git a/src/spake2.rs b/src/spake2.rs index abd0373..d69b847 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -9,7 +9,7 @@ use sha2::{Sha256, Digest}; use hkdf::Hkdf; use num_bigint::BigUint; -use hex::ToHex; +//use hex::ToHex; #[derive(Debug, PartialEq)] pub enum ErrorType { @@ -142,17 +142,18 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i % q let okm = Hkdf::::new(s, b"").derive(b"SPAKE2 pw", 32+16); - println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok + //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian for (i, x) in okm.iter().enumerate().take(32+16) { reducible[32+16-1-i] = *x; } - println!("reducible: {}", reducible.iter().to_hex()); - let reduced = c2_Scalar::reduce(&reducible); - println!("reduced: {}", reduced.as_bytes().to_hex()); - println!("done"); - reduced + //println!("reducible: {}", reducible.iter().to_hex()); + c2_Scalar::reduce(&reducible) + //let reduced = c2_Scalar::reduce(&reducible); + //println!("reduced: {}", reduced.as_bytes().to_hex()); + //println!("done"); + //reduced } fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], @@ -185,7 +186,7 @@ fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], transcript[128..160].copy_from_slice(second_msg); transcript[160..192].copy_from_slice(key_bytes); - println!("transcript: {:?}", transcript.iter().to_hex()); + //println!("transcript: {:?}", transcript.iter().to_hex()); //let mut hash = G::TranscriptHash::default(); let mut hash = Sha256::new(); -- cgit v1.2.3 From b9ef2eebeaca2689e79cd2ee00b0efe79c522e9f Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 21 Sep 2017 13:45:13 -0700 Subject: add benchmarks: run 'cargo bench' --- Cargo.toml | 5 +++++ benches/spake2.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 benches/spake2.rs diff --git a/Cargo.toml b/Cargo.toml index f2a286f..e7850da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,8 @@ hex = "0.2" [dev-dependencies] hex = "0.2" +bencher = "0.1" + +[[bench]] +name = "spake2" +harness = false diff --git a/benches/spake2.rs b/benches/spake2.rs new file mode 100644 index 0000000..dbe25dd --- /dev/null +++ b/benches/spake2.rs @@ -0,0 +1,44 @@ +#[macro_use] +extern crate bencher; + +extern crate spake2; + +use bencher::Bencher; +use spake2::{SPAKE2, Ed25519Group}; + +fn spake2_start(bench: &mut Bencher) { + bench.iter(|| { + let (_, _) = SPAKE2::::start_a(b"password", b"A", b"B"); + }) +} + +/* +fn spake2_finish(bench: &mut Bencher) { + // this doesn't work, because s1 is consumed by doing finish() + let (s1, msg1) = SPAKE2::::start_a(b"password", + b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::start_b(b"password", + b"idA", b"idB"); + let msg2_slice = msg2.as_slice(); + bench.iter(|| { + s1.finish(msg2_slice) + }) +}*/ + +fn spake2_start_and_finish(bench: &mut Bencher) { + let (_, msg2) = SPAKE2::::start_b(b"password", + b"idA", b"idB"); + let msg2_slice = msg2.as_slice(); + bench.iter(|| { + let (s1, _) = SPAKE2::::start_a(b"password", + b"idA", b"idB"); + s1.finish(msg2_slice) + }) +} + + +benchmark_group!(benches, + spake2_start, + //spake2_finish, + spake2_start_and_finish); +benchmark_main!(benches); -- cgit v1.2.3 From f4f23fb64ea4f33094412fb392c40813cbce8c4f Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 21 Sep 2017 14:01:41 -0700 Subject: release 0.0.2, still preliminary --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e7850da..a4a1a96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.1" +version = "0.0.2" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From adf7129d4fccc29f457c647d3cd30c223fec85ab Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 8 Oct 2017 16:54:57 -0400 Subject: add test coverage (cargo-tarpaulin) and codecov.io reporting --- .gitignore | 1 + .travis.yml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/.gitignore b/.gitignore index 50281a4..8006158 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk +/cobertura.xml diff --git a/.travis.yml b/.travis.yml index 57ec1af..6103f76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,22 @@ language: rust rust: - stable + - beta + - nightly + +matrix: + allow_failures: + - rust: nightly script: + - cargo clean + - cargo build - cargo test + +after_success: | + if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + # Uncomment the following two lines create and upload a report for codecov.io + cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + fi -- cgit v1.2.3 From 48e41a6ff295fb0aa663ef8b657d2795500f9347 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 12 Oct 2017 12:07:01 -0700 Subject: update to the curve25519-dalek 0.12 API --- Cargo.toml | 2 +- src/spake2.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a4a1a96..d02065f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.11" +curve25519-dalek = "0.12" rand = "0.3" sha2 = "0.6" hkdf = "0.2" diff --git a/src/spake2.rs b/src/spake2.rs index d69b847..61797d3 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -97,7 +97,7 @@ impl Group for Ed25519Group { -s } fn element_to_bytes(s: &c2_Element) -> Vec { - s.compress_edwards().as_bytes().to_vec() + s.compress().as_bytes().to_vec() } fn element_length() -> usize { 32 @@ -440,7 +440,7 @@ mod test { fn test_serialize_basepoint() { // make sure elements are serialized same as the python library let exp = "5866666666666666666666666666666666666666666666666666666666666666"; - let base_vec = ED25519_BASEPOINT_POINT.compress_edwards().as_bytes().to_vec(); + let base_vec = ED25519_BASEPOINT_POINT.compress().as_bytes().to_vec(); let base_hex = base_vec.to_hex(); println!("exp: {:?}", exp); println!("got: {:?}", base_hex); -- cgit v1.2.3 From 8512188b78ba9988c4807cb1de95b8e9c7d5fc0d Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 15 Nov 2017 01:11:31 -0800 Subject: update to hex-0.3 --- Cargo.toml | 4 ++-- src/spake2.rs | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d02065f..d685dfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,10 @@ rand = "0.3" sha2 = "0.6" hkdf = "0.2" num-bigint = "0.1" -hex = "0.2" +hex = "0.3" [dev-dependencies] -hex = "0.2" +hex = "0.3" bencher = "0.1" [[bench]] diff --git a/src/spake2.rs b/src/spake2.rs index 61797d3..c746232 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -416,7 +416,7 @@ mod test { use curve25519_dalek::scalar::Scalar; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use spake2::{SPAKE2, Ed25519Group}; - use hex::ToHex; + use hex; use super::*; // the python tests show the long-integer form of scalars. the rust code @@ -441,7 +441,7 @@ mod test { // make sure elements are serialized same as the python library let exp = "5866666666666666666666666666666666666666666666666666666666666666"; let base_vec = ED25519_BASEPOINT_POINT.compress().as_bytes().to_vec(); - let base_hex = base_vec.to_hex(); + let base_hex = hex::encode(base_vec); println!("exp: {:?}", exp); println!("got: {:?}", base_hex); assert_eq!(exp, base_hex); @@ -452,8 +452,8 @@ mod test { let password = b"password"; let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839"); let pw_scalar = Ed25519Group::hash_to_scalar(password); - println!("exp: {:?}", expected_pw_scalar.as_bytes().to_hex()); - println!("got: {:?}", pw_scalar.as_bytes().to_hex()); + println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes())); + println!("got: {:?}", hex::encode(pw_scalar.as_bytes())); assert_eq!(&pw_scalar, &expected_pw_scalar); } @@ -491,7 +491,7 @@ mod test { b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"); let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd"; - assert_eq!(key.to_hex(), expected_key); + assert_eq!(hex::encode(key), expected_key); } #[test] @@ -502,7 +502,7 @@ mod test { b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"); let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b"; - assert_eq!(key.to_hex(), expected_key); + assert_eq!(hex::encode(key), expected_key); } #[test] @@ -511,36 +511,36 @@ mod test { let scalar_b = decimal_to_scalar(b"7002393159576182977806091886122272758628412261510164356026361256515836884383"); let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839"); - println!("scalar_a is {}", scalar_a.as_bytes().to_hex()); + println!("scalar_a is {}", hex::encode(scalar_a.as_bytes())); let (s1, msg1) = SPAKE2::::start_a_internal( b"password", b"idA", b"idB", scalar_a); let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9"; println!(); - println!("xys1: {:?}", s1.xy_scalar.as_bytes().to_hex()); + println!("xys1: {:?}", hex::encode(s1.xy_scalar.as_bytes())); println!(); - println!("pws1: {:?}", s1.password_scalar.as_bytes().to_hex()); - println!("exp : {:?}", expected_pw_scalar.as_bytes().to_hex()); + println!("pws1: {:?}", hex::encode(s1.password_scalar.as_bytes())); + println!("exp : {:?}", hex::encode(expected_pw_scalar.as_bytes())); println!(); - println!("msg1: {:?}", msg1.to_hex()); + println!("msg1: {:?}", hex::encode(&msg1)); println!("exp : {:?}", expected_msg1); println!(); - assert_eq!(expected_pw_scalar.as_bytes().to_hex(), - s1.password_scalar.as_bytes().to_hex()); - assert_eq!(msg1.to_hex(), expected_msg1); + assert_eq!(hex::encode(expected_pw_scalar.as_bytes()), + hex::encode(s1.password_scalar.as_bytes())); + assert_eq!(hex::encode(&msg1), expected_msg1); let (s2, msg2) = SPAKE2::::start_b_internal( b"password", b"idA", b"idB", scalar_b); assert_eq!(expected_pw_scalar, s2.password_scalar); - assert_eq!(msg2.to_hex(), + assert_eq!(hex::encode(&msg2), "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1, key2); - assert_eq!(key1.to_hex(), + assert_eq!(hex::encode(key1), "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"); } -- cgit v1.2.3 From bf3d3abd7f11725ab53c2ecd44e073e368a8c7eb Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 15 Nov 2017 11:17:54 -0800 Subject: README: add badge for codecov.io --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 23ae232..bb53328 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # spake2.rs The SPAKE2 password-authenticated key-exchange algorithm, in Rust. -[![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) [![Crates.io](https://img.shields.io/crates/v/spake2.svg)](https://crates.io/crates/spake2) [![Docs.rs](https://docs.rs/spake2/badge.svg)](https://docs.rs/spake2) +[![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) +[![codecov](https://codecov.io/gh/warner/spake2.rs/branch/master/graph/badge.svg)](https://codecov.io/gh/warner/spake2.rs) +[![Crates.io](https://img.shields.io/crates/v/spake2.svg)](https://crates.io/crates/spake2) +[![Docs.rs](https://docs.rs/spake2/badge.svg)](https://docs.rs/spake2) + NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number (at least 0.1.something) and has been published to crates.io. -- cgit v1.2.3 From 7c4155e6e5871c45a766a73cc1ba2b4cbe8020d2 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 25 Nov 2017 12:07:09 -0600 Subject: travis: add OS-X --- .travis.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6103f76..73fa8d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,19 @@ rust: - beta - nightly +os: + - linux + - osx + matrix: allow_failures: - rust: nightly script: - cargo clean - - cargo build - - cargo test + - cargo build --verbose --all +# - cargo run --verbose --example XYZ + - cargo test --verbose --features "test" --all after_success: | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then -- cgit v1.2.3 From acd58c71510f56ff5d88b41b8e7b30bdffc4cb49 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 25 Nov 2017 12:10:34 -0600 Subject: travis: fix test invocation --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 73fa8d7..3ab58db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,8 @@ script: - cargo clean - cargo build --verbose --all # - cargo run --verbose --example XYZ - - cargo test --verbose --features "test" --all +# - cargo test --verbose --features "test" --all + - cargo test --verbose --all after_success: | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then -- cgit v1.2.3 From 5c5ee649702f8bc3334c4b033500b9562db2ce65 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 25 Nov 2017 12:11:38 -0600 Subject: update to curve25519-dalek 0.13 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d685dfa..52245f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.12" +curve25519-dalek = "0.13" rand = "0.3" sha2 = "0.6" hkdf = "0.2" -- cgit v1.2.3 From b0d898fbd8713cefbd1e0e8bab92489d07379b86 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 29 Nov 2017 17:05:36 -0600 Subject: update to hkdf-0.3 and sha2-0.7 (new Digest API) --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52245f2..cd4edd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ travis-ci = { repository = "warner/spake2.rs" } [dependencies] curve25519-dalek = "0.13" rand = "0.3" -sha2 = "0.6" -hkdf = "0.2" +sha2 = "0.7" +hkdf = "0.3" num-bigint = "0.1" hex = "0.3" -- cgit v1.2.3 From e014de2eaad5e2cf9aa3c7f3c7754566f5d37b57 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 29 Nov 2017 17:49:58 -0600 Subject: (cargo-release) version 0.0.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cd4edd0..e25d153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.2" +version = "0.0.3" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 4448ccfe5a66ec2a2fa510f2750ca0507511c0b4 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 29 Nov 2017 17:50:08 -0600 Subject: (cargo-release) start next development iteration 0.0.4-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e25d153..944e995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.3" +version = "0.0.4-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 3e322cb85003f7bca198da316148b19face7e59d Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 5 Dec 2017 23:09:36 -0800 Subject: update to curve25519-dalek-0.14 --- Cargo.toml | 2 +- src/spake2.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 944e995..fb3673d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.13" +curve25519-dalek = "0.14" rand = "0.3" sha2 = "0.7" hkdf = "0.3" diff --git a/src/spake2.rs b/src/spake2.rs index c746232..e92f1c1 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -129,9 +129,9 @@ impl Group for Ed25519Group { fn decimal_to_scalar(d: &[u8]) -> c2_Scalar { let bytes = BigUint::parse_bytes(d, 10).unwrap().to_bytes_le(); assert_eq!(bytes.len(), 32); - let mut s = c2_Scalar([0u8; 32]); - s.0.copy_from_slice(&bytes); - s + let mut b2 = [0u8; 32]; + b2.copy_from_slice(&bytes); + c2_Scalar::from_bytes_mod_order(b2) } fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { @@ -149,7 +149,7 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { reducible[32+16-1-i] = *x; } //println!("reducible: {}", reducible.iter().to_hex()); - c2_Scalar::reduce(&reducible) + c2_Scalar::from_bytes_mod_order_wide(&reducible) //let reduced = c2_Scalar::reduce(&reducible); //println!("reduced: {}", reduced.as_bytes().to_hex()); //println!("done"); @@ -413,7 +413,6 @@ mod test { deterministic RNG (used only for tests, of course) into the per-Group "random_scalar()" function, which results in some particular scalar. */ - use curve25519_dalek::scalar::Scalar; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use spake2::{SPAKE2, Ed25519Group}; use hex; @@ -427,12 +426,13 @@ mod test { fn test_convert() { let t1_decimal = b"2238329342913194256032495932344128051776374960164957527413114840482143558222"; let t1_scalar = decimal_to_scalar(t1_decimal); - let expected: Scalar = Scalar( + let t1_bytes = t1_scalar.to_bytes(); + let expected = [0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, 0x7d, 0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, - 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9, 0xf2, 0x04]); - assert_eq!(t1_scalar, expected); + 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9, 0xf2, 0x04]; + assert_eq!(t1_bytes, expected); //println!("t1_scalar is {:?}", t1_scalar); } -- cgit v1.2.3 From 1eaedabe6a65abb67844c3da3be8bcdb0939159e Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 18 Dec 2017 23:13:59 -0800 Subject: format everything with rustfmt --- benches/spake2.rs | 19 ++-- src/lib.rs | 53 +++++---- src/spake2.rs | 317 +++++++++++++++++++++++++++++++++--------------------- 3 files changed, 227 insertions(+), 162 deletions(-) diff --git a/benches/spake2.rs b/benches/spake2.rs index dbe25dd..d945d5a 100644 --- a/benches/spake2.rs +++ b/benches/spake2.rs @@ -4,7 +4,7 @@ extern crate bencher; extern crate spake2; use bencher::Bencher; -use spake2::{SPAKE2, Ed25519Group}; +use spake2::{Ed25519Group, SPAKE2}; fn spake2_start(bench: &mut Bencher) { bench.iter(|| { @@ -26,19 +26,18 @@ fn spake2_finish(bench: &mut Bencher) { }*/ fn spake2_start_and_finish(bench: &mut Bencher) { - let (_, msg2) = SPAKE2::::start_b(b"password", - b"idA", b"idB"); + let (_, msg2) = SPAKE2::::start_b(b"password", b"idA", b"idB"); let msg2_slice = msg2.as_slice(); bench.iter(|| { - let (s1, _) = SPAKE2::::start_a(b"password", - b"idA", b"idB"); + let (s1, _) = SPAKE2::::start_a(b"password", b"idA", b"idB"); s1.finish(msg2_slice) }) } - -benchmark_group!(benches, - spake2_start, - //spake2_finish, - spake2_start_and_finish); +benchmark_group!( + benches, + spake2_start, + //spake2_finish, + spake2_start_and_finish +); benchmark_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index 7a7bdbd..4737738 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,8 @@ - -extern crate rand; extern crate curve25519_dalek; -extern crate sha2; extern crate hkdf; extern crate num_bigint; +extern crate rand; +extern crate sha2; //extern crate hex; @@ -15,14 +14,12 @@ extern crate hex; #[cfg(test)] mod tests { - use spake2::{SPAKE2, SPAKEErr, Ed25519Group, ErrorType}; + use spake2::{Ed25519Group, ErrorType, SPAKE2, SPAKEErr}; #[test] fn test_basic() { - let (s1, msg1) = SPAKE2::::start_a(b"password", - b"idA", b"idB"); - let (s2, msg2) = SPAKE2::::start_b(b"password", - b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::start_b(b"password", b"idA", b"idB"); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); @@ -30,10 +27,8 @@ mod tests { #[test] fn test_mismatch() { - let (s1, msg1) = SPAKE2::::start_a(b"password", - b"idA", b"idB"); - let (s2, msg2) = SPAKE2::::start_b(b"password2", - b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::start_b(b"password2", b"idA", b"idB"); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_ne!(key1, key2); @@ -41,37 +36,41 @@ mod tests { #[test] fn test_reflected_message() { - let (s1, msg1) = SPAKE2::::start_a(b"password", - b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); let r = s1.finish(msg1.as_slice()); - assert_eq!(r.unwrap_err(), SPAKEErr{kind: ErrorType::BadSide}); + assert_eq!( + r.unwrap_err(), + SPAKEErr { + kind: ErrorType::BadSide, + } + ); } #[test] fn test_bad_length() { - let (s1, msg1) = SPAKE2::::start_a(b"password", - b"idA", b"idB"); - let mut msg2 = Vec::::with_capacity(msg1.len()+1); - msg2.resize(msg1.len()+1, 0u8); + let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let mut msg2 = Vec::::with_capacity(msg1.len() + 1); + msg2.resize(msg1.len() + 1, 0u8); let r = s1.finish(&msg2); - assert_eq!(r.unwrap_err(), SPAKEErr{kind: ErrorType::WrongLength}); + assert_eq!( + r.unwrap_err(), + SPAKEErr { + kind: ErrorType::WrongLength, + } + ); } #[test] fn test_basic_symmetric() { - let (s1, msg1) = SPAKE2::::start_symmetric(b"password", - b"idS"); - let (s2, msg2) = SPAKE2::::start_symmetric(b"password", - b"idS"); + let (s1, msg1) = SPAKE2::::start_symmetric(b"password", b"idS"); + let (s2, msg2) = SPAKE2::::start_symmetric(b"password", b"idS"); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); } - #[test] - fn it_works() { - } + fn it_works() {} #[test] #[should_panic(expected = "nope")] diff --git a/src/spake2.rs b/src/spake2.rs index e92f1c1..382e69d 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -4,8 +4,8 @@ use curve25519_dalek::scalar::Scalar as c2_Scalar; use curve25519_dalek::edwards::ExtendedPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::CompressedEdwardsY; -use rand::{Rng, OsRng}; -use sha2::{Sha256, Digest}; +use rand::{OsRng, Rng}; +use sha2::{Digest, Sha256}; use hkdf::Hkdf; use num_bigint::BigUint; @@ -59,32 +59,33 @@ impl Group for Ed25519Group { // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.M.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))" // 15cfd18e385952982b6a8f8c7854963b58e34388c8e6dae891db756481a02312 CompressedEdwardsY([ - 0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, - 0x8c, 0x78, 0x54, 0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, - 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64, 0x81, 0xa0, 0x23, 0x12, - ]).decompress().unwrap() + 0x15, 0xcf, 0xd1, 0x8e, 0x38, 0x59, 0x52, 0x98, 0x2b, 0x6a, 0x8f, 0x8c, 0x78, 0x54, + 0x96, 0x3b, 0x58, 0xe3, 0x43, 0x88, 0xc8, 0xe6, 0xda, 0xe8, 0x91, 0xdb, 0x75, 0x64, + 0x81, 0xa0, 0x23, 0x12, + ]).decompress() + .unwrap() } fn const_n() -> c2_Element { // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.N.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))" // f04f2e7eb734b2a8f8b472eaf9c3c632576ac64aea650b496a8a20ff00e583c3 CompressedEdwardsY([ - 0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, - 0xea, 0xf9, 0xc3, 0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, - 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff, 0x00, 0xe5, 0x83, 0xc3, - ]).decompress().unwrap() - + 0xf0, 0x4f, 0x2e, 0x7e, 0xb7, 0x34, 0xb2, 0xa8, 0xf8, 0xb4, 0x72, 0xea, 0xf9, 0xc3, + 0xc6, 0x32, 0x57, 0x6a, 0xc6, 0x4a, 0xea, 0x65, 0x0b, 0x49, 0x6a, 0x8a, 0x20, 0xff, + 0x00, 0xe5, 0x83, 0xc3, + ]).decompress() + .unwrap() } fn const_s() -> c2_Element { // python -c "import binascii, spake2; b=binascii.hexlify(spake2.ParamsEd25519.S.to_bytes()); print(', '.join(['0x'+b[i:i+2] for i in range(0,len(b),2)]))" // 6f00dae87c1be1a73b5922ef431cd8f57879569c222d22b1cd71e8546ab8e6f1 CompressedEdwardsY([ - 0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, - 0xef, 0x43, 0x1c, 0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, - 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54, 0x6a, 0xb8, 0xe6, 0xf1, - ]).decompress().unwrap() - + 0x6f, 0x00, 0xda, 0xe8, 0x7c, 0x1b, 0xe1, 0xa7, 0x3b, 0x59, 0x22, 0xef, 0x43, 0x1c, + 0xd8, 0xf5, 0x78, 0x79, 0x56, 0x9c, 0x22, 0x2d, 0x22, 0xb1, 0xcd, 0x71, 0xe8, 0x54, + 0x6a, 0xb8, 0xe6, 0xf1, + ]).decompress() + .unwrap() } fn hash_to_scalar(s: &[u8]) -> c2_Scalar { @@ -103,7 +104,9 @@ impl Group for Ed25519Group { 32 } fn bytes_to_element(b: &[u8]) -> Option { - if b.len() != 32 { return None; } + if b.len() != 32 { + return None; + } //let mut bytes: [u8; 32] = let mut bytes = [0u8; 32]; bytes.copy_from_slice(b); @@ -141,12 +144,12 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i = int(h, 16) // i % q - let okm = Hkdf::::new(s, b"").derive(b"SPAKE2 pw", 32+16); + let okm = Hkdf::::new(s, b"").derive(b"SPAKE2 pw", 32 + 16); //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian - for (i, x) in okm.iter().enumerate().take(32+16) { - reducible[32+16-1-i] = *x; + for (i, x) in okm.iter().enumerate().take(32 + 16) { + reducible[32 + 16 - 1 - i] = *x; } //println!("reducible: {}", reducible.iter().to_hex()); c2_Scalar::from_bytes_mod_order_wide(&reducible) @@ -156,9 +159,14 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { //reduced } -fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], - first_msg: &[u8], second_msg: &[u8], key_bytes: &[u8]) - -> Vec { +fn ed25519_hash_ab( + password_vec: &[u8], + id_a: &[u8], + id_b: &[u8], + first_msg: &[u8], + second_msg: &[u8], + key_bytes: &[u8], +) -> Vec { assert_eq!(first_msg.len(), 32); assert_eq!(second_msg.len(), 32); // the transcript is fixed-length, made up of 6 32-byte values: @@ -168,7 +176,7 @@ fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], // byte 96-127 : X_msg // byte 128-159: Y_msg // byte 160-191: K_bytes - let mut transcript = [0u8; 6*32]; + let mut transcript = [0u8; 6 * 32]; let mut pw_hash = Sha256::new(); pw_hash.input(password_vec); @@ -187,16 +195,20 @@ fn ed25519_hash_ab(password_vec: &[u8], id_a: &[u8], id_b: &[u8], transcript[160..192].copy_from_slice(key_bytes); //println!("transcript: {:?}", transcript.iter().to_hex()); - + //let mut hash = G::TranscriptHash::default(); let mut hash = Sha256::new(); hash.input(&transcript); hash.result().to_vec() } -fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], - msg_u: &[u8], msg_v: &[u8], key_bytes: &[u8]) - -> Vec { +fn ed25519_hash_symmetric( + password_vec: &[u8], + id_s: &[u8], + msg_u: &[u8], + msg_v: &[u8], + key_bytes: &[u8], +) -> Vec { assert_eq!(msg_u.len(), 32); assert_eq!(msg_v.len(), 32); // # since we don't know which side is which, we must sort the messages @@ -211,7 +223,7 @@ fn ed25519_hash_symmetric(password_vec: &[u8], id_s: &[u8], // byte 64-95 : X_msg // byte 96-127 : Y_msg // byte 128-159: K_bytes - let mut transcript = [0u8; 5*32]; + let mut transcript = [0u8; 5 * 32]; let mut pw_hash = Sha256::new(); pw_hash.input(password_vec); @@ -242,7 +254,8 @@ enum Side { B, Symmetric, } -pub struct SPAKE2 { //where &G::Scalar: Neg { +pub struct SPAKE2 { + //where &G::Scalar: Neg { side: Side, xy_scalar: G::Scalar, password_vec: Vec, @@ -254,10 +267,14 @@ pub struct SPAKE2 { //where &G::Scalar: Neg { } impl SPAKE2 { - fn start_internal(side: Side, - password: &[u8], - id_a: &[u8], id_b: &[u8], id_s: &[u8], - xy_scalar: G::Scalar) -> (SPAKE2, Vec) { + fn start_internal( + side: Side, + password: &[u8], + id_a: &[u8], + id_b: &[u8], + id_s: &[u8], + xy_scalar: G::Scalar, + ) -> (SPAKE2, Vec) { //let password_scalar: G::Scalar = hash_to_scalar::(password); let password_scalar: G::Scalar = G::hash_to_scalar(password); @@ -269,8 +286,10 @@ impl SPAKE2 { Side::B => G::const_n(), Side::Symmetric => G::const_s(), }; - let m1: G::Element = G::add(&G::basepoint_mult(&xy_scalar), - &G::scalarmult(&blinding, &password_scalar)); + let m1: G::Element = G::add( + &G::basepoint_mult(&xy_scalar), + &G::scalarmult(&blinding, &password_scalar), + ); //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar); let msg1: Vec = G::element_to_bytes(&m1); let mut password_vec = Vec::new(); @@ -284,59 +303,66 @@ impl SPAKE2 { let mut msg_and_side = Vec::new(); msg_and_side.push(match side { - Side::A => 0x41, // 'A' - Side::B => 0x42, // 'B' + Side::A => 0x41, // 'A' + Side::B => 0x42, // 'B' Side::Symmetric => 0x53, // 'S' }); msg_and_side.extend_from_slice(&msg1); - (SPAKE2 { - side: side, - xy_scalar: xy_scalar, - password_vec: password_vec, // string - id_a: id_a_copy, - id_b: id_b_copy, - id_s: id_s_copy, - msg1: msg1.clone(), - password_scalar: password_scalar, // scalar - }, msg_and_side) + ( + SPAKE2 { + side: side, + xy_scalar: xy_scalar, + password_vec: password_vec, // string + id_a: id_a_copy, + id_b: id_b_copy, + id_s: id_s_copy, + msg1: msg1.clone(), + password_scalar: password_scalar, // scalar + }, + msg_and_side, + ) } - fn start_a_internal(password: &[u8], id_a: &[u8], id_b: &[u8], - xy_scalar: G::Scalar) -> (SPAKE2, Vec) { - Self::start_internal(Side::A, - password, id_a, id_b, b"", xy_scalar) + fn start_a_internal( + password: &[u8], + id_a: &[u8], + id_b: &[u8], + xy_scalar: G::Scalar, + ) -> (SPAKE2, Vec) { + Self::start_internal(Side::A, password, id_a, id_b, b"", xy_scalar) } - fn start_b_internal(password: &[u8], id_a: &[u8], id_b: &[u8], - xy_scalar: G::Scalar) -> (SPAKE2, Vec) { - Self::start_internal(Side::B, - password, id_a, id_b, b"", xy_scalar) + fn start_b_internal( + password: &[u8], + id_a: &[u8], + id_b: &[u8], + xy_scalar: G::Scalar, + ) -> (SPAKE2, Vec) { + Self::start_internal(Side::B, password, id_a, id_b, b"", xy_scalar) } - fn start_symmetric_internal(password: &[u8], id_s: &[u8], - xy_scalar: G::Scalar) -> (SPAKE2, Vec) { - Self::start_internal(Side::Symmetric, - password, b"", b"", id_s, xy_scalar) + fn start_symmetric_internal( + password: &[u8], + id_s: &[u8], + xy_scalar: G::Scalar, + ) -> (SPAKE2, Vec) { + Self::start_internal(Side::Symmetric, password, b"", b"", id_s, xy_scalar) } - - pub fn start_a(password: &[u8], id_a: &[u8], id_b: &[u8]) - -> (SPAKE2, Vec) { + pub fn start_a(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); Self::start_a_internal(password, id_a, id_b, xy_scalar) } - pub fn start_b(password: &[u8], id_a: &[u8], id_b: &[u8]) - -> (SPAKE2, Vec) { + pub fn start_b(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); Self::start_b_internal(password, id_a, id_b, xy_scalar) } - pub fn start_symmetric(password: &[u8], id_s: &[u8]) - -> (SPAKE2, Vec) { + pub fn start_symmetric(password: &[u8], id_s: &[u8]) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); Self::start_symmetric_internal(password, id_s, xy_scalar) @@ -344,28 +370,46 @@ impl SPAKE2 { pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { if msg2.len() != 1 + G::element_length() { - return Err(SPAKEErr{kind: ErrorType::WrongLength}); + return Err(SPAKEErr { + kind: ErrorType::WrongLength, + }); } let msg_side = msg2[0]; match self.side { Side::A => match msg_side { 0x42 => (), // 'B' - _ => return Err(SPAKEErr{kind: ErrorType::BadSide}), + _ => { + return Err(SPAKEErr { + kind: ErrorType::BadSide, + }) + } }, Side::B => match msg_side { 0x41 => (), // 'A' - _ => return Err(SPAKEErr{kind: ErrorType::BadSide}), + _ => { + return Err(SPAKEErr { + kind: ErrorType::BadSide, + }) + } }, Side::Symmetric => match msg_side { 0x53 => (), // 'S' - _ => return Err(SPAKEErr{kind: ErrorType::BadSide}), + _ => { + return Err(SPAKEErr { + kind: ErrorType::BadSide, + }) + } }, } let msg2_element = match G::bytes_to_element(&msg2[1..]) { Some(x) => x, - None => {return Err(SPAKEErr{kind: ErrorType::CorruptMessage})}, + None => { + return Err(SPAKEErr { + kind: ErrorType::CorruptMessage, + }) + } }; // a: K = (Y+N*(-pw))*x @@ -375,8 +419,7 @@ impl SPAKE2 { Side::B => G::const_m(), Side::Symmetric => G::const_s(), }; - let tmp1 = G::scalarmult(&unblinding, - &G::scalar_neg(&self.password_scalar)); + let tmp1 = G::scalarmult(&unblinding, &G::scalar_neg(&self.password_scalar)); let tmp2 = G::add(&msg2_element, &tmp1); let key_element = G::scalarmult(&tmp2, &self.xy_scalar); let key_bytes = G::element_to_bytes(&key_element); @@ -389,23 +432,33 @@ impl SPAKE2 { // note that both sides must use the same order Ok(match self.side { - Side::A => ed25519_hash_ab(&self.password_vec, - &self.id_a, &self.id_b, - self.msg1.as_slice(), &msg2[1..], - &key_bytes), - Side::B => ed25519_hash_ab(&self.password_vec, - &self.id_a, &self.id_b, - &msg2[1..], self.msg1.as_slice(), - &key_bytes), - Side::Symmetric => ed25519_hash_symmetric(&self.password_vec, - &self.id_s, - &self.msg1, &msg2[1..], - &key_bytes), + Side::A => ed25519_hash_ab( + &self.password_vec, + &self.id_a, + &self.id_b, + self.msg1.as_slice(), + &msg2[1..], + &key_bytes, + ), + Side::B => ed25519_hash_ab( + &self.password_vec, + &self.id_a, + &self.id_b, + &msg2[1..], + self.msg1.as_slice(), + &key_bytes, + ), + Side::Symmetric => ed25519_hash_symmetric( + &self.password_vec, + &self.id_s, + &self.msg1, + &msg2[1..], + &key_bytes, + ), }) } } - #[cfg(test)] mod test { /* This compares results against the python compatibility tests: @@ -414,7 +467,7 @@ mod test { "random_scalar()" function, which results in some particular scalar. */ use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; - use spake2::{SPAKE2, Ed25519Group}; + use spake2::{Ed25519Group, SPAKE2}; use hex; use super::*; @@ -424,14 +477,15 @@ mod test { #[test] fn test_convert() { - let t1_decimal = b"2238329342913194256032495932344128051776374960164957527413114840482143558222"; + let t1_decimal = + b"2238329342913194256032495932344128051776374960164957527413114840482143558222"; let t1_scalar = decimal_to_scalar(t1_decimal); let t1_bytes = t1_scalar.to_bytes(); - let expected = - [0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, - 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, 0x7d, 0x52, - 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, - 0xd4, 0x49, 0xf4, 0xa8, 0x79, 0xd9, 0xf2, 0x04]; + let expected = [ + 0x4e, 0x5a, 0xb4, 0x34, 0x5d, 0x47, 0x08, 0x84, 0x59, 0x13, 0xb4, 0x64, 0x1b, 0xc2, + 0x7d, 0x52, 0x52, 0xa5, 0x85, 0x10, 0x1b, 0xcc, 0x42, 0x44, 0xd4, 0x49, 0xf4, 0xa8, + 0x79, 0xd9, 0xf2, 0x04, + ]; assert_eq!(t1_bytes, expected); //println!("t1_scalar is {:?}", t1_scalar); } @@ -450,7 +504,9 @@ mod test { #[test] fn test_password_to_scalar() { let password = b"password"; - let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839"); + let expected_pw_scalar = decimal_to_scalar( + b"3515301705789368674385125653994241092664323519848410154015274772661223168839", + ); let pw_scalar = Ed25519Group::hash_to_scalar(password); println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes())); println!("got: {:?}", hex::encode(pw_scalar.as_bytes())); @@ -459,23 +515,19 @@ mod test { #[test] fn test_sizes() { - let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", - b"idB"); - assert_eq!(msg1.len(), 1+32); - let (s2, msg2) = SPAKE2::::start_b(b"password", b"idA", - b"idB"); - assert_eq!(msg2.len(), 1+32); + let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + assert_eq!(msg1.len(), 1 + 32); + let (s2, msg2) = SPAKE2::::start_b(b"password", b"idA", b"idB"); + assert_eq!(msg2.len(), 1 + 32); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1.len(), 32); assert_eq!(key2.len(), 32); - let (s1, msg1) = SPAKE2::::start_symmetric(b"password", - b"idS"); - assert_eq!(msg1.len(), 1+32); - let (s2, msg2) = SPAKE2::::start_symmetric(b"password", - b"idS"); - assert_eq!(msg2.len(), 1+32); + let (s1, msg1) = SPAKE2::::start_symmetric(b"password", b"idS"); + assert_eq!(msg1.len(), 1 + 32); + let (s2, msg2) = SPAKE2::::start_symmetric(b"password", b"idS"); + assert_eq!(msg2.len(), 1 + 32); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1.len(), 32); @@ -486,10 +538,12 @@ mod test { fn test_hash_ab() { let key = ed25519_hash_ab( b"pw", - b"idA", b"idB", + b"idA", + b"idB", b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // len=32 b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", - b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"); + b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK", + ); let expected_key = "d59d9ba920f7092565cec747b08d5b2e981d553ac32fde0f25e5b4a4cfca3efd"; assert_eq!(hex::encode(key), expected_key); } @@ -497,24 +551,32 @@ mod test { #[test] fn test_hash_symmetric() { let key = ed25519_hash_symmetric( - b"pw", b"idSymmetric", + b"pw", + b"idSymmetric", b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", b"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", - b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"); + b"KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK", + ); let expected_key = "b0b31e4401aae37d91a9a8bf6fbb1298cafc005ff9142e3ffc5b9799fb11128b"; assert_eq!(hex::encode(key), expected_key); } #[test] fn test_asymmetric() { - let scalar_a = decimal_to_scalar(b"2611694063369306139794446498317402240796898290761098242657700742213257926693"); - let scalar_b = decimal_to_scalar(b"7002393159576182977806091886122272758628412261510164356026361256515836884383"); - let expected_pw_scalar = decimal_to_scalar(b"3515301705789368674385125653994241092664323519848410154015274772661223168839"); + let scalar_a = decimal_to_scalar( + b"2611694063369306139794446498317402240796898290761098242657700742213257926693", + ); + let scalar_b = decimal_to_scalar( + b"7002393159576182977806091886122272758628412261510164356026361256515836884383", + ); + let expected_pw_scalar = decimal_to_scalar( + b"3515301705789368674385125653994241092664323519848410154015274772661223168839", + ); println!("scalar_a is {}", hex::encode(scalar_a.as_bytes())); - let (s1, msg1) = SPAKE2::::start_a_internal( - b"password", b"idA", b"idB", scalar_a); + let (s1, msg1) = + SPAKE2::::start_a_internal(b"password", b"idA", b"idB", scalar_a); let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9"; println!(); @@ -527,22 +589,27 @@ mod test { println!("exp : {:?}", expected_msg1); println!(); - assert_eq!(hex::encode(expected_pw_scalar.as_bytes()), - hex::encode(s1.password_scalar.as_bytes())); + assert_eq!( + hex::encode(expected_pw_scalar.as_bytes()), + hex::encode(s1.password_scalar.as_bytes()) + ); assert_eq!(hex::encode(&msg1), expected_msg1); - let (s2, msg2) = SPAKE2::::start_b_internal( - b"password", b"idA", b"idB", scalar_b); + let (s2, msg2) = + SPAKE2::::start_b_internal(b"password", b"idA", b"idB", scalar_b); assert_eq!(expected_pw_scalar, s2.password_scalar); - assert_eq!(hex::encode(&msg2), - "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309"); + assert_eq!( + hex::encode(&msg2), + "42354e97b88406922b1df4bea1d7870f17aed3dba7c720b313edae315b00959309" + ); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1, key2); - assert_eq!(hex::encode(key1), - "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1"); + assert_eq!( + hex::encode(key1), + "712295de7219c675ddd31942184aa26e0a957cf216bc230d165b215047b520c1" + ); } - } -- cgit v1.2.3 From 54eb6ca383a5cefdac4a285fb75454b170517e04 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 25 Jan 2018 13:17:34 -0800 Subject: update to rand-0.4, since dalek-0.14.4 uses it for some reason this was causing compile errors against dalek-0.14.4 but not 0.14.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fb3673d..6c9075a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ travis-ci = { repository = "warner/spake2.rs" } [dependencies] curve25519-dalek = "0.14" -rand = "0.3" +rand = "0.4" sha2 = "0.7" hkdf = "0.3" num-bigint = "0.1" -- cgit v1.2.3 From 9b3b99f92c234fb7f75df4944367777a50132943 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 28 Jan 2018 11:38:08 -0800 Subject: (cargo-release) version 0.0.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c9075a..7cf9abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.4-alpha.0" +version = "0.0.4" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From e350b76cbde9bda0f6628b2f3baa07cc652fec16 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 28 Jan 2018 11:38:22 -0800 Subject: (cargo-release) start next development iteration 0.0.5-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7cf9abc..b4fa517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.4" +version = "0.0.5-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 06fe1108aec6f649d3f37c47904a46ebaa3ad281 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 10 Feb 2018 18:08:02 -0800 Subject: spake2.rs: remove unnecessary ref, thanks clippy --- src/spake2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spake2.rs b/src/spake2.rs index 382e69d..9615fe8 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -117,7 +117,7 @@ impl Group for Ed25519Group { fn basepoint_mult(s: &c2_Scalar) -> c2_Element { //c2_Element::basepoint_mult(s) - &ED25519_BASEPOINT_POINT * s + ED25519_BASEPOINT_POINT * s } fn scalarmult(e: &c2_Element, s: &c2_Scalar) -> c2_Element { e * s -- cgit v1.2.3 From c0c286c82ec3dc5496d99334d20e6032506ad855 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 26 Feb 2018 22:11:16 -0800 Subject: remove redundant field names, thanks clippy --- src/spake2.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index 9615fe8..c64a365 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -311,14 +311,14 @@ impl SPAKE2 { ( SPAKE2 { - side: side, - xy_scalar: xy_scalar, - password_vec: password_vec, // string + side, + xy_scalar, + password_vec, // string id_a: id_a_copy, id_b: id_b_copy, id_s: id_s_copy, msg1: msg1.clone(), - password_scalar: password_scalar, // scalar + password_scalar, // scalar }, msg_and_side, ) -- cgit v1.2.3 From c715bf47645f44653e111f6440783e3d6dd7890f Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 5 Mar 2018 16:42:48 -0800 Subject: update to curve25519-dalek-0.15 --- Cargo.toml | 2 +- src/spake2.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b4fa517..d50b06c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.14" +curve25519-dalek = "0.15" rand = "0.4" sha2 = "0.7" hkdf = "0.3" diff --git a/src/spake2.rs b/src/spake2.rs index c64a365..8363401 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use curve25519_dalek::scalar::Scalar as c2_Scalar; -use curve25519_dalek::edwards::ExtendedPoint as c2_Element; +use curve25519_dalek::edwards::EdwardsPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::CompressedEdwardsY; use rand::{OsRng, Rng}; -- cgit v1.2.3 From 7978338df2d33920667348cc549ce320033362f8 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 24 Mar 2018 15:37:46 -0700 Subject: update to hkdf-0.4 --- Cargo.toml | 2 +- src/spake2.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d50b06c..3e760bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ travis-ci = { repository = "warner/spake2.rs" } curve25519-dalek = "0.15" rand = "0.4" sha2 = "0.7" -hkdf = "0.3" +hkdf = "0.4" num-bigint = "0.1" hex = "0.3" diff --git a/src/spake2.rs b/src/spake2.rs index 8363401..5fbbcd5 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -144,7 +144,7 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i = int(h, 16) // i % q - let okm = Hkdf::::new(s, b"").derive(b"SPAKE2 pw", 32 + 16); + let okm = Hkdf::::extract(b"", s).expand(b"SPAKE2 pw", 32 + 16); //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian -- cgit v1.2.3 From bff1b1fd9e8e8920247913e126bfc335fb770127 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 24 Mar 2018 15:39:59 -0700 Subject: update to curve25519-dalek-0.16 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3e760bd..ed7eb20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.15" +curve25519-dalek = "0.16" rand = "0.4" sha2 = "0.7" hkdf = "0.4" -- cgit v1.2.3 From 2a9fd36a749485b8c512c962f51f6e864bd0469e Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 29 Apr 2018 14:27:50 -0700 Subject: (cargo-release) version 0.0.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ed7eb20..2944c99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.5-alpha.0" +version = "0.0.5" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 3705d533697e86b9e962e7be2e013669e1146cca Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 29 Apr 2018 14:28:08 -0700 Subject: (cargo-release) start next development iteration 0.0.6-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2944c99..d4dbc33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.5" +version = "0.0.6-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From ec98460f58c0a7388f2173a73b47ac0f90f982cc Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 22 May 2018 16:52:19 -0700 Subject: Cargo.toml: update to latest rand and curve25519-dalek --- Cargo.toml | 4 ++-- src/spake2.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4dbc33..128a787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.16" -rand = "0.4" +curve25519-dalek = "0.17" +rand = "0.5" sha2 = "0.7" hkdf = "0.4" num-bigint = "0.1" diff --git a/src/spake2.rs b/src/spake2.rs index 5fbbcd5..7a32f5a 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -4,7 +4,7 @@ use curve25519_dalek::scalar::Scalar as c2_Scalar; use curve25519_dalek::edwards::EdwardsPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::CompressedEdwardsY; -use rand::{OsRng, Rng}; +use rand::{OsRng, Rng, CryptoRng}; use sha2::{Digest, Sha256}; use hkdf::Hkdf; use num_bigint::BigUint; @@ -35,7 +35,7 @@ pub trait Group { fn const_n() -> Self::Element; fn const_s() -> Self::Element; fn hash_to_scalar(s: &[u8]) -> Self::Scalar; - fn random_scalar(cspring: &mut T) -> Self::Scalar; + fn random_scalar(cspring: &mut T) -> Self::Scalar where T: Rng+CryptoRng; fn scalar_neg(s: &Self::Scalar) -> Self::Scalar; fn element_to_bytes(e: &Self::Element) -> Vec; fn bytes_to_element(b: &[u8]) -> Option; @@ -91,7 +91,8 @@ impl Group for Ed25519Group { fn hash_to_scalar(s: &[u8]) -> c2_Scalar { ed25519_hash_to_scalar(s) } - fn random_scalar(cspring: &mut T) -> c2_Scalar { + fn random_scalar(cspring: &mut T) -> c2_Scalar + where T: Rng + CryptoRng { c2_Scalar::random(cspring) } fn scalar_neg(s: &c2_Scalar) -> c2_Scalar { -- cgit v1.2.3 From 52326a626ad8b0283f70d76f0e5b0c35c65f560b Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 22 May 2018 16:55:10 -0700 Subject: Cargo.toml: update to latest hkdf --- Cargo.toml | 2 +- src/spake2.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 128a787..404bc0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ travis-ci = { repository = "warner/spake2.rs" } curve25519-dalek = "0.17" rand = "0.5" sha2 = "0.7" -hkdf = "0.4" +hkdf = "0.5" num-bigint = "0.1" hex = "0.3" diff --git a/src/spake2.rs b/src/spake2.rs index 7a32f5a..7f26958 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -145,7 +145,7 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i = int(h, 16) // i % q - let okm = Hkdf::::extract(b"", s).expand(b"SPAKE2 pw", 32 + 16); + let okm = Hkdf::::extract(Some(b""), s).expand(b"SPAKE2 pw", 32 + 16); //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian -- cgit v1.2.3 From 0c500f320f794ea3124533d6c35b677aa82ba93a Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 22 May 2018 17:15:46 -0700 Subject: cargo fmt --- src/spake2.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index 7f26958..d1cb4d9 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -1,13 +1,13 @@ #![allow(dead_code)] -use curve25519_dalek::scalar::Scalar as c2_Scalar; -use curve25519_dalek::edwards::EdwardsPoint as c2_Element; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::CompressedEdwardsY; -use rand::{OsRng, Rng, CryptoRng}; -use sha2::{Digest, Sha256}; +use curve25519_dalek::edwards::EdwardsPoint as c2_Element; +use curve25519_dalek::scalar::Scalar as c2_Scalar; use hkdf::Hkdf; use num_bigint::BigUint; +use rand::{CryptoRng, OsRng, Rng}; +use sha2::{Digest, Sha256}; //use hex::ToHex; @@ -35,7 +35,9 @@ pub trait Group { fn const_n() -> Self::Element; fn const_s() -> Self::Element; fn hash_to_scalar(s: &[u8]) -> Self::Scalar; - fn random_scalar(cspring: &mut T) -> Self::Scalar where T: Rng+CryptoRng; + fn random_scalar(cspring: &mut T) -> Self::Scalar + where + T: Rng + CryptoRng; fn scalar_neg(s: &Self::Scalar) -> Self::Scalar; fn element_to_bytes(e: &Self::Element) -> Vec; fn bytes_to_element(b: &[u8]) -> Option; @@ -92,7 +94,9 @@ impl Group for Ed25519Group { ed25519_hash_to_scalar(s) } fn random_scalar(cspring: &mut T) -> c2_Scalar - where T: Rng + CryptoRng { + where + T: Rng + CryptoRng, + { c2_Scalar::random(cspring) } fn scalar_neg(s: &c2_Scalar) -> c2_Scalar { @@ -467,10 +471,10 @@ mod test { deterministic RNG (used only for tests, of course) into the per-Group "random_scalar()" function, which results in some particular scalar. */ + use super::*; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; - use spake2::{Ed25519Group, SPAKE2}; use hex; - use super::*; + use spake2::{Ed25519Group, SPAKE2}; // the python tests show the long-integer form of scalars. the rust code // wants an array of bytes (little-endian). Make sure the way we convert -- cgit v1.2.3 From 2da29cfacc3a48222a4e1147b952015d6a5a61c2 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 22 May 2018 17:22:55 -0700 Subject: (cargo-release) version 0.0.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 404bc0c..23fbc98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.6-alpha.0" +version = "0.0.6" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 2c1f68d6be76af9c6f93c2f80880de23511b00ab Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 22 May 2018 17:23:21 -0700 Subject: (cargo-release) start next development iteration 0.0.7-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 23fbc98..7752944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.6" +version = "0.0.7-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 2e0fbf22e56ee9cb706a131aacee02c28891ec71 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 May 2018 11:54:30 -0700 Subject: implement Debug for SPAKE2, to help downstream applications derive it --- src/lib.rs | 6 +----- src/spake2.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4737738..dfc4b23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,13 @@ extern crate curve25519_dalek; +extern crate hex; extern crate hkdf; extern crate num_bigint; extern crate rand; extern crate sha2; -//extern crate hex; - mod spake2; pub use spake2::*; -#[cfg(test)] -extern crate hex; - #[cfg(test)] mod tests { use spake2::{Ed25519Group, ErrorType, SPAKE2, SPAKEErr}; diff --git a/src/spake2.rs b/src/spake2.rs index d1cb4d9..07c92a3 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -4,10 +4,12 @@ use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::CompressedEdwardsY; use curve25519_dalek::edwards::EdwardsPoint as c2_Element; use curve25519_dalek::scalar::Scalar as c2_Scalar; +use hex; use hkdf::Hkdf; use num_bigint::BigUint; use rand::{CryptoRng, OsRng, Rng}; use sha2::{Digest, Sha256}; +use std::fmt; //use hex::ToHex; @@ -47,6 +49,7 @@ pub trait Group { fn add(a: &Self::Element, b: &Self::Element) -> Self::Element; } +#[derive(Debug)] pub struct Ed25519Group; impl Group for Ed25519Group { @@ -254,11 +257,13 @@ fn ed25519_hash_symmetric( /* "session type pattern" */ +#[derive(Debug)] enum Side { A, B, Symmetric, } + pub struct SPAKE2 { //where &G::Scalar: Neg { side: Side, @@ -464,6 +469,26 @@ impl SPAKE2 { } } +fn maybe_utf8(s: &[u8]) -> String { + match String::from_utf8(s.to_vec()) { + Ok(m) => format!("(s={})", m), + Err(_) => format!("(hex={})", hex::encode(s)), + } +} + +impl fmt::Debug for SPAKE2 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "SPAKE2(G=?, side={:?}, idA={}, idB={}, idS={})", + self.side, + maybe_utf8(&self.id_a), + maybe_utf8(&self.id_b), + maybe_utf8(&self.id_s) + ) + } +} + #[cfg(test)] mod test { /* This compares results against the python compatibility tests: @@ -617,4 +642,12 @@ mod test { ); } + #[test] + fn test_debug() { + let (s1, _msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + println!("s1: {:?}", s1); + let (s2, _msg1) = SPAKE2::::start_symmetric(b"password", b"idS"); + println!("s2: {:?}", s2); + } + } -- cgit v1.2.3 From 6b8be3f04ee580c7962f508c5725865f14ef0951 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 May 2018 11:56:44 -0700 Subject: Cargo.toml: remove redundant dev-dep on hex --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7752944..682d65d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ num-bigint = "0.1" hex = "0.3" [dev-dependencies] -hex = "0.3" bencher = "0.1" [[bench]] -- cgit v1.2.3 From f9d4a223c48e5f9fe161f6a9a47c806c0752835b Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 May 2018 12:11:16 -0700 Subject: derive Eq and PartialEq too --- src/spake2.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index 07c92a3..31be328 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -13,14 +13,14 @@ use std::fmt; //use hex::ToHex; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum ErrorType { BadSide, WrongLength, CorruptMessage, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub struct SPAKEErr { pub kind: ErrorType, } @@ -49,7 +49,7 @@ pub trait Group { fn add(a: &Self::Element, b: &Self::Element) -> Self::Element; } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct Ed25519Group; impl Group for Ed25519Group { @@ -257,13 +257,15 @@ fn ed25519_hash_symmetric( /* "session type pattern" */ -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] enum Side { A, B, Symmetric, } +// we implement a custom Debug below, to avoid revealing secrets in a dump +#[derive(PartialEq, Eq)] pub struct SPAKE2 { //where &G::Scalar: Neg { side: Side, -- cgit v1.2.3 From db65799f4aef435eee446596e55cd7b0977fa97d Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 May 2018 12:17:08 -0700 Subject: travis: add rustfmt, ignore WIP-* branches --- .travis.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3ab58db..3998753 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,18 @@ os: - linux - osx +branches: + except: + - /^WIP-.*$/ + matrix: + include: + - rust: 1.26.0 # lock down for consistent rustfmt behavior + env: RUSTFMT + install: + - rustup component add rustfmt-preview + script: + - cargo fmt -- --write-mode=diff allow_failures: - rust: nightly -- cgit v1.2.3 From 71baf98729b8fba7ff624f151e675799ce861c8b Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 May 2018 12:23:51 -0700 Subject: (cargo-release) version 0.0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 682d65d..7aeb82c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.7-alpha.0" +version = "0.0.7" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 1b4418d60820b11201d17c427e2875a676ccc791 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 May 2018 12:24:15 -0700 Subject: (cargo-release) start next development iteration 0.0.8-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7aeb82c..f401ec3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.7" +version = "0.0.8-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 4df7100bcb525634a6bad58859e3cb334e71fdde Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 26 May 2018 12:54:25 -0700 Subject: update deps: num-bigint=0.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f401ec3..4fbf631 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ curve25519-dalek = "0.17" rand = "0.5" sha2 = "0.7" hkdf = "0.5" -num-bigint = "0.1" +num-bigint = "0.2" hex = "0.3" [dev-dependencies] -- cgit v1.2.3 From 2958035067bc7cb01e1c6672f53daf8d0f1e8096 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 26 May 2018 13:12:13 -0700 Subject: (cargo-release) version 0.0.8 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4fbf631..fdabe09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.8-alpha.0" +version = "0.0.8" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 9bd20219ac9d6727a4b99cf6dd48e44b3190c6c9 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 26 May 2018 13:12:27 -0700 Subject: (cargo-release) start next development iteration 0.0.9-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fdabe09..f3001e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.8" +version = "0.0.9-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 0869881573ec805ec2337469ed5c4184cb0382e2 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sun, 3 Jun 2018 13:57:19 -0700 Subject: use newtypes for Password and Identity to avoid usage errors This a breaking API change. The next release should bump the minor version number. As discussed in https://github.com/warner/spake2.rs/issues/3 and https://github.com/warner/magic-wormhole.rs/issues/32 , if an application were to accidentally swap the "password" and "identity" arguments (mainly for start_symmetric which only takes two args), the app would appear to work, but would contain a devastating security vulnerability (online brute-force password attack, with precomputation enabled). You might think of newtypes as giving the API named parameters. Instead of: `s = start_symmetric(b"pw", b"appid")` you get: `s = start_symmetric(&Password::new(b"pw"), &Identity::new(b"appid"))` but it protects you (with a compile-time error) against mistakes like: `s = start_symmetric(&Identity::new(b"appid"), &Password::new(b"pw"))` I'd like to find a way to remove requirement to pass a reference (and enable `start_symmetric(Password::new(..)..)`). --- benches/spake2.rs | 41 +++++++++----- src/lib.rs | 48 +++++++++++++---- src/spake2.rs | 159 +++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 189 insertions(+), 59 deletions(-) diff --git a/benches/spake2.rs b/benches/spake2.rs index d945d5a..8323342 100644 --- a/benches/spake2.rs +++ b/benches/spake2.rs @@ -4,32 +4,49 @@ extern crate bencher; extern crate spake2; use bencher::Bencher; -use spake2::{Ed25519Group, SPAKE2}; +use spake2::{Ed25519Group, Identity, Password, SPAKE2}; fn spake2_start(bench: &mut Bencher) { bench.iter(|| { - let (_, _) = SPAKE2::::start_a(b"password", b"A", b"B"); + let (_, _) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); }) } /* fn spake2_finish(bench: &mut Bencher) { // this doesn't work, because s1 is consumed by doing finish() - let (s1, msg1) = SPAKE2::::start_a(b"password", - b"idA", b"idB"); - let (s2, msg2) = SPAKE2::::start_b(b"password", - b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); + let (s2, msg2) = SPAKE2::::start_b( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); let msg2_slice = msg2.as_slice(); - bench.iter(|| { - s1.finish(msg2_slice) - }) -}*/ + bench.iter(|| s1.finish(msg2_slice)) +} +*/ fn spake2_start_and_finish(bench: &mut Bencher) { - let (_, msg2) = SPAKE2::::start_b(b"password", b"idA", b"idB"); + let (_, msg2) = SPAKE2::::start_b( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); let msg2_slice = msg2.as_slice(); bench.iter(|| { - let (s1, _) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s1, _) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); s1.finish(msg2_slice) }) } diff --git a/src/lib.rs b/src/lib.rs index dfc4b23..030498e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,20 @@ pub use spake2::*; #[cfg(test)] mod tests { - use spake2::{Ed25519Group, ErrorType, SPAKE2, SPAKEErr}; + use spake2::{Ed25519Group, ErrorType, Identity, Password, SPAKE2, SPAKEErr}; #[test] fn test_basic() { - let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); - let (s2, msg2) = SPAKE2::::start_b(b"password", b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); + let (s2, msg2) = SPAKE2::::start_b( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); @@ -23,8 +31,16 @@ mod tests { #[test] fn test_mismatch() { - let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); - let (s2, msg2) = SPAKE2::::start_b(b"password2", b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); + let (s2, msg2) = SPAKE2::::start_b( + &Password::new(b"password2"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_ne!(key1, key2); @@ -32,7 +48,11 @@ mod tests { #[test] fn test_reflected_message() { - let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); let r = s1.finish(msg1.as_slice()); assert_eq!( r.unwrap_err(), @@ -44,7 +64,11 @@ mod tests { #[test] fn test_bad_length() { - let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); let mut msg2 = Vec::::with_capacity(msg1.len() + 1); msg2.resize(msg1.len() + 1, 0u8); let r = s1.finish(&msg2); @@ -58,8 +82,14 @@ mod tests { #[test] fn test_basic_symmetric() { - let (s1, msg1) = SPAKE2::::start_symmetric(b"password", b"idS"); - let (s2, msg2) = SPAKE2::::start_symmetric(b"password", b"idS"); + let (s1, msg1) = SPAKE2::::start_symmetric( + &Password::new(b"password"), + &Identity::new(b"idS"), + ); + let (s2, msg2) = SPAKE2::::start_symmetric( + &Password::new(b"password"), + &Identity::new(b"idS"), + ); let key1 = s1.finish(msg2.as_slice()).unwrap(); let key2 = s2.finish(msg1.as_slice()).unwrap(); assert_eq!(key1, key2); diff --git a/src/spake2.rs b/src/spake2.rs index 31be328..fe416cb 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -10,9 +10,42 @@ use num_bigint::BigUint; use rand::{CryptoRng, OsRng, Rng}; use sha2::{Digest, Sha256}; use std::fmt; +use std::ops::Deref; //use hex::ToHex; +/* "newtype pattern": it's a Vec, but only used for a specific argument + * type, to distinguish between ones that are meant as passwords, and ones + * that are meant as identity strings */ + +#[derive(PartialEq, Eq, Clone)] +pub struct Password(Vec); +impl Password { + pub fn new(p: &[u8]) -> Password { + Password(p.to_vec()) + } +} +impl Deref for Password { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } +} + +#[derive(PartialEq, Eq, Clone)] +pub struct Identity(Vec); +impl Deref for Identity { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } +} +impl Identity { + pub fn new(p: &[u8]) -> Identity { + Identity(p.to_vec()) + } +} + #[derive(Debug, PartialEq, Eq)] pub enum ErrorType { BadSide, @@ -281,14 +314,14 @@ pub struct SPAKE2 { impl SPAKE2 { fn start_internal( side: Side, - password: &[u8], - id_a: &[u8], - id_b: &[u8], - id_s: &[u8], + password: &Password, + id_a: &Identity, + id_b: &Identity, + id_s: &Identity, xy_scalar: G::Scalar, ) -> (SPAKE2, Vec) { //let password_scalar: G::Scalar = hash_to_scalar::(password); - let password_scalar: G::Scalar = G::hash_to_scalar(password); + let password_scalar: G::Scalar = G::hash_to_scalar(&password); // a: X = B*x + M*pw // b: Y = B*y + N*pw @@ -305,13 +338,13 @@ impl SPAKE2 { //let m1: G::Element = &G::basepoint_mult(&x) + &(blinding * &password_scalar); let msg1: Vec = G::element_to_bytes(&m1); let mut password_vec = Vec::new(); - password_vec.extend_from_slice(password); + password_vec.extend_from_slice(&password); let mut id_a_copy = Vec::new(); - id_a_copy.extend_from_slice(id_a); + id_a_copy.extend_from_slice(&id_a); let mut id_b_copy = Vec::new(); - id_b_copy.extend_from_slice(id_b); + id_b_copy.extend_from_slice(&id_b); let mut id_s_copy = Vec::new(); - id_s_copy.extend_from_slice(id_s); + id_s_copy.extend_from_slice(&id_s); let mut msg_and_side = Vec::new(); msg_and_side.push(match side { @@ -337,47 +370,68 @@ impl SPAKE2 { } fn start_a_internal( - password: &[u8], - id_a: &[u8], - id_b: &[u8], + password: &Password, + id_a: &Identity, + id_b: &Identity, xy_scalar: G::Scalar, ) -> (SPAKE2, Vec) { - Self::start_internal(Side::A, password, id_a, id_b, b"", xy_scalar) + Self::start_internal( + Side::A, + &password, + &id_a, + &id_b, + &Identity::new(b""), + xy_scalar, + ) } fn start_b_internal( - password: &[u8], - id_a: &[u8], - id_b: &[u8], + password: &Password, + id_a: &Identity, + id_b: &Identity, xy_scalar: G::Scalar, ) -> (SPAKE2, Vec) { - Self::start_internal(Side::B, password, id_a, id_b, b"", xy_scalar) + Self::start_internal( + Side::B, + &password, + &id_a, + &id_b, + &Identity::new(b""), + xy_scalar, + ) } fn start_symmetric_internal( - password: &[u8], - id_s: &[u8], + password: &Password, + id_s: &Identity, xy_scalar: G::Scalar, ) -> (SPAKE2, Vec) { - Self::start_internal(Side::Symmetric, password, b"", b"", id_s, xy_scalar) + Self::start_internal( + Side::Symmetric, + &password, + &Identity::new(b""), + &Identity::new(b""), + &id_s, + xy_scalar, + ) } - pub fn start_a(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { + pub fn start_a(password: &Password, id_a: &Identity, id_b: &Identity) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); - Self::start_a_internal(password, id_a, id_b, xy_scalar) + Self::start_a_internal(&password, &id_a, &id_b, xy_scalar) } - pub fn start_b(password: &[u8], id_a: &[u8], id_b: &[u8]) -> (SPAKE2, Vec) { + pub fn start_b(password: &Password, id_a: &Identity, id_b: &Identity) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); - Self::start_b_internal(password, id_a, id_b, xy_scalar) + Self::start_b_internal(&password, &id_a, &id_b, xy_scalar) } - pub fn start_symmetric(password: &[u8], id_s: &[u8]) -> (SPAKE2, Vec) { + pub fn start_symmetric(password: &Password, id_s: &Identity) -> (SPAKE2, Vec) { let mut cspring: OsRng = OsRng::new().unwrap(); let xy_scalar: G::Scalar = G::random_scalar(&mut cspring); - Self::start_symmetric_internal(password, id_s, xy_scalar) + Self::start_symmetric_internal(&password, &id_s, xy_scalar) } pub fn finish(self, msg2: &[u8]) -> Result, SPAKEErr> { @@ -535,11 +589,11 @@ mod test { #[test] fn test_password_to_scalar() { - let password = b"password"; + let password = Password::new(b"password"); let expected_pw_scalar = decimal_to_scalar( b"3515301705789368674385125653994241092664323519848410154015274772661223168839", ); - let pw_scalar = Ed25519Group::hash_to_scalar(password); + let pw_scalar = Ed25519Group::hash_to_scalar(&password); println!("exp: {:?}", hex::encode(expected_pw_scalar.as_bytes())); println!("got: {:?}", hex::encode(pw_scalar.as_bytes())); assert_eq!(&pw_scalar, &expected_pw_scalar); @@ -547,18 +601,32 @@ mod test { #[test] fn test_sizes() { - let (s1, msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s1, msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); assert_eq!(msg1.len(), 1 + 32); - let (s2, msg2) = SPAKE2::::start_b(b"password", b"idA", b"idB"); + let (s2, msg2) = SPAKE2::::start_b( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); assert_eq!(msg2.len(), 1 + 32); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); assert_eq!(key1.len(), 32); assert_eq!(key2.len(), 32); - let (s1, msg1) = SPAKE2::::start_symmetric(b"password", b"idS"); + let (s1, msg1) = SPAKE2::::start_symmetric( + &Password::new(b"password"), + &Identity::new(b"idS"), + ); assert_eq!(msg1.len(), 1 + 32); - let (s2, msg2) = SPAKE2::::start_symmetric(b"password", b"idS"); + let (s2, msg2) = SPAKE2::::start_symmetric( + &Password::new(b"password"), + &Identity::new(b"idS"), + ); assert_eq!(msg2.len(), 1 + 32); let key1 = s1.finish(&msg2).unwrap(); let key2 = s2.finish(&msg1).unwrap(); @@ -607,8 +675,12 @@ mod test { println!("scalar_a is {}", hex::encode(scalar_a.as_bytes())); - let (s1, msg1) = - SPAKE2::::start_a_internal(b"password", b"idA", b"idB", scalar_a); + let (s1, msg1) = SPAKE2::::start_a_internal( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + scalar_a, + ); let expected_msg1 = "416fc960df73c9cf8ed7198b0c9534e2e96a5984bfc5edc023fd24dacf371f2af9"; println!(); @@ -627,8 +699,12 @@ mod test { ); assert_eq!(hex::encode(&msg1), expected_msg1); - let (s2, msg2) = - SPAKE2::::start_b_internal(b"password", b"idA", b"idB", scalar_b); + let (s2, msg2) = SPAKE2::::start_b_internal( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + scalar_b, + ); assert_eq!(expected_pw_scalar, s2.password_scalar); assert_eq!( hex::encode(&msg2), @@ -646,9 +722,16 @@ mod test { #[test] fn test_debug() { - let (s1, _msg1) = SPAKE2::::start_a(b"password", b"idA", b"idB"); + let (s1, _msg1) = SPAKE2::::start_a( + &Password::new(b"password"), + &Identity::new(b"idA"), + &Identity::new(b"idB"), + ); println!("s1: {:?}", s1); - let (s2, _msg1) = SPAKE2::::start_symmetric(b"password", b"idS"); + let (s2, _msg1) = SPAKE2::::start_symmetric( + &Password::new(b"password"), + &Identity::new(b"idS"), + ); println!("s2: {:?}", s2); } -- cgit v1.2.3 From 47702913072fd1410269fca830bc4ac13cc1edf3 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 25 Jun 2018 22:18:10 -0700 Subject: update deps: curve25519-dalek=0.18 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f3001e6..90d3aa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.17" +curve25519-dalek = "0.18" rand = "0.5" sha2 = "0.7" hkdf = "0.5" -- cgit v1.2.3 From 475d722f4a2a0c0d007fd3cb5dd8a26f7ed04b42 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 19 Jul 2018 16:40:06 -0700 Subject: forbid unsafe_code, and warnings durings tests --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 030498e..c2858ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#![forbid(unsafe_code)] +#![cfg_attr(test, deny(warnings))] + extern crate curve25519_dalek; extern crate hex; extern crate hkdf; -- cgit v1.2.3 From 3d29e394b69eedaa27937ed656ed65d910491e85 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 27 Jul 2018 11:07:56 -0700 Subject: update deps: curve25519-dalek=0.19 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 90d3aa7..2022d3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ travis-ci = { repository = "warner/spake2.rs" } [dependencies] -curve25519-dalek = "0.18" +curve25519-dalek = "0.19" rand = "0.5" sha2 = "0.7" hkdf = "0.5" -- cgit v1.2.3 From e0bc10980efff253d02f571393c083d2adeaee13 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:36:33 -0700 Subject: update deps: hkdf-0.6 --- Cargo.toml | 2 +- src/spake2.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2022d3c..bbabe21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ travis-ci = { repository = "warner/spake2.rs" } curve25519-dalek = "0.19" rand = "0.5" sha2 = "0.7" -hkdf = "0.5" +hkdf = "0.6" num-bigint = "0.2" hex = "0.3" diff --git a/src/spake2.rs b/src/spake2.rs index fe416cb..67c2acc 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -185,7 +185,8 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i = int(h, 16) // i % q - let okm = Hkdf::::extract(Some(b""), s).expand(b"SPAKE2 pw", 32 + 16); + let mut okm = [0u8; 32+16]; + Hkdf::::extract(Some(b""), s).expand(b"SPAKE2 pw", &mut okm).unwrap(); //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian -- cgit v1.2.3 From 4119bda304dd15547ae04ce0429e627c5e6ba3cb Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:39:41 -0700 Subject: cargo fmt --- src/spake2.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/spake2.rs b/src/spake2.rs index 67c2acc..a258c4c 100644 --- a/src/spake2.rs +++ b/src/spake2.rs @@ -185,8 +185,10 @@ fn ed25519_hash_to_scalar(s: &[u8]) -> c2_Scalar { // i = int(h, 16) // i % q - let mut okm = [0u8; 32+16]; - Hkdf::::extract(Some(b""), s).expand(b"SPAKE2 pw", &mut okm).unwrap(); + let mut okm = [0u8; 32 + 16]; + Hkdf::::extract(Some(b""), s) + .expand(b"SPAKE2 pw", &mut okm) + .unwrap(); //println!("expanded: {}{}", "................................", okm.iter().to_hex()); // ok let mut reducible = [0u8; 64]; // little-endian -- cgit v1.2.3 From 891ac99a1258bea2aa0f551b5f2d265dd04a2031 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:39:48 -0700 Subject: README: soften the disclaimer by roughly 24% --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb53328..13342d2 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,4 @@ The SPAKE2 password-authenticated key-exchange algorithm, in Rust. [![Docs.rs](https://docs.rs/spake2/badge.svg)](https://docs.rs/spake2) -NOTE: this is just a beginning: you should absolutely not use this for anything at all yet. It has a long way to go. Wait until it has a real version number (at least 0.1.something) and has been published to crates.io. +This is still pretty early, but seems to do the job. It needs a proper security review before you should consider using it for anything serious. -- cgit v1.2.3 From 36270864db817943b112d0dacbfee034731dbb64 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:44:43 -0700 Subject: (cargo-release) version 0.0.9 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bbabe21..e633fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.9-alpha.0" +version = "0.0.9" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 033cd4c90fc1d34bd0379cd3e4c5b5db21910740 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:45:11 -0700 Subject: (cargo-release) start next development iteration 0.0.10-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e633fa2..b6d783f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.9" +version = "0.0.10-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From c1621a73e27b3b284d51f1f1b57a7f65582d4395 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:52:16 -0700 Subject: README: oops, I was supposed to bump the minor version for this release I introduced a breaking API change, and should have named that last release 0.1.0 instead of 0.0.9. I'll release this as 0.1.0. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 13342d2..74b4225 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,5 @@ The SPAKE2 password-authenticated key-exchange algorithm, in Rust. This is still pretty early, but seems to do the job. It needs a proper security review before you should consider using it for anything serious. + +Note that the API has changed since 0.0.8 . I released 0.0.9 by mistake. -- cgit v1.2.3 From 2fe67eb0f0c686923c5c4be15dddf7eb48775fdf Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:53:46 -0700 Subject: (cargo-release) version 0.1.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b6d783f..f6c1a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.0.10-alpha.0" +version = "0.1.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From 76dd73f9a9185e22775d57a186d15eb0ae3e79e9 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 20 Aug 2018 21:54:04 -0700 Subject: (cargo-release) start next development iteration 0.1.1-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f6c1a2a..94c91e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spake2" -version = "0.1.0" +version = "0.1.1-alpha.0" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm, in Rust." documentation = "https://docs.rs/spake2" -- cgit v1.2.3 From f6e9d07dffa9a9b39f203c23043f93337ebe1ab6 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Mon, 24 Sep 2018 11:44:06 -0700 Subject: README/Cargo.toml: add more badges --- Cargo.toml | 2 ++ README.md | 27 ++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94c91e3..8736f35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ exclude = [ [badges] travis-ci = { repository = "warner/spake2.rs" } +is-it-maintained-issue-resolution = { repository = "warner/spake2.rs" } +is-it-maintained-open-issues = { repository = "warner/spake2.rs" } [dependencies] curve25519-dalek = "0.19" diff --git a/README.md b/README.md index 74b4225..0d32749 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,29 @@ # spake2.rs The SPAKE2 password-authenticated key-exchange algorithm, in Rust. -[![Build Status](https://travis-ci.org/warner/spake2.rs.svg?branch=master)](https://travis-ci.org/warner/spake2.rs) -[![codecov](https://codecov.io/gh/warner/spake2.rs/branch/master/graph/badge.svg)](https://codecov.io/gh/warner/spake2.rs) -[![Crates.io](https://img.shields.io/crates/v/spake2.svg)](https://crates.io/crates/spake2) -[![Docs.rs](https://docs.rs/spake2/badge.svg)](https://docs.rs/spake2) - +[![Build Status][build-status-image]][build-status-url] +[![Codecov][codecov-image]][codecov-url] +[![Is-It-Maintained-Resolution-Time][iim-resolution-image]][iim-resolution-url] +[![Is-It-Maintained-Open-Issues][iim-open-image]][iim-open-url] +[![Crates.io][crates-io-image]][crates-io-url] +[![Docs.rs][docs-image]][docs-url] +[![License][license-image]][license-url] This is still pretty early, but seems to do the job. It needs a proper security review before you should consider using it for anything serious. Note that the API has changed since 0.0.8 . I released 0.0.9 by mistake. + +[build-status-image]: https://travis-ci.org/warner/spake2.rs.svg?branch=master +[build-status-url]: https://travis-ci.org/warner/spake2.rs +[codecov-image]: https://codecov.io/gh/warner/spake2.rs/branch/master/graph/badge.svg +[codecov-url]: https://codecov.io/gh/warner/spake2.rs +[crates-io-image]: https://img.shields.io/crates/v/spake2.svg +[crates-io-url]: https://crates.io/crates/spake2 +[docs-image]: https://docs.rs/spake2/badge.svg +[docs-url]: https://docs.rs/spake2 +[license-image]: https://img.shields.io/badge/License-MIT-blue.svg +[license-url]: LICENSE +[iim-resolution-image]: http://isitmaintained.com/badge/resolution/warner/spake2.rs.svg +[iim-resolution-url]: http://isitmaintained.com/project/warner/spake2.rs +[iim-open-image]: http://isitmaintained.com/badge/open/warner/spake2.rs.svg +[iim-open-url]: http://isitmaintained.com/project/warner/spake2.rs -- cgit v1.2.3