27 Commits
Author SHA1 Message Date
Kevin LewiandKevin Lewi 349329cdeb Releasing v0.6.0 2021-06-30 12:20:57 -07:00
Kevin LewiandKevin Lewi c8c57785af Fixing minor nits: conversion to u16 and removing keypair constructor 2021-06-28 19:19:11 -07:00
Kevin LewiandKevin Lewi 809337f458 One-liner fix for deserialization test for CredentialResponse 2021-06-28 18:16:30 -07:00
Kevin LewiandKevin Lewi e86fbac0b7 Add copyright header and update server consistency documentation 2021-06-28 13:16:16 -07:00
daxpeddaandKevin Lewi 81b2719587 More missing common traits 2021-06-28 02:06:20 -07:00
daxpeddaandKevin Lewi f05fcf0278 Expose public key in ClientRegistrationFinishResult 2021-06-28 02:06:10 -07:00
daxpeddaandKevin Lewi eb59676a94 Implement common traits 2021-06-22 20:46:34 -07:00
daxpeddaandKevin Lewi 48590056ca Remove Cargo.lock 2021-06-22 14:35:14 -07:00
daxpeddaandKevin Lewi ed086c9528 Update dependencies 2021-06-22 14:35:14 -07:00
daxpeddaandKevin Lewi ca50d92f96 Remove scrypt 2021-06-21 12:26:22 -07:00
daxpeddaandKevin Lewi 535b9b8ee4 Argon2 implementation 2021-06-21 12:26:22 -07:00
Kevin LewiandKevin Lewi 1572ff0104 Adding support for "internal mode" and fake credential response + test vectors (#155)
* Adding support for internal and external mode
2021-06-21 01:29:39 -07:00
Kevin LewiandKevin Lewi f0c13945d1 Adding client enumeration mitigations (#153) 2021-06-21 01:29:39 -07:00
Kevin LewiandKevin Lewi 98f1821897 Adding identity element checks and ensuring non-zero scalar selection 2021-06-15 18:39:33 -07:00
Valentin TolmerandKevin Lewi 210e0e99df Enforce public vs private keys via types 2021-06-15 14:47:35 -07:00
Valentin TolmerandKevin Lewi cd85efc603 Fix some clippy lint warnings 2021-06-15 12:59:36 -07:00
Valentin TolmerandKevin Lewi 2c7fe4e382 Implement Clone for every message type 2021-06-15 10:06:32 -07:00
Kevin LewiandKevin Lewi 30e27a11e2 Ensure that all public keys are being checked when deserialized 2021-06-14 22:39:13 -07:00
Kevin LewiandKevin Lewi 0935bea8ff Adding documentation of slow-hash + other features 2021-06-13 15:45:53 -07:00
Kevin LewiandKevin Lewi 51b14f34e0 Fixing some clippy errors and CI 2021-06-12 23:24:27 -07:00
Marcelin DuprazandKevin Lewi 055e76a115 Implement serde serialization and deserialization to follow Rust's standards. 2021-06-12 23:24:27 -07:00
Kevin LewiandKevin Lewi 940d1dcdb2 Ensuring mac operations are constant-time 2021-06-04 17:07:10 -07:00
Kevin LewiandKevin Lewi 8bc5e7dc02 Add zeroize on drop for remaining intermediate API states and tests 2021-06-04 16:37:54 -07:00
TonyandKevin Lewi 468e0690d7 Zeroize keys on drop (#156) 2021-06-04 16:37:54 -07:00
Kevin LewiandGitHub b15c89f997 Adding reference to wasm package and link to examples in README (#160) 2021-05-10 21:40:53 -07:00
Kevin LewiandGitHub acaf778ee7 Fixing year typo in CHANGELOG.md (#151) 2021-03-02 00:39:53 -08:00
Kevin LewiandGitHub ba8e940e08 Updating to 0.5 with removing generic_bytes_derive (#150) 2021-03-01 19:23:09 -08:00
28 changed files with 2880 additions and 2901 deletions
+30 -6
View File
@@ -2,7 +2,6 @@ name: Rust CI
on: on:
push: push:
branches: branches:
- tls
- master - master
pull_request: pull_request:
types: [opened, repoened, synchronize] types: [opened, repoened, synchronize]
@@ -16,16 +15,19 @@ jobs:
backend_feature: backend_feature:
- u64_backend - u64_backend
- u32_backend - u32_backend
toolchain:
- nightly
- 1.41.0
name: test name: test
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install nightly toolchain - name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: nightly toolchain: ${{ matrix.toolchain }}
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
@@ -70,11 +72,29 @@ jobs:
- uses: hecrj/setup-rust-action@v1 - uses: hecrj/setup-rust-action@v1
- run: cargo test --verbose --features slow-hash --no-default-features --features ${{ matrix.backend_feature }} - run: cargo test --verbose --features slow-hash --no-default-features --features ${{ matrix.backend_feature }}
serde-test:
name: Test on ${{ matrix.target }} with serde support
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: cargo test --verbose --features serialize --no-default-features --features ${{ matrix.backend_feature }}
simple-login-test: simple-login-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix:
toolchain:
- nightly
- 1.41.0
name: test simple_login command-line example name: test simple_login command-line example
steps: steps:
- name: install expect - name: install expect
@@ -85,7 +105,7 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: nightly toolchain: ${{ matrix.toolchain }}
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
- name: Run expect (which then runs cargo run) - name: Run expect (which then runs cargo run)
@@ -95,6 +115,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix:
toolchain:
- nightly
- 1.41.0
name: test digital_locker command-line example name: test digital_locker command-line example
steps: steps:
- name: install expect - name: install expect
@@ -105,7 +129,7 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: nightly toolchain: ${{ matrix.toolchain }}
override: true override: true
components: rustfmt, clippy components: rustfmt, clippy
- name: Run expect (which then runs cargo run) - name: Run expect (which then runs cargo run)
@@ -118,7 +142,7 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install nightly toolchain - name: Install stable toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
+1
View File
@@ -2,4 +2,5 @@
.vscode/ .vscode/
src/.DS_Store src/.DS_Store
/target /target
Cargo.lock
**/*.rs.bk **/*.rs.bk
+20 -3
View File
@@ -1,6 +1,23 @@
# Changelog # Changelog
## 0.4.0 (February 26, 2020) ## 0.6.0 (June 30, 2021)
* Synced implementation with draft-irtf-cfrg-opaque-05, which changes
the envelope structure and introduces a ServerSetup object to be
maintained by the server
* Various security improvements: non-zero scalars, zeroizing on drop,
constant-time operations
* Adding serde support behind a feature
* Supporting common traits (eb59676)
* Swapping out scrypt for argon2 (535b9b8) for the slow-hash feature
* Adding support for common traits on public structs
* Updated dependencies
## 0.5.0 (March 1, 2021)
* Removed dependency on generic-bytes-derive package
## 0.4.0 (February 26, 2021)
* Adherence to protocol format described in * Adherence to protocol format described in
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03 https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
@@ -9,13 +26,13 @@
* Conformed all message type parameters to be parameterized in the * Conformed all message type parameters to be parameterized in the
Ciphersuite object Ciphersuite object
## 0.3.1 (February 11, 2020) ## 0.3.1 (February 11, 2021)
* Re-exporting the rand library (and including it as a dependency instead of * Re-exporting the rand library (and including it as a dependency instead of
just rand_core) just rand_core)
* Exposing a convenience function for converting from byte array to Key type * Exposing a convenience function for converting from byte array to Key type
## 0.3.0 (February 8, 2020) ## 0.3.0 (February 8, 2021)
* General API and documentation improvements, including the support of custom * General API and documentation improvements, including the support of custom
identifiers, optional result parameters, and the use of the export key identifiers, optional result parameters, and the use of the export key
Generated
-1268
View File
File diff suppressed because it is too large Load Diff
+13 -10
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "opaque-ke" name = "opaque-ke"
version = "0.4.0" version = "0.6.0"
repository = "https://github.com/novifinancial/opaque-ke" repository = "https://github.com/novifinancial/opaque-ke"
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"] keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol" description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
@@ -10,30 +10,33 @@ edition = "2018"
readme = "README.md" readme = "README.md"
[features] [features]
default = ["u64_backend"] default = ["u64_backend", "serialize"]
slow-hash = ["scrypt"] slow-hash = ["argon2"]
bench = [] bench = []
u64_backend = ["curve25519-dalek/u64_backend"] u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"] u32_backend = ["curve25519-dalek/u32_backend"]
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"]
[dependencies] [dependencies]
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] } argon2 = { version = "0.2", optional = true }
base64 = { version = "0.13", optional = true }
curve25519-dalek = { version = "3.1.0", default-features = false, features = ["std"] }
digest = "0.9.0" digest = "0.9.0"
displaydoc = "0.1.7" displaydoc = "0.1.7"
generic-array = "0.14.4" generic-array = "0.14.4"
generic-bytes = { version = "0.1.0" } generic-bytes = { version = "0.1.0" }
generic-bytes-derive = { version = "0.1.0" } hkdf = "0.11.0"
hkdf = "0.10.0" hmac = "0.11.0"
hmac = "0.10.1"
rand = "0.8" rand = "0.8"
scrypt = { version = "0.5.0", optional = true } serde = { version = "1", features = ["derive"], optional = true }
subtle = { version = "2.3.0", default-features = false } subtle = { version = "2.3.0", default-features = false }
thiserror = "1.0.22" thiserror = "1.0.22"
zeroize = "1.1.1" zeroize = { version = "1.1.1", features = ["zeroize_derive"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.35" anyhow = "1.0.35"
base64 = "0.13.0" base64 = "0.13.0"
bincode = "1"
chacha20poly1305 = "0.7.1" chacha20poly1305 = "0.7.1"
criterion = "0.3.3" criterion = "0.3.3"
hex = "0.4.2" hex = "0.4.2"
@@ -41,7 +44,7 @@ lazy_static = "1.4.0"
serde_json = "1.0.60" serde_json = "1.0.60"
sha2 = "0.9.2" sha2 = "0.9.2"
proptest = "0.10.1" proptest = "0.10.1"
rustyline = "7.0.0" rustyline = "6.3.0"
[[bench]] [[bench]]
name = "oprf" name = "oprf"
+4 -3
View File
@@ -14,7 +14,7 @@ OPAQUE is a PKI-free aPAKE that is secure against pre-computation attacks and ca
Documentation Documentation
------------- -------------
The API can be found [here](https://docs.rs/opaque-ke/) along with an example for usage. The API can be found [here](https://docs.rs/opaque-ke/) along with an example for usage. More examples can be found in the [examples](./examples) directory.
Installation Installation
------------ ------------
@@ -22,15 +22,16 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`: Add the following line to the dependencies of your `Cargo.toml`:
``` ```
opaque-ke = "0.4.0" opaque-ke = "0.6.0"
``` ```
Resources Resources
--------- ---------
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security - [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
- [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html), containing a detailed (byte-level) specification for OPAQUE - [draft-irtf-cfrg-opaque-05](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-05.html), containing a detailed (byte-level) specification for OPAQUE
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE - ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
- [opaque-wasm](https://github.com/marucjmar/opaque-wasm), a WebAssembly package for this library
Contributors Contributors
------------ ------------
+1 -1
View File
@@ -44,7 +44,7 @@ yanked = "warn"
# The lint level for crates with security notices. Note that as of # The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in # 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db # https://github.com/rustsec/advisory-db
notice = "warn" notice = "deny"
# A list of advisory IDs to ignore. Note that ignored advisories will still # A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered. # output a note when they are encountered.
ignore = [ ignore = [
+26 -23
View File
@@ -33,10 +33,10 @@ use std::process::exit;
use opaque_ke::{ use opaque_ke::{
ciphersuite::CipherSuite, ciphersuite::CipherSuite,
rand::{rngs::OsRng, RngCore}, rand::{rngs::OsRng, RngCore},
ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistration, ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest, ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin, CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration, ServerLoginStartParameters, ServerRegistration, ServerSetup,
}; };
// The ciphersuite trait allows to specify the underlying primitives // The ciphersuite trait allows to specify the underlying primitives
@@ -81,7 +81,8 @@ fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
// Password-based registration and encryption of client secret message between a client and server // Password-based registration and encryption of client secret message between a client and server
fn register_locker( fn register_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>, server_setup: &ServerSetup<Default>,
locker_id: usize,
password: String, password: String,
secret_message: String, secret_message: String,
) -> Locker { ) -> Locker {
@@ -91,12 +92,10 @@ fn register_locker(
let registration_request_bytes = client_registration_start_result.message.serialize(); let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server // Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start( let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng, &server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(), RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(), &locker_id.to_be_bytes(),
) )
.unwrap(); .unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize(); let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -121,10 +120,9 @@ fn register_locker(
// Client sends message_bytes to server // Client sends message_bytes to server
let password_file = server_registration_start_result let password_file = ServerRegistration::finish(
.state RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap()) );
.unwrap();
Locker { Locker {
contents: ciphertext, contents: ciphertext,
@@ -134,17 +132,14 @@ fn register_locker(
// Open the contents of a locker with a password between a client and server // Open the contents of a locker with a password between a client and server
fn open_locker( fn open_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>, server_setup: &ServerSetup<Default>,
locker_id: usize,
password: String, password: String,
locker: &Locker, locker: &Locker,
) -> Result<String, String> { ) -> Result<String, String> {
let mut client_rng = OsRng; let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start( let client_login_start_result =
&mut client_rng, ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize(); let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server // Client sends credential_request_bytes to server
@@ -154,9 +149,10 @@ fn open_locker(
let mut server_rng = OsRng; let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start( let server_login_start_result = ServerLogin::start(
&mut server_rng, &mut server_rng,
password_file, &server_setup,
&server_kp.private(), Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(), CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginStartParameters::default(), ServerLoginStartParameters::default(),
) )
.unwrap(); .unwrap();
@@ -200,7 +196,7 @@ fn open_locker(
fn main() { fn main() {
let mut rng = OsRng; let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut rng); let server_setup = ServerSetup::<Default>::new(&mut rng);
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let mut registered_lockers: Vec<Locker> = vec![]; let mut registered_lockers: Vec<Locker> = vec![];
@@ -225,8 +221,10 @@ fn main() {
&mut rl, &mut rl,
None, None,
); );
let locker_id = registered_lockers.len();
registered_lockers.push(register_locker( registered_lockers.push(register_locker(
&server_kp, &server_setup,
locker_id,
password, password,
secret_message, secret_message,
)); ));
@@ -252,7 +250,12 @@ fn main() {
continue; continue;
} }
match open_locker(&server_kp, password, &registered_lockers[locker_index]) { match open_locker(
&server_setup,
locker_index,
password,
&registered_lockers[locker_index],
) {
Ok(contents) => { Ok(contents) => {
println!("\n\nSuccess! Contents: {}\n\n", contents); println!("\n\nSuccess! Contents: {}\n\n", contents);
} }
+24 -25
View File
@@ -27,10 +27,9 @@ use std::process::exit;
use opaque_ke::{ use opaque_ke::{
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters, ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters, ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization,
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest, CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
RegistrationResponse, RegistrationUpload, ServerLogin, ServerLoginStartParameters, RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration, ServerSetup,
ServerRegistration,
}; };
// The ciphersuite trait allows to specify the underlying primitives // The ciphersuite trait allows to specify the underlying primitives
@@ -46,7 +45,8 @@ impl CipherSuite for Default {
// Password-based registration between a client and server // Password-based registration between a client and server
fn account_registration( fn account_registration(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>, server_setup: &ServerSetup<Default>,
username: String,
password: String, password: String,
) -> Vec<u8> { ) -> Vec<u8> {
let mut client_rng = OsRng; let mut client_rng = OsRng;
@@ -56,11 +56,10 @@ fn account_registration(
// Client sends registration_request_bytes to server // Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start( let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng, &server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(), RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(), username.as_bytes(),
) )
.unwrap(); .unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize(); let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -79,26 +78,22 @@ fn account_registration(
// Client sends message_bytes to server // Client sends message_bytes to server
let password_file = server_registration_start_result let password_file = ServerRegistration::finish(
.state RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap()) );
.unwrap();
password_file.serialize() password_file.serialize()
} }
// Password-based login between a client and server // Password-based login between a client and server
fn account_login( fn account_login(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>, server_setup: &ServerSetup<Default>,
username: String,
password: String, password: String,
password_file_bytes: &[u8], password_file_bytes: &[u8],
) -> bool { ) -> bool {
let mut client_rng = OsRng; let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start( let client_login_start_result =
&mut client_rng, ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize(); let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server // Client sends credential_request_bytes to server
@@ -107,9 +102,10 @@ fn account_login(
let mut server_rng = OsRng; let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start( let server_login_start_result = ServerLogin::start(
&mut server_rng, &mut server_rng,
password_file, &server_setup,
&server_kp.private(), Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(), CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
username.as_bytes(),
ServerLoginStartParameters::default(), ServerLoginStartParameters::default(),
) )
.unwrap(); .unwrap();
@@ -141,7 +137,7 @@ fn account_login(
fn main() { fn main() {
let mut rng = OsRng; let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut rng); let server_setup = ServerSetup::<Default>::new(&mut rng);
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let mut registered_users = HashMap::<String, Vec<u8>>::new(); let mut registered_users = HashMap::<String, Vec<u8>>::new();
@@ -164,13 +160,16 @@ fn main() {
let (username, password) = get_two_strings("Username", "Password", &mut rl, None); let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
match line.as_ref() { match line.as_ref() {
"1" => { "1" => {
registered_users registered_users.insert(
.insert(username, account_registration(&server_kp, password)); username.clone(),
account_registration(&server_setup, username, password),
);
continue; continue;
} }
"2" => match registered_users.get(&username) { "2" => match registered_users.get(&username) {
Some(password_file_bytes) => { Some(password_file_bytes) => {
if account_login(&server_kp, password, password_file_bytes) { if account_login(&server_setup, username, password, password_file_bytes)
{
println!("\nLogin success!"); println!("\nLogin success!");
} else { } else {
// Note that at this point, the client knows whether or not the login // Note that at this point, the client knows whether or not the login
+2 -9
View File
@@ -6,13 +6,11 @@
//! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE //! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE
use crate::{ use crate::{
hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair, hash::Hash, key_exchange::traits::KeyExchange, map_to_curve::GroupWithMapToCurve,
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash, slow_hash::SlowHash,
}; };
use digest::Digest; use digest::Digest;
use rand::{CryptoRng, RngCore};
/// Configures the underlying primitives used in OPAQUE /// Configures the underlying primitives used in OPAQUE
/// * `Group`: a finite cyclic group along with a point representation, along /// * `Group`: a finite cyclic group along with a point representation, along
/// with an extension trait PasswordToCurve that allows some customization on /// with an extension trait PasswordToCurve that allows some customization on
@@ -33,9 +31,4 @@ pub trait CipherSuite {
type Hash: Hash; type Hash: Hash;
/// A slow hashing function, typically used for password hashing /// A slow hashing function, typically used for password hashing
type SlowHash: SlowHash<Self::Hash>; type SlowHash: SlowHash<Self::Hash>;
/// Generating a random key pair given a cryptographic rng
fn generate_random_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> KeyPair<Self::Group> {
KeyPair::<Self::Group>::generate_random(rng)
}
} }
+192 -194
View File
@@ -4,10 +4,13 @@
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
use crate::{ use crate::{
errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError}, ciphersuite::CipherSuite,
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
group::Group,
hash::Hash, hash::Hash,
keypair::Key, keypair::{KeyPair, PrivateKey, PublicKey},
serialization::serialize, map_to_curve::GroupWithMapToCurve,
opaque::{bytestrings_from_identifiers, Identifiers},
}; };
use digest::Digest; use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray}; use generic_array::{typenum::Unsigned, GenericArray};
@@ -16,70 +19,65 @@ use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac}; use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::convert::TryFrom; use std::convert::TryFrom;
use zeroize::Zeroize;
// Constant string used as salt for HKDF computation // Constant string used as salt for HKDF computation
const STR_PAD: &[u8] = b"Pad";
const STR_AUTH_KEY: &[u8] = b"AuthKey"; const STR_AUTH_KEY: &[u8] = b"AuthKey";
const STR_EXPORT_KEY: &[u8] = b"ExportKey"; const STR_EXPORT_KEY: &[u8] = b"ExportKey";
const STR_PRIVATE_KEY: &[u8] = b"PrivateKey";
const STR_OPAQUE_HASH_TO_SCALAR: &[u8] = b"OPAQUE-HashToScalar";
const NONCE_LEN: usize = 32; const NONCE_LEN: usize = 32;
#[derive(Clone, Copy, PartialEq)] fn build_inner_envelope_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<PublicKey, InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalPakeError::HkdfError)?;
let client_static_keypair =
KeyPair::<CS::Group>::from_private_key_slice(CS::Group::scalar_as_bytes(
&CS::Group::hash_to_scalar::<CS::Hash>(&keypair_seed[..], STR_OPAQUE_HASH_TO_SCALAR)?,
))?;
Ok(client_static_keypair.public().clone())
}
fn recover_keys_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<KeyPair<CS::Group>, InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalPakeError::HkdfError)?;
let client_static_keypair =
KeyPair::<CS::Group>::from_private_key_slice(CS::Group::scalar_as_bytes(
&CS::Group::hash_to_scalar::<CS::Hash>(&keypair_seed[..], STR_OPAQUE_HASH_TO_SCALAR)?,
))?;
Ok(client_static_keypair)
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Zeroize)]
#[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode { pub(crate) enum InnerEnvelopeMode {
Base = 1, Zero = 0,
CustomIdentifier = 2, Internal = 1,
} }
impl TryFrom<u8> for InnerEnvelopeMode { impl TryFrom<u8> for InnerEnvelopeMode {
type Error = PakeError; type Error = PakeError;
fn try_from(x: u8) -> Result<Self, Self::Error> { fn try_from(x: u8) -> Result<Self, Self::Error> {
match x { match x {
1 => Ok(InnerEnvelopeMode::Base), 1 => Ok(InnerEnvelopeMode::Internal),
2 => Ok(InnerEnvelopeMode::CustomIdentifier),
_ => Err(PakeError::SerializationError), _ => Err(PakeError::SerializationError),
} }
} }
} }
pub(crate) struct InnerEnvelope {
mode: InnerEnvelopeMode,
nonce: Vec<u8>,
ciphertext: Vec<u8>,
}
impl InnerEnvelope {
pub(crate) fn serialize(&self) -> Vec<u8> {
[&[self.mode as u8], &self.nonce[..], &self.ciphertext[..]].concat()
}
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
if input.is_empty() {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let mode = InnerEnvelopeMode::try_from(input[0])?;
let key_len = <Key as SizedBytes>::Len::to_usize();
let bytes = &input[1..];
if bytes.len() < NONCE_LEN + key_len {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
Ok((
Self {
mode,
nonce: bytes[..NONCE_LEN].to_vec(),
ciphertext: bytes[NONCE_LEN..NONCE_LEN + key_len].to_vec(),
},
bytes[NONCE_LEN + key_len..].to_vec(),
))
}
}
/// This struct is an instantiation of the envelope as described in /// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4 /// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
/// ///
@@ -90,130 +88,153 @@ impl InnerEnvelope {
/// The specification update has simplified this assumption by taking /// The specification update has simplified this assumption by taking
/// an XOR-based approach without compromising on security, and to avoid /// an XOR-based approach without compromising on security, and to avoid
/// the confusion around the implementation of an RKR-secure encryption. /// the confusion around the implementation of an RKR-secure encryption.
pub(crate) struct Envelope<D: Hash> { pub(crate) struct Envelope<CS: CipherSuite> {
inner_envelope: InnerEnvelope, mode: InnerEnvelopeMode,
hmac: GenericArray<u8, <D as Digest>::OutputSize>, nonce: Vec<u8>,
hmac: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
} }
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for Envelope<CS> {
fn clone(&self) -> Self {
Self {
mode: self.mode,
nonce: self.nonce.clone(),
hmac: self.hmac.clone(),
}
}
}
impl_debug_eq_hash_for!(struct Envelope<CS: CipherSuite>, [mode, nonce, hmac]);
// Note that this struct represents an envelope that has been "opened" with the asssociated // Note that this struct represents an envelope that has been "opened" with the asssociated
// key. This key is also used to derive the export_key parameter, which is technically // key. This key is also used to derive the export_key parameter, which is technically
// unrelated to the envelope's encrypted and authenticated contents. // unrelated to the envelope's encrypted and authenticated contents.
pub(crate) struct OpenedEnvelope<D: Hash> { pub(crate) struct OpenedEnvelope<CS: CipherSuite> {
pub(crate) client_s_sk: Vec<u8>, pub(crate) client_static_keypair: KeyPair<CS::Group>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>, pub(crate) export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
pub(crate) id_u: Vec<u8>,
pub(crate) id_s: Vec<u8>,
} }
pub(crate) struct OpenedInnerEnvelope<D: Hash> { pub(crate) struct OpenedInnerEnvelope<D: Hash> {
pub(crate) plaintext: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>, pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
} }
impl<D: Hash> Envelope<D> { impl<CS: CipherSuite> Envelope<CS> {
fn hmac_key_size() -> usize { fn hmac_key_size() -> usize {
<D as Digest>::OutputSize::to_usize() <CS::Hash as Digest>::OutputSize::to_usize()
} }
fn export_key_size() -> usize { fn export_key_size() -> usize {
<D as Digest>::OutputSize::to_usize() <CS::Hash as Digest>::OutputSize::to_usize()
} }
pub(crate) fn get_mode(&self) -> InnerEnvelopeMode { pub(crate) fn len() -> usize {
self.inner_envelope.mode <CS::Hash as Digest>::OutputSize::to_usize() + NONCE_LEN
}
/// The format of the output is:
/// mode | nonce | ciphertext | hmac
/// u8 | nonce_size bytes | variable length | hmac_size bytes
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let (result, remainder) = Self::deserialize(bytes)
.map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?;
if !remainder.is_empty() {
return Err(InternalPakeError::InvalidEnvelopeStructureError);
}
Ok(result)
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
self.serialize()
} }
pub(crate) fn serialize(&self) -> Vec<u8> { pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.inner_envelope.serialize(), &self.hmac[..]].concat() [&self.nonce[..], &self.hmac[..]].concat()
} }
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> { if bytes.len() < NONCE_LEN {
let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?; return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let nonce = bytes[..NONCE_LEN].to_vec();
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(),
};
let hmac_key_size = Self::hmac_key_size(); let hmac_key_size = Self::hmac_key_size();
let hmac_and_remainder = let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
Ok(( Ok(Self {
Self { mode,
inner_envelope, nonce,
hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]), hmac: GenericArray::clone_from_slice(hmac),
}, })
hmac_and_remainder[hmac_key_size..].to_vec(),
))
} }
// Creates a dummy envelope object that serializes to the all-zeros byte string
pub(crate) fn dummy() -> Self {
Self {
mode: InnerEnvelopeMode::Zero,
nonce: vec![0u8; NONCE_LEN],
hmac: GenericArray::clone_from_slice(&vec![
0u8;
<CS::Hash as Digest>::OutputSize::to_usize()
]),
}
}
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>( pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
key: &[u8], key: &[u8],
client_s_sk: &[u8],
server_s_pk: &[u8], server_s_pk: &[u8],
optional_ids: Option<(Vec<u8>, Vec<u8>)>, optional_ids: Option<Identifiers>,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> { ) -> Result<
let aad = construct_aad(server_s_pk, &optional_ids); (
Self::seal_raw(rng, key, &client_s_sk, &aad, mode_from_ids(&optional_ids)) Self,
PublicKey,
GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
),
InternalPakeError,
> {
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let (mode, client_s_pk) = (
InnerEnvelopeMode::Internal,
build_inner_envelope_internal::<CS>(key, &nonce)?,
);
let (id_u, id_s) =
bytestrings_from_identifiers(&optional_ids, &client_s_pk.to_arr(), server_s_pk);
let aad = construct_aad(&id_u, &id_s, server_s_pk);
let (envelope, export_key) = Self::seal_raw(key, &nonce, &aad, mode)?;
Ok((envelope, client_s_pk, export_key))
} }
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field. /// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
/// Note that a new nonce is sampled for each call to seal. /// Note that a new nonce is sampled for each call to seal.
pub(crate) fn seal_raw<R: RngCore + CryptoRng>( #[allow(clippy::type_complexity)]
rng: &mut R, pub(crate) fn seal_raw(
key: &[u8], key: &[u8],
plaintext: &[u8], nonce: &[u8],
aad: &[u8], aad: &[u8],
mode: InnerEnvelopeMode, mode: InnerEnvelopeMode,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> { ) -> Result<(Self, GenericArray<u8, <CS::Hash as Digest>::OutputSize>), InternalPakeError> {
let mut nonce = vec![0u8; NONCE_LEN]; let h = Hkdf::<CS::Hash>::new(None, key);
rng.fill_bytes(&mut nonce);
let h = Hkdf::<D>::new(Some(&nonce), &key);
let mut xor_key = vec![0u8; plaintext.len()];
let mut hmac_key = vec![0u8; Self::hmac_key_size()]; let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()]; let mut export_key = vec![0u8; Self::export_key_size()];
h.expand(STR_PAD, &mut xor_key) h.expand(&[nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?; .map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_AUTH_KEY, &mut hmac_key) h.expand(&[nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?; .map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = xor_key let mut hmac = Hmac::<CS::Hash>::new_from_slice(&hmac_key)
.iter() .map_err(|_| InternalPakeError::HmacError)?;
.zip(plaintext.iter()) hmac.update(nonce);
.map(|(&x1, &x2)| x1 ^ x2) hmac.update(aad);
.collect();
let inner_envelope = InnerEnvelope {
mode,
nonce,
ciphertext,
};
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&inner_envelope.serialize());
hmac.update(&aad);
let hmac_bytes = hmac.finalize().into_bytes(); let hmac_bytes = hmac.finalize().into_bytes();
Ok(( Ok((
Self { Self {
inner_envelope, mode,
nonce: nonce.to_vec(),
hmac: hmac_bytes, hmac: hmac_bytes,
}, },
GenericArray::clone_from_slice(&export_key), GenericArray::clone_from_slice(&export_key),
@@ -224,24 +245,29 @@ impl<D: Hash> Envelope<D> {
&self, &self,
key: &[u8], key: &[u8],
server_s_pk: &[u8], server_s_pk: &[u8],
optional_ids: &Option<(Vec<u8>, Vec<u8>)>, optional_ids: &Option<Identifiers>,
) -> Result<OpenedEnvelope<D>, InternalPakeError> { ) -> Result<OpenedEnvelope<CS>, InternalPakeError> {
// First, check that mode matches let client_static_keypair = match self.mode {
if self.inner_envelope.mode != mode_from_ids(optional_ids) { InnerEnvelopeMode::Zero => {
return Err(InternalPakeError::IncompatibleEnvelopeModeError); return Err(InternalPakeError::IncompatibleEnvelopeModeError)
} }
InnerEnvelopeMode::Internal => recover_keys_internal::<CS>(key, &self.nonce)?,
};
let (id_u, id_s) = bytestrings_from_identifiers(
optional_ids,
&client_static_keypair.public().to_arr(),
server_s_pk,
);
let aad = construct_aad(&id_u, &id_s, server_s_pk);
let aad = construct_aad(server_s_pk, optional_ids);
let opened = self.open_raw(key, &aad)?; let opened = self.open_raw(key, &aad)?;
if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
// Plaintext should consist of a single key
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
}
Ok(OpenedEnvelope { Ok(OpenedEnvelope {
client_s_sk: opened.plaintext, client_static_keypair,
export_key: opened.export_key, export_key: opened.export_key,
id_u,
id_s,
}) })
} }
@@ -251,82 +277,54 @@ impl<D: Hash> Envelope<D> {
&self, &self,
key: &[u8], key: &[u8],
aad: &[u8], aad: &[u8],
) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> { ) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalPakeError> {
let h = Hkdf::<D>::new(Some(&self.inner_envelope.nonce), &key); let h = Hkdf::<CS::Hash>::new(None, key);
let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
let mut hmac_key = vec![0u8; Self::hmac_key_size()]; let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()]; let mut export_key = vec![0u8; Self::export_key_size()];
h.expand(STR_PAD, &mut xor_key) h.expand(&[&self.nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?; .map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_AUTH_KEY, &mut hmac_key) h.expand(&[&self.nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?; .map_err(|_| InternalPakeError::HkdfError)?;
let mut hmac = let mut hmac = Hmac::<CS::Hash>::new_from_slice(&hmac_key)
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?; .map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.inner_envelope.serialize()); hmac.update(&self.nonce);
hmac.update(aad); hmac.update(aad);
if hmac.verify(&self.hmac).is_err() { if hmac.verify(&self.hmac).is_err() {
return Err(InternalPakeError::SealOpenHmacError); return Err(InternalPakeError::SealOpenHmacError);
} }
let plaintext: Vec<u8> = xor_key
.iter()
.zip(self.inner_envelope.ciphertext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok(OpenedInnerEnvelope { Ok(OpenedInnerEnvelope {
plaintext, export_key: GenericArray::<u8, <CS::Hash as Digest>::OutputSize>::clone_from_slice(
export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
&export_key, &export_key,
), ),
}) })
} }
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![(self.hmac.as_ptr(), self.hmac.len())]
}
}
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for Envelope<CS> {
fn zeroize(&mut self) {
self.mode.zeroize();
self.nonce.zeroize();
self.hmac.zeroize();
}
}
impl<CS: CipherSuite> Drop for Envelope<CS> {
fn drop(&mut self) {
self.zeroize();
}
} }
// Helper functions // Helper functions
fn construct_aad(server_s_pk: &[u8], optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> { fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> {
let ids = optional_ids [server_s_pk, id_s, id_u].concat()
.iter()
.flat_map(|(l, r)| [serialize(l, 2), serialize(r, 2)].concat())
.collect();
[server_s_pk.to_vec(), ids].concat()
}
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
match optional_ids {
Some(_) => InnerEnvelopeMode::CustomIdentifier,
None => InnerEnvelopeMode::Base,
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn seal_and_open() {
let mut rng = OsRng;
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
&mut rng,
&key,
&msg,
b"aad",
InnerEnvelopeMode::Base,
)
.unwrap();
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
}
} }
+7 -3
View File
@@ -8,7 +8,7 @@ use displaydoc::Display;
use thiserror::Error; use thiserror::Error;
/// Represents an error in the manipulation of internal cryptographic data /// Represents an error in the manipulation of internal cryptographic data
#[derive(Debug, Display, Error)] #[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum InternalPakeError { pub enum InternalPakeError {
/// Deserializing from a byte sequence failed /// Deserializing from a byte sequence failed
InvalidByteSequence, InvalidByteSequence,
@@ -56,7 +56,7 @@ pub enum InternalPakeError {
} }
/// Represents an error in password checking /// Represents an error in password checking
#[derive(Debug, Display, Error)] #[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum PakeError { pub enum PakeError {
/// This error results from an internal error during PRF construction /// This error results from an internal error during PRF construction
/// ///
@@ -73,6 +73,8 @@ pub enum PakeError {
InvalidLoginError, InvalidLoginError,
/// Error with serializing / deserializing protocol messages /// Error with serializing / deserializing protocol messages
SerializationError, SerializationError,
/// Identity group element was encountered during deserialization, which is invalid
IdentityGroupElementError,
} }
// This is meant to express future(ly) non-trivial ways of converting the // This is meant to express future(ly) non-trivial ways of converting the
@@ -84,12 +86,14 @@ impl From<InternalPakeError> for PakeError {
} }
/// Represents an error in protocol handling /// Represents an error in protocol handling
#[derive(Debug, Display, Error)] #[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum ProtocolError { pub enum ProtocolError {
/// This error results from an error during password verification /// This error results from an error during password verification
/// ///
/// Internal error during password verification: {0} /// Internal error during password verification: {0}
VerificationError(PakeError), VerificationError(PakeError),
/// This error occurs when the inner envelope is malformed
InvalidInnerEnvelopeError,
/// This error occurs when the server answer cannot be handled /// This error occurs when the server answer cannot be handled
/// Server response cannot be handled. /// Server response cannot be handled.
ServerError, ServerError,
+31 -14
View File
@@ -12,6 +12,7 @@ use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT, constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint}, ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar, scalar::Scalar,
traits::Identity,
}; };
use generic_array::{ use generic_array::{
typenum::{U32, U64}, typenum::{U32, U64},
@@ -35,7 +36,7 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
scalar_bits: &GenericArray<u8, Self::ScalarLen>, scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError>; ) -> Result<Self::Scalar, InternalPakeError>;
/// picks a scalar at random /// picks a scalar at random
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar; fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes /// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>; fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar /// The multiplicative inverse of this scalar
@@ -64,6 +65,9 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
/// Multiply the point by a scalar, represented as a slice /// Multiply the point by a scalar, represented as a slice
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self; fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool;
} }
/// The implementation of such a subgroup for Ristretto /// The implementation of such a subgroup for Ristretto
@@ -77,20 +81,28 @@ impl Group for RistrettoPoint {
bits.copy_from_slice(scalar_bits); bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits)) Ok(Scalar::from_bytes_mod_order(bits))
} }
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar { fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
#[cfg(not(test))] loop {
{ let scalar = {
let mut scalar_bytes = [0u8; 64]; #[cfg(not(test))]
rng.fill_bytes(&mut scalar_bytes); {
Scalar::from_bytes_mod_order_wide(&scalar_bytes) let mut scalar_bytes = [0u8; 64];
} rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng // Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)] #[cfg(test)]
{ {
let mut scalar_bytes = [0u8; 32]; let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes); rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes) Scalar::from_bytes_mod_order(scalar_bytes)
}
};
if scalar != Scalar::zero() {
break scalar;
}
} }
} }
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> { fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
@@ -134,4 +146,9 @@ impl Group for RistrettoPoint {
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length"); let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
self * Scalar::from_bits(arr) self * Scalar::from_bits(arr)
} }
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self == &Self::identity()
}
} }
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
macro_rules! impl_debug_eq_hash_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? std::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: std::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("$name")
.field("$field1", &self.$field1)
$(.field("$field2", &self.$field2))*
.finish()
}
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
}
impl$(<$($gen$(: $bound)?),+>)? std::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: std::hash::Hash,)+)?
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::hash::Hash::hash(&self.$field1, state);
$(std::hash::Hash::hash(&self.$field2, state);)*
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? std::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: std::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("$name")
.field(&self.$field1)
$(.field(&self.$field2))*
.finish()
}
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
}
impl$(<$($gen$(: $bound)?),+>)? std::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: std::hash::Hash,)+)?
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::hash::Hash::hash(&self.$field1, state);
$(std::hash::Hash::hash(&self.$field2, state);)*
}
}
};
}
macro_rules! impl_clone_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self {
$field1: self.$field1.clone(),
$($field2: self.$field2.clone(),)*
}
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self(
self.$field1.clone(),
$(self.$field2.clone(),)*
)
}
}
};
}
+28 -16
View File
@@ -4,24 +4,23 @@
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
use crate::{ use crate::{
ciphersuite::CipherSuite,
errors::{PakeError, ProtocolError}, errors::{PakeError, ProtocolError},
group::Group, group::Group,
hash::Hash, hash::Hash,
keypair::Key, keypair::{PrivateKey, PublicKey},
}; };
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use zeroize::Zeroize;
use std::convert::TryFrom;
pub trait KeyExchange<D: Hash, G: Group> { pub trait KeyExchange<D: Hash, G: Group> {
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes; type KE1State: FromBytes + ToBytesWithPointers + Zeroize + Clone;
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes; type KE2State: FromBytes + ToBytesWithPointers + Zeroize + Clone;
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes; type KE1Message: FromBytes + ToBytes + Clone;
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes; type KE2Message: FromBytes + ToBytes + Clone;
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes; type KE3Message: FromBytes + ToBytes + Clone;
fn generate_ke1<R: RngCore + CryptoRng>( fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R, rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>; ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
@@ -31,12 +30,12 @@ pub trait KeyExchange<D: Hash, G: Group> {
l1_bytes: Vec<u8>, l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>, l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message, ke1_message: Self::KE1Message,
client_s_pk: Key, client_s_pk: PublicKey,
server_s_sk: Key, server_s_sk: PrivateKey,
id_u: Vec<u8>, id_u: Vec<u8>,
id_s: Vec<u8>, id_s: Vec<u8>,
e_info: Vec<u8>, context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>; ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)] #[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3( fn generate_ke3(
@@ -44,11 +43,12 @@ pub trait KeyExchange<D: Hash, G: Group> {
ke2_message: Self::KE2Message, ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State, ke1_state: &Self::KE1State,
serialized_credential_request: &[u8], serialized_credential_request: &[u8],
server_s_pk: Key, server_s_pk: PublicKey,
client_s_sk: Key, client_s_sk: PrivateKey,
id_u: Vec<u8>, id_u: Vec<u8>,
id_s: Vec<u8>, id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>; context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError>;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn finish_ke( fn finish_ke(
@@ -59,6 +59,18 @@ pub trait KeyExchange<D: Hash, G: Group> {
fn ke2_message_size() -> usize; fn ke2_message_size() -> usize;
} }
pub trait FromBytes: Sized {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError>;
}
pub trait ToBytes { pub trait ToBytes {
fn to_bytes(&self) -> Vec<u8>; fn to_bytes(&self) -> Vec<u8>;
} }
pub trait ToBytesWithPointers {
fn to_bytes(&self) -> Vec<u8>;
// Only used for tests to grab raw pointers to data
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)>;
}
+143 -150
View File
@@ -5,15 +5,16 @@
//! An implementation of the Triple Diffie-Hellman key exchange protocol //! An implementation of the Triple Diffie-Hellman key exchange protocol
use crate::{ use crate::{
ciphersuite::CipherSuite,
errors::{ errors::{
utils::{check_slice_size, check_slice_size_atleast}, utils::{check_slice_size, check_slice_size_atleast},
InternalPakeError, PakeError, ProtocolError, InternalPakeError, PakeError, ProtocolError,
}, },
group::Group, group::Group,
hash::Hash, hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes}, key_exchange::traits::{FromBytes, KeyExchange, ToBytes, ToBytesWithPointers},
keypair::{Key, KeyPair, SizedBytesExt}, keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
serialization::{serialize, tokenize}, serialization::serialize,
}; };
use digest::{Digest, FixedOutput}; use digest::{Digest, FixedOutput};
use generic_array::{ use generic_array::{
@@ -24,20 +25,18 @@ use generic_bytes::SizedBytes;
use hkdf::Hkdf; use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac}; use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use std::convert::TryFrom; use std::convert::TryFrom;
use zeroize::Zeroize;
const KEY_LEN: usize = 32; const KEY_LEN: usize = 32;
pub(crate) type NonceLen = U32; pub(crate) type NonceLen = U32;
static STR_3DH: &[u8] = b"3DH"; static STR_RFC: &[u8] = b"RFCXXXX";
static STR_CLIENT_MAC: &[u8] = b"client mac"; static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
static STR_HANDSHAKE_SECRET: &[u8] = b"handshake secret"; static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
static STR_SERVER_MAC: &[u8] = b"server mac"; static STR_SERVER_MAC: &[u8] = b"ServerMAC";
static STR_HANDSHAKE_ENC: &[u8] = b"handshake enc"; static STR_SESSION_KEY: &[u8] = b"SessionKey";
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad"; static STR_OPAQUE: &[u8] = b"OPAQUE-";
static STR_SESSION_SECRET: &[u8] = b"session secret";
static STR_OPAQUE: &[u8] = b"OPAQUE ";
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
/// The Triple Diffie-Hellman key exchange implementation /// The Triple Diffie-Hellman key exchange implementation
@@ -51,7 +50,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>; type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng>( fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R, rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> { ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyPair::<G>::generate_random(rng); let client_e_kp = KeyPair::<G>::generate_random(rng);
@@ -59,7 +57,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
let ke1_message = Ke1Message { let ke1_message = Ke1Message {
client_nonce, client_nonce,
info,
client_e_pk: client_e_kp.public().clone(), client_e_pk: client_e_kp.public().clone(),
}; };
@@ -78,25 +75,26 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
serialized_credential_request: Vec<u8>, serialized_credential_request: Vec<u8>,
l2_bytes: Vec<u8>, l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message, ke1_message: Self::KE1Message,
client_s_pk: Key, client_s_pk: PublicKey,
server_s_sk: Key, server_s_sk: PrivateKey,
id_u: Vec<u8>, id_u: Vec<u8>,
id_s: Vec<u8>, id_s: Vec<u8>,
e_info: Vec<u8>, context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> { ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyPair::<G>::generate_random(rng); let server_e_kp = KeyPair::<G>::generate_random(rng);
let server_nonce = generate_nonce::<R>(rng); let server_nonce = generate_nonce::<R>(rng);
let mut transcript_hasher = D::new() let mut transcript_hasher = D::new()
.chain(STR_3DH) .chain(STR_RFC)
.chain(&serialize(&id_u, 2)) .chain(&serialize(&context, 2))
.chain(&id_u)
.chain(&serialized_credential_request[..]) .chain(&serialized_credential_request[..])
.chain(&serialize(&id_s, 2)) .chain(&id_s)
.chain(&l2_bytes[..]) .chain(&l2_bytes[..])
.chain(&server_nonce[..]) .chain(&server_nonce[..])
.chain(&server_e_kp.public().to_arr()); .chain(&server_e_kp.public().to_arr());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>( let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents { TripleDHComponents {
pk1: ke1_message.client_e_pk.clone(), pk1: ke1_message.client_e_pk.clone(),
sk1: server_e_kp.private().clone(), sk1: server_e_kp.private().clone(),
@@ -108,28 +106,14 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
&transcript_hasher.clone().finalize(), &transcript_hasher.clone().finalize(),
)?; )?;
// Compute encryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = encryption_pad
.iter()
.zip(e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
transcript_hasher.update(&serialize(&ciphertext, 2));
let mut mac_hasher = let mut mac_hasher =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?; Hmac::<D>::new_from_slice(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac_hasher.update(&transcript_hasher.clone().finalize()); mac_hasher.update(&transcript_hasher.clone().finalize());
let mac = mac_hasher.finalize().into_bytes(); let mac = mac_hasher.finalize().into_bytes();
transcript_hasher.update(&mac); transcript_hasher.update(&mac);
Ok(( Ok((
ke1_message.info,
Ke2State { Ke2State {
km3, km3,
hashed_transcript: transcript_hasher.finalize(), hashed_transcript: transcript_hasher.finalize(),
@@ -138,7 +122,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
Ke2Message { Ke2Message {
server_nonce, server_nonce,
server_e_pk: server_e_kp.public().clone(), server_e_pk: server_e_kp.public().clone(),
e_info: ciphertext,
mac, mac,
}, },
)) ))
@@ -150,20 +133,22 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
ke2_message: Self::KE2Message, ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State, ke1_state: &Self::KE1State,
serialized_credential_request: &[u8], serialized_credential_request: &[u8],
server_s_pk: Key, server_s_pk: PublicKey,
client_s_sk: Key, client_s_sk: PrivateKey,
id_u: Vec<u8>, id_u: Vec<u8>,
id_s: Vec<u8>, id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> { context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
let mut transcript_hasher = D::new() let mut transcript_hasher = D::new()
.chain(STR_3DH) .chain(STR_RFC)
.chain(&serialize(&id_u, 2)) .chain(&serialize(&context, 2))
.chain(&id_u)
.chain(&serialized_credential_request) .chain(&serialized_credential_request)
.chain(&serialize(&id_s, 2)) .chain(&id_s)
.chain(&l2_component[..]) .chain(&l2_component[..])
.chain(&ke2_message.to_bytes_without_info_or_mac()); .chain(&ke2_message.to_bytes_without_info_or_mac());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>( let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents { TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(), pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(), sk1: ke1_state.client_e_sk.clone(),
@@ -175,13 +160,11 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
&transcript_hasher.clone().finalize(), &transcript_hasher.clone().finalize(),
)?; )?;
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2));
let mut server_mac = let mut server_mac =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?; Hmac::<D>::new_from_slice(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&transcript_hasher.clone().finalize()); server_mac.update(&transcript_hasher.clone().finalize());
if ke2_message.mac != server_mac.finalize().into_bytes() { if server_mac.verify(&ke2_message.mac).is_err() {
return Err(ProtocolError::VerificationError( return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError, PakeError::KeyExchangeMacValidationError,
)); ));
@@ -190,22 +173,10 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
transcript_hasher.update(ke2_message.mac.to_vec()); transcript_hasher.update(ke2_message.mac.to_vec());
let mut client_mac = let mut client_mac =
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?; Hmac::<D>::new_from_slice(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&transcript_hasher.finalize()); client_mac.update(&transcript_hasher.finalize());
// Compute decryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let plaintext: Vec<u8> = encryption_pad
.iter()
.zip(ke2_message.e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok(( Ok((
plaintext,
session_key.to_vec(), session_key.to_vec(),
Ke3Message { Ke3Message {
mac: client_mac.finalize().into_bytes(), mac: client_mac.finalize().into_bytes(),
@@ -219,10 +190,10 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
ke2_state: &Self::KE2State, ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> { ) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac = let mut client_mac =
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?; Hmac::<D>::new_from_slice(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript); client_mac.update(&ke2_state.hashed_transcript);
if ke3_message.mac != client_mac.finalize().into_bytes() { if client_mac.verify(&ke3_message.mac).is_err() {
return Err(ProtocolError::VerificationError( return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError, PakeError::KeyExchangeMacValidationError,
)); ));
@@ -237,29 +208,29 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
} }
/// The client state produced after the first key exchange message /// The client state produced after the first key exchange message
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq, Debug, Hash, Zeroize, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[zeroize(drop)]
pub struct Ke1State { pub struct Ke1State {
client_e_sk: Key, client_e_sk: PrivateKey,
client_nonce: GenericArray<u8, NonceLen>, client_nonce: GenericArray<u8, NonceLen>,
} }
/// The first key exchange message /// The first key exchange message
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Ke1Message { pub struct Ke1Message {
pub(crate) client_nonce: GenericArray<u8, NonceLen>, pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) info: Vec<u8>, pub(crate) client_e_pk: PublicKey,
pub(crate) client_e_pk: Key,
} }
impl TryFrom<&[u8]> for Ke1State { impl FromBytes for Ke1State {
type Error = PakeError; fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize(); let nonce_len = NonceLen::to_usize();
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?; let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
Ok(Self { Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?, client_e_sk: PrivateKey::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: GenericArray::clone_from_slice( client_nonce: GenericArray::clone_from_slice(
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len], &checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
), ),
@@ -267,59 +238,68 @@ impl TryFrom<&[u8]> for Ke1State {
} }
} }
impl ToBytes for Ke1State { impl ToBytesWithPointers for Ke1State {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat(); let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output output
} }
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(
self.client_e_sk.as_ptr(),
<PrivateKey as SizedBytes>::Len::to_usize(),
),
(self.client_nonce.as_ptr(), NonceLen::to_usize()),
]
}
} }
impl ToBytes for Ke1Message { impl ToBytes for Ke1Message {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
[ [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
&self.client_nonce[..],
&serialize(&self.info, 2),
&self.client_e_pk.to_arr(),
]
.concat()
} }
} }
impl TryFrom<&[u8]> for Ke1Message { impl FromBytes for Ke1Message {
type Error = PakeError; fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, PakeError> {
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize(); let nonce_len = NonceLen::to_usize();
let checked_nonce = let checked_nonce =
check_slice_size_atleast(ke1_message_bytes, nonce_len, "ke1_message nonce")?; check_slice_size(ke1_message_bytes, nonce_len + KEY_LEN, "ke1_message nonce")?;
let (info, remainder) = tokenize(&checked_nonce[nonce_len..], 2)?;
let checked_client_e_pk = check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
Ok(Self { Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
info, client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
}) })
} }
} }
/// The server state produced after the second key exchange message /// The server state produced after the second key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2State<HashLen: ArrayLength<u8>> { pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>, km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>, hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>, session_key: GenericArray<u8, HashLen>,
} }
/// The second key exchange message // This can't be derived because of the use of a phantom parameter
pub struct Ke2Message<HashLen: ArrayLength<u8>> { impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
server_nonce: GenericArray<u8, NonceLen>, fn zeroize(&mut self) {
server_e_pk: Key, self.km3.zeroize();
e_info: Vec<u8>, self.hashed_transcript.zeroize();
mac: GenericArray<u8, HashLen>, self.session_key.zeroize();
}
} }
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> { impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
[ [
&self.km3[..], &self.km3[..],
@@ -328,12 +308,29 @@ impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> {
] ]
.concat() .concat()
} }
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.km3.as_ptr(), HashLen::to_usize()),
(self.hashed_transcript.as_ptr(), HashLen::to_usize()),
(self.session_key.as_ptr(), HashLen::to_usize()),
]
}
} }
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> { /// The second key exchange message
type Error = PakeError; #[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: PublicKey,
mac: GenericArray<u8, HashLen>,
}
fn try_from(input: &[u8]) -> Result<Self, Self::Error> { impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError> {
let hash_len = HashLen::to_usize(); let hash_len = HashLen::to_usize();
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?; let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
@@ -349,12 +346,7 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> { impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> { fn to_bytes(&self) -> Vec<u8> {
[ [&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
&self.to_bytes_without_info_or_mac(),
&serialize(&self.e_info, 2),
&self.mac[..],
]
.concat()
} }
} }
@@ -364,25 +356,31 @@ impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
} }
} }
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2Message<HashLen> { impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
type Error = PakeError; fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError> {
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize(); let nonce_len = NonceLen::to_usize();
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?; let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
let checked_server_e_pk = check_slice_size_atleast(
let unchecked_server_e_pk = check_slice_size_atleast(
&checked_nonce[nonce_len..], &checked_nonce[nonce_len..],
KEY_LEN, KEY_LEN,
"ke2_message server_e_pk", "ke2_message server_e_pk",
)?; )?;
let (e_info, remainder) = tokenize(&checked_server_e_pk[KEY_LEN..], 2)?; let checked_mac = check_slice_size(
let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?; &unchecked_server_e_pk[KEY_LEN..],
HashLen::to_usize(),
"ke1_message mac",
)?;
// Check the public key bytes
let server_e_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&unchecked_server_e_pk[..KEY_LEN],
)?)?;
Ok(Self { Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]), server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: Key::from_bytes(&checked_server_e_pk[..KEY_LEN])?, server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
e_info, mac: GenericArray::clone_from_slice(checked_mac),
mac: GenericArray::clone_from_slice(&checked_mac),
}) })
} }
} }
@@ -390,24 +388,26 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2Message<HashLen> {
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
// The triple of public and private components used in the 3DH computation // The triple of public and private components used in the 3DH computation
struct TripleDHComponents { struct TripleDHComponents {
pk1: Key, pk1: PublicKey,
sk1: Key, sk1: PrivateKey,
pk2: Key, pk2: PublicKey,
sk2: Key, sk2: PrivateKey,
pk3: Key, pk3: PublicKey,
sk3: Key, sk3: PrivateKey,
} }
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
// Consists of a session key, followed by two mac keys and an encryption key: (session_key, km2, ke2, km3) // Consists of a session key, followed by two mac keys: (session_key, km2, km3)
type TripleDHDerivationResult<D> = ( type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as FixedOutput>::OutputSize>, GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>, GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>, GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
); );
/// The third key exchange message /// The third key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke3Message<HashLen: ArrayLength<u8>> { pub struct Ke3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>, mac: GenericArray<u8, HashLen>,
} }
@@ -418,14 +418,12 @@ impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
} }
} }
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke3Message<HashLen> { impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
type Error = PakeError; fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
let checked_bytes = check_slice_size(bytes, HashLen::to_usize(), "ke3_message")?;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&bytes, HashLen::to_usize(), "ke3_message")?;
Ok(Self { Ok(Self {
mac: GenericArray::clone_from_slice(&checked_bytes), mac: GenericArray::clone_from_slice(checked_bytes),
}) })
} }
} }
@@ -448,30 +446,24 @@ fn derive_3dh_keys<D: Hash, G: Group>(
let extracted_ikm = Hkdf::<D>::new(None, &ikm); let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>( let handshake_secret = derive_secrets::<D>(
&extracted_ikm, &extracted_ikm,
&STR_HANDSHAKE_SECRET, STR_HANDSHAKE_SECRET,
&hashed_derivation_transcript, hashed_derivation_transcript,
)?; )?;
let session_key = derive_secrets::<D>( let session_key = derive_secrets::<D>(
&extracted_ikm, &extracted_ikm,
&STR_SESSION_SECRET, STR_SESSION_KEY,
&hashed_derivation_transcript, hashed_derivation_transcript,
)?; )?;
let km2 = hkdf_expand_label::<D>( let km2 = hkdf_expand_label::<D>(
&handshake_secret, &handshake_secret,
&STR_SERVER_MAC, STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let ke2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_HANDSHAKE_ENC,
b"", b"",
<D as Digest>::OutputSize::to_usize(), <D as Digest>::OutputSize::to_usize(),
)?; )?;
let km3 = hkdf_expand_label::<D>( let km3 = hkdf_expand_label::<D>(
&handshake_secret, &handshake_secret,
&STR_CLIENT_MAC, STR_CLIENT_MAC,
b"", b"",
<D as Digest>::OutputSize::to_usize(), <D as Digest>::OutputSize::to_usize(),
)?; )?;
@@ -479,7 +471,6 @@ fn derive_3dh_keys<D: Hash, G: Group>(
Ok(( Ok((
GenericArray::clone_from_slice(&session_key), GenericArray::clone_from_slice(&session_key),
GenericArray::clone_from_slice(&km2), GenericArray::clone_from_slice(&km2),
GenericArray::clone_from_slice(&ke2),
GenericArray::clone_from_slice(&km3), GenericArray::clone_from_slice(&km3),
)) ))
} }
@@ -503,14 +494,16 @@ fn hkdf_expand_label_extracted<D: Hash>(
let mut okm = vec![0u8; length]; let mut okm = vec![0u8; length];
let mut hkdf_label: Vec<u8> = Vec::new(); let mut hkdf_label: Vec<u8> = Vec::new();
hkdf_label.extend_from_slice(&length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
let length_u16: u16 = u16::try_from(length).map_err(|_| PakeError::SerializationError)?;
hkdf_label.extend_from_slice(&length_u16.to_be_bytes());
let mut opaque_label: Vec<u8> = Vec::new(); let mut opaque_label: Vec<u8> = Vec::new();
opaque_label.extend_from_slice(&STR_OPAQUE); opaque_label.extend_from_slice(STR_OPAQUE);
opaque_label.extend_from_slice(&label); opaque_label.extend_from_slice(label);
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1)); hkdf_label.extend_from_slice(&serialize(&opaque_label, 1));
hkdf_label.extend_from_slice(&serialize(&context, 1)); hkdf_label.extend_from_slice(&serialize(context, 1));
hkdf.expand(&hkdf_label, &mut okm) hkdf.expand(&hkdf_label, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?; .map_err(|_| InternalPakeError::HkdfError)?;
@@ -525,7 +518,7 @@ fn derive_secrets<D: Hash>(
hkdf_expand_label_extracted::<D>( hkdf_expand_label_extracted::<D>(
hkdf, hkdf,
label, label,
&hashed_derivation_transcript, hashed_derivation_transcript,
<D as Digest>::OutputSize::to_usize(), <D as Digest>::OutputSize::to_usize(),
) )
} }
+164 -35
View File
@@ -5,11 +5,14 @@
//! Contains the keypair types that must be supplied for the OPAQUE API //! Contains the keypair types that must be supplied for the OPAQUE API
#![allow(unsafe_code)]
use crate::errors::InternalPakeError; use crate::errors::InternalPakeError;
use crate::group::Group; use crate::group::Group;
#[cfg(test)]
use generic_array::typenum::Unsigned;
use generic_array::{typenum::U32, GenericArray}; use generic_array::{typenum::U32, GenericArray};
use generic_bytes::{SizedBytes, TryFromSizedBytesError}; use generic_bytes::{SizedBytes, TryFromSizedBytesError};
use generic_bytes_derive::TryFromForSizedBytes;
#[cfg(test)] #[cfg(test)]
use proptest::prelude::*; use proptest::prelude::*;
#[cfg(test)] #[cfg(test)]
@@ -18,6 +21,7 @@ use rand::{CryptoRng, RngCore};
use std::fmt::Debug; use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use zeroize::Zeroize;
/// Convenience extension trait of SizedBytes /// Convenience extension trait of SizedBytes
pub trait SizedBytesExt: SizedBytes { pub trait SizedBytesExt: SizedBytes {
@@ -31,74 +35,105 @@ pub trait SizedBytesExt: SizedBytes {
impl<T> SizedBytesExt for T where T: SizedBytes {} impl<T> SizedBytesExt for T where T: SizedBytes {}
/// A Keypair trait with public-private verification /// A Keypair trait with public-private verification
#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct KeyPair<G> { pub struct KeyPair<G> {
pk: Key, pk: PublicKey,
sk: Key, sk: PrivateKey,
_g: PhantomData<G>, _g: PhantomData<G>,
} }
impl_clone_for!(
struct KeyPair<G>,
[pk, sk, _g],
);
impl_debug_eq_hash_for!(
struct KeyPair<G>,
[pk, sk, _g],
);
// This can't be derived because of the use of a phantom parameter
impl<G> Zeroize for KeyPair<G> {
fn zeroize(&mut self) {
self.pk.zeroize();
self.sk.zeroize();
}
}
impl<G> Drop for KeyPair<G> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<G: Group> KeyPair<G> { impl<G: Group> KeyPair<G> {
/// The public key component /// The public key component
pub fn public(&self) -> &Key { pub fn public(&self) -> &PublicKey {
&self.pk &self.pk
} }
/// The private key component /// The private key component
pub fn private(&self) -> &Key { pub fn private(&self) -> &PrivateKey {
&self.sk &self.sk
} }
/// A constructor that receives public and private key independently as
/// bytes
pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
Ok(Self {
pk: public,
sk: private,
_g: PhantomData,
})
}
/// Generating a random key pair given a cryptographic rng /// Generating a random key pair given a cryptographic rng
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self { pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_scalar(rng); let sk = G::random_nonzero_scalar(rng);
let sk_bytes = G::scalar_as_bytes(&sk); let sk_bytes = G::scalar_as_bytes(&sk);
let pk = G::base_point().mult_by_slice(&sk_bytes); let pk = G::base_point().mult_by_slice(sk_bytes);
Self { Self {
pk: Key(pk.to_arr().to_vec()), pk: PublicKey(Key(pk.to_arr().to_vec())),
sk: Key(sk_bytes.to_vec()), sk: PrivateKey(Key(sk_bytes.to_vec())),
_g: PhantomData, _g: PhantomData,
} }
} }
/// Obtaining a public key from secret bytes. At all times, we should have /// Obtaining a public key from secret bytes. At all times, we should have
/// &public_from_private(self.private()) == self.public() /// &public_from_private(self.private()) == self.public()
pub(crate) fn public_from_private(bytes: &Key) -> Key { pub(crate) fn public_from_private(bytes: &PrivateKey) -> PublicKey {
let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]); let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]);
Key(G::base_point().mult_by_slice(&bytes_data).to_arr().to_vec()) PublicKey(Key(G::base_point()
.mult_by_slice(bytes_data)
.to_arr()
.to_vec()))
} }
/// Check whether a public key is valid. This is meant to be applied on /// Check whether a public key is valid. This is meant to be applied on
/// material provided through the network which fits the key /// material provided through the network which fits the key
/// representation (i.e. can be mapped to a curve point), but presents /// representation (i.e. can be mapped to a curve point), but presents
/// some risk - e.g. small subgroup check /// some risk - e.g. small subgroup check
pub(crate) fn check_public_key(key: Key) -> Result<Key, InternalPakeError> { pub(crate) fn check_public_key(key: PublicKey) -> Result<PublicKey, InternalPakeError> {
G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key) G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key)
} }
/// Computes the diffie hellman function on a public key and private key /// Computes the diffie hellman function on a public key and private key
pub(crate) fn diffie_hellman(pk: Key, sk: Key) -> Result<Vec<u8>, InternalPakeError> { pub(crate) fn diffie_hellman(
pk: PublicKey,
sk: PrivateKey,
) -> Result<Vec<u8>, InternalPakeError> {
let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]); let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]);
let point = G::from_element_slice(&pk_data)?; let point = G::from_element_slice(pk_data)?;
let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]); let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]);
Ok(G::mult_by_slice(&point, &secret_data).to_arr().to_vec()) Ok(G::mult_by_slice(&point, secret_data).to_arr().to_vec())
} }
/// Obtains a KeyPair from a slice representing the private key /// Obtains a KeyPair from a slice representing the private key
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> { pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
let sk = Key::from_arr(GenericArray::from_slice(&input))?; let sk = PrivateKey(Key::from_arr(GenericArray::from_slice(input))?);
let pk = Self::public_from_private(&sk); let pk = Self::public_from_private(&sk);
Self::new(pk, sk) Ok(Self {
pk,
sk,
_g: PhantomData,
})
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.pk.as_ptr(), KeyLen::to_usize()),
(self.sk.as_ptr(), KeyLen::to_usize()),
]
} }
} }
@@ -119,9 +154,13 @@ impl<G: Group + Debug> KeyPair<G> {
} }
} }
type KeyLen = U32;
/// A minimalist key type built around a \[u8; 32\] /// A minimalist key type built around a \[u8; 32\]
#[derive(Debug, PartialEq, Eq, Clone, TryFromForSizedBytes)] #[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)]
#[ErrorType = "::generic_bytes::TryFromSizedBytesError"] #[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)] #[repr(transparent)]
pub struct Key(Vec<u8>); pub struct Key(Vec<u8>);
@@ -133,22 +172,112 @@ impl Deref for Key {
} }
} }
impl SizedBytes for Key { // Don't make it implement SizedBytes so that it's not constructible outside of this module.
type Len = U32; impl Key {
fn to_arr(&self) -> GenericArray<u8, KeyLen> {
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
GenericArray::clone_from_slice(&self.0[..]) GenericArray::clone_from_slice(&self.0[..])
} }
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> { #[allow(clippy::unnecessary_wraps)]
fn from_arr(key_bytes: &GenericArray<u8, KeyLen>) -> Result<Self, TryFromSizedBytesError> {
Ok(Key(key_bytes.to_vec())) Ok(Key(key_bytes.to_vec()))
} }
} }
/// Wrapper around a Key to enforce that it's a private one.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)]
pub struct PrivateKey(Key);
impl Deref for PrivateKey {
type Target = Key;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl SizedBytes for PrivateKey {
type Len = KeyLen;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
self.0.to_arr()
}
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
Ok(PrivateKey(Key::from_arr(key_bytes)?))
}
}
/// Wrapper around a Key to enforce that it's a public one.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)]
pub struct PublicKey(Key);
impl Deref for PublicKey {
type Target = Key;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl SizedBytes for PublicKey {
type Len = KeyLen;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
self.0.to_arr()
}
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
Ok(PublicKey(Key::from_arr(key_bytes)?))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::errors::*;
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use rand::rngs::OsRng;
use std::slice::from_raw_parts;
#[test]
fn test_zeroize_key() -> Result<(), ProtocolError> {
let key_len = KeyLen::to_usize();
let mut key = Key(vec![1u8; key_len]);
let ptr = key.as_ptr();
key.zeroize();
let bytes = unsafe { from_raw_parts(ptr, key_len) };
assert!(bytes.iter().all(|&x| x == 0));
Ok(())
}
#[test]
fn test_zeroize_keypair() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut keypair = KeyPair::<RistrettoPoint>::generate_random(&mut rng);
let ptrs = keypair.as_byte_ptrs();
keypair.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
proptest! { proptest! {
#[test] #[test]
+174 -130
View File
@@ -5,7 +5,7 @@
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol //! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
//! //!
//! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html), //! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-05](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-05.html),
//! but this specification is subject to change, until the final version published by the IETF. //! but this specification is subject to change, until the final version published by the IETF.
//! //!
//! # Overview //! # Overview
@@ -19,7 +19,7 @@
//! //!
//! We will use the following choices in this example: //! We will use the following choices in this example:
//! ``` //! ```
//! use opaque_ke::ciphersuite::CipherSuite; //! use opaque_ke::CipherSuite;
//! struct Default; //! struct Default;
//! impl CipherSuite for Default { //! impl CipherSuite for Default {
//! type Group = curve25519_dalek::ristretto::RistrettoPoint; //! type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -32,14 +32,15 @@
//! for a working example of a simple password-based login using OPAQUE. //! for a working example of a simple password-based login using OPAQUE.
//! //!
//! Note that our choice of slow hashing function in this example, `NoOpHash`, is selected only to ensure //! Note that our choice of slow hashing function in this example, `NoOpHash`, is selected only to ensure
//! that the tests execute quickly. A real application should use an actual slow hashing function, such as `scrypt`, //! that the tests execute quickly. A real application should use an actual slow hashing function, such as `Argon2`,
//! which can be enabled through the `slow-hash` feature. //! which can be enabled through the `slow-hash` feature. See more details in the [features](#features) section.
//! //!
//! ## Setup //! ## Setup
//! To set up the protocol, the server begins by generating a static keypair: //! To set up the protocol, the server begins by creating a `ServerSetup` object:
//! ``` //! ```
//! # use opaque_ke::errors::ProtocolError; //! # use opaque_ke::errors::ProtocolError;
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # use opaque_ke::ServerSetup;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -49,17 +50,17 @@
//! # } //! # }
//! use rand::{rngs::OsRng, RngCore}; //! use rand::{rngs::OsRng, RngCore};
//! let mut rng = OsRng; //! let mut rng = OsRng;
//! let server_kp = Default::generate_random_keypair(&mut rng); //! let server_setup = ServerSetup::<Default>::new(&mut rng);
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
//! The server must persist this keypair for the registration and login steps, where the public component will be //! The server must persist an instance of [ServerSetup] for the registration and login steps.
//! used by the client during both registration and login, and the private component will be used by the server during login.
//! //!
//! ## Registration //! ## Registration
//! The registration protocol between the client and server consists of four steps along with three messages: //! The registration protocol between the client and server consists of four steps along with three messages:
//! [RegistrationRequest], [RegistrationResponse], and [RegistrationUpload]. A successful execution of the registration protocol results in the //! [RegistrationRequest], [RegistrationResponse], and [RegistrationUpload]. A successful execution of the registration protocol results in the
//! server producing a password file corresponding to the password provided by //! server producing a password file corresponding to a server-side identifier for the client, along with the password provided by
//! the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client. //! the client. This password file is typically stored in a key-value database, where the keys consist of these server-side identifiers for each client,
//! and the values consist of their corresponding password files, to be retrieved upon future login attempts made by the client.
//! //!
//! ### Client Registration Start //! ### Client Registration Start
//! In the first step of registration, the client chooses as input a registration password. The client runs [ClientRegistration::start] //! In the first step of registration, the client chooses as input a registration password. The client runs [ClientRegistration::start]
@@ -71,7 +72,7 @@
//! # ServerRegistration, //! # ServerRegistration,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -90,18 +91,18 @@
//! ``` //! ```
//! //!
//! ### Server Registration Start //! ### Server Registration Start
//! In the second step of registration, the server takes as input the instance of [RegistrationRequest] from the client, and //! In the second step of registration, the server takes as input a persisted instance of [ServerSetup], a [RegistrationRequest] from the client, and
//! the server's public key `server_kp.public()`. //! a server-side identifier for the client.
//! The server runs [ServerRegistration::start] to produce an a [ServerRegistrationStartResult], which consists of //! The server runs [ServerRegistration::start] to produce a [ServerRegistrationStartResult], which consists of
//! a [RegistrationResponse] to be returned to the client and //! a [RegistrationResponse] to be returned to the client.
//! a [ServerRegistration] which must be persisted on the server for the final step of server registration.
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, //! # ClientRegistration,
//! # ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -116,12 +117,12 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! use opaque_ke::ServerRegistration; //! use opaque_ke::ServerRegistration;
//! let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! let server_registration_start_result = ServerRegistration::<Default>::start( //! let server_registration_start_result = ServerRegistration::<Default>::start(
//! &mut server_rng, //! &server_setup,
//! client_registration_start_result.message, //! client_registration_start_result.message,
//! server_kp.public(), //! b"[email protected]",
//! )?; //! )?;
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
@@ -135,10 +136,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -153,8 +154,8 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! let client_registration_finish_result = client_registration_start_result.state.finish( //! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng, //! &mut client_rng,
//! server_registration_start_result.message, //! server_registration_start_result.message,
@@ -173,10 +174,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -191,19 +192,20 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?; //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! let password_file = server_registration_start_result.state.finish( //! let password_file = ServerRegistration::<Default>::finish(
//! client_registration_finish_result.message, //! client_registration_finish_result.message,
//! )?; //! );
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
//! //!
//! ## Login //! ## Login
//! The login protocol between a client and server also consists of four steps along with three messages: //! The login protocol between a client and server also consists of four steps along with three messages:
//! [CredentialRequest], [CredentialResponse], [CredentialFinalization]. The server is expected to have access to the password file //! [CredentialRequest], [CredentialResponse], [CredentialFinalization]. The server is expected to have access to the password file
//! corresponding to an output of the registration phase. The login protocol will execute successfully only if the same password //! corresponding to an output of the registration phase (see [Dummy Server Login](#dummy-server-login) for handling the scenario where
//! no password file is available). The login protocol will execute successfully only if the same password
//! was used in the registration phase that produced the password file that the server is testing against. //! was used in the registration phase that produced the password file that the server is testing against.
//! //!
//! ### Client Login Start //! ### Client Login Start
@@ -217,7 +219,7 @@
//! # ClientRegistration, ServerRegistration, ServerLogin, CredentialFinalization, //! # ClientRegistration, ServerRegistration, ServerLogin, CredentialFinalization,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -226,31 +228,31 @@
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # } //! # }
//! # use rand::{rngs::OsRng, RngCore}; //! # use rand::{rngs::OsRng, RngCore};
//! use opaque_ke::{ClientLogin, ClientLoginStartParameters}; //! use opaque_ke::ClientLogin;
//! let mut client_rng = OsRng; //! let mut client_rng = OsRng;
//! let client_login_start_result = ClientLogin::<Default>::start( //! let client_login_start_result = ClientLogin::<Default>::start(
//! &mut client_rng, //! &mut client_rng,
//! b"password", //! b"password",
//! ClientLoginStartParameters::default(),
//! )?; //! )?;
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
//! //!
//! ### Server Login Start //! ### Server Login Start
//! In the second step of login, the server takes as input //! In the second step of login, the server takes as input
//! a [CredentialRequest] from the client, //! a persisted instance of [ServerSetup],
//! the server's private key `server_kp.private()`, and //! the password file output from registration,
//! the password file output from registration. //! a [CredentialRequest] from the client, and
//! a server-side identifier for the client.
//! The server runs [ServerLogin::start] to produce an output consisting of //! The server runs [ServerLogin::start] to produce an output consisting of
//! a [CredentialResponse] which is returned to the client, and //! a [CredentialResponse] which is returned to the client, and
//! a [ServerLogin] which must be persisted on the server for the final step of login. //! a [ServerLogin] which must be persisted on the server for the final step of login.
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -265,27 +267,32 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?; //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters}; //! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?; //! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! let mut server_rng = OsRng; //! let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start( //! let server_login_start_result = ServerLogin::start(
//! &mut server_rng, //! &mut server_rng,
//! password_file, //! &server_setup,
//! &server_kp.private(), //! Some(password_file),
//! client_login_start_result.message, //! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters::default(), //! ServerLoginStartParameters::default(),
//! )?; //! )?;
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
//! Note that if there is no corresponding password file found for the user,
//! the server can use `None` in place of `Some(password_file)` in order to generate
//! a [CredentialResponse] that is indistinguishable from a valid [CredentialResponse]
//! returned for a registered client. This allows the server to prevent leaking information
//! about whether or not a client has previously registered with the server.
//! //!
//! ### Client Login Finish //! ### Client Login Finish
//! In the third step of login, the client takes as input a [CredentialResponse] from the server. //! In the third step of login, the client takes as input a [CredentialResponse] from the server.
@@ -295,10 +302,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -313,21 +320,20 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?; //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::deserialize( //! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..], //! # &password_file_bytes[..],
//! # )?; //! # )?;
//! # let server_login_start_result = //! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?; //! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! let client_login_finish_result = client_login_start_result.state.finish( //! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message, //! server_login_start_result.message,
//! ClientLoginFinishParameters::default(), //! ClientLoginFinishParameters::default(),
@@ -341,10 +347,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -359,21 +365,20 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?; //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::deserialize( //! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..], //! # &password_file_bytes[..],
//! # )?; //! # )?;
//! # let server_login_start_result = //! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?; //! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! # let client_login_finish_result = client_login_start_result.state.finish( //! # let client_login_finish_result = client_login_start_result.state.finish(
//! # server_login_start_result.message, //! # server_login_start_result.message,
//! # ClientLoginFinishParameters::default(), //! # ClientLoginFinishParameters::default(),
@@ -412,14 +417,15 @@
//! //!
//! A [ClientLoginFinishResult] contains the `server_s_pk` field, which is represents the static public key of the server that is established //! A [ClientLoginFinishResult] contains the `server_s_pk` field, which is represents the static public key of the server that is established
//! during the setup phase. This can be used by the client to verify the authenticity of the server it engages with during the login phase. In particular, //! during the setup phase. This can be used by the client to verify the authenticity of the server it engages with during the login phase. In particular,
//! the client can check that the static public key of the server supplied during registration matches this field during login. //! the client can check that the static public key of the server supplied during registration (with the `server_s_pk` field of
//! [ClientRegistrationFinishResult]) matches this field during login.
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -434,25 +440,26 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! // During setup, server generates its static keypair //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! // During registration, the client obtains a ClientRegistrationFinishResult with
//! //! // a server_s_pk field
//! // During setup or registration, the server transmits its static public key to the client //! let client_registration_finish_result = client_registration_start_result.state.finish(
//! let server_s_pk = server_kp.public(); // obtained from the server //! &mut client_rng,
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?; //! server_registration_start_result.message,
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! ClientRegistrationFinishParameters::default(),
//! )?;
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::deserialize( //! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..], //! # &password_file_bytes[..],
//! # )?; //! # )?;
//! # let server_login_start_result = //! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?; //! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! //!
//! // And then later, during login... //! // And then later, during login...
//! let client_login_finish_result = client_login_start_result.state.finish( //! let client_login_finish_result = client_login_start_result.state.finish(
@@ -460,11 +467,11 @@
//! ClientLoginFinishParameters::default(), //! ClientLoginFinishParameters::default(),
//! )?; //! )?;
//! //!
//! // Check that the server's static public key matches what was obtained during //! // Check that the server's static public key obtained from login matches what
//! // setup or registration //! // was obtained during registration
//! assert_eq!( //! assert_eq!(
//! &client_registration_finish_result.server_s_pk,
//! &client_login_finish_result.server_s_pk, //! &client_login_finish_result.server_s_pk,
//! server_s_pk,
//! ); //! );
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
@@ -492,10 +499,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -510,26 +517,25 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! // During registration... //! // During registration...
//! let client_registration_finish_result = client_registration_start_result.state.finish( //! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng, //! &mut client_rng,
//! server_registration_start_result.message, //! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::default() //! ClientRegistrationFinishParameters::default()
//! )?; //! )?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::deserialize( //! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..], //! # &password_file_bytes[..],
//! # )?; //! # )?;
//! # let server_login_start_result = //! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?; //! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! //!
//! // And then later, during login... //! // And then later, during login...
//! let client_login_finish_result = client_login_start_result.state.finish( //! let client_login_finish_result = client_login_start_result.state.finish(
@@ -546,8 +552,10 @@
//! //!
//! ## Custom Identifiers //! ## Custom Identifiers
//! //!
//! Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered "username" which is sent to the server to //! Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered username which is sent to the server to
//! identify the corresponding password file established during registration. The server may also have an identifier corresponding to an entity (e.g. facebook.com). //! identify the corresponding password file established during registration. This username may or may not coincide with the server-side identifier;
//! however, this username must be known to both the client and the server (whereas the server-side identifier does not need to be exposed to the client).
//! The server may also have an identifier corresponding to an entity (e.g. Facebook).
//! By default, neither of these public identifiers need to be supplied to the OPAQUE protocol. //! By default, neither of these public identifiers need to be supplied to the OPAQUE protocol.
//! //!
//! But, for applications that wish to cryptographically bind these identities to //! But, for applications that wish to cryptographically bind these identities to
@@ -556,10 +564,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, //! # ClientRegistration, ClientRegistrationFinishParameters, Identifiers, ServerRegistration, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -574,14 +582,16 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! let client_registration_finish_result = client_registration_start_result.state.finish( //! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng, //! &mut client_rng,
//! server_registration_start_result.message, //! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::WithIdentifiers( //! ClientRegistrationFinishParameters::WithIdentifiers(
//! b"username".to_vec(), //! Identifiers::ClientAndServerIdentifiers(
//! b"facebook.com".to_vec(), //! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! ),
//! ), //! ),
//! )?; //! )?;
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
@@ -591,10 +601,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, CredentialFinalization, Identifiers, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -609,26 +619,28 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?; //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters}; //! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?; //! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start( //! let server_login_start_result = ServerLogin::start(
//! &mut server_rng, //! &mut server_rng,
//! password_file, //! &server_setup,
//! &server_kp.private(), //! Some(password_file),
//! client_login_start_result.message, //! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters::WithIdentifiers( //! ServerLoginStartParameters::WithIdentifiers(
//! b"username".to_vec(), //! Identifiers::ClientAndServerIdentifiers(
//! b"facebook.com".to_vec(), //! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! ),
//! ), //! ),
//! )?; //! )?;
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
@@ -638,10 +650,10 @@
//! ``` //! ```
//! # use opaque_ke::{ //! # use opaque_ke::{
//! # errors::ProtocolError, //! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, //! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, Identifiers, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash, //! # slow_hash::NoOpHash,
//! # }; //! # };
//! # use opaque_ke::ciphersuite::CipherSuite; //! # use opaque_ke::CipherSuite;
//! # struct Default; //! # struct Default;
//! # impl CipherSuite for Default { //! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -656,48 +668,74 @@
//! # b"password", //! # b"password",
//! # )?; //! # )?;
//! # let mut server_rng = OsRng; //! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng); //! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?; //! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?; //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize(); //! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start( //! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng, //! # &mut client_rng,
//! # b"password", //! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?; //! # )?;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::deserialize( //! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..], //! # &password_file_bytes[..],
//! # )?; //! # )?;
//! # let server_login_start_result = //! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?; //! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::WithIdentifiers(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
//! let client_login_finish_result = client_login_start_result.state.finish( //! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message, //! server_login_start_result.message,
//! ClientLoginFinishParameters::WithIdentifiers( //! ClientLoginFinishParameters::WithIdentifiers(
//! b"username".to_vec(), //! Identifiers::ClientAndServerIdentifiers(
//! b"facebook.com".to_vec(), //! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! ),
//! ), //! ),
//! )?; //! )?;
//!
//! # Ok::<(), ProtocolError>(()) //! # Ok::<(), ProtocolError>(())
//! ``` //! ```
//! Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete //! Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete
//! the protocol! //! the protocol!
//! //!
//! ## Key Exchange Additional Data //! Note that if only one of the client and server identifiers are present, then [Identifiers::ClientIdentifier] and [Identifiers::ServerIdentifier] can be
//! used to specify them individually.
//! //!
//! A key exchange protocol typically supports the passing of data between the two parties before the exchange is complete, so as to bind the integrity //! ## Key Exchange Context
//! and/or confidentiality of application-specific data to the security of the key exchange. During the login phase, the client and server can pass
//! additional data alongside the first two messages of the protocol, with confidential data being supported for the second message.
//! //!
//! The following three messages support passing of additional data: //! A key exchange protocol typically allows for the specifying of shared "context" information between the two parties before the exchange is complete,
//! - The first login message, where the client can populate [ClientLoginStartParameters::WithInfo] with plaintext additional data, and //! so as to bind the integrity of application-specific data or configuration parameters to the security of the key exchange.
//! the server can retrieve using the `plain_info` field of [ServerLoginStartResult]. //! During the login phase, the client and server can specify this context using:
//! - The second login message, where the server can populate [ServerLoginStartParameters::WithInfo] with confidential additional data, //! - The second login message, where the server can populate [ServerLoginStartParameters::WithContext], and
//! and the client can retrieve using the `confidential_info` field of [ClientLoginFinishResult]. //! - The third login message, where the client can populate [ClientLoginFinishParameters::WithContext].
//! //!
//! For the second login message, the `WithInfoAndIdentifiers` variant can be used to specify these fields in addition to //! For both of these messages, the `WithContextAndIdentifiers` variant can be used to specify these fields in addition to
//! [custom identifiers](#custom-identifiers), with the ordering of the fields as `WithInfoAndIdentifiers(confidential_info, username, server_name)`. //! [custom identifiers](#custom-identifiers), with the ordering of the fields as
//! `WithContextAndIdentifiers(context, Identifiers::ClientAndServerIdentifiers(username, server_name))`.
//! //!
//! ## Dummy Server Login
//!
//! For applications in which the server does not wish to reveal to the client whether an existing password file has been
//! registered, the server can return a "dummy" credential response message to the client for an unregistered client,
//! which is indistinguishable from the normal credential response message that the server would return for a registered client.
//! The dummy message is created by passing a `None` to the password_file parameter for [ServerLogin::start].
//!
//! # Features
//!
//! - The `slow-hash` feature, when enabled, introduces a dependency on `argon2` and implements the `SlowHash` trait for `Argon2`
//! with a set of default parameters. In general, secure instantiations should choose to invoke a memory-hard password
//! hashing function when the client's password is expected to have low entropy, instead of relying on [slow_hash::NoOpHash]
//! as done in the above example. The more computationally intensive the `SlowHash` function is, the more resistant the server's
//! password file records will be against offline dictionary and precomputation attacks; see
//! [the OPAQUE paper](https://eprint.iacr.org/2018/163.pdf) for more details.
//!
//! - The `serialize` feature, enabled by default, provides convenience functions for serializing and deserializing with
//! [serde](https://serde.rs/).
//!
//! - The `u32_backend` and `u64_backend` features are re-exported from
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and allow for selecting
//! the corresponding backend for the curve arithmetic used. The `u64_backend` feature is included as the default.
//!
//! - The `bench` feature is used only for running performance benchmarks for this implementation.
//! //!
#![cfg_attr(not(feature = "bench"), deny(missing_docs))] #![cfg_attr(not(feature = "bench"), deny(missing_docs))]
@@ -712,6 +750,11 @@ compile_error!(
// Error types // Error types
pub mod errors; pub mod errors;
#[macro_use]
mod impls;
#[macro_use]
mod serialization;
// High-level API // High-level API
mod opaque; mod opaque;
@@ -735,8 +778,6 @@ mod oprf;
pub mod slow_hash; pub mod slow_hash;
mod serialization;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@@ -744,17 +785,20 @@ mod tests;
pub use rand; pub use rand;
pub use ciphersuite::CipherSuite;
pub use crate::messages::{ pub use crate::messages::{
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest, CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload, RegistrationResponse, RegistrationUpload,
}; };
pub use crate::opaque::{ClientLogin, ClientRegistration, ServerLogin, ServerRegistration};
pub use crate::opaque::{ pub use crate::opaque::{
ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistrationFinishParameters, ClientLogin, ClientRegistration, ServerLogin, ServerRegistration, ServerSetup,
ServerLoginStartParameters, };
pub use crate::opaque::{
ClientLoginFinishParameters, ClientRegistrationFinishParameters, ServerLoginStartParameters,
}; };
pub use crate::opaque::{ pub use crate::opaque::{
ClientLoginFinishResult, ClientLoginStartResult, ClientRegistrationFinishResult, ClientLoginFinishResult, ClientLoginStartResult, ClientRegistrationFinishResult,
ClientRegistrationStartResult, ServerLoginFinishResult, ServerLoginStartResult, ClientRegistrationStartResult, Identifiers, ServerLoginFinishResult, ServerLoginStartResult,
ServerRegistrationStartResult, ServerRegistrationStartResult,
}; };
+16
View File
@@ -24,6 +24,10 @@ pub trait GroupWithMapToCurve: Group {
/// transforms a password and domain separation tag (DST) into a curve point /// transforms a password and domain separation tag (DST) into a curve point
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>; fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8])
-> Result<Self::Scalar, InternalPakeError>;
/// Generates the contextString parameter as defined in /// Generates the contextString parameter as defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt> /// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
fn get_context_string(mode: u8) -> Vec<u8> { fn get_context_string(mode: u8) -> Vec<u8> {
@@ -43,6 +47,18 @@ impl GroupWithMapToCurve for RistrettoPoint {
&GenericArray::clone_from_slice(&uniform_bytes[..]), &GenericArray::clone_from_slice(&uniform_bytes[..]),
)) ))
} }
fn hash_to_scalar<H: Hash>(
input: &[u8],
dst: &[u8],
) -> Result<Self::Scalar, InternalPakeError> {
const LEN_IN_BYTES: usize = 64;
let uniform_bytes = expand_message_xmd::<H>(input, dst, LEN_IN_BYTES)?;
let mut bits = [0u8; LEN_IN_BYTES];
bits.copy_from_slice(&uniform_bytes[..]);
Ok(Self::Scalar::from_bytes_mod_order_wide(&bits))
}
} }
// Computes ceil(x / y) // Computes ceil(x / y)
+196 -60
View File
@@ -13,12 +13,14 @@ use crate::{
PakeError, ProtocolError, PakeError, ProtocolError,
}, },
group::Group, group::Group,
key_exchange::traits::{KeyExchange, ToBytes}, key_exchange::traits::{FromBytes, KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytesExt}, keypair::{KeyPair, PublicKey, SizedBytesExt},
opaque::ServerSetup,
}; };
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray}; use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes; use generic_bytes::SizedBytes;
use std::convert::TryFrom; use rand::{CryptoRng, RngCore};
// Messages // Messages
// ========= // =========
@@ -29,6 +31,23 @@ pub struct RegistrationRequest<CS: CipherSuite> {
pub(crate) alpha: CS::Group, pub(crate) alpha: CS::Group,
} }
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes
#[cfg(test)]
pub fn get_alpha_for_testing(&self) -> CS::Group {
self.alpha
}
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
fn clone(&self) -> Self {
Self { alpha: self.alpha }
}
}
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [alpha], [CS::Group]);
impl<CS: CipherSuite> RegistrationRequest<CS> { impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -38,64 +57,108 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize(); let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = check_slice_size(&input, elem_len, "first_message_bytes")?; let checked_slice = check_slice_size(input, elem_len, "first_message_bytes")?;
// Check that the message is actually containing an element of the // Check that the message is actually containing an element of the
// correct subgroup // correct subgroup
let arr = GenericArray::from_slice(checked_slice); let arr = GenericArray::from_slice(checked_slice);
let alpha = CS::Group::from_element_slice(arr)?; let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
Ok(Self { alpha }) Ok(Self { alpha })
} }
} }
impl_serialize_and_deserialize_for!(RegistrationRequest);
/// The answer sent by the server to the user, upon reception of the /// The answer sent by the server to the user, upon reception of the
/// registration attempt /// registration attempt
pub struct RegistrationResponse<CS: CipherSuite> { pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output /// The server's oprf output
pub(crate) beta: CS::Group, pub(crate) beta: CS::Group,
/// Server's static public key /// Server's static public key
pub(crate) server_s_pk: Vec<u8>, pub(crate) server_s_pk: PublicKey,
} }
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationResponse<CS> {
fn clone(&self) -> Self {
Self {
beta: self.beta,
server_s_pk: self.server_s_pk.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct RegistrationResponse<CS: CipherSuite>,
[beta, server_s_pk],
[CS::Group],
);
impl<CS: CipherSuite> RegistrationResponse<CS> { impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
[self.beta.to_arr().to_vec(), self.server_s_pk.clone()].concat() [self.beta.to_arr().to_vec(), self.server_s_pk.to_vec()].concat()
} }
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize(); let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize(); let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let checked_slice = let checked_slice =
check_slice_size(&input, elem_len + key_len, "registration_response_bytes")?; check_slice_size(input, elem_len + key_len, "registration_response_bytes")?;
// Check that the message is actually containing an element of the // Check that the message is actually containing an element of the
// correct subgroup // correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]); let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let beta = CS::Group::from_element_slice(arr)?; let beta = CS::Group::from_element_slice(arr)?;
Ok(Self { // Throw an error if the identity group element is encountered
server_s_pk: checked_slice[elem_len..].to_vec(), if beta.is_identity() {
beta, return Err(PakeError::IdentityGroupElementError.into());
}) }
// Ensure that public key is valid
let server_s_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[elem_len..],
)?)?;
Ok(Self { server_s_pk, beta })
} }
} }
impl_serialize_and_deserialize_for!(RegistrationResponse);
/// The final message from the client, containing sealed cryptographic /// The final message from the client, containing sealed cryptographic
/// identifiers /// identifiers
pub struct RegistrationUpload<CS: CipherSuite> { pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed /// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers /// cryptographic identifiers
pub(crate) envelope: Envelope<CS::Hash>, pub(crate) envelope: Envelope<CS>,
/// The masking key used to mask the envelope
pub(crate) masking_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The user's public key /// The user's public key
pub(crate) client_s_pk: Key, pub(crate) client_s_pk: PublicKey,
} }
impl_clone_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl<CS: CipherSuite> RegistrationUpload<CS> { impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
[ [
self.client_s_pk.to_arr().to_vec(), self.client_s_pk.to_arr().to_vec(),
self.masking_key.to_vec(),
self.envelope.serialize(), self.envelope.serialize(),
] ]
.concat() .concat()
@@ -103,25 +166,40 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <Key as SizedBytes>::Len::to_usize(); let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let hash_len = <CS::Hash as Digest>::OutputSize::to_usize();
let checked_slice = check_slice_size_atleast(&input, key_len, "registration_upload_bytes")?; let checked_slice =
check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[key_len..])?; let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.into());
}
Ok(Self { Ok(Self {
envelope, envelope,
client_s_pk: KeyPair::<CS::Group>::check_public_key(Key::from_bytes( masking_key: GenericArray::clone_from_slice(
&checked_slice[key_len..key_len + hash_len],
),
client_s_pk: KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[..key_len], &checked_slice[..key_len],
)?)?, )?)?,
}) })
} }
// Creates a dummy instance used for faking a [CredentialResponse]
pub(crate) fn dummy<R: RngCore + CryptoRng>(
rng: &mut R,
server_setup: &ServerSetup<CS>,
) -> Self {
let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
rng.fill_bytes(&mut masking_key);
Self {
envelope: Envelope::<CS>::dummy(),
masking_key: GenericArray::clone_from_slice(&masking_key),
client_s_pk: server_setup.fake_keypair.public().clone(),
}
}
} }
impl_serialize_and_deserialize_for!(RegistrationUpload);
/// The message sent by the user to the server, to initiate registration /// The message sent by the user to the server, to initiate registration
pub struct CredentialRequest<CS: CipherSuite> { pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information /// blinded password information
@@ -129,29 +207,49 @@ pub struct CredentialRequest<CS: CipherSuite> {
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message, pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message,
} }
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialRequest<CS> {
fn clone(&self) -> Self {
Self {
alpha: self.alpha,
ke1_message: self.ke1_message.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct CredentialRequest<CS: CipherSuite>,
[alpha, ke1_message],
[
CS::Group,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message
],
);
impl<CS: CipherSuite> CredentialRequest<CS> { impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut credential_request: Vec<u8> = Vec::new(); [self.alpha.to_arr().to_vec(), self.ke1_message.to_bytes()].concat()
credential_request.extend_from_slice(&self.alpha.to_arr());
credential_request.extend_from_slice(&self.ke1_message.to_bytes());
credential_request
} }
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize(); let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = let checked_slice = check_slice_size_atleast(input, elem_len, "login_first_message_bytes")?;
check_slice_size_atleast(&input, elem_len, "login_first_message_bytes")?;
// Check that the message is actually containing an element of the // Check that the message is actually containing an element of the
// correct subgroup // correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]); let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let alpha = CS::Group::from_element_slice(arr)?; let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let ke1_message = let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from( <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::from_bytes::<CS>(
&checked_slice[elem_len..], &checked_slice[elem_len..],
)?; )?;
@@ -159,22 +257,44 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
} }
} }
impl_serialize_and_deserialize_for!(CredentialRequest);
/// The answer sent by the server to the user, upon reception of the /// The answer sent by the server to the user, upon reception of the
/// login attempt /// login attempt
pub struct CredentialResponse<CS: CipherSuite> { pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output /// the server's oprf output
pub(crate) beta: CS::Group, pub(crate) beta: CS::Group,
pub(crate) server_s_pk: Key, pub(crate) masking_nonce: Vec<u8>,
/// the user's sealed information, pub(crate) masked_response: Vec<u8>,
pub(crate) envelope: Envelope<CS::Hash>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message, pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
} }
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialResponse<CS> {
fn clone(&self) -> Self {
Self {
beta: self.beta,
masking_nonce: self.masking_nonce.clone(),
masked_response: self.masked_response.clone(),
ke2_message: self.ke2_message.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct CredentialResponse<CS: CipherSuite>,
[beta, masking_nonce, masked_response, ke2_message],
[
CS::Group,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
],
);
impl<CS: CipherSuite> CredentialResponse<CS> { impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
[ [
Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope), Self::serialize_without_ke(&self.beta, &self.masking_nonce, &self.masked_response),
self.ke2_message.to_bytes(), self.ke2_message.to_bytes(),
] ]
.concat() .concat()
@@ -182,23 +302,26 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
pub(crate) fn serialize_without_ke( pub(crate) fn serialize_without_ke(
beta: &CS::Group, beta: &CS::Group,
server_s_pk: &Key, masking_nonce: &[u8],
envelope: &Envelope<CS::Hash>, masked_response: &[u8],
) -> Vec<u8> { ) -> Vec<u8> {
[ [&beta.to_arr(), masking_nonce, masked_response].concat()
&beta.to_arr(),
&server_s_pk.to_arr()[..],
&envelope.to_bytes(),
]
.concat()
} }
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize(); let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize(); let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let checked_slice = let nonce_len: usize = 32;
check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?; let envelope_len = Envelope::<CS>::len();
let masked_response_len = key_len + envelope_len;
let ke2_message_len = CS::KeyExchange::ke2_message_size();
let checked_slice = check_slice_size_atleast(
input,
elem_len + nonce_len + masked_response_len + ke2_message_len,
"credential_response_bytes",
)?;
// Check that the message is actually containing an element of the // Check that the message is actually containing an element of the
// correct subgroup // correct subgroup
@@ -206,35 +329,44 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
let arr = GenericArray::from_slice(beta_bytes); let arr = GenericArray::from_slice(beta_bytes);
let beta = CS::Group::from_element_slice(arr)?; let beta = CS::Group::from_element_slice(arr)?;
let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?; // Throw an error if the identity group element is encountered
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?; if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let (envelope, remainder) = let masking_nonce = checked_slice[elem_len..elem_len + nonce_len].to_vec();
Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len + key_len..])?; let masked_response = checked_slice
[elem_len + nonce_len..elem_len + nonce_len + masked_response_len]
let ke2_message_size = CS::KeyExchange::ke2_message_size(); .to_vec();
let checked_remainder =
check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
let ke2_message = let ke2_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::try_from( <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::from_bytes::<CS>(
&checked_remainder, &checked_slice[elem_len + nonce_len + masked_response_len..],
)?; )?;
Ok(Self { Ok(Self {
beta, beta,
server_s_pk, masking_nonce,
envelope, masked_response,
ke2_message, ke2_message,
}) })
} }
} }
impl_serialize_and_deserialize_for!(CredentialResponse);
/// The answer sent by the client to the server, upon reception of the /// The answer sent by the client to the server, upon reception of the
/// sealed envelope /// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> { pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message, pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message,
} }
impl_clone_for!(struct CredentialFinalization<CS: CipherSuite>, [ke3_message]);
impl_debug_eq_hash_for!(
struct CredentialFinalization<CS: CipherSuite>,
[ke3_message],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message],
);
impl<CS: CipherSuite> CredentialFinalization<CS> { impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -244,7 +376,11 @@ impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let ke3_message = let ke3_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(input)?; <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::from_bytes::<CS>(
input,
)?;
Ok(Self { ke3_message }) Ok(Self { ke3_message })
} }
} }
impl_serialize_and_deserialize_for!(CredentialFinalization);
+562 -436
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -12,11 +12,15 @@ use generic_array::GenericArray;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
/// Used to store the OPRF input and blinding factor /// Used to store the OPRF input and blinding factor
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Token<Grp: Group> { pub struct Token<Grp: Group> {
pub(crate) data: Vec<u8>, pub(crate) data: Vec<u8>,
pub(crate) blind: Grp::Scalar, pub(crate) blind: Grp::Scalar,
} }
impl_clone_for!(struct Token<Grp: Group>, [data, blind]);
impl_debug_eq_hash_for!(struct Token<Grp: Group>, [data, blind], [Grp::Scalar]);
static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-"; static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-";
static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-"; static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-";
static MODE_BASE: u8 = 0x00; static MODE_BASE: u8 = 0x00;
@@ -29,7 +33,8 @@ pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8], input: &[u8],
blinding_factor_rng: &mut R, blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), InternalPakeError> { ) -> Result<(Token<G>, G), InternalPakeError> {
let blind = G::random_scalar(blinding_factor_rng); // Choose a random scalar that must be non-zero
let blind = G::random_nonzero_scalar(blinding_factor_rng);
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat(); let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
let mapped_point = G::map_to_curve::<H>(input, &dst)?; let mapped_point = G::map_to_curve::<H>(input, &dst)?;
let blind_token = mapped_point * &blind; let blind_token = mapped_point * &blind;
+67
View File
@@ -53,5 +53,72 @@ pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec<u8>, Vec<
)) ))
} }
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
macro_rules! impl_serialize_and_deserialize_for {
($t:ident) => {
#[cfg(feature = "serialize")]
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
$t::<CS>::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
} else {
struct ByteVisitor<CS: CipherSuite> {
marker: std::marker::PhantomData<CS>,
}
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
type Value = $t<CS>;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter.write_str(std::concat!(
"the byte representation of a ",
std::stringify!($t)
))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
$t::<CS>::deserialize(value).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Bytes(value),
&std::concat!(
"invalid byte sequence for ",
std::stringify!($t)
),
)
})
}
}
deserializer.deserialize_bytes(ByteVisitor::<CS> {
marker: std::marker::PhantomData,
})
}
}
}
};
}
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
+100 -100
View File
@@ -6,24 +6,24 @@
use crate::{ use crate::{
ciphersuite::CipherSuite, ciphersuite::CipherSuite,
envelope::{Envelope, InnerEnvelopeMode}, envelope::{Envelope, InnerEnvelopeMode},
errors::*,
group::Group, group::Group,
key_exchange::{ key_exchange::{
traits::{KeyExchange, ToBytes}, traits::{FromBytes, KeyExchange, ToBytes},
tripledh::{NonceLen, TripleDH}, tripledh::{NonceLen, TripleDH},
}, },
opaque::*, keypair::{KeyPair, PublicKey},
serialization::{i2osp, os2ip, serialize}, serialization::{i2osp, os2ip, serialize},
*, *,
}; };
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
use generic_array::typenum::Unsigned; use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes; use generic_bytes::SizedBytes;
use proptest::{collection::vec, prelude::*}; use proptest::{collection::vec, prelude::*};
use rand::{rngs::OsRng, RngCore}; use rand::{rngs::OsRng, RngCore};
use sha2::Digest; use sha2::Digest;
use std::convert::TryFrom;
struct Default; struct Default;
impl CipherSuite for Default { impl CipherSuite for Default {
@@ -33,7 +33,7 @@ impl CipherSuite for Default {
type SlowHash = crate::slow_hash::NoOpHash; type SlowHash = crate::slow_hash::NoOpHash;
} }
const MAX_INFO_LENGTH: usize = 10; const HASH_SIZE: usize = 64; // Because of SHA512
const MAC_SIZE: usize = 64; // Because of SHA512 const MAC_SIZE: usize = 64; // Because of SHA512
fn random_ristretto_point() -> RistrettoPoint { fn random_ristretto_point() -> RistrettoPoint {
@@ -54,7 +54,7 @@ fn random_ristretto_point() -> RistrettoPoint {
fn client_registration_roundtrip() { fn client_registration_roundtrip() {
let pw = b"hunter2"; let pw = b"hunter2";
let mut rng = OsRng; let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng); let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
// serialization order: scalar, password // serialization order: scalar, password
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat(); let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
@@ -68,28 +68,20 @@ fn server_registration_roundtrip() {
// If we don't have envelope and client_pk, the server registration just // If we don't have envelope and client_pk, the server registration just
// contains the prf key // contains the prf key
let mut rng = OsRng; let mut rng = OsRng;
let oprf_key = <RistrettoPoint as Group>::random_scalar(&mut rng); let mut masking_key = [0u8; HASH_SIZE];
let mut oprf_bytes: Vec<u8> = vec![]; rng.fill_bytes(&mut masking_key);
oprf_bytes.extend_from_slice(oprf_key.as_bytes());
let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, oprf_bytes);
let mut ciphertext = [0u8; 32];
rng.fill_bytes(&mut ciphertext);
// Construct a mock envelope // Construct a mock envelope
let mut mock_envelope_bytes = Vec::new(); let mut mock_envelope_bytes = Vec::new();
mock_envelope_bytes.extend_from_slice(&[1; 1]); // mode = 1
mock_envelope_bytes.extend_from_slice(&vec![0; NonceLen::to_usize()]); // empty nonce mock_envelope_bytes.extend_from_slice(&vec![0; NonceLen::to_usize()]); // empty nonce
mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key // mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
let mock_client_kp = Default::generate_random_keypair(&mut rng); let mock_client_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
// serialization order: oprf_key, public key, envelope // serialization order: oprf_key, public key, envelope
let mut bytes = Vec::<u8>::new(); let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(oprf_key.as_bytes());
bytes.extend_from_slice(&mock_client_kp.public().to_arr()); bytes.extend_from_slice(&mock_client_kp.public().to_arr());
bytes.extend_from_slice(&masking_key);
bytes.extend_from_slice(&mock_envelope_bytes); bytes.extend_from_slice(&mock_envelope_bytes);
let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap(); let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize(); let reg_bytes = reg.serialize();
@@ -107,6 +99,17 @@ fn registration_request_roundtrip() {
let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap(); let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap();
let r1_bytes = r1.serialize(); let r1_bytes = r1.serialize();
assert_eq!(input, r1_bytes); assert_eq!(input, r1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(
match RegistrationRequest::<Default>::deserialize(identity_bytes.as_slice()) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
}
);
} }
#[test] #[test]
@@ -114,7 +117,7 @@ fn registration_response_roundtrip() {
let pt = random_ristretto_point(); let pt = random_ristretto_point();
let beta_bytes = pt.to_arr(); let beta_bytes = pt.to_arr();
let mut rng = OsRng; let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng); let skp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
let pubkey_bytes = skp.public().to_arr(); let pubkey_bytes = skp.public().to_arr();
let mut input = Vec::new(); let mut input = Vec::new();
@@ -124,32 +127,41 @@ fn registration_response_roundtrip() {
let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap(); let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap();
let r2_bytes = r2.serialize(); let r2_bytes = r2.serialize();
assert_eq!(input, r2_bytes); assert_eq!(input, r2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match RegistrationResponse::<Default>::deserialize(
&[identity_bytes, pubkey_bytes.to_vec()].concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
} }
#[test] #[test]
fn registration_upload_roundtrip() { fn registration_upload_roundtrip() {
let mut rng = OsRng; let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng); let skp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
let pubkey_bytes = skp.public().to_arr(); let pubkey_bytes = skp.public().to_arr();
let mut key = [0u8; 32]; let mut key = [0u8; 32];
rng.fill_bytes(&mut key); rng.fill_bytes(&mut key);
let mut nonce = [0u8; 32];
rng.fill_bytes(&mut nonce);
let mut msg = [0u8; 32]; let mut masking_key = vec![0u8; <sha2::Sha512 as Digest>::OutputSize::to_usize()];
rng.fill_bytes(&mut msg); rng.fill_bytes(&mut masking_key);
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw( let (envelope, _) =
&mut rng, Envelope::<Default>::seal_raw(&key, &nonce, &pubkey_bytes, InnerEnvelopeMode::Internal)
&key, .unwrap();
&msg,
&pubkey_bytes,
InnerEnvelopeMode::Base,
)
.unwrap();
let envelope_bytes = envelope.serialize(); let envelope_bytes = envelope.serialize();
let mut input = Vec::new(); let mut input = Vec::new();
input.extend_from_slice(&pubkey_bytes[..]); input.extend_from_slice(&pubkey_bytes[..]);
input.extend_from_slice(&masking_key[..]);
input.extend_from_slice(&envelope_bytes); input.extend_from_slice(&envelope_bytes);
let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap(); let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap();
@@ -163,19 +175,11 @@ fn credential_request_roundtrip() {
let alpha = random_ristretto_point(); let alpha = random_ristretto_point();
let alpha_bytes = alpha.to_arr().to_vec(); let alpha_bytes = alpha.to_arr().to_vec();
let client_e_kp = Default::generate_random_keypair(&mut rng); let client_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()]; let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce); rng.fill_bytes(&mut client_nonce);
let mut info = [0u8; MAX_INFO_LENGTH]; let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
rng.fill_bytes(&mut info);
let ke1m: Vec<u8> = [
&client_nonce[..],
&serialize(&info.to_vec(), 2),
&client_e_kp.public(),
]
.concat();
let mut input = Vec::new(); let mut input = Vec::new();
input.extend_from_slice(&alpha_bytes); input.extend_from_slice(&alpha_bytes);
@@ -184,6 +188,17 @@ fn credential_request_roundtrip() {
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap(); let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap();
let l1_bytes = l1.serialize(); let l1_bytes = l1.serialize();
assert_eq!(input, l1_bytes); assert_eq!(input, l1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialRequest::<Default>::deserialize(
&[identity_bytes, ke1m.to_vec()].concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
} }
#[test] #[test]
@@ -192,50 +207,48 @@ fn credential_response_roundtrip() {
let pt_bytes = pt.to_arr().to_vec(); let pt_bytes = pt.to_arr().to_vec();
let mut rng = OsRng; let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng);
let pubkey_bytes = skp.public().to_arr();
let mut key = [0u8; 32]; let mut masking_nonce = vec![0u8; 32];
rng.fill_bytes(&mut key); rng.fill_bytes(&mut masking_nonce);
let mut msg = [0u8; 32]; let mut masked_response =
rng.fill_bytes(&mut msg); vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<Default>::len()];
rng.fill_bytes(&mut masked_response);
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw( let server_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
&mut rng,
&key,
&msg,
&pubkey_bytes,
InnerEnvelopeMode::Base,
)
.unwrap();
let server_e_kp = Default::generate_random_keypair(&mut rng);
let mut mac = [0u8; MAC_SIZE]; let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac); rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::to_usize()]; let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce); rng.fill_bytes(&mut server_nonce);
let mut e_info = [0u8; MAX_INFO_LENGTH]; let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
rng.fill_bytes(&mut e_info);
let ke2m: Vec<u8> = [
&server_nonce[..],
&server_e_kp.public(),
&serialize(&e_info.to_vec(), 2),
&mac[..],
]
.concat();
let mut input = Vec::new(); let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice()); input.extend_from_slice(pt_bytes.as_slice());
input.extend_from_slice(&pubkey_bytes.as_slice()); input.extend_from_slice(&masking_nonce);
input.extend_from_slice(&envelope.serialize()); input.extend_from_slice(&masked_response);
input.extend_from_slice(&ke2m[..]); input.extend_from_slice(&ke2m[..]);
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap(); let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
let l2_bytes = l2.serialize(); let l2_bytes = l2.serialize();
assert_eq!(input, l2_bytes); assert_eq!(input, l2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialResponse::<Default>::deserialize(
&[
identity_bytes,
masking_nonce.to_vec(),
masked_response,
ke2m.to_vec()
]
.concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
} }
#[test] #[test]
@@ -255,9 +268,9 @@ fn login_third_message_roundtrip() {
fn client_login_roundtrip() { fn client_login_roundtrip() {
let pw = b"hunter2"; let pw = b"hunter2";
let mut rng = OsRng; let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng); let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let client_e_kp = Default::generate_random_keypair(&mut rng); let client_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()]; let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce); rng.fill_bytes(&mut client_nonce);
@@ -281,22 +294,15 @@ fn client_login_roundtrip() {
fn ke1_message_roundtrip() { fn ke1_message_roundtrip() {
let mut rng = OsRng; let mut rng = OsRng;
let client_e_kp = Default::generate_random_keypair(&mut rng); let client_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()]; let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce); rng.fill_bytes(&mut client_nonce);
let mut info = [0u8; MAX_INFO_LENGTH]; let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
rng.fill_bytes(&mut info); let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::from_bytes::<
Default,
let ke1m: Vec<u8> = [ >(&ke1m[..])
&client_nonce[..], .unwrap();
&serialize(&info.to_vec(), 2),
&client_e_kp.public(),
]
.concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::try_from(&ke1m[..])
.unwrap();
let reg_bytes = reg.to_bytes(); let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke1m); assert_eq!(reg_bytes, ke1m);
} }
@@ -305,25 +311,18 @@ fn ke1_message_roundtrip() {
fn ke2_message_roundtrip() { fn ke2_message_roundtrip() {
let mut rng = OsRng; let mut rng = OsRng;
let server_e_kp = Default::generate_random_keypair(&mut rng); let server_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
let mut mac = [0u8; MAC_SIZE]; let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac); rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::to_usize()]; let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce); rng.fill_bytes(&mut server_nonce);
let mut e_info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut e_info);
let ke2m: Vec<u8> = [ let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
&server_nonce[..],
&server_e_kp.public(),
&serialize(&e_info.to_vec(), 2),
&mac[..],
]
.concat();
let reg = let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::from_bytes::<
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::try_from(&ke2m[..]) Default,
.unwrap(); >(&ke2m[..])
.unwrap();
let reg_bytes = reg.to_bytes(); let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke2m); assert_eq!(reg_bytes, ke2m);
} }
@@ -336,9 +335,10 @@ fn ke3_message_roundtrip() {
let ke3m: Vec<u8> = [&mac[..]].concat(); let ke3m: Vec<u8> = [&mac[..]].concat();
let reg = let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::from_bytes::<
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::try_from(&ke3m[..]) Default,
.unwrap(); >(&ke3m[..])
.unwrap();
let reg_bytes = reg.to_bytes(); let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke3m); assert_eq!(reg_bytes, ke3m);
} }
+10 -12
View File
@@ -31,22 +31,20 @@ impl<D: Hash> SlowHash<D> for NoOpHash {
} }
#[cfg(feature = "slow-hash")] #[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_LOG_N: u8 = 15u8; impl<D: Hash> SlowHash<D> for argon2::Argon2<'_> {
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_R: u32 = 8u32;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_P: u32 = 1u32;
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
fn hash( fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>, input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> { ) -> Result<Vec<u8>, InternalPakeError> {
let params = let params = argon2::Argon2::default();
scrypt::ScryptParams::new(DEFAULT_SCRYPT_LOG_N, DEFAULT_SCRYPT_R, DEFAULT_SCRYPT_P)
.map_err(|_| InternalPakeError::SlowHashError)?;
let mut output = vec![0u8; <D as Digest>::OutputSize::to_usize()]; let mut output = vec![0u8; <D as Digest>::OutputSize::to_usize()];
scrypt::scrypt(&input, &[], &params, &mut output) params
.hash_password_into(
argon2::Algorithm::Argon2id,
&input,
&[0; argon2::MIN_SALT_LENGTH],
&[],
&mut output,
)
.map_err(|_| InternalPakeError::SlowHashError)?; .map_err(|_| InternalPakeError::SlowHashError)?;
Ok(output) Ok(output)
} }
+492 -142
View File
@@ -3,23 +3,26 @@
// This source code is licensed under the MIT license found in the // This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
#![allow(unsafe_code)]
use crate::{ use crate::{
ciphersuite::CipherSuite, ciphersuite::CipherSuite,
errors::*, errors::*,
group::Group, group::Group,
key_exchange::tripledh::{NonceLen, TripleDH}, key_exchange::tripledh::{NonceLen, TripleDH},
keypair::Key, keypair::KeyPair,
opaque::*, opaque::*,
slow_hash::NoOpHash, slow_hash::NoOpHash,
tests::mock_rng::CycleRng, tests::mock_rng::CycleRng,
*, *,
}; };
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
use generic_array::typenum::Unsigned; use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes; use generic_bytes::SizedBytes;
use rand::{rngs::OsRng, RngCore}; use rand::{rngs::OsRng, RngCore};
use serde_json::Value; use serde_json::Value;
use std::convert::TryFrom; use std::slice::from_raw_parts;
use zeroize::Zeroize;
// Tests // Tests
// ===== // =====
@@ -41,16 +44,18 @@ pub struct TestVectorParameters {
pub server_s_sk: Vec<u8>, pub server_s_sk: Vec<u8>,
pub server_e_pk: Vec<u8>, pub server_e_pk: Vec<u8>,
pub server_e_sk: Vec<u8>, pub server_e_sk: Vec<u8>,
pub fake_sk: Vec<u8>,
pub credential_identifier: Vec<u8>,
pub id_u: Vec<u8>, pub id_u: Vec<u8>,
pub id_s: Vec<u8>, pub id_s: Vec<u8>,
pub password: Vec<u8>, pub password: Vec<u8>,
pub blinding_factor: Vec<u8>, pub blinding_factor: Vec<u8>,
pub oprf_key: Vec<u8>, pub oprf_seed: Vec<u8>,
pub masking_nonce: Vec<u8>,
pub envelope_nonce: Vec<u8>, pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>, pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>, pub server_nonce: Vec<u8>,
pub info1: Vec<u8>, pub context: Vec<u8>,
pub einfo2: Vec<u8>,
pub registration_request: Vec<u8>, pub registration_request: Vec<u8>,
pub registration_response: Vec<u8>, pub registration_response: Vec<u8>,
pub registration_upload: Vec<u8>, pub registration_upload: Vec<u8>,
@@ -58,7 +63,6 @@ pub struct TestVectorParameters {
pub credential_response: Vec<u8>, pub credential_response: Vec<u8>,
pub credential_finalization: Vec<u8>, pub credential_finalization: Vec<u8>,
client_registration_state: Vec<u8>, client_registration_state: Vec<u8>,
server_registration_state: Vec<u8>,
client_login_state: Vec<u8>, client_login_state: Vec<u8>,
server_login_state: Vec<u8>, server_login_state: Vec<u8>,
pub password_file: Vec<u8>, pub password_file: Vec<u8>,
@@ -66,39 +70,43 @@ pub struct TestVectorParameters {
pub session_key: Vec<u8>, pub session_key: Vec<u8>,
} }
static STR_PASSWORD: &str = "password";
static STR_CREDENTIAL_IDENTIFIER: &str = "credential_identifier";
static TEST_VECTOR: &str = r#" static TEST_VECTOR: &str = r#"
{ {
"client_s_pk": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd215", "client_s_pk": "b47c69b4ea5e87139649349516c2842145993a2a00cc6e63d27c57f170475260",
"client_s_sk": "3000848b34d9073885d427e766b7093cc13bdce992ef31cd00ba2c77ff074504", "client_s_sk": "60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c06",
"client_e_pk": "5260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718", "client_e_pk": "5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c",
"client_e_sk": "1526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e4009", "client_e_sk": "9f42ca864614d4175e1540e4c56fe18362cb56b778dccf6b0a9446a23735dc03",
"server_s_pk": "c21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273", "server_s_pk": "8ed3fd51aa5e6931559fa6ae9be9829e609e441efbabb0846933fd5e30a3a268",
"server_s_sk": "2a38b385e9fb7e0a89aa0f005c729b0c4e22eeedea8c105cf136d9c5c334880f", "server_s_sk": "a514a8842cd760449887fb2f943440b17073b5073691ceeaa0552210e693ea01",
"server_e_pk": "125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f325", "server_e_pk": "e8249649f7614f6268df01e54eb992043d49df04c98f8c8cea27c263d95dec4b",
"server_e_sk": "7ef6ada36f3983c0f24705a29d3a453e3e17c28d347f226b24c0aa5ab12a700d", "server_e_sk": "a4b66443250a0cc39ad9baae6ada72c243ddee53b712eb48933993230c13500f",
"fake_sk": "60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c06",
"credential_identifier": "637265644964656e746966696572",
"id_u": "696455", "id_u": "696455",
"id_s": "696453", "id_s": "696453",
"password": "70617373776f7264", "password": "70617373776f7264",
"blinding_factor": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105", "blinding_factor": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a",
"oprf_key": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02", "oprf_seed": "1e7fddf167679cb1e83a179d4275034c09d2d745a1fec311a5e59ed30d0b80e2100ee8e6bbc996dc298f7f9e7dcc03c052853a02e4273d33c2973c7a6128affd",
"envelope_nonce": "78b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a468", "masking_nonce": "2b49d01802a69aacdad4979c503b96d08f44e4c67eaf82bbf6e71c6bb5473aa819359428f408bda29976beb0243c8a91fbeb2ee57840b90c62f4d87f88344db0",
"client_nonce": "43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a64", "envelope_nonce": "f4351a2d4f1efb09877fbef82d44bff3a963b08cc727874aa75c5d57d604aa2e",
"server_nonce": "680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e", "client_nonce": "e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a",
"info1": "696e666f31", "server_nonce": "9b78591d87600abf26789c0691dd5f760d5620aa58e34181cb24503bf04c936a",
"einfo2": "65696e666f32", "context": "636f6e74657874",
"registration_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d", "registration_request": "0cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832",
"registration_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273", "registration_response": "e88418f5a9145287062e50b060e6f6790583ec8646430af1bff0a2729bf20d1b8ed3fd51aa5e6931559fa6ae9be9829e609e441efbabb0846933fd5e30a3a268",
"registration_upload": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010", "registration_upload": "d6f1486284e595707ae341a4d083d454477933b1bcf770bfc4087127c0a8e844e833f76e997aef5b46d2108811667183d08f0cc0a8465dac277287591cac1e42933ed23a2c9476cfa939854a40fc746c21606535b19f0a48cf8cc565f7c3e6df60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c0654e4188e55b7fe2eed8a7aee79ae6cfefabab86e7b7822f05bc422ac7e7a9acb968001b3dc5ead255a2d7599a7be60aa97ebed89808db20faa445e912f7df2da",
"credential_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718", "credential_request": "0cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c",
"credential_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b2730278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f32500068a4c321f3c375613862ba83f7e5abb8f1d26dbd8035d39f192eb1324c2214457098054a2cbdf5d4ab2894eaf152af8c4be61d701c5a1ab1ec3e1cee5810898140b81771db0be", "credential_response": "e88418f5a9145287062e50b060e6f6790583ec8646430af1bff0a2729bf20d1b2b49d01802a69aacdad4979c503b96d08f44e4c67eaf82bbf6e71c6bb5473aa8718337df372fbb0de1beb29e2f4e6a2419858326ffe3f2a24172cca25e6344edd7db031cac3e206218eda4555d816f341c428317a4d37ed63441a278f78185b202b675b620e6f35056964d400c311cad23a1e6b0d9a91837d9d0021280bf0facf422961c96cffea530a24eb2486d4fa91adadaf7ac9a17d35b329b2add32e368a4b66443250a0cc39ad9baae6ada72c243ddee53b712eb48933993230c13500f2896e6f69e8610ced17584f34c09d872300bac6c99b8157392517ab9e9ed1f4aa163f8040d899cc77cf1f0ca2c4be6aef1616288cd3a6ac21989bdfc07bc4e94a284cf4c588583b2361195feab1ddcd390defde6282db2edc3eb535ede66404b",
"credential_finalization": "330aa8ac01bbc9d9642fe1c286187379efe12da14aceab86b22449d21f242d89adabc0295751b4e007beabc413ae9cbf9979e324749953705fd85b87c9c1b1a2", "credential_finalization": "2f8c71675d7db1b32ed3daaa7f15fc353f6af536ab1199e41e43ece9871d8b69336b8c84c4906810bb87c1a0407bd5f5d780c7d10a1c94016103639e507cf6d0",
"client_registration_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba10570617373776f7264", "client_registration_state": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a70617373776f7264",
"client_login_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105006714ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b11971800401526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e400943497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a6470617373776f7264", "client_login_state": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a00600cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c00409f42ca864614d4175e1540e4c56fe18362cb56b778dccf6b0a9446a23735dc03e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a70617373776f7264",
"server_registration_state": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02", "server_login_state": "a62f305635e341c151f5e51b89307940031337a0ad8f1369ddec9b672dc31f35d59be00eb66d77bda0079d6eda94809c863da359fef3a636704ae3fa1c9b9b2d18eb9b193528fbb392a5eab5da8068b7c276c8fe00814213ddd70d02157902bebfce850b403aaa4c99f8dbd5ff50d4ad3e703fb564a3fc474861e3f69d7c9a90037d3dbf36f215082644d5c5bc91e138f9665e7bc538f4bc70f97c91dfcd029b1c027b03dc99137478b3570d9da27922b88a8784f1c2f07cd04a0db04246531a",
"server_login_state": "89cd93dce8f59cf7b187736c50cbc3ca2e0bbbad0a0be1ddee180a2d95db60695b384be2dd673434ef94bcdbb1b457f63f41cd79ed2422c021c34ca1433b70576920ebb950ad1c40c8c015ec6832e12427e755ba21d005f0b6d5d66d2368ab5ec6f32151fced5a3aa25472c425912242de3638ef57f28b0dd02a956064e5bb9b8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47", "password_file": "d6f1486284e595707ae341a4d083d454477933b1bcf770bfc4087127c0a8e844e833f76e997aef5b46d2108811667183d08f0cc0a8465dac277287591cac1e42933ed23a2c9476cfa939854a40fc746c21606535b19f0a48cf8cc565f7c3e6df60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c0654e4188e55b7fe2eed8a7aee79ae6cfefabab86e7b7822f05bc422ac7e7a9acb968001b3dc5ead255a2d7599a7be60aa97ebed89808db20faa445e912f7df2da",
"password_file": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b026e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010", "export_key": "f1abeb7ab0a43ff1924b59d744053b271d999f341eedc740f1f62d785d19bec939479e5e39f2ec25f5ef712ecd10a085653ad1ed9049092cb2a3d44d6cc205ba",
"export_key": "8197f91f0d4de1ab126d8dfd06abd0d5df420ce40a135ef376e4ffe515930f413632390e7dc3dcfd19afff62b9113e10eb6c359fc327df6e9ad4d0f06c242322", "session_key": "037d3dbf36f215082644d5c5bc91e138f9665e7bc538f4bc70f97c91dfcd029b1c027b03dc99137478b3570d9da27922b88a8784f1c2f07cd04a0db04246531a"
"session_key": "8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47"
} }
"#; "#;
@@ -118,16 +126,18 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
server_s_sk: decode(&values, "server_s_sk").unwrap(), server_s_sk: decode(&values, "server_s_sk").unwrap(),
server_e_pk: decode(&values, "server_e_pk").unwrap(), server_e_pk: decode(&values, "server_e_pk").unwrap(),
server_e_sk: decode(&values, "server_e_sk").unwrap(), server_e_sk: decode(&values, "server_e_sk").unwrap(),
fake_sk: decode(&values, "fake_sk").unwrap(),
credential_identifier: decode(&values, "credential_identifier").unwrap(),
id_u: decode(&values, "id_u").unwrap(), id_u: decode(&values, "id_u").unwrap(),
id_s: decode(&values, "id_s").unwrap(), id_s: decode(&values, "id_s").unwrap(),
password: decode(&values, "password").unwrap(), password: decode(&values, "password").unwrap(),
blinding_factor: decode(&values, "blinding_factor").unwrap(), blinding_factor: decode(&values, "blinding_factor").unwrap(),
oprf_key: decode(&values, "oprf_key").unwrap(), oprf_seed: decode(&values, "oprf_seed").unwrap(),
masking_nonce: decode(&values, "masking_nonce").unwrap(),
envelope_nonce: decode(&values, "envelope_nonce").unwrap(), envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
client_nonce: decode(&values, "client_nonce").unwrap(), client_nonce: decode(&values, "client_nonce").unwrap(),
server_nonce: decode(&values, "server_nonce").unwrap(), server_nonce: decode(&values, "server_nonce").unwrap(),
info1: decode(&values, "info1").unwrap(), context: decode(&values, "context").unwrap(),
einfo2: decode(&values, "einfo2").unwrap(),
registration_request: decode(&values, "registration_request").unwrap(), registration_request: decode(&values, "registration_request").unwrap(),
registration_response: decode(&values, "registration_response").unwrap(), registration_response: decode(&values, "registration_response").unwrap(),
registration_upload: decode(&values, "registration_upload").unwrap(), registration_upload: decode(&values, "registration_upload").unwrap(),
@@ -136,7 +146,6 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
credential_finalization: decode(&values, "credential_finalization").unwrap(), credential_finalization: decode(&values, "credential_finalization").unwrap(),
client_registration_state: decode(&values, "client_registration_state").unwrap(), client_registration_state: decode(&values, "client_registration_state").unwrap(),
client_login_state: decode(&values, "client_login_state").unwrap(), client_login_state: decode(&values, "client_login_state").unwrap(),
server_registration_state: decode(&values, "server_registration_state").unwrap(),
server_login_state: decode(&values, "server_login_state").unwrap(), server_login_state: decode(&values, "server_login_state").unwrap(),
password_file: decode(&values, "password_file").unwrap(), password_file: decode(&values, "password_file").unwrap(),
export_key: decode(&values, "export_key").unwrap(), export_key: decode(&values, "export_key").unwrap(),
@@ -155,6 +164,13 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str()); s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str()); s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str()); s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
s.push_str(
format!(
"\"credential_identifier\": \"{}\",\n",
hex::encode(&p.credential_identifier)
)
.as_str(),
);
s.push_str(format!("\"id_u\": \"{}\",\n", hex::encode(&p.id_u)).as_str()); s.push_str(format!("\"id_u\": \"{}\",\n", hex::encode(&p.id_u)).as_str());
s.push_str(format!("\"id_s\": \"{}\",\n", hex::encode(&p.id_s)).as_str()); s.push_str(format!("\"id_s\": \"{}\",\n", hex::encode(&p.id_s)).as_str());
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str()); s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
@@ -165,7 +181,14 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
) )
.as_str(), .as_str(),
); );
s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str()); s.push_str(format!("\"oprf_seed\": \"{}\",\n", hex::encode(&p.oprf_seed)).as_str());
s.push_str(
format!(
"\"masking_nonce\": \"{}\",\n",
hex::encode(&p.masking_nonce)
)
.as_str(),
);
s.push_str( s.push_str(
format!( format!(
"\"envelope_nonce\": \"{}\",\n", "\"envelope_nonce\": \"{}\",\n",
@@ -175,8 +198,7 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
); );
s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str()); s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str()); s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
s.push_str(format!("\"info1\": \"{}\",\n", hex::encode(&p.info1)).as_str()); s.push_str(format!("\"context\": \"{}\",\n", hex::encode(&p.context)).as_str());
s.push_str(format!("\"einfo2\": \"{}\",\n", hex::encode(&p.einfo2)).as_str());
s.push_str( s.push_str(
format!( format!(
"\"registration_request\": \"{}\",\n", "\"registration_request\": \"{}\",\n",
@@ -233,13 +255,6 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
) )
.as_str(), .as_str(),
); );
s.push_str(
format!(
"\"server_registration_state\": \"{}\",\n",
hex::encode(&p.server_registration_state)
)
.as_str(),
);
s.push_str( s.push_str(
format!( format!(
"\"server_login_state\": \"{}\",\n", "\"server_login_state\": \"{}\",\n",
@@ -264,15 +279,20 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
let mut rng = OsRng; let mut rng = OsRng;
// Inputs // Inputs
let server_s_kp = CS::generate_random_keypair(&mut rng); let server_s_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
let server_e_kp = CS::generate_random_keypair(&mut rng); let server_e_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
let client_s_kp = CS::generate_random_keypair(&mut rng); let client_s_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
let client_e_kp = CS::generate_random_keypair(&mut rng); let client_e_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
let fake_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
let credential_identifier = b"credIdentifier";
let id_u = b"idU"; let id_u = b"idU";
let id_s = b"idS"; let id_s = b"idS";
let password = b"password"; let password = b"password";
let mut oprf_key_raw = [0u8; 32]; let context = b"context";
rng.fill_bytes(&mut oprf_key_raw); let mut oprf_seed = [0u8; 64];
rng.fill_bytes(&mut oprf_seed);
let mut masking_nonce = [0u8; 64];
rng.fill_bytes(&mut masking_nonce);
let mut envelope_nonce = [0u8; 32]; let mut envelope_nonce = [0u8; 32];
rng.fill_bytes(&mut envelope_nonce); rng.fill_bytes(&mut envelope_nonce);
let mut client_nonce = vec![0u8; NonceLen::to_usize()]; let mut client_nonce = vec![0u8; NonceLen::to_usize()];
@@ -280,11 +300,14 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
let mut server_nonce = vec![0u8; NonceLen::to_usize()]; let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce); rng.fill_bytes(&mut server_nonce);
let blinding_factor = CS::Group::random_scalar(&mut rng); let fake_sk: Vec<u8> = fake_kp.private().to_vec();
let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone(); let server_setup = ServerSetup::<CS>::deserialize(
&[&oprf_seed, &server_s_kp.private().to_arr()[..], &fake_sk].concat(),
)
.unwrap();
let info1 = b"info1"; let blinding_factor = CS::Group::random_nonzero_scalar(&mut rng);
let einfo2 = b"einfo2"; let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone();
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_bytes.to_vec()); let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_bytes.to_vec());
let client_registration_start_result = let client_registration_start_result =
@@ -302,20 +325,16 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
.to_vec(); .to_vec();
let client_registration_state = client_registration_start_result.state.serialize().to_vec(); let client_registration_state = client_registration_start_result.state.serialize().to_vec();
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
let server_registration_start_result = ServerRegistration::<CS>::start( let server_registration_start_result = ServerRegistration::<CS>::start(
&mut oprf_key_rng, &server_setup,
client_registration_start_result.message, client_registration_start_result.message,
server_s_kp.public(), &credential_identifier[..],
) )
.unwrap(); .unwrap();
let registration_response_bytes = server_registration_start_result let registration_response_bytes = server_registration_start_result
.message .message
.serialize() .serialize()
.to_vec(); .to_vec();
let oprf_key_bytes =
CS::Group::scalar_as_bytes(&server_registration_start_result.state.oprf_key).clone();
let server_registration_state = server_registration_start_result.state.serialize().to_vec();
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new(); let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().to_arr()); client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().to_arr());
@@ -327,7 +346,9 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
.finish( .finish(
&mut finish_registration_rng, &mut finish_registration_rng,
server_registration_start_result.message, server_registration_start_result.message,
ClientRegistrationFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()), ClientRegistrationFinishParameters::WithIdentifiers(
Identifiers::ClientAndServerIdentifiers(id_u.to_vec(), id_s.to_vec()),
),
) )
.unwrap(); .unwrap();
let registration_upload_bytes = client_registration_finish_result let registration_upload_bytes = client_registration_finish_result
@@ -335,10 +356,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
.serialize() .serialize()
.to_vec(); .to_vec();
let password_file = server_registration_start_result let password_file = ServerRegistration::finish(client_registration_finish_result.message);
.state
.finish(client_registration_finish_result.message)
.unwrap();
let password_file_bytes = password_file.serialize(); let password_file_bytes = password_file.serialize();
let mut client_login_start: Vec<u8> = Vec::new(); let mut client_login_start: Vec<u8> = Vec::new();
@@ -347,17 +365,14 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
client_login_start.extend_from_slice(&client_nonce); client_login_start.extend_from_slice(&client_nonce);
let mut client_login_start_rng = CycleRng::new(client_login_start); let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<CS>::start( let client_login_start_result =
&mut client_login_start_rng, ClientLogin::<CS>::start(&mut client_login_start_rng, password).unwrap();
password,
ClientLoginStartParameters::WithInfo(info1.to_vec()),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize().to_vec(); let credential_request_bytes = client_login_start_result.message.serialize().to_vec();
let client_login_state = client_login_start_result.state.serialize().to_vec(); let client_login_state = client_login_start_result.state.serialize().to_vec();
let mut server_e_sk_and_nonce_rng = CycleRng::new( let mut server_e_sk_and_nonce_rng = CycleRng::new(
[ [
masking_nonce.to_vec(),
server_e_kp.private().to_arr().to_vec(), server_e_kp.private().to_arr().to_vec(),
server_nonce.to_vec(), server_nonce.to_vec(),
] ]
@@ -365,13 +380,13 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
); );
let server_login_start_result = ServerLogin::<CS>::start( let server_login_start_result = ServerLogin::<CS>::start(
&mut server_e_sk_and_nonce_rng, &mut server_e_sk_and_nonce_rng,
password_file, &server_setup,
server_s_kp.private(), Some(password_file),
client_login_start_result.message, client_login_start_result.message,
ServerLoginStartParameters::WithInfoAndIdentifiers( credential_identifier,
einfo2.to_vec(), ServerLoginStartParameters::WithContextAndIdentifiers(
id_u.to_vec(), context.to_vec(),
id_s.to_vec(), Identifiers::ClientAndServerIdentifiers(id_u.to_vec(), id_s.to_vec()),
), ),
) )
.unwrap(); .unwrap();
@@ -382,7 +397,10 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
.state .state
.finish( .finish(
server_login_start_result.message, server_login_start_result.message,
ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()), ClientLoginFinishParameters::WithContextAndIdentifiers(
context.to_vec(),
Identifiers::ClientAndServerIdentifiers(id_u.to_vec(), id_s.to_vec()),
),
) )
.unwrap(); .unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize(); let credential_finalization_bytes = client_login_finish_result.message.serialize();
@@ -396,16 +414,18 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
server_s_sk: server_s_kp.private().to_arr().to_vec(), server_s_sk: server_s_kp.private().to_arr().to_vec(),
server_e_pk: server_e_kp.public().to_arr().to_vec(), server_e_pk: server_e_kp.public().to_arr().to_vec(),
server_e_sk: server_e_kp.private().to_arr().to_vec(), server_e_sk: server_e_kp.private().to_arr().to_vec(),
fake_sk,
credential_identifier: credential_identifier.to_vec(),
id_u: id_u.to_vec(), id_u: id_u.to_vec(),
id_s: id_s.to_vec(), id_s: id_s.to_vec(),
password: password.to_vec(), password: password.to_vec(),
blinding_factor: blinding_factor_bytes.to_vec(), blinding_factor: blinding_factor_bytes.to_vec(),
oprf_key: oprf_key_bytes.to_vec(), oprf_seed: oprf_seed.to_vec(),
masking_nonce: masking_nonce.to_vec(),
envelope_nonce: envelope_nonce.to_vec(), envelope_nonce: envelope_nonce.to_vec(),
client_nonce: client_nonce.to_vec(), client_nonce: client_nonce.to_vec(),
server_nonce: server_nonce.to_vec(), server_nonce: server_nonce.to_vec(),
info1: info1.to_vec(), context: context.to_vec(),
einfo2: einfo2.to_vec(),
registration_request: registration_request_bytes, registration_request: registration_request_bytes,
registration_response: registration_response_bytes, registration_response: registration_response_bytes,
registration_upload: registration_upload_bytes, registration_upload: registration_upload_bytes,
@@ -414,7 +434,6 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
credential_finalization: credential_finalization_bytes, credential_finalization: credential_finalization_bytes,
password_file: password_file_bytes, password_file: password_file_bytes,
client_registration_state, client_registration_state,
server_registration_state,
client_login_state, client_login_state,
server_login_state, server_login_state,
session_key: client_login_finish_result.session_key, session_key: client_login_finish_result.session_key,
@@ -445,24 +464,65 @@ fn test_registration_request() -> Result<(), ProtocolError> {
Ok(()) Ok(())
} }
#[cfg(feature = "serialize")]
#[test]
fn test_serialization() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut rng = CycleRng::new(parameters.blinding_factor.to_vec());
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(&mut rng, &parameters.password)?;
{
// Test the json serialization (human-readable, base64).
let registration_request_json =
serde_json::to_string(&client_registration_start_result.message).unwrap();
assert_eq!(
registration_request_json,
r#""DNx98cypibVpF8lRJ+WeyPBb2nxgbLRecUv6WCtCmDI=""#
);
let registration_request: RegistrationRequest<RistrettoSha5123dhNoSlowHash> =
serde_json::from_str(&registration_request_json).unwrap();
assert_eq!(
hex::encode(client_registration_start_result.message.serialize()),
hex::encode(registration_request.serialize()),
);
}
{
// Test the bincode serialization (binary).
let registration_request_bin =
bincode::serialize(&client_registration_start_result.message).unwrap();
assert_eq!(registration_request_bin.len(), 40);
let registration_request: RegistrationRequest<RistrettoSha5123dhNoSlowHash> =
bincode::deserialize(&registration_request_bin).unwrap();
assert_eq!(
hex::encode(client_registration_start_result.message.serialize()),
hex::encode(registration_request.serialize()),
);
}
Ok(())
}
#[test] #[test]
fn test_registration_response() -> Result<(), ProtocolError> { fn test_registration_response() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_s_sk[..],
&parameters.fake_sk[..],
]
.concat(),
)?;
let server_registration_start_result = let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start( ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut oprf_key_rng, &server_setup,
RegistrationRequest::deserialize(&parameters.registration_request[..])?, RegistrationRequest::deserialize(&parameters.registration_request[..])?,
&Key::try_from(&parameters.server_s_pk[..])?, &parameters.credential_identifier,
)?; )?;
assert_eq!( assert_eq!(
hex::encode(parameters.registration_response), hex::encode(parameters.registration_response),
hex::encode(server_registration_start_result.message.serialize()) hex::encode(server_registration_start_result.message.serialize())
); );
assert_eq!(
hex::encode(&parameters.server_registration_state),
hex::encode(server_registration_start_result.state.serialize())
);
Ok(()) Ok(())
} }
@@ -479,7 +539,9 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
.finish( .finish(
&mut finish_registration_rng, &mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..])?, RegistrationResponse::deserialize(&parameters.registration_response[..])?,
ClientRegistrationFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s), ClientRegistrationFinishParameters::WithIdentifiers(
Identifiers::ClientAndServerIdentifiers(parameters.id_u, parameters.id_s),
),
)?; )?;
assert_eq!( assert_eq!(
@@ -498,12 +560,11 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
fn test_password_file() -> Result<(), ProtocolError> { fn test_password_file() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let server_registration = ServerRegistration::<RistrettoSha5123dhNoSlowHash>::deserialize( let password_file = ServerRegistration::finish(RegistrationUpload::<
&parameters.server_registration_state[..], RistrettoSha5123dhNoSlowHash,
)?; >::deserialize(
let password_file = server_registration.finish(RegistrationUpload::deserialize( &parameters.registration_upload[..]
&parameters.registration_upload[..], )?);
)?)?;
assert_eq!( assert_eq!(
hex::encode(parameters.password_file), hex::encode(parameters.password_file),
@@ -526,7 +587,6 @@ fn test_credential_request() -> Result<(), ProtocolError> {
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start( let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_login_start_rng, &mut client_login_start_rng,
&parameters.password, &parameters.password,
ClientLoginStartParameters::WithInfo(parameters.info1),
)?; )?;
assert_eq!( assert_eq!(
hex::encode(&parameters.credential_request), hex::encode(&parameters.credential_request),
@@ -543,25 +603,38 @@ fn test_credential_request() -> Result<(), ProtocolError> {
fn test_credential_response() -> Result<(), ProtocolError> { fn test_credential_response() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap()); let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut server_e_sk_and_nonce_rng = let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::deserialize(
CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat()); &[
&parameters.oprf_seed[..],
&parameters.server_s_sk[..],
&parameters.fake_sk[..],
]
.concat(),
)?;
let mut server_e_sk_and_nonce_rng = CycleRng::new(
[
parameters.masking_nonce,
parameters.server_e_sk,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start( let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_e_sk_and_nonce_rng, &mut server_e_sk_and_nonce_rng,
ServerRegistration::deserialize(&parameters.password_file[..])?, &server_setup,
&Key::try_from(&parameters.server_s_sk[..])?, Some(ServerRegistration::deserialize(
&parameters.password_file[..],
)?),
CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize( CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize(
&parameters.credential_request[..], &parameters.credential_request[..],
)?, )?,
ServerLoginStartParameters::WithInfoAndIdentifiers( &parameters.credential_identifier,
parameters.einfo2.to_vec(), ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.id_u, parameters.context,
parameters.id_s, Identifiers::ClientAndServerIdentifiers(parameters.id_u, parameters.id_s),
), ),
)?; )?;
assert_eq!(
hex::encode(&parameters.info1),
hex::encode(server_login_start_result.plain_info),
);
assert_eq!( assert_eq!(
hex::encode(&parameters.credential_response), hex::encode(&parameters.credential_response),
hex::encode(server_login_start_result.message.serialize()) hex::encode(server_login_start_result.message.serialize())
@@ -584,13 +657,12 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize( CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize(
&parameters.credential_response[..], &parameters.credential_response[..],
)?, )?,
ClientLoginFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s), ClientLoginFinishParameters::WithContextAndIdentifiers(
parameters.context,
Identifiers::ClientAndServerIdentifiers(parameters.id_u, parameters.id_s),
),
)?; )?;
assert_eq!(
hex::encode(&parameters.einfo2),
hex::encode(&client_login_finish_result.confidential_info)
);
assert_eq!( assert_eq!(
hex::encode(&parameters.server_s_pk), hex::encode(&parameters.server_s_pk),
hex::encode(&client_login_finish_result.server_s_pk.to_arr().to_vec()) hex::encode(&client_login_finish_result.server_s_pk.to_arr().to_vec())
@@ -624,7 +696,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
assert_eq!( assert_eq!(
hex::encode(parameters.session_key), hex::encode(parameters.session_key),
hex::encode(server_login_result.session_key) hex::encode(&server_login_result.session_key)
); );
Ok(()) Ok(())
@@ -634,9 +706,10 @@ fn test_complete_flow(
registration_password: &[u8], registration_password: &[u8],
login_password: &[u8], login_password: &[u8],
) -> Result<(), ProtocolError> { ) -> Result<(), ProtocolError> {
let credential_identifier = b"credentialIdentifier";
let mut client_rng = OsRng; let mut client_rng = OsRng;
let mut server_rng = OsRng; let mut server_rng = OsRng;
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng); let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
let client_registration_start_result = let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start( ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng, &mut client_rng,
@@ -644,28 +717,24 @@ fn test_complete_flow(
)?; )?;
let server_registration_start_result = let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start( ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_rng, &server_setup,
client_registration_start_result.message, client_registration_start_result.message,
server_kp.public(), credential_identifier,
)?; )?;
let client_registration_finish_result = client_registration_start_result.state.finish( let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng, &mut client_rng,
server_registration_start_result.message, server_registration_start_result.message,
ClientRegistrationFinishParameters::default(), ClientRegistrationFinishParameters::default(),
)?; )?;
let p_file = server_registration_start_result let p_file = ServerRegistration::finish(client_registration_finish_result.message);
.state let client_login_start_result =
.finish(client_registration_finish_result.message)?; ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(&mut client_rng, login_password)?;
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
login_password,
ClientLoginStartParameters::default(),
)?;
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start( let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_rng, &mut server_rng,
p_file, &server_setup,
&server_kp.private(), Some(p_file),
client_login_start_result.message, client_login_start_result.message,
credential_identifier,
ServerLoginStartParameters::default(), ServerLoginStartParameters::default(),
)?; )?;
@@ -681,21 +750,18 @@ fn test_complete_flow(
.finish(client_login_finish_result.message)?; .finish(client_login_finish_result.message)?;
assert_eq!( assert_eq!(
hex::encode(server_login_finish_result.session_key), hex::encode(&server_login_finish_result.session_key),
hex::encode(client_login_finish_result.session_key) hex::encode(&client_login_finish_result.session_key)
); );
assert_eq!( assert_eq!(
hex::encode(client_registration_finish_result.export_key), hex::encode(client_registration_finish_result.export_key),
hex::encode(client_login_finish_result.export_key) hex::encode(client_login_finish_result.export_key)
); );
} else { } else {
let res = matches!( assert!(match client_login_result {
client_login_result, Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
Err(ProtocolError::VerificationError( _ => false,
PakeError::InvalidLoginError });
))
);
assert!(res);
} }
Ok(()) Ok(())
@@ -710,3 +776,287 @@ fn test_complete_flow_success() -> Result<(), ProtocolError> {
fn test_complete_flow_fail() -> Result<(), ProtocolError> { fn test_complete_flow_fail() -> Result<(), ProtocolError> {
test_complete_flow(b"good password", b"bad password") test_complete_flow(b"good password", b"bad password")
} }
// Zeroize tests
#[test]
fn test_zeroize_client_registration_start() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let mut state = client_registration_start_result.state;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&server_setup,
client_registration_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
)?;
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;
let mut state = client_registration_finish_result.state;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&server_setup,
client_registration_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
)?;
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;
let p_file = ServerRegistration::finish(client_registration_finish_result.message);
let mut state = p_file;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let mut state = client_login_start_result.state;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&server_setup,
client_registration_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
)?;
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;
let p_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_rng,
&server_setup,
Some(p_file),
client_login_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
ServerLoginStartParameters::default(),
)?;
let mut state = server_login_start_result.state;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&server_setup,
client_registration_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
)?;
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;
let p_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_rng,
&server_setup,
Some(p_file),
client_login_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
ServerLoginStartParameters::default(),
)?;
let client_login_finish_result = client_login_start_result.state.finish(
server_login_start_result.message,
ClientLoginFinishParameters::default(),
)?;
let mut state = client_login_finish_result.state;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&server_setup,
client_registration_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
)?;
let client_registration_finish_result = client_registration_start_result.state.finish(
&mut client_rng,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)?;
let p_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
STR_PASSWORD.as_bytes(),
)?;
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_rng,
&server_setup,
Some(p_file),
client_login_start_result.message,
STR_CREDENTIAL_IDENTIFIER.as_bytes(),
ServerLoginStartParameters::default(),
)?;
let client_login_finish_result = client_login_start_result.state.finish(
server_login_start_result.message,
ClientLoginFinishParameters::default(),
)?;
let server_login_finish_result = server_login_start_result
.state
.finish(client_login_finish_result.message)?;
let mut state = server_login_finish_result.state;
let ptrs = state.as_byte_ptrs();
state.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
#[test]
fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
// Start out with a bunch of zeros to force resampling of scalar
let mut client_registration_rng = CycleRng::new([vec![0u8; 128], vec![1u8; 128]].concat());
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_registration_rng,
STR_PASSWORD.as_bytes(),
)?;
assert_ne!(
RistrettoPoint::identity(),
client_registration_start_result
.message
.get_alpha_for_testing()
);
Ok(())
}
+468 -259
View File
@@ -4,12 +4,13 @@
// LICENSE file in the root directory of this source tree. // LICENSE file in the root directory of this source tree.
use crate::{ use crate::{
ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::Key, opaque::*, ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::PrivateKey,
slow_hash::NoOpHash, tests::mock_rng::CycleRng, *, opaque::*, slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
}; };
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes;
use serde_json::Value; use serde_json::Value;
use std::convert::TryFrom;
// Tests // Tests
// ===== // =====
@@ -30,20 +31,24 @@ pub enum EnvelopeMode {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct TestVectorParameters { pub struct TestVectorParameters {
pub dummy_private_key: Vec<u8>,
pub dummy_masking_key: Vec<u8>,
pub context: Vec<u8>,
pub envelope_mode: EnvelopeMode, pub envelope_mode: EnvelopeMode,
pub client_public_key: Vec<u8>, pub client_private_key: Option<Vec<u8>>,
pub client_private_key: Vec<u8>,
pub client_keyshare: Vec<u8>, pub client_keyshare: Vec<u8>,
pub client_private_keyshare: Vec<u8>, pub client_private_keyshare: Vec<u8>,
pub server_public_key: Vec<u8>, pub server_public_key: Vec<u8>,
pub server_private_key: Vec<u8>, pub server_private_key: Vec<u8>,
pub server_keyshare: Vec<u8>, pub server_keyshare: Vec<u8>,
pub server_private_keyshare: Vec<u8>, pub server_private_keyshare: Vec<u8>,
pub client_identity: Vec<u8>, pub client_identity: Option<Vec<u8>>,
pub server_identity: Vec<u8>, pub server_identity: Option<Vec<u8>>,
pub credential_identifier: Vec<u8>,
pub password: Vec<u8>, pub password: Vec<u8>,
pub blind_registration: Vec<u8>, pub blind_registration: Vec<u8>,
pub oprf_key: Vec<u8>, pub oprf_seed: Vec<u8>,
pub masking_nonce: Vec<u8>,
pub envelope_nonce: Vec<u8>, pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>, pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>, pub server_nonce: Vec<u8>,
@@ -64,206 +69,329 @@ pub struct TestVectorParameters {
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/ // of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
static TEST_VECTORS: &[&str] = &[ static TEST_VECTORS: &[&str] = &[
r#" r#"
## OPAQUE-3DH Test Vector 1
### Configuration
~~~
OPRF: 0001 OPRF: 0001
Hash: SHA512 Hash: SHA512
SlowHash: Identity MHF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01 EnvelopeMode: 01
Group: ristretto255 Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64 Nh: 64
Npk: 32 Npk: 32
Nsk: 32 Nsk: 32
Nm: 64
Nx: 64
Nok: 32
~~~
### Input Values
~~~
oprf_seed: 5c4f99877d253be5817b4b03f37b6da680b0d5671d1ec5351fa61c5d82
eab28b9de4c4e170f27e433ba377c71c49aa62ad26391ee1cac17011d8a7e9406657c
8
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65 password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da54 envelope_nonce: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd
20d3eebcd2b 539c4676775
client_private_key: 8bcb0b70dac18de24eef12e737d6b28724d3e37774e0b092f masking_nonce: 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab
9f70b255defaf04 86ff39ed7f
client_public_key: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa1905 server_private_key: 16eb9dc74a3df2033cd738bf2cfb7a3670c569d7749f284b2
df6ea129343b09 b241cb237e7d10f
server_private_key: f3a0829898a89239dce29ccc98ec8b449a34b255ba1e6f944 server_public_key: 18d5035fd0a9c1d6412226df037125901a43f4dff660c0549d
829d18e0d589b0f 402f672bcc0933
server_public_key: 66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb74 server_nonce: f9c5ec75a8cd571370add249e99cb8a8c43f6ef05610ac6e354642b
9c22c762389c3c f4fedbf69
client_info: 68656c6c6f20626f62 client_nonce: 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f
server_info: 6772656574696e677320616c696365 2e9784f69
server_nonce: 98b8081059f60ffed9336f026fd8e124737205ac73f5348ae5bebdb server_keyshare: 6e77d4749eb304c4d74be9457c597546bc22aed699225499910f
49456c70f c913b3e90712
client_nonce: 58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e client_keyshare: f67926bd036c5dc4971816b9376e9f64737f361ef8269c18f69f
36427ca44 1ab555e96d4a
server_keyshare: 5214e3ddc73db786480b79fa2da787f2080b82cbe922c2a9592b server_private_keyshare: f8e3e31543dd6fc86833296726773d51158291ab9afd
44597d9a702e 666bb55dce83474c1101
client_keyshare: a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492 client_private_keyshare: 4230d62ea740b13e178185fc517cf2c313e6908c4cd9
b98265a5e651 fb42154870ff3490c608
server_private_keyshare: c4d002aa4cfcf281657cf36fe562bc60d9133e0e72a7 blind_registration: c62937d17dc9aa213c9038f84fe8c5bf3d953356db01c4d48
4432f685b2b6a4b42a0c acb7cae48e6a504
client_private_keyshare: de2e98f422bf7b99be19f7da7cac62f1599d35a225ec blind_login: b5f458822ea11c900ad776e38e29d7be361f75b4d79b55ad74923299
6340149a0aaff3102003 bf8d6503
blind_registration: 7e5bcbf82a46109ee0d24e9bcab41fc830a6ce8b82fc1e921 oprf_key: 23d431bab39aea4d2737ac391a50076300210730971788e3a6a8c29ad3c
3a043b743b95800 5930e
blind_login: c4d5a15f0d5ffc354e340454ec779f575e4573a3886ab5e57e4da298 ~~~
4bdd5306
oprf_key: 080d0a4d352de92672ab709b1ae1888cb48dfabc2d6ca5b914b335512fe ### Intermediate Values
70508
auth_key: 7bb7f2b831ee30d3e5cc4012c8f721a4d8f9dd494932d53776e043df9bd ~~~
2aa284025b8b006fd8449536446ff50698f46c73fccb53f20d80898f185307d1d39e5 client_public_key: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a33
prk: b0aefddbb21d1b97bc40c07b172e0bf172ec740de4f6274f69d46350a447e9b1 439911e66c3c27
b3fb1e4cefc7d8e393ff58a5c45c74d0615ee0eecde116f3d4e744142eb2ee89 auth_key: 27972f9b1cf2ce524d50a7afa40a2ee6957904e2bef29976bdbda452a84
pseudorandom_pad: 36a828b3b57bf242c4c47ccd9cb84e5b3cefaffe09629c6b94d fcf01023f3ddd8182e64ea5287f99765dd39b83fa89fe189db227212a144134684783
eba0ccec5fa39 randomized_pwd: 750ef06299c2fb102242fd84e59613616338f83e69c09c1dc3f91
envelope: 01cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3 c57ac0642876ccbe785e94aa094262efdc6aed08b3faff7c1bddfa14c434c5a908ad6
eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a55 c5f9d5
3d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e envelope: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c46
906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e 76775455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e
handshake_secret: 2b041dcf12ac9b75dded88f891c25d76746ce9e2c1a43118ac4 43d4c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
aa5721cdc1bc2f0691e6c012a1ea9eb95ab4899b3e7058d37fe9546c46b0511877e40 handshake_secret: 02fb23a668b7138b029c95d21f1e0eec9e10377be933bdbf3e5
f55aac6c 33ea39073d3ce9d1ef16b55a8a8464f3bf6a991cc645d14c1fa3d9d6cfe36c6c0dcc2
handshake_encrypt_key: ceef10f15d869a4cea8174fa98d0d96c7aaf8602d006fe 691d7109
0c5274a40173db76cac820138c5890bb63fb974d1e3e925850cc2464e2c10f0a9a776 server_mac_key: e75ce46beeebd26f22540d7988de9809a69cf34fec6c050750708
9a45e80889b1e e91232297fdbb51e875cd37167d5ce661ebccf0004dbbf96311daf64ddec7faae04c4
server_mac_key: f8fd7fdc349b5ae1339515e05912c89a795f561a117cdc84d8d8b 8bbd89
5f05b05751abfb87fa01c799c5d367244d1e32eab67ff926833c6025c556acffa4af1 client_mac_key: 4bce132daa031fff2a6e5ac29287c4641e3b9dc2560394b8c73f3
f3871a b748f1e51e577b932a960b236981217b33bee220b0bce2696638cfb7791f427ade292
client_mac_key: 92a30cc82c374c06895aa07e81f0cf5f25309a24b595faefcd225 d60f55
1f9219b47e47d17da4fe8b572dedefa350ed365f87b217973e90d0b647a2ccf1d796a ~~~
8970f6
registration_request: ec9027daa5e9a901d641286a7ded51364142936ac7636e1 ### Output Values
42e3f4368b4bd8124
registration_response: 8867d7c8c2c576a6322d49d46078ea32f479aed917c70a ~~~
636d3ada4397ea1c0e66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749 registration_request: 80576bce33c6ce89f9e1a06d8595cd9d09d9aef46b20dad
c22c762389c3c d57a845dc50e7c074
registration_upload: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa19 registration_response: 1a80fdb4f4eb1985587b5b95661d2cff1ef2493cdcdd88
05df6ea129343b0901cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da b5699f39048f0d6c2618d5035fd0a9c1d6412226df037125901a43f4dff660c0549d4
5420d3eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129 02f672bcc0933
932a553d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3cc registration_upload: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a
d8699e906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e 33439911e66c3c2795014d8fc0c710bd763c981c5b9329c95e149c6717af91bad2cec
KE1: e06a32011e1b1704eb686b263e5d132fff4e9f6429cd93b98db107485006792c daf87f2c3c9c11914cb6d44aaee5679e3e61e1b65241fda74902cca908a065495c0b2
58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e36427ca4400096 8b799e71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c467677
8656c6c6f20626f62a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492 5455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e43d4
b98265a5e651 c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
KE2: 66f6b5fa1a4eb6bd7a0c93ed2639a31cba0d02e2df744003641d5a30a4a12364 KE1: 60d71c9f5d2a14568807b869e2c251a8e5f7ad8951cd8386c7e32c0634b26b16
66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749c22c762389c3c01cc7 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f2e9784f69f6792
abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3eebcd2bbd6323c3 6bd036c5dc4971816b9376e9f64737f361ef8269c18f69f1ab555e96d4a
6fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a553d469ffa9999fcb KE2: 78a428204f552d3532bad040c961324edb22c738d98f1dd770d65caba0bd8966
d37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e906027842694b62 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab86ff39ed7fbcbbb
93b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e98b8081059f60ffed9336f0 84a18810b8eb1dc898d9af686f5901a21d0768720b325279fde4931ee52f0d4a0d0d9
26fd8e124737205ac73f5348ae5bebdb49456c70f5214e3ddc73db786480b79fa2da7 cd1cd7c424d4622b1588ba554cd9241352a59ef52bbe85e0f865021404b115ba954f5
87f2080b82cbe922c2a9592b44597d9a702e000f72f38a0945819089c44c86820c51d 540cf2d811a6566a93876cac1239b1f75f39b070250af5a84a819e08b13e9e437a80f
89cc35f77df03d330101bbed3b2f69066112f32529bdda0998657350fc9f8da4cde73 c25cc130f8475dde43efe6d900c664e9bac300298bb0f9c5ec75a8cd571370add249e
408ad931f4c2ea6237ccae4696483388b174f50cf96d439139b0f8680c3b 99cb8a8c43f6ef05610ac6e354642bf4fedbf696e77d4749eb304c4d74be9457c5975
KE3: 9f0e4f73455ca9fe06bb52ad02670b09be5a03db11a73be4422f19963be082b0 46bc22aed699225499910fc913b3e907120485942e3e077f71c1dd2d87053b39f0d31
eb55871022e8d1d87adc3ab50de7c738058eb659866d091648f2fed12e23fd53 bfe5d5f90df0e85ad9ce771e4f4d1ab697a10a02002cd73916051b887da9554465d58
export_key: 66c0b72aa829f13a166fb1a1168f1e26023921f0eed1126def4f81ba0 68811fd8b22b8f457ed5a4b0
4924ad6012e42b63656ec199ba27670d1e7f23dc0a927714edc140134dde5a5d2063d KE3: b4f8aece9fb4f6b7b5ffe1c98747a91f4ec7bf5481fe5719ba4baad668e3fd4e
fc 8aba4fa227bd4c688ed9e17f6c6d28ab5e5617a883207d80979dc4797ca89304
session_key: 951c2bb1b876725fa7d3829db791dddd406a688507b47e24101bd0cc export_key: 045f61f4baa0a945c2e85dfb7a85fe4df8a49e6c31344920e863c286b
5d071760b6fba59e8758a6ea6d7e5f51a715b49a47c50fee9a7c8a0451243c3ee837f c8a17fe25fc16c84836335b4b5ecc9743c5d3a221101ab004aa99ce65026b6953ad6c
d30 c0
session_key: 91187690e5ea0da3110a1dd7d5ffd7c4c3111950c587d9fcf3b9f34b
f73b86dbeafed42a05024fa875a32415c6143d20c39cd732eb0e31db5e60ea3fb2551
cf7
~~~
"#, "#,
r#" r#"
## OPAQUE-3DH Test Vector 2
### Configuration
~~~
OPRF: 0001 OPRF: 0001
Hash: SHA512 Hash: SHA512
SlowHash: Identity MHF: Identity
EnvelopeMode: 02 KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01
Group: ristretto255 Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64 Nh: 64
Npk: 32 Npk: 32
Nsk: 32 Nsk: 32
client_identity: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613b2 Nm: 64
e16a6dde6b05 Nx: 64
server_identity: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8e Nok: 32
de8d6d272c65 ~~~
### Input Values
~~~
client_identity: 616c696365
server_identity: 626f62
oprf_seed: db5c1c16e264b8933d5da56439e7cfed23ab7287b474fe3cdcd58df089
a365a426ea849258d9f4bc13573601f2e727c90ecc19d448cf3145a662e0065f157ba
5
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65 password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb241809 envelope_nonce: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf
0438877888c 2747829b2d2
client_private_key: dc70a99bbabf1ebe98b192e93cedceb9c0164e95b891bd8bc masking_nonce: 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b
81721b83d66b00b 5def228c85
client_public_key: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613 server_private_key: eeb2fcc794f98501b16139771720a0713a2750b9e528adfd3
b2e16a6dde6b05 662ad56a7e19b04
server_private_key: 709687a36c94592ab76579f42ce1be6961f0700496e71df80 server_public_key: 8aa90cb321a38759fc253c444f317782962ca18d33101eab2c
6ebd5320554720d 8cda04405a181f
server_public_key: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c server_nonce: 3fa57f7ef652185f89114109f5a61cc8c9216fdd7398246bb7a0c20
8ede8d6d272c65 e2fbca2d8
client_info: 68656c6c6f20626f62 client_nonce: a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e608
server_info: 6772656574696e677320616c696365 5d8c1187a
server_nonce: ef49d83cef5f1411ea30abb82b08bd85423aadb86e2c19df5930b3c server_keyshare: ae070cdffe5bb4b1c373e71be8e7d8f356ee5de37881533f1039
8498b9f97 7bcd84d35445
client_nonce: 4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d6 client_keyshare: 642e7eecf19b804a62817486663d6c6c239396f709b663a4350c
77165cd3b da67d025687a
server_keyshare: 96a9587e233e67f2397f10fec6355b68102534f1f1b115b4ddf7 server_private_keyshare: 0974010a8528b813f5b33ae0d791df88516c8839c152
485840efcd7c b030697637878b2d8b0a
client_keyshare: 54f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3 client_private_keyshare: 03b52f066898929f4aca48014b2b97365205ce691ee3
e5033632611a 444b0a7cecec3c7efb01
server_private_keyshare: 6650d64df70618a878504ce73dcca27b1af125c67e48 blind_registration: a66ffb41ccf1194a8d7dda900f8b6b0652e4c7fac4610066f
1e7bd49d0b24709b200f e0489a804d3bb05
client_private_keyshare: ebb01c59f99bc955df622548e247f7ef180732909ff3 blind_login: e6f161ac189e6873a19a54efca4baa0719e801e336d929d35ca28b5b
c5f87ff8c7867b8be704 4f60560e
blind_registration: 308f1d3fa1fea402f3c90b04601274050a3c6f467387c2f48 oprf_key: 1e0550d2dbb9ce5dd9bdbb5f808afbb724c573dc03306dcfc7217796465
878823949b0e109 ce607
blind_login: 141e21373228a44b09d4c00da9a6bbaf9a5e54a1687c07f327833643 ~~~
4245510b
oprf_key: b7126967aa0cb69c311b71343843ea041bae30e2bde41b548b8fbd8bced ### Intermediate Values
97604
auth_key: 9f761a5a56a74269b382403aeba47c1d24b9e200e1839efcb616fb280da ~~~
1b6ba7bd71d6455dd3cd979c545608cfbfc1c4e9ba677e1d40848054a00696c4b2589 client_public_key: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7e1
prk: 30bc3f37a757890ac17ce46043f3c5ed30c96fb8743205e77e84dc167d98e114 c9004f20d5db71
6093150ff7b4d002f793bfe717e88d174ed2669abdd9e96af473a7ac82973b0a auth_key: 5142ae6f6bd80686039656fd7a03cdd7e39cc6e869aa637220d4b5fb64f
pseudorandom_pad: b909f990c94b9c5949a2b8d0874d602f846b7981b331fd79978 afee2f284a1581fff95ad3a5261b413c5e5b91115f78a3c35486fa56023c300d1726b
b530cc46c8670 randomized_pwd: cea240b632b9c1d704034920cc3dc3c664ed8cd82cf5c0339af76
envelope: 02f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438 4d6350d2ee9ba1f675ce8df7b6cf8692d1efb158bafa3c2695ac03a2d92346c19810c
877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a36 1a698b
7bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc envelope: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf274782
439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282 9b2d26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c
handshake_secret: ff89f264f8c3974238f4c8d736af7b0a55f2e4edc487cbf3e5c 1517b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
7b4bbf21acd7c1d28354c2c8555fba57c4d4b1fbb4b772bfdf909881f67dd517cc9f4 handshake_secret: 7925c12d7bf3050e62fe5c8caaece3c85737754c5df79bc59a6
f6ebaeac 0fa87929ab1f4a4730f903b87be8b7d89ded8ec97aaec97bc8e7d53a555fd4ad74c4f
handshake_encrypt_key: 3071b181f639062cf70b74d0ffe5ec8fa695da13cd2f00 33b9bc83
e74b8b7ef348ae7a5df9c3a32c9f7aeaad5a28379712cf849b9707e221dce124abfad server_mac_key: 27d6036335c5654132fb08cc81d95b3067ef7fe795f017531231a
d0225a8e8e045 e3fa03cd3ab72f1f5e81473318f9c01f990263d885dfce4b6ac8630fdc8ee8abc6a36
server_mac_key: 7c37b344d189cbbeff80bbb4b78e2703d1a80dc28239923094287 7c2339
62f7ce2a93b11f6e85dd45c02809afe8583d4aad6377e72788773af92eef33c690692 client_mac_key: ebb3693bac6310075a89922c7a40599d14d03d9104b7a331106e8
20ae76 a578a32a4944751f9d3c230a6690a5747137388a86159cf587969d13dadc0a3830218
client_mac_key: 2c4aff12ba7aa911a51f9e5b7a7c01439d854c97e4b8ec842a9db dfbca5
d78345760328fd5a72424e49e25ec8fe1b6d9d42f774516f400948bd5a105d995d000 ~~~
2fc83b
registration_request: 3c8b89966e261a5aaf7aeb6dcdd94c87ce311bf197221b8 ### Output Values
7ef44632d58f18a05
registration_response: caf9243d7ef3e267815632bf79c85a27a23f218a438815 ~~~
2a523f6a310949807beae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8 registration_request: f841cbb85844967568c7405f3831a58c4f5f37ccddb0baa
ede8d6d272c65 4972ea912c960ae66
registration_upload: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc6 registration_response: 0256257cc6e2b04444edc076b9ad44d8b31593e050bea8
13b2e16a6dde6b0502f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418 06485707a818f8a93f8aa90cb321a38759fc253c444f317782962ca18d33101eab2c8
090438877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4 cda04405a181f
f90a367bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d registration_upload: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7
2566cc439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282 e1c9004f20d5db71146e42585d25fa19913876edce4b5ee99b638eb37b1d8a8a76607
KE1: 8261a1efd78bea73faf256a23c200d729259886530fa43b875c1ca124b09bc7e efaa12299e828641ba4fbf1c46fc2c3776e0a0c9791f88a15b9ddfb5495d63ce92d8f
4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d677165cd3b00096 58823bd0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf2747829b2d
8656c6c6f20626f6254f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3 26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c1517
e5033632611a b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
KE2: fa1f33a43a03123ebe35345ef93aa23b57ea8bfbee7022b05a179d60768ba02e KE1: 14cc586d982b6db9846c78e0b3c543591e95fbf2fc877fa0e5eff89897dd3050
eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8ede8d6d272c6502f41 a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e6085d8c1187a642e7
e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438877888c6579500b eecf19b804a62817486663d6c6c239396f709b663a4350cda67d025687a
73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a367bdb425fa1dd4c4 KE2: 8ab71c17547f376ae787741c367142790087090cdde6327dabb2581197bffa59
9e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc439c83030464eff 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b5def228c85dd973
ecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282ef49d83cef5f1411ea30abb a1ac59244f674da4a1c057961886661bd29e0c1346f0fcf75bf1c78d4781815c2f9f6
82b08bd85423aadb86e2c19df5930b3c8498b9f9796a9587e233e67f2397f10fec635 f2f9fe0e370b256f6e82fb2e14c7ffc374d42caf26abf13dca169a6faafd5cff8baa9
5b68102534f1f1b115b4ddf7485840efcd7c000f7ebe71d4ab326006a3aeca802435d 717090bc1fc5e1ba56acb93492d1a8b789f33ff29b6004c4be9a755ff590d7d00d6e8
c995a38ac6662221f974cb920992d82b8ef8d147c77e29b628a82b5ccb01ea2f7bb60 893e7e54e639aebf69d18f2182a9bb0f2e1c27c81ba73fa57f7ef652185f89114109f
af94cd1860e1bd974a11a1c9bd827789f663c4758eb71058c244138de0c2 5a61cc8c9216fdd7398246bb7a0c20e2fbca2d8ae070cdffe5bb4b1c373e71be8e7d8
KE3: d81f93397cdba85a43993d4d9afbdc67f147adfa2b223213b19692cb820eef48 f356ee5de37881533f10397bcd84d35445401c619d464ab3a134c71da4d9874f2f736
5073eda4c8236b2f47702404ad60d9a875d189626fc7b7cc861825385470ae54 189b8bbb659c28f8db25a58b9f089272132e3091efa87d6b07d10321ba464047be011
export_key: 03192555940b5b42e64e6200bf55cc701f1bace3d402a2f8d83977843 3e91514aba299fd1553bcebb
51a1e3fa1f07a471b783b208acb1d92be47903b6fa3a0df9f4d4b7956ee4f431e2950 KE3: c4a0d5b8148f3ac0f8611b38de38bda085d4eb00d561397ae59676f36dc705be
1c939e7bfdd7301103af5eb164bdfb70298aab889bd2ac797e419a82bfb442e6
export_key: 6b50ae4dba956930c0465b4a26c3cee58e05afcab623c1c254ae34acc
38babf954530a53475672ff46a1cf7fd53ef9e808f85b08793d021bb5c6d2a1bb9204
f6 f6
session_key: 58a7fa98bf3b7b52da21406abfb11d98734354edd47d7b32462c0513 session_key: c9bc2b7e2237f6fbeccd92dc6ec6d51faeb886492f8d23f21743a967
f0617c89824ea6031d4147a86fc9f6c6837ce640c12fb937d764f296d1a9421ad1b2a 597025215df02a4afb75349acbafeef9dfd4f19e6d38da8bea4912f7b691b70849b0d
5d5 78e
~~~
"#, "#,
]; ];
static FAKE_TEST_VECTORS: &[&str] = &[r#"
### OPAQUE-3DH Fake Test Vector 1
#### Configuration
~~~
OPRF: 0001
Hash: SHA512
MHF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
~~~
#### Input Values
~~~
client_identity: 616c696365
server_identity: 626f62
oprf_seed: d3cb00535339fe4063c7ba5506a990c243a2b5c77b06848a0be9a0568c
252fb0d7425382babd267deeed669e56d1d5654c036211f49b42f4489f96f37100779
f
credential_identifier: 31323334
masking_nonce: 3058799f42516228746821dc8c8530d0e8273ebde81941591d69ca
5aea773090
client_private_key: 83c9bcc31a9da0ffa4489900d3d1f85bb65c27f26e9ae4e3b
66f6e02e098c503
client_public_key: 56717b74a5e1770edb14c65f22cee0487046bd96e122ba97da
ffed06c4bf4052
server_private_key: 8d3a9355f9757e7071b3f836e3fb1461a6436e92971625b17
cd7e580dd27c009
server_public_key: 7a464761cb19c8b6e832fdfcfd18779b0edc246fe808f5de6c
e7bdb54df41b67
server_nonce: 4e2a8098173efa2968036f1762f2e5df41ab976fb1bfb91dae29950
f8526de4c
server_keyshare: 0e247410004d83d7cbe3af89c62ff03f942127aec4b0084c9eb5
88e74ce6dd06
server_private_keyshare: 326345820acc8aacf4948fce775a1fd265e4e93fd579
cec8177d6389ee379b0a
masking_key: e968bfe56ad934c3e1088115bcbf1af8b405fd0de94cdf301f9192cc
2781de00617e568b14b7235cc1189265811ea354031ea39b62e31a104f181c01d3dae
4b8
KE1: 480b6c0066c9320c50dce20f8b6b63e4ded7681defd9da3f70ecdc15770f9e68
05603c1acb64ea417c0dabaab858a5f9da046d4a0cdbf092034c00451ccdc6e1ee835
5c91d5ed7aa5ea75b8a730ba8dc45f6b41ae9713e6aa7126211346e8754
~~~
#### Output Values
~~~
KE2: 04013bca360b4b9ba95b2f494927375e0f234dac23053822e466a9738f781522
3058799f42516228746821dc8c8530d0e8273ebde81941591d69ca5aea77309078577
13efdc95f69166737cd7a80ead60e1a1f805c1da9cccbc0d29120f34be291518798c7
00793f232374e66182495b76b388d9e11f479580cc2297da02fecee88a99cea6bc411
b9467e8bfa9a4006aba7f21b74b4ce3bccd686785878b0ec9b3fc4200228014d5d073
69d42d1d1b1669ecd2ad8905734ca0a641d8f16667ca4e2a8098173efa2968036f176
2f2e5df41ab976fb1bfb91dae29950f8526de4c0e247410004d83d7cbe3af89c62ff0
3f942127aec4b0084c9eb588e74ce6dd06fb1a0fd81da51bc1d87c740c186d881ed79
71fdba5ad1d5cfc94ffe6a731241c78ea7ea5dae503e987edc37355b7348883dc65cd
b57aec04e64593007f98a405
~~~
"#];
macro_rules! parse { macro_rules! parse {
( $v:ident, $s:expr ) => { ( $v:ident, $s:expr ) => {
parse_default!($v, $s, vec![])
};
}
macro_rules! parse_default {
( $v:ident, $s:expr, $d:expr ) => {
match decode(&$v, $s) { match decode(&$v, $s) {
Some(x) => x, Some(x) => x,
None => vec![], None => $d,
} }
}; };
} }
@@ -280,19 +408,23 @@ fn rfc_to_json(input: &str) -> String {
let mut json = vec![]; let mut json = vec![];
for line in input.lines() { for line in input.lines() {
// If line contains colon, then // If line contains colon, then
if line.contains(":") { if line.contains(':') {
if json.len() > 0 { if !json.is_empty() {
// Adding closing quote for previous line, comma, and newline // Adding closing quote for previous line, comma, and newline
json.push("\",\n".to_string()); json.push("\",\n".to_string());
} }
let mut iter = line.split(":"); let mut iter = line.split(':');
let key = iter.next().unwrap().split_whitespace().next().unwrap(); let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap(); let val = iter.next().unwrap().split_whitespace().next().unwrap();
json.push(format!(" \"{}\": \"{}", key, val)); json.push(format!(" \"{}\": \"{}", key, val));
} else { } else {
let s = line.trim().to_string(); let s = line.trim().to_string();
if s.contains("~") || s.contains("#") {
// Ignore comment lines
continue;
}
if s.len() > 0 { if s.len() > 0 {
json.push(s); json.push(s);
} }
@@ -310,24 +442,32 @@ fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
fn populate_test_vectors(values: &Value) -> TestVectorParameters { fn populate_test_vectors(values: &Value) -> TestVectorParameters {
TestVectorParameters { TestVectorParameters {
dummy_private_key: parse_default!(
values,
"client_private_key",
vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()]
),
dummy_masking_key: parse_default!(values, "masking_key", vec![0u8; 64]),
context: parse!(values, "Context"),
envelope_mode: match values["EnvelopeMode"].as_str() { envelope_mode: match values["EnvelopeMode"].as_str() {
Some("01") => EnvelopeMode::Base, Some("01") => EnvelopeMode::Base,
Some("02") => EnvelopeMode::CustomIdentifier, Some("02") => EnvelopeMode::CustomIdentifier,
_ => panic!("Could not match envelope mode"), _ => panic!("Could not match envelope mode"),
}, },
client_public_key: parse!(values, "client_public_key"), client_private_key: decode(values, "client_private_key"),
client_private_key: parse!(values, "client_private_key"),
client_keyshare: parse!(values, "client_keyshare"), client_keyshare: parse!(values, "client_keyshare"),
client_private_keyshare: parse!(values, "client_private_keyshare"), client_private_keyshare: parse!(values, "client_private_keyshare"),
server_public_key: parse!(values, "server_public_key"), server_public_key: parse!(values, "server_public_key"),
server_private_key: parse!(values, "server_private_key"), server_private_key: parse!(values, "server_private_key"),
server_keyshare: parse!(values, "server_keyshare"), server_keyshare: parse!(values, "server_keyshare"),
server_private_keyshare: parse!(values, "server_private_keyshare"), server_private_keyshare: parse!(values, "server_private_keyshare"),
client_identity: parse!(values, "client_identity"), client_identity: decode(values, "client_identity"),
server_identity: parse!(values, "server_identity"), server_identity: decode(values, "server_identity"),
credential_identifier: parse!(values, "credential_identifier"),
password: parse!(values, "password"), password: parse!(values, "password"),
blind_registration: parse!(values, "blind_registration"), blind_registration: parse!(values, "blind_registration"),
oprf_key: parse!(values, "oprf_key"), oprf_seed: parse!(values, "oprf_seed"),
masking_nonce: parse!(values, "masking_nonce"),
envelope_nonce: parse!(values, "envelope_nonce"), envelope_nonce: parse!(values, "envelope_nonce"),
client_nonce: parse!(values, "client_nonce"), client_nonce: parse!(values, "client_nonce"),
server_nonce: parse!(values, "server_nonce"), server_nonce: parse!(values, "server_nonce"),
@@ -346,21 +486,25 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
} }
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> { fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key.clone()); let password_file = ServerRegistration::<Ristretto255Sha512NoSlowHash>::finish(
let server_registration_start_result = RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap(),
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start( );
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::try_from(&parameters.server_public_key[..]).unwrap(),
)?;
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
Ok(password_file.serialize()) Ok(password_file.serialize())
} }
fn parse_identifiers(
client_identity: Option<Vec<u8>>,
server_identity: Option<Vec<u8>>,
) -> Option<Identifiers> {
match (client_identity, server_identity) {
(None, None) => None,
(Some(x), None) => Some(Identifiers::ClientIdentifier(x)),
(None, Some(y)) => Some(Identifiers::ServerIdentifier(y)),
(Some(x), Some(y)) => Some(Identifiers::ClientAndServerIdentifiers(x, y)),
}
}
#[test] #[test]
fn test_registration_request() -> Result<(), ProtocolError> { fn test_registration_request() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) { for parameters in rfc_to_params!(TEST_VECTORS) {
@@ -381,12 +525,19 @@ fn test_registration_request() -> Result<(), ProtocolError> {
#[test] #[test]
fn test_registration_response() -> Result<(), ProtocolError> { fn test_registration_response() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) { for parameters in rfc_to_params!(TEST_VECTORS) {
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key); let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let server_registration_start_result = let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start( ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng, &server_setup,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(), RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::try_from(&parameters.server_public_key[..]).unwrap(), &parameters.credential_identifier,
)?; )?;
assert_eq!( assert_eq!(
hex::encode(parameters.registration_response), hex::encode(parameters.registration_response),
@@ -406,19 +557,13 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
&parameters.password, &parameters.password,
)?; )?;
let sk_u_and_nonce: Vec<u8> = let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce);
[parameters.client_private_key, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
let result = client_registration_start_result.state.finish( let result = client_registration_start_result.state.finish(
&mut finish_registration_rng, &mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(), RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier { match parse_identifiers(parameters.client_identity, parameters.server_identity) {
ClientRegistrationFinishParameters::WithIdentifiers( None => ClientRegistrationFinishParameters::Default,
parameters.client_identity, Some(ids) => ClientRegistrationFinishParameters::WithIdentifiers(ids),
parameters.server_identity,
)
} else {
ClientRegistrationFinishParameters::default()
}, },
)?; )?;
@@ -448,7 +593,6 @@ fn test_ke1() -> Result<(), ProtocolError> {
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start( let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng, &mut client_login_start_rng,
&parameters.password, &parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?; )?;
assert_eq!( assert_eq!(
hex::encode(&parameters.KE1), hex::encode(&parameters.KE1),
@@ -461,30 +605,42 @@ fn test_ke1() -> Result<(), ProtocolError> {
#[test] #[test]
fn test_ke2() -> Result<(), ProtocolError> { fn test_ke2() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) { for parameters in rfc_to_params!(TEST_VECTORS) {
let password_file_bytes = get_password_file_bytes(&parameters)?; let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let mut server_private_keyshare_and_nonce_rng = let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat()); &get_password_file_bytes(&parameters)?[..],
)?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
parameters.masking_nonce,
parameters.server_private_keyshare,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start( let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng, &mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(), &server_setup,
&Key::try_from(&parameters.server_private_key[..]).unwrap(), Some(record),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..]) CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(), .unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier { &parameters.credential_identifier,
ServerLoginStartParameters::WithInfoAndIdentifiers( match parse_identifiers(parameters.client_identity, parameters.server_identity) {
parameters.server_info.to_vec(), None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
parameters.client_identity, Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.server_identity, parameters.context.to_vec(),
) ids,
} else { ),
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
}, },
)?; )?;
assert_eq!(
hex::encode(&parameters.client_info),
hex::encode(server_login_start_result.plain_info),
);
assert_eq!( assert_eq!(
hex::encode(&parameters.KE2), hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize()) hex::encode(server_login_start_result.message.serialize())
@@ -506,25 +662,18 @@ fn test_ke3() -> Result<(), ProtocolError> {
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start( let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng, &mut client_login_start_rng,
&parameters.password, &parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?; )?;
let client_login_finish_result = client_login_start_result.state.finish( let client_login_finish_result = client_login_start_result.state.finish(
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?, CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?,
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier { match parse_identifiers(parameters.client_identity, parameters.server_identity) {
ClientLoginFinishParameters::WithIdentifiers( None => ClientLoginFinishParameters::WithContext(parameters.context),
parameters.client_identity, Some(ids) => {
parameters.server_identity, ClientLoginFinishParameters::WithContextAndIdentifiers(parameters.context, ids)
) }
} else {
ClientLoginFinishParameters::default()
}, },
)?; )?;
assert_eq!(
hex::encode(&parameters.server_info),
hex::encode(&client_login_finish_result.confidential_info)
);
assert_eq!( assert_eq!(
hex::encode(&parameters.session_key), hex::encode(&parameters.session_key),
hex::encode(&client_login_finish_result.session_key) hex::encode(&client_login_finish_result.session_key)
@@ -544,24 +693,40 @@ fn test_ke3() -> Result<(), ProtocolError> {
#[test] #[test]
fn test_server_login_finish() -> Result<(), ProtocolError> { fn test_server_login_finish() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) { for parameters in rfc_to_params!(TEST_VECTORS) {
let password_file_bytes = get_password_file_bytes(&parameters)?; let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let mut server_private_keyshare_and_nonce_rng = let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat()); &get_password_file_bytes(&parameters)?[..],
)?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
parameters.masking_nonce,
parameters.server_private_keyshare,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start( let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng, &mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(), &server_setup,
&Key::try_from(&parameters.server_private_key[..]).unwrap(), Some(record),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..]) CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(), .unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier { &parameters.credential_identifier,
ServerLoginStartParameters::WithInfoAndIdentifiers( match parse_identifiers(parameters.client_identity, parameters.server_identity) {
parameters.server_info.to_vec(), None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
parameters.client_identity, Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.server_identity, parameters.context.to_vec(),
) ids,
} else { ),
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
}, },
)?; )?;
@@ -571,7 +736,51 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
assert_eq!( assert_eq!(
hex::encode(parameters.session_key), hex::encode(parameters.session_key),
hex::encode(server_login_result.session_key) hex::encode(&server_login_result.session_key)
);
}
Ok(())
}
#[test]
fn test_fake_vectors() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(FAKE_TEST_VECTORS) {
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
parameters.dummy_masking_key,
parameters.masking_nonce,
parameters.server_private_keyshare,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
None,
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
&parameters.credential_identifier,
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.context.to_vec(),
ids,
),
},
)?;
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize())
); );
} }
Ok(()) Ok(())
+1 -1
View File
@@ -21,7 +21,7 @@ struct VOPRFTestVectorParameters {
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md // Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
// in base mode // in base mode
static OPRF_RISTRETTO255_SHA512: &'static [&str] = &[ static OPRF_RISTRETTO255_SHA512: &[&str] = &[
r#" r#"
{ {
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a", "sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",