From 689dc0ab6af950b027b4bab96f73c427d2c42d6e Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 20 Dec 2021 18:46:23 -0700 Subject: 2021 edition bump + doc improvements; MSRV 1.56 (#80) - Bumps both `spake2` and `srp` to Rust 2021 edition - Uses the new `doc = include_str!(...)` attribute to include README.md files in the rustdoc - Improves the README.md files, adding an initial one for `srp` - clippy fixes for Rust 1.56 --- .github/workflows/spake2.yml | 2 +- .github/workflows/srp.yml | 2 +- .github/workflows/workspace.yml | 2 +- Cargo.lock | 4 +- Cargo.toml | 1 + spake2/Cargo.toml | 10 +-- spake2/README.md | 142 ++++++++++++++++++++++++++++++++-------- spake2/src/lib.rs | 120 +++++++-------------------------- spake2/src/tests.rs | 2 +- spake2/tests/mod.rs | 84 ------------------------ spake2/tests/spake2.rs | 84 ++++++++++++++++++++++++ srp/Cargo.toml | 6 +- srp/README.md | 73 +++++++++++++++++++++ srp/src/lib.rs | 18 ++--- srp/src/types.rs | 2 +- srp/tests/mod.rs | 63 ------------------ srp/tests/srp.rs | 62 ++++++++++++++++++ 17 files changed, 378 insertions(+), 299 deletions(-) delete mode 100644 spake2/tests/mod.rs create mode 100644 spake2/tests/spake2.rs create mode 100644 srp/README.md delete mode 100644 srp/tests/mod.rs create mode 100644 srp/tests/srp.rs diff --git a/.github/workflows/spake2.yml b/.github/workflows/spake2.yml index 21ab115..e3b6c0c 100644 --- a/.github/workflows/spake2.yml +++ b/.github/workflows/spake2.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/srp.yml b/.github/workflows/srp.yml index d66d135..4e6485f 100644 --- a/.github/workflows/srp.yml +++ b/.github/workflows/srp.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 9117e88..128fc90 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.41.0 # MSRV + toolchain: 1.56.0 # MSRV components: clippy override: true profile: minimal diff --git a/Cargo.lock b/Cargo.lock index d5a0eb6..5789c25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,7 +363,7 @@ dependencies = [ [[package]] name = "spake2" -version = "0.2.1-alpha.0" +version = "0.3.0-pre" dependencies = [ "bencher", "curve25519-dalek", @@ -376,7 +376,7 @@ dependencies = [ [[package]] name = "srp" -version = "0.5.0" +version = "0.6.0-pre" dependencies = [ "digest 0.9.0", "generic-array 0.14.4", diff --git a/Cargo.toml b/Cargo.toml index 0dc3885..14a3005 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "srp", "spake2", diff --git a/spake2/Cargo.toml b/spake2/Cargo.toml index 4436183..5df4705 100644 --- a/spake2/Cargo.toml +++ b/spake2/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "spake2" -version = "0.2.1-alpha.0" -edition = "2018" +version = "0.3.0-pre" authors = ["Brian Warner "] description = "The SPAKE2 password-authenticated key-exchange algorithm." documentation = "https://docs.rs/spake2" @@ -10,9 +9,10 @@ repository = "https://github.com/RustCrypto/PAKEs" license = "MIT OR Apache-2.0" keywords = ["crypto", "pake", "authentication"] categories = ["cryptography", "authentication"] -exclude = [ - ".gitignore" -] +exclude = [".gitignore"] +readme = "README.md" +edition = "2018" +rust-version = "1.56" [package.metadata.release] tag-prefix = "spake2-v" diff --git a/spake2/README.md b/spake2/README.md index 50fa5bf..e2cd59f 100644 --- a/spake2/README.md +++ b/spake2/README.md @@ -1,29 +1,115 @@ -# spake2.rs -The SPAKE2 password-authenticated key-exchange algorithm, in Rust. - -[![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/RustCrypto/PAKEs.svg?branch=master -[build-status-url]: https://travis-ci.org/RustCrypto/PAKEs -[codecov-image]: https://codecov.io/gh/RustCrypto/PAKEs/branch/master/graph/badge.svg -[codecov-url]: https://codecov.io/gh/RustCrypto/PAKEs -[crates-io-image]: https://img.shields.io/crates/v/spake2.svg -[crates-io-url]: https://crates.io/crates/spake2 +# [RustCrypto]: SPAKE2 + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Pure Rust implementation of the [SPAKE2] password-authenticated key-exchange algorithm. + +[Documentation][docs-link] + +## About + +This library implements the SPAKE2 password-authenticated key exchange +("PAKE") algorithm. This allows two parties, who share a weak password, to +safely derive a strong shared secret (and therefore build an +encrypted+authenticated channel). + +A passive attacker who eavesdrops on the connection learns no information +about the password or the generated secret. An active attacker +(man-in-the-middle) gets exactly one guess at the password, and unless they +get it right, they learn no information about the password or the generated +secret. Each execution of the protocol enables one guess. The use of a weak +password is made safer by the rate-limiting of guesses: no off-line +dictionary attack is available to the network-level attacker, and the +protocol does not depend upon having previously-established confidentiality +of the network (unlike e.g. sending a plaintext password over TLS). + +The protocol requires the exchange of one pair of messages, so only one round +trip is necessary to establish the session key. If key-confirmation is +necessary, that will require a second round trip. + +All messages are bytestrings. For the default security level (using the +Ed25519 elliptic curve, roughly equivalent to an 128-bit symmetric key), the +message is 33 bytes long. + +This implementation is generic over a `Group`, which defines the cyclic +group to use, the functions which convert group elements and scalars to +and from bytestrings, and the three distinctive group elements used in +the blinding process. Only one such Group is implemented, named +`Ed25519Group`, which provides fast operations and high security, and is +compatible with my [python implementation](https://github.com/warner/python-spake2). + +# What Is It Good For? + +PAKE can be used in a pairing protocol, like the initial version of Firefox +Sync (the one with J-PAKE), to introduce one device to another and help them +share secrets. In this mode, one device creates a random code, the user +copies that code to the second device, then both devices use the code as a +one-time password and run the PAKE protocol. Once both devices have a shared +strong key, they can exchange other secrets safely. + +PAKE can also be used (carefully) in a login protocol, where SRP is perhaps +the best-known approach. Traditional non-PAKE login consists of sending a +plaintext password through a TLS-encrypted channel, to a server which then +checks it (by hashing/stretching and comparing against a stored verifier). In +a PAKE login, both sides put the password into their PAKE protocol, and then +confirm that their generated key is the same. This nominally does not require +the initial TLS-protected channel. However note that it requires other, +deeper design considerations (the PAKE protocol must be bound to whatever +protected channel you end up using, else the attacker can wait for PAKE to +complete normally and then steal the channel), and is not simply a drop-in +replacement. In addition, the server cannot hash/stretch the password very +much (see the note on "Augmented PAKE" below), so unless the client is +willing to perform key-stretching before running PAKE, the server's stored +verifier will be vulnerable to a low-cost dictionary attack. + +## ⚠️ Security Warning + +This crate has never received an independent third party audit for security and +correctness. + +USE AT YOUR OWN RISK! + +## Minimum Supported Rust Version + +Rust **1.56** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/spake2.svg +[crate-link]: 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/crates/l/spake2.svg -[license-url]: LICENSE-MIT -[iim-resolution-image]: http://isitmaintained.com/badge/resolution/RustCrypto/PAKEs.svg -[iim-resolution-url]: http://isitmaintained.com/project/RustCrypto/PAKEs -[iim-open-image]: http://isitmaintained.com/badge/open/RustCrypto/PAKEs.svg -[iim-open-url]: http://isitmaintained.com/project/RustCrypto/PAKEs +[docs-link]: https://docs.rs/spake2/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260045-PAKEs +[build-image]: https://github.com/RustCrypto/PAKEs/actions/workflows/spake2.yml/badge.svg +[build-link]: https://github.com/RustCrypto/PAKEs/actions/workflows/spake2.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[SPAKE2]: https://tools.ietf.org/id/draft-irtf-cfrg-spake2-10.html diff --git a/spake2/src/lib.rs b/spake2/src/lib.rs index 562f389..5980b95 100644 --- a/spake2/src/lib.rs +++ b/spake2/src/lib.rs @@ -1,77 +1,10 @@ -//! An implementation of the [SPAKE2][1] password-authenticated key-exchange -//! algorithm -//! -//! This library implements the SPAKE2 password-authenticated key exchange -//! ("PAKE") algorithm. This allows two parties, who share a weak password, to -//! safely derive a strong shared secret (and therefore build an -//! encrypted+authenticated channel). -//! -//! A passive attacker who eavesdrops on the connection learns no information -//! about the password or the generated secret. An active attacker -//! (man-in-the-middle) gets exactly one guess at the password, and unless they -//! get it right, they learn no information about the password or the generated -//! secret. Each execution of the protocol enables one guess. The use of a weak -//! password is made safer by the rate-limiting of guesses: no off-line -//! dictionary attack is available to the network-level attacker, and the -//! protocol does not depend upon having previously-established confidentiality -//! of the network (unlike e.g. sending a plaintext password over TLS). -//! -//! The protocol requires the exchange of one pair of messages, so only one round -//! trip is necessary to establish the session key. If key-confirmation is -//! necessary, that will require a second round trip. -//! -//! All messages are bytestrings. For the default security level (using the -//! Ed25519 elliptic curve, roughly equivalent to an 128-bit symmetric key), the -//! message is 33 bytes long. -//! -//! This implementation is generic over a `Group`, which defines the cyclic -//! group to use, the functions which convert group elements and scalars to -//! and from bytestrings, and the three distinctive group elements used in -//! the blinding process. Only one such Group is implemented, named -//! `Ed25519Group`, which provides fast operations and high security, and is -//! compatible with my [python -//! implementation](https://github.com/warner/python-spake2). -//! -//! # What Is It Good For? -//! -//! PAKE can be used in a pairing protocol, like the initial version of Firefox -//! Sync (the one with J-PAKE), to introduce one device to another and help them -//! share secrets. In this mode, one device creates a random code, the user -//! copies that code to the second device, then both devices use the code as a -//! one-time password and run the PAKE protocol. Once both devices have a shared -//! strong key, they can exchange other secrets safely. -//! -//! PAKE can also be used (carefully) in a login protocol, where SRP is perhaps -//! the best-known approach. Traditional non-PAKE login consists of sending a -//! plaintext password through a TLS-encrypted channel, to a server which then -//! checks it (by hashing/stretching and comparing against a stored verifier). In -//! a PAKE login, both sides put the password into their PAKE protocol, and then -//! confirm that their generated key is the same. This nominally does not require -//! the initial TLS-protected channel. However note that it requires other, -//! deeper design considerations (the PAKE protocol must be bound to whatever -//! protected channel you end up using, else the attacker can wait for PAKE to -//! complete normally and then steal the channel), and is not simply a drop-in -//! replacement. In addition, the server cannot hash/stretch the password very -//! much (see the note on "Augmented PAKE" below), so unless the client is -//! willing to perform key-stretching before running PAKE, the server's stored -//! verifier will be vulnerable to a low-cost dictionary attack. -//! +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms, unused_qualifications)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] +#![doc = include_str!("../README.md")] + //! # Usage //! -//! Add the `spake2 dependency to your `Cargo.toml`: -//! -//! ```toml -//! [dependencies] -//! spake2 = "0.1" -//! ``` -//! -//! and this to your crate root: -//! -//! ```rust -//! extern crate spake2; -//! ``` -//! -//! //! Alice and Bob both initialize their SPAKE2 instances with the same (weak) //! password. They will exchange messages to (hopefully) derive a shared secret //! key. The protocol is symmetric: for each operation that Alice does, Bob will @@ -139,7 +72,7 @@ //! //! The shared key can be used as an HMAC key to provide data integrity on //! subsequent messages, or as an authenticated-encryption key (e.g. -//! nacl.secretbox). It can also be fed into [HKDF] [1] to derive other +//! nacl.secretbox). It can also be fed into [HKDF][1] to derive other //! session keys as necessary. //! //! The `SPAKE2` instances, and the messages they create, are single-use. Create @@ -284,10 +217,6 @@ //! [6]: http://eprint.iacr.org/2003/038.pdf "Pretty-Simple Password-Authenticated Key-Exchange Under Standard Assumptions" //! [7]: https://moderncrypto.org/mail-archive/curves/2015/000419.html "PAKE questions" -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] -#![deny(warnings)] -#![forbid(unsafe_code)] - use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::CompressedEdwardsY; use curve25519_dalek::edwards::EdwardsPoint as c2_Element; @@ -608,7 +537,7 @@ impl SPAKE2 { 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 @@ -625,20 +554,19 @@ 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 { + let mut msg_and_side = vec![match side { Side::A => 0x41, // 'A' Side::B => 0x42, // 'B' Side::Symmetric => 0x53, // 'S' - }); + }]; msg_and_side.extend_from_slice(&msg1); ( @@ -664,9 +592,9 @@ impl SPAKE2 { ) -> (SPAKE2, Vec) { Self::start_internal( Side::A, - &password, - &id_a, - &id_b, + password, + id_a, + id_b, &Identity::new(b""), xy_scalar, ) @@ -680,9 +608,9 @@ impl SPAKE2 { ) -> (SPAKE2, Vec) { Self::start_internal( Side::B, - &password, - &id_a, - &id_b, + password, + id_a, + id_b, &Identity::new(b""), xy_scalar, ) @@ -695,10 +623,10 @@ impl SPAKE2 { ) -> (SPAKE2, Vec) { Self::start_internal( Side::Symmetric, - &password, + password, &Identity::new(b""), &Identity::new(b""), - &id_s, + id_s, xy_scalar, ) } @@ -706,19 +634,19 @@ impl SPAKE2 { 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: &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: &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> { diff --git a/spake2/src/tests.rs b/spake2/src/tests.rs index 0328266..e452fce 100644 --- a/spake2/src/tests.rs +++ b/spake2/src/tests.rs @@ -4,7 +4,7 @@ //! "random_scalar()" function, which results in some particular scalar. use super::*; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; -use hex; + use num_bigint::BigUint; // the python tests show the long-integer form of scalars. the rust code diff --git a/spake2/tests/mod.rs b/spake2/tests/mod.rs deleted file mode 100644 index 07ba946..0000000 --- a/spake2/tests/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -use spake2::{Ed25519Group, ErrorType, Identity, Password, SPAKEErr, SPAKE2}; - -#[test] -fn test_basic() { - 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); -} - -#[test] -fn test_mismatch() { - 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); -} - -#[test] -fn test_reflected_message() { - 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(), - SPAKEErr { - kind: ErrorType::BadSide, - } - ); -} - -#[test] -fn test_bad_length() { - 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); - assert_eq!( - r.unwrap_err(), - SPAKEErr { - kind: ErrorType::WrongLength, - } - ); -} - -#[test] -fn test_basic_symmetric() { - 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/spake2/tests/spake2.rs b/spake2/tests/spake2.rs new file mode 100644 index 0000000..07ba946 --- /dev/null +++ b/spake2/tests/spake2.rs @@ -0,0 +1,84 @@ +use spake2::{Ed25519Group, ErrorType, Identity, Password, SPAKEErr, SPAKE2}; + +#[test] +fn test_basic() { + 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); +} + +#[test] +fn test_mismatch() { + 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); +} + +#[test] +fn test_reflected_message() { + 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(), + SPAKEErr { + kind: ErrorType::BadSide, + } + ); +} + +#[test] +fn test_bad_length() { + 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); + assert_eq!( + r.unwrap_err(), + SPAKEErr { + kind: ErrorType::WrongLength, + } + ); +} + +#[test] +fn test_basic_symmetric() { + 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/srp/Cargo.toml b/srp/Cargo.toml index 92748e3..9230f8e 100644 --- a/srp/Cargo.toml +++ b/srp/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "srp" -version = "0.5.0" -edition = "2018" +version = "0.6.0-pre" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" description = "Secure Remote Password (SRP) protocol implementation" @@ -9,6 +8,9 @@ documentation = "https://docs.rs/srp" repository = "https://github.com/RustCrypto/PAKEs" keywords = ["crypto", "pake", "authentication"] categories = ["cryptography", "authentication"] +readme = "README.md" +edition = "2018" +rust-version = "1.56" [dependencies] num-bigint = "0.4" diff --git a/srp/README.md b/srp/README.md new file mode 100644 index 0000000..3aa7583 --- /dev/null +++ b/srp/README.md @@ -0,0 +1,73 @@ +# [RustCrypto]: SRP + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Pure Rust implementation of the [Secure Remote Password] password-authenticated +key-exchange algorithm. + +[Documentation][docs-link] + +## About + +This implementation is generic over hash functions using the [`Digest`] trait, +so you will need to choose a hash function, e.g. `Sha256` from [`sha2`] crate. + +Additionally this crate allows to use a specialized password hashing +algorithm for private key computation instead of method described in the +SRP literature. + +Compatibility with other implementations has not yet been tested. + +## ⚠️ Security Warning + +This crate has never received an independent third party audit for security and +correctness. + +USE AT YOUR OWN RISK! + +## Minimum Supported Rust Version + +Rust **1.56** or higher. + +Minimum supported Rust version can be changed in the future, but it will be +done with a minor version bump. + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/srp.svg +[crate-link]: https://crates.io/crates/srp +[docs-image]: https://docs.rs/srp/badge.svg +[docs-link]: https://docs.rs/srp/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260045-PAKEs +[build-image]: https://github.com/RustCrypto/PAKEs/actions/workflows/srp.yml/badge.svg +[build-link]: https://github.com/RustCrypto/PAKEs/actions/workflows/srp.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[Secure Remote Password]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol +[`Digest`]: https://docs.rs/digest +[`sha2`]: https://crates.io/crates/sha2 diff --git a/srp/src/lib.rs b/srp/src/lib.rs index ada3b8c..375dfb3 100644 --- a/srp/src/lib.rs +++ b/srp/src/lib.rs @@ -1,14 +1,7 @@ -//! [Secure Remote Password][1] (SRP) protocol implementation. -//! -//! This implementation is generic over hash functions using -//! [`Digest`](https://docs.rs/digest) trait, so you will need to choose a hash -//! function, e.g. `Sha256` from [`sha2`](https://crates.io/crates/sha2) crate. -//! Additionally this crate allows to use a specialized password hashing -//! algorithm for private key computation instead of method described in the -//! SRP literature. -//! -//! Compatibility with other implementations was not yet tested. -//! +#![allow(clippy::many_single_char_names)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] +#![doc = include_str!("../README.md")] + //! # Usage //! Add `srp` dependency to your `Cargo.toml`: //! @@ -63,9 +56,6 @@ //! [1]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol //! [2]: https://tools.ietf.org/html/rfc5054 -#![allow(clippy::many_single_char_names)] -#![doc(html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo_small.png")] - pub mod client; pub mod groups; pub mod server; diff --git a/srp/src/types.rs b/srp/src/types.rs index 41742d5..a23954e 100644 --- a/srp/src/types.rs +++ b/srp/src/types.rs @@ -46,7 +46,7 @@ impl SrpGroup { let mut d = D::new(); d.update(&n); d.update(&buf); - BigUint::from_bytes_be(&d.finalize().as_slice()) + BigUint::from_bytes_be(d.finalize().as_slice()) } /// Compute `Hash(N) xor Hash(g)` with given hash function and return SRP parameters diff --git a/srp/tests/mod.rs b/srp/tests/mod.rs deleted file mode 100644 index 58bfa0f..0000000 --- a/srp/tests/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use rand; -use rand::RngCore; -use sha2::Sha256; - -use srp::client::{srp_private_key, SrpClient}; -use srp::groups::G_2048; -use srp::server::{SrpServer, UserRecord}; - -fn auth_test(reg_pwd: &[u8], auth_pwd: &[u8]) { - let mut rng = rand::rngs::OsRng::new().unwrap(); - let username = b"alice"; - - // Client instance creation - let mut a = [0u8; 64]; - rng.fill_bytes(&mut a); - let client = SrpClient::::new(&a, &G_2048); - - // Registration - let mut salt = [0u8; 16]; - rng.fill_bytes(&mut salt); - let reg_priv_key = srp_private_key::(username, reg_pwd, &salt); - let verif = client.get_password_verifier(®_priv_key); - - // User sends handshake - let a_pub = client.get_a_pub(); - - // Server retrieve user record from db and processes handshake - let user = UserRecord { - username, - salt: &salt, - verifier: &verif, - }; - let mut b = [0u8; 64]; - rng.fill_bytes(&mut b); - let server = SrpServer::::new(&user, &a_pub, &b, &G_2048).unwrap(); - let (salt, b_pub) = (&user.salt, server.get_b_pub()); - - // Client processes handshake reply - let auth_priv_key = srp_private_key::(username, auth_pwd, salt); - let client2 = client.process_reply(&auth_priv_key, &b_pub).unwrap(); - let proof = client2.get_proof(); - - // Server processes verification data - println!("Client verification"); - let proof2 = server.verify(&proof).unwrap(); - let server_key = server.get_key(); - - // Client verifies server - println!("Server verification"); - let user_key = client2.verify_server(&proof2).unwrap(); - assert_eq!(server_key, user_key, "server and client keys are not equal"); -} - -#[test] -fn good_password() { - auth_test(b"password", b"password"); -} - -#[test] -#[should_panic] -fn bad_password() { - auth_test(b"password", b"paSsword"); -} diff --git a/srp/tests/srp.rs b/srp/tests/srp.rs new file mode 100644 index 0000000..bf6c30f --- /dev/null +++ b/srp/tests/srp.rs @@ -0,0 +1,62 @@ +use rand::RngCore; +use sha2::Sha256; + +use srp::client::{srp_private_key, SrpClient}; +use srp::groups::G_2048; +use srp::server::{SrpServer, UserRecord}; + +fn auth_test(reg_pwd: &[u8], auth_pwd: &[u8]) { + let mut rng = rand::rngs::OsRng::new().unwrap(); + let username = b"alice"; + + // Client instance creation + let mut a = [0u8; 64]; + rng.fill_bytes(&mut a); + let client = SrpClient::::new(&a, &G_2048); + + // Registration + let mut salt = [0u8; 16]; + rng.fill_bytes(&mut salt); + let reg_priv_key = srp_private_key::(username, reg_pwd, &salt); + let verif = client.get_password_verifier(®_priv_key); + + // User sends handshake + let a_pub = client.get_a_pub(); + + // Server retrieve user record from db and processes handshake + let user = UserRecord { + username, + salt: &salt, + verifier: &verif, + }; + let mut b = [0u8; 64]; + rng.fill_bytes(&mut b); + let server = SrpServer::::new(&user, &a_pub, &b, &G_2048).unwrap(); + let (salt, b_pub) = (&user.salt, server.get_b_pub()); + + // Client processes handshake reply + let auth_priv_key = srp_private_key::(username, auth_pwd, salt); + let client2 = client.process_reply(&auth_priv_key, &b_pub).unwrap(); + let proof = client2.get_proof(); + + // Server processes verification data + println!("Client verification"); + let proof2 = server.verify(&proof).unwrap(); + let server_key = server.get_key(); + + // Client verifies server + println!("Server verification"); + let user_key = client2.verify_server(&proof2).unwrap(); + assert_eq!(server_key, user_key, "server and client keys are not equal"); +} + +#[test] +fn good_password() { + auth_test(b"password", b"password"); +} + +#[test] +#[should_panic] +fn bad_password() { + auth_test(b"password", b"paSsword"); +} -- cgit v1.2.3