36 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
Kevin LewiandGitHub ac08801f18 Updating to version 0.4.0 and simplifying transcript computation logic in TripleDH (#148) 2021-02-26 15:18:10 -08:00
Konstantinos ChalkiasandGitHub a49e089e54 Merge pull request #146 from novifinancial/kostas-duplicate-code-v2
Helper generate_nonce to avoid duplicated code + default values for Scrypt
2021-02-21 22:33:12 -08:00
Kostas Chalkias 37f98b3742 Helper function generate_nonce to avoid duplicates + default values for Scrypt 2021-02-21 20:29:26 -08:00
Kevin LewiandGitHub fa0fb48654 Fixing test vector bug with envelope ciphertext serialization and updating VOPRF (#145) 2021-02-21 18:44:36 -08:00
Kevin LewiandGitHub 4da6bbce0c Fixing key sizes and removing serialization (#144) 2021-02-20 03:51:04 -08:00
Kevin LewiandGitHub a457d82793 Updating transcript computation for 3DH (#143) 2021-02-19 12:02:44 -08:00
Kevin LewiandGitHub 782eeb7090 Converting to_bytes and try_from to serialize() and deserialize() (#138) 2021-02-17 02:47:00 -08:00
Kevin LewiandGitHub bb7d17a102 Fixing deny.toml (#142) 2021-02-17 02:28:49 -08:00
Kevin LewiandGitHub 7f84984f61 Making message type parameters set to CipherSuite (#140) 2021-02-17 02:13:03 -08:00
29 changed files with 3109 additions and 3328 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
+28 -2
View File
@@ -1,12 +1,38 @@
# Changelog # Changelog
## 0.3.1 (February 11, 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
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
* Renamed to_bytes() and try_from() to serialize() and deserialize() for
top-level structs
* Conformed all message type parameters to be parameterized in the
Ciphersuite object
## 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
+14 -11
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "opaque-ke" name = "opaque-ke"
version = "0.3.1" 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.3" serde = { version = "1", features = ["derive"], optional = true }
scrypt = { version = "0.5.0", 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.3.1" 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-02](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-02.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
------------ ------------
+3 -3
View File
@@ -11,7 +11,7 @@ use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::arr; use generic_array::arr;
use opaque_ke::{ use opaque_ke::{
group::Group, group::Group,
oprf::{blind_shim, evaluate_shim, unblind_and_finalize_shim}, oprf::{blind_shim, evaluate_shim, finalize_shim},
}; };
use rand::{prelude::ThreadRng, thread_rng}; use rand::{prelude::ThreadRng, thread_rng};
use sha2::Sha512; use sha2::Sha512;
@@ -57,9 +57,9 @@ fn oprf3(c: &mut Criterion) {
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap(); let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt); let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
c.bench_function("unblind_and_finalize with Ristretto", move |b| { c.bench_function("finalize with Ristretto", move |b| {
b.iter(|| { b.iter(|| {
let _res = unblind_and_finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap(); let _res = finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
}) })
}); });
} }
+2 -2
View File
@@ -34,7 +34,7 @@ targets = [
# The path where the advisory database is cloned/fetched into # The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db" db-path = "~/.cargo/advisory-db"
# The url of the advisory database to use # The url of the advisory database to use
db-url = "https://github.com/rustsec/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities # The lint level for security vulnerabilities
vulnerability = "deny" vulnerability = "deny"
# The lint level for unmaintained crates # The lint level for unmaintained crates
@@ -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 = [
+29 -26
View File
@@ -28,16 +28,15 @@ use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::Editor; use rustyline::Editor;
use std::convert::TryFrom;
use std::process::exit; 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
@@ -82,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 {
@@ -92,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();
@@ -122,41 +120,39 @@ 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,
password_file: password_file.to_bytes(), password_file: password_file.serialize(),
} }
} }
// 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
let password_file = ServerRegistration::<Default>::try_from(&locker.password_file[..]).unwrap(); let password_file =
ServerRegistration::<Default>::deserialize(&locker.password_file[..]).unwrap();
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);
} }
+26 -28
View File
@@ -23,15 +23,13 @@
use rustyline::error::ReadlineError; use rustyline::error::ReadlineError;
use rustyline::Editor; use rustyline::Editor;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom;
use std::process::exit; 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
@@ -47,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;
@@ -57,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();
@@ -80,37 +78,34 @@ 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.to_bytes()
} }
// 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
let password_file = ServerRegistration::<Default>::try_from(password_file_bytes).unwrap(); let password_file = ServerRegistration::<Default>::deserialize(password_file_bytes).unwrap();
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();
@@ -142,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();
@@ -165,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)
}
} }
+193 -207
View File
@@ -4,86 +4,80 @@
// 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,
serialization::{serialize, tokenize}, keypair::{KeyPair, PrivateKey, PublicKey},
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};
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;
// 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[..],
&serialize(&self.ciphertext, 2)[..],
]
.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 bytes = &input[1..];
if bytes.len() < NONCE_LEN {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let nonce = &bytes[..NONCE_LEN];
let (ciphertext, remainder) = tokenize(&bytes[NONCE_LEN..], 2)?;
Ok((
Self {
mode,
nonce: nonce.to_vec(),
ciphertext,
},
remainder,
))
}
}
/// 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
/// ///
@@ -94,136 +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> {
/// The additional number of bytes added to the plaintext
pub(crate) fn additional_size() -> usize {
NONCE_LEN + <D as Digest>::OutputSize::to_usize()
}
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 plaintext = serialize(&client_s_sk, 2); (
let aad = construct_aad(server_s_pk, &optional_ids); Self,
Self::seal_raw(rng, key, &plaintext, &aad, mode_from_ids(&optional_ids)) 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),
@@ -234,26 +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)?;
let (client_s_sk, remainder) = tokenize(&opened.plaintext, 2)
.map_err(|_| InternalPakeError::UnexpectedEnvelopeContentsError)?;
if !remainder.is_empty() {
// Should not have anything else in plaintext
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
}
Ok(OpenedEnvelope { Ok(OpenedEnvelope {
client_s_sk, client_static_keypair,
export_key: opened.export_key, export_key: opened.export_key,
id_u,
id_s,
}) })
} }
@@ -263,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();
[serialize(server_s_pk, 2), 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,
+32 -15
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},
@@ -25,7 +26,7 @@ use zeroize::Zeroize;
/// A prime-order subgroup of a base field (EC, prime-order field ...). This /// A prime-order subgroup of a base field (EC, prime-order field ...). This
/// subgroup is noted additively — as in the draft RFC — in this trait. /// subgroup is noted additively — as in the draft RFC — in this trait.
pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> { pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
/// The type of base field scalars /// The type of base field scalars
type Scalar: Zeroize + Clone; type Scalar: Zeroize + Clone;
/// The byte length necessary to represent scalars /// The byte length necessary to represent scalars
@@ -35,7 +36,7 @@ pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
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: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
/// 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)>;
}
+181 -207
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 keys"; 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_SERVER_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_KEY: &[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,19 +50,13 @@ 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);
let client_nonce: GenericArray<u8, NonceLen> = { let client_nonce = generate_nonce::<R>(rng);
let mut client_nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce_bytes);
GenericArray::clone_from_slice(&client_nonce_bytes)
};
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(),
}; };
@@ -82,20 +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: GenericArray<u8, NonceLen> = { let server_nonce = generate_nonce::<R>(rng);
let mut server_nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce_bytes);
GenericArray::clone_from_slice(&server_nonce_bytes)
};
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>( let mut transcript_hasher = D::new()
.chain(STR_RFC)
.chain(&serialize(&context, 2))
.chain(&id_u)
.chain(&serialized_credential_request[..])
.chain(&id_s)
.chain(&l2_bytes[..])
.chain(&server_nonce[..])
.chain(&server_e_kp.public().to_arr());
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(),
@@ -104,56 +103,25 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
pk3: client_s_pk, pk3: client_s_pk,
sk3: server_e_kp.private().clone(), sk3: server_e_kp.private().clone(),
}, },
&ke1_message.client_nonce, &transcript_hasher.clone().finalize(),
&server_nonce,
&id_u,
&id_s,
)?; )?;
// 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();
let transcript2: Vec<u8> = [
&serialized_credential_request[..],
&l2_bytes[..],
&server_nonce[..],
&server_e_kp.public().to_arr(),
&serialize(&ciphertext, 2),
]
.concat();
let mut hasher = D::new();
hasher.update(&transcript2);
let hashed_transcript_without_mac = hasher.finalize_reset();
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(&hashed_transcript_without_mac); mac_hasher.update(&transcript_hasher.clone().finalize());
let mac = mac_hasher.finalize().into_bytes(); let mac = mac_hasher.finalize().into_bytes();
hasher.update(&transcript2); transcript_hasher.update(&mac);
hasher.update(&mac);
let hashed_transcript = hasher.finalize();
Ok(( Ok((
ke1_message.info,
Ke2State { Ke2State {
km3, km3,
hashed_transcript, hashed_transcript: transcript_hasher.finalize(),
session_key, session_key,
}, },
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,
}, },
)) ))
@@ -165,12 +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>,
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>( ) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
let mut transcript_hasher = D::new()
.chain(STR_RFC)
.chain(&serialize(&context, 2))
.chain(&id_u)
.chain(&serialized_credential_request)
.chain(&id_s)
.chain(&l2_component[..])
.chain(&ke2_message.to_bytes_without_info_or_mac());
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(),
@@ -179,54 +157,26 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
pk3: ke2_message.server_e_pk.clone(), pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk, sk3: client_s_sk,
}, },
&ke1_state.client_nonce, &transcript_hasher.clone().finalize(),
&ke2_message.server_nonce,
&id_u,
&id_s,
)?; )?;
let transcript: Vec<u8> = [
&serialized_credential_request,
&l2_component[..],
&ke2_message.to_bytes_without_mac(),
]
.concat();
let mut hasher = D::new();
hasher.update(&transcript);
let hashed_transcript_without_mac = hasher.finalize_reset();
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(&hashed_transcript_without_mac); 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,
)); ));
} }
hasher.update(transcript); transcript_hasher.update(ke2_message.mac.to_vec());
hasher.update(ke2_message.mac.to_vec());
let hashed_transcript = hasher.finalize();
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(&hashed_transcript); 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(),
@@ -240,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,
)); ));
@@ -258,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],
), ),
@@ -288,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[..],
@@ -349,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")?;
@@ -370,40 +346,41 @@ 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_mac(), &self.mac[..]].concat() [&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
} }
} }
impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> { impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
fn to_bytes_without_mac(&self) -> Vec<u8> { fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
[ [&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
&self.server_nonce[..],
&self.server_e_pk.to_arr(),
&serialize(&self.e_info, 2),
]
.concat()
} }
} }
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),
}) })
} }
} }
@@ -411,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>,
} }
@@ -439,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),
}) })
} }
} }
@@ -457,10 +434,7 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke3Message<HashLen> {
// with some auxiliary metadata, to produce the session key and two MAC keys // with some auxiliary metadata, to produce the session key and two MAC keys
fn derive_3dh_keys<D: Hash, G: Group>( fn derive_3dh_keys<D: Hash, G: Group>(
dh: TripleDHComponents, dh: TripleDHComponents,
client_nonce: &GenericArray<u8, NonceLen>, hashed_derivation_transcript: &[u8],
server_nonce: &GenericArray<u8, NonceLen>,
id_u: &[u8],
id_s: &[u8],
) -> Result<TripleDHDerivationResult<D>, ProtocolError> { ) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
let ikm: Vec<u8> = [ let ikm: Vec<u8> = [
&KeyPair::<G>::diffie_hellman(dh.pk1, dh.sk1)?[..], &KeyPair::<G>::diffie_hellman(dh.pk1, dh.sk1)?[..],
@@ -469,34 +443,27 @@ fn derive_3dh_keys<D: Hash, G: Group>(
] ]
.concat(); .concat();
let info: Vec<u8> = [
STR_3DH,
&serialize(&client_nonce, 2),
&serialize(&server_nonce, 2),
&serialize(id_u, 2),
&serialize(id_s, 2),
]
.concat();
let extracted_ikm = Hkdf::<D>::new(None, &ikm); let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>(&extracted_ikm, &STR_HANDSHAKE_SECRET, &info)?; let handshake_secret = derive_secrets::<D>(
let session_key = derive_secrets::<D>(&extracted_ikm, &STR_SESSION_KEY, &info)?; &extracted_ikm,
STR_HANDSHAKE_SECRET,
hashed_derivation_transcript,
)?;
let session_key = derive_secrets::<D>(
&extracted_ikm,
STR_SESSION_KEY,
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_SERVER_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(),
)?; )?;
@@ -504,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),
)) ))
} }
@@ -528,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)?;
@@ -545,13 +513,19 @@ fn hkdf_expand_label_extracted<D: Hash>(
fn derive_secrets<D: Hash>( fn derive_secrets<D: Hash>(
hkdf: &Hkdf<D>, hkdf: &Hkdf<D>,
label: &[u8], label: &[u8],
transcript: &[u8], hashed_derivation_transcript: &[u8],
) -> Result<Vec<u8>, ProtocolError> { ) -> Result<Vec<u8>, ProtocolError> {
let hashed_transcript = D::digest(transcript);
hkdf_expand_label_extracted::<D>( hkdf_expand_label_extracted::<D>(
hkdf, hkdf,
label, label,
&hashed_transcript, hashed_derivation_transcript,
<D as Digest>::OutputSize::to_usize(), <D as Digest>::OutputSize::to_usize(),
) )
} }
// Generate a random nonce up to NonceLen::to_usize() bytes.
fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
let mut nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut nonce_bytes);
GenericArray::clone_from_slice(&nonce_bytes)
}
+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]
+182 -145
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-02](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-02.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,
@@ -169,14 +170,14 @@
//! a [ServerRegistration] from the second step. //! a [ServerRegistration] from the second step.
//! The server runs [ServerRegistration::finish] to produce a finalized [ServerRegistration]. //! The server runs [ServerRegistration::finish] to produce a finalized [ServerRegistration].
//! At this point, the client can be considered as successfully registered, and the server can invoke //! At this point, the client can be considered as successfully registered, and the server can invoke
//! [ServerRegistration::to_bytes] to store the password file for use during the login protocol. //! [ServerRegistration::serialize] to store the password file for use during the login protocol.
//! ``` //! ```
//! # 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,28 +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)?.to_bytes(); //! # 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};
//! use std::convert::TryFrom; //! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! let password_file = ServerRegistration::<Default>::try_from(&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.
@@ -296,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;
@@ -314,22 +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)?.to_bytes(); //! # 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 std::convert::TryFrom;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::try_from( //! # 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(),
@@ -343,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;
@@ -361,22 +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)?.to_bytes(); //! # 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 std::convert::TryFrom;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::try_from( //! # 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(),
@@ -415,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;
@@ -437,26 +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)?.to_bytes(); //! 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(),
//! # )?; //! # )?;
//! # use std::convert::TryFrom;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::try_from( //! # 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(
@@ -464,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>(())
//! ``` //! ```
@@ -496,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;
@@ -514,27 +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)?.to_bytes(); //! # 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 std::convert::TryFrom;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::try_from( //! # 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(
@@ -551,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
@@ -561,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;
@@ -579,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>(())
@@ -596,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;
@@ -614,27 +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)?.to_bytes(); //! # 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};
//! # use std::convert::TryFrom; //! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! # let password_file = ServerRegistration::<Default>::try_from(&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>(())
@@ -644,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;
@@ -662,49 +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)?.to_bytes(); //! # 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 std::convert::TryFrom;
//! # let password_file = //! # let password_file =
//! # ServerRegistration::<Default>::try_from( //! # 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))]
@@ -719,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;
@@ -742,8 +778,6 @@ mod oprf;
pub mod slow_hash; pub mod slow_hash;
mod serialization;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@@ -751,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)
+210 -184
View File
@@ -13,45 +13,42 @@ use crate::{
PakeError, ProtocolError, PakeError, ProtocolError,
}, },
group::Group, group::Group,
hash::Hash, key_exchange::traits::{FromBytes, KeyExchange, ToBytes},
key_exchange::traits::{KeyExchange, ToBytes}, keypair::{KeyPair, PublicKey, SizedBytesExt},
keypair::{Key, KeyPair, SizedBytesExt}, opaque::ServerSetup,
serialization::{serialize, tokenize},
}; };
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};
use std::marker::PhantomData;
// Messages // Messages
// ========= // =========
/// The message sent by the client to the server, to initiate registration /// The message sent by the client to the server, to initiate registration
pub struct RegistrationRequest<Grp> { pub struct RegistrationRequest<CS: CipherSuite> {
/// blinded password information /// blinded password information
pub(crate) alpha: Grp, pub(crate) alpha: CS::Group,
} }
impl<Grp: Group> TryFrom<&[u8]> for RegistrationRequest<Grp> { impl<CS: CipherSuite> RegistrationRequest<CS> {
type Error = ProtocolError; /// Only used for testing purposes
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> { #[cfg(test)]
let elem_len = Grp::ElemLen::to_usize(); pub fn get_alpha_for_testing(&self) -> CS::Group {
let checked_slice = check_slice_size(first_message_bytes, elem_len, "first_message_bytes")?; self.alpha
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[checked_slice.len() - elem_len..]);
let alpha = Grp::from_element_slice(arr)?;
Ok(Self { alpha })
} }
} }
impl<Grp: Group> RegistrationRequest<Grp> { // Cannot be derived because it would require for CS to be Clone.
/// Byte representation for the registration request impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
pub fn to_bytes(&self) -> Vec<u8> { fn clone(&self) -> Self {
self.alpha.to_arr().to_vec() Self { alpha: self.alpha }
} }
}
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [alpha], [CS::Group]);
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
self.alpha.to_arr().to_vec() self.alpha.to_arr().to_vec()
@@ -59,143 +56,150 @@ impl<Grp: Group> RegistrationRequest<Grp> {
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let checked_slice = let elem_len = <CS::Group as Group>::ElemLen::to_usize();
check_slice_size(&input, Grp::ElemLen::to_usize(), "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 = Grp::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<Grp> { pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output /// The server's oprf output
pub(crate) beta: Grp, 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,
} }
impl<Grp> TryFrom<&[u8]> for RegistrationResponse<Grp> // Cannot be derived because it would require for CS to be Clone.
where impl<CS: CipherSuite> Clone for RegistrationResponse<CS> {
Grp: Group, fn clone(&self) -> Self {
{ Self {
type Error = ProtocolError; beta: self.beta,
server_s_pk: self.server_s_pk.clone(),
}
}
}
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { impl_debug_eq_hash_for!(
let elem_len = Grp::ElemLen::to_usize(); struct RegistrationResponse<CS: CipherSuite>,
let checked_slice = check_slice_size_atleast(bytes, elem_len, "second_message_bytes")?; [beta, server_s_pk],
[CS::Group],
);
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[self.beta.to_arr().to_vec(), self.server_s_pk.to_vec()].concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let checked_slice =
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 = Grp::from_element_slice(arr)?; let beta = CS::Group::from_element_slice(arr)?;
// FIXME check public key bytes // Throw an error if the identity group element is encountered
let server_s_pk = checked_slice[elem_len..].to_vec(); if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
Ok(Self { beta, server_s_pk })
}
}
impl<Grp> RegistrationResponse<Grp>
where
Grp: Group,
{
/// Byte representation for the registration response message. This does not
/// include the envelope credentials format
pub fn to_bytes(&self) -> Vec<u8> {
[&self.beta.to_arr().to_vec()[..], &self.server_s_pk[..]].concat()
}
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let mut registration_response: Vec<u8> = Vec::new();
registration_response.extend_from_slice(&self.beta.to_arr());
registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2));
registration_response
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let checked_slice =
check_slice_size_atleast(&input, Grp::ElemLen::to_usize(), "second_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..Grp::ElemLen::to_usize()]);
let beta = Grp::from_element_slice(arr)?;
let (server_s_pk, remainder) = tokenize(&checked_slice[Grp::ElemLen::to_usize()..], 2)?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.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 }) 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<D: Hash, G: Group> { 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<D>, 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,
pub(crate) _g: PhantomData<G>,
} }
impl<D: Hash, G: Group> TryFrom<&[u8]> for RegistrationUpload<D, G> { impl_clone_for!(
type Error = ProtocolError; 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],
);
fn try_from(third_message_bytes: &[u8]) -> Result<Self, Self::Error> { impl<CS: CipherSuite> RegistrationUpload<CS> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let envelope_size = key_len + Envelope::<D>::additional_size();
let checked_bytes = check_slice_size(
third_message_bytes,
envelope_size + key_len,
"third_message",
)?;
let unchecked_client_s_pk = Key::from_bytes(&checked_bytes[envelope_size..])?;
let client_s_pk = KeyPair::<G>::check_public_key(unchecked_client_s_pk)?;
Ok(Self {
envelope: Envelope::<D>::from_bytes(&checked_bytes[..envelope_size])?,
client_s_pk,
_g: PhantomData,
})
}
}
impl<D: Hash, G: Group> RegistrationUpload<D, G> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
let mut message: Vec<u8> = Vec::new(); [
message.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2)); self.client_s_pk.to_arr().to_vec(),
message.extend_from_slice(&self.envelope.serialize()); self.masking_key.to_vec(),
message self.envelope.serialize(),
]
.concat()
} }
/// Deserialization from bytes /// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> { pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let (client_s_pk, remainder) = tokenize(&input, 2)?; let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let (envelope, remainder) = Envelope::<D>::deserialize(&remainder)?; let hash_len = <CS::Hash as Digest>::OutputSize::to_usize();
let checked_slice =
if !remainder.is_empty() { check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
return Err(PakeError::SerializationError.into()); let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
}
Ok(Self { Ok(Self {
envelope, envelope,
client_s_pk: KeyPair::<G>::check_public_key(Key::from_bytes(&client_s_pk)?)?, masking_key: GenericArray::clone_from_slice(
_g: PhantomData, &checked_slice[key_len..key_len + hash_len],
),
client_s_pk: KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&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
@@ -203,41 +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,
} }
impl<CS: CipherSuite> TryFrom<&[u8]> for CredentialRequest<CS> { // Cannot be derived because it would require for CS to be Clone.
type Error = ProtocolError; impl<CS: CipherSuite> Clone for CredentialRequest<CS> {
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> { fn clone(&self) -> Self {
Self::deserialize(first_message_bytes) Self {
alpha: self.alpha,
ke1_message: self.ke1_message.clone(),
}
} }
} }
impl<CS: CipherSuite> CredentialRequest<CS> { impl_debug_eq_hash_for!(
/// byte representation for the login request struct CredentialRequest<CS: CipherSuite>,
pub(crate) fn to_bytes(&self) -> Vec<u8> { [alpha, ke1_message],
[&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat() [
} CS::Group,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message
],
);
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..],
)?; )?;
@@ -245,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()
@@ -268,29 +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(),
&serialize(&server_s_pk.to_arr().to_vec(), 2)[..],
&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> {
Self::try_from(input)
}
}
impl<CS: CipherSuite> TryFrom<&[u8]> for CredentialResponse<CS> {
type Error = ProtocolError;
fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize(); let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = let key_len = <PublicKey as SizedBytes>::Len::to_usize();
check_slice_size_atleast(second_message_bytes, elem_len, "login_second_message_bytes")?; let nonce_len: usize = 32;
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
@@ -298,49 +329,43 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for 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 (serialized_server_s_pk, remainder) = tokenize(&checked_slice[elem_len..], 2)?; // Throw an error if the identity group element is encountered
let sized_server_s_pk = check_slice_size( if beta.is_identity() {
&serialized_server_s_pk[..], return Err(PakeError::IdentityGroupElementError.into());
<Key as SizedBytes>::Len::to_usize(), }
"server_s_pk in credential_response",
)?;
let unchecked_server_s_pk = Key::from_bytes(sized_server_s_pk)?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&remainder)?; let masking_nonce = checked_slice[elem_len..elem_len + nonce_len].to_vec();
let masked_response = checked_slice
let ke2_message_size = CS::KeyExchange::ke2_message_size(); [elem_len + nonce_len..elem_len + nonce_len + masked_response_len]
let checked_remainder = .to_vec();
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<CS: CipherSuite> TryFrom<&[u8]> for CredentialFinalization<CS> { impl_clone_for!(struct CredentialFinalization<CS: CipherSuite>, [ke3_message]);
type Error = ProtocolError; impl_debug_eq_hash_for!(
struct CredentialFinalization<CS: CipherSuite>,
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> { [ke3_message],
let ke3_message = [<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message],
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(bytes)?; );
Ok(Self { ke3_message })
}
}
impl<CS: CipherSuite> CredentialFinalization<CS> { impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes /// Serialization into bytes
@@ -350,11 +375,12 @@ 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> {
Self::try_from(input) let ke3_message =
} <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::from_bytes::<CS>(
input,
/// byte representation for the login finalization )?;
pub fn to_bytes(&self) -> Vec<u8> { Ok(Self { ke3_message })
self.ke3_message.to_bytes()
} }
} }
impl_serialize_and_deserialize_for!(CredentialFinalization);
+609 -511
View File
File diff suppressed because it is too large Load Diff
+24 -23
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;
@@ -50,21 +55,23 @@ pub(crate) fn evaluate<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which /// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
/// the client unblinds the server's message. /// the client unblinds the server's message.
pub(crate) fn unblind<G: Group>(token: &Token<G>, point: G) -> Vec<u8> { pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
let unblinded = point * &G::scalar_invert(&token.blind); input: &[u8],
unblinded.to_arr().to_vec() blind: &G::Scalar,
evaluated_element: G,
) -> GenericArray<u8, <H as Digest>::OutputSize> {
let unblinded_element = evaluated_element * &G::scalar_invert(blind);
finalize_after_unblind::<G, H>(input, unblinded_element)
} }
pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>( fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
token_data: &[u8], input: &[u8],
issued_token: &[u8], unblinded_element: G,
info: &[u8],
) -> GenericArray<u8, <H as Digest>::OutputSize> { ) -> GenericArray<u8, <H as Digest>::OutputSize> {
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat(); let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
let hash_input = [ let hash_input = [
serialize(token_data, 2), serialize(input, 2),
serialize(issued_token, 2), serialize(&unblinded_element.to_arr().to_vec(), 2),
serialize(info, 2),
serialize(&finalize_dst, 2), serialize(&finalize_dst, 2),
] ]
.concat(); .concat();
@@ -95,15 +102,11 @@ pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
#[cfg(feature = "bench")] #[cfg(feature = "bench")]
#[doc(hidden)] #[doc(hidden)]
#[inline] #[inline]
pub fn unblind_and_finalize_shim<G: GroupWithMapToCurve, H: Hash>( pub fn finalize_shim<G: GroupWithMapToCurve, H: Hash>(
token: &Token<G>, token: &Token<G>,
point: G, point: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> { ) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
Ok(finalize::<G, H>( Ok(finalize::<G, H>(&token.data, &token.blind, point))
&token.data,
&unblind::<G>(token, point),
b"",
))
} }
/////////// ///////////
@@ -127,7 +130,7 @@ mod tests {
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap(); RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar; let res = point * scalar;
finalize::<RistrettoPoint, sha2::Sha512>(&input, &res.to_arr().to_vec(), b"") finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res)
} }
#[test] #[test]
@@ -141,8 +144,7 @@ mod tests {
]; ];
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?; let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key); let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
let res = let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta);
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &unblind(&token, beta), b"");
let res2 = prf(&input[..], &oprf_key.as_bytes()); let res2 = prf(&input[..], &oprf_key.as_bytes());
assert_eq!(res, res2); assert_eq!(res, res2);
Ok(()) Ok(())
@@ -154,12 +156,11 @@ mod tests {
let mut input = vec![0u8; 64]; let mut input = vec![0u8; 64];
rng.fill_bytes(&mut input); rng.fill_bytes(&mut input);
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap(); let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
let res = let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha);
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &unblind(&token, alpha), b"");
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat(); let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap(); let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
let res2 = finalize::<RistrettoPoint, sha2::Sha512>(&input, &point.to_arr().to_vec(), b""); let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point);
assert_eq!(res, res2); assert_eq!(res, res2);
} }
+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;
+130 -137
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,12 +54,12 @@ 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();
let reg = ClientRegistration::<Default>::try_from(&bytes[..]).unwrap(); let reg = ClientRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes(); let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes); assert_eq!(reg_bytes, bytes);
} }
@@ -68,119 +68,118 @@ 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>::try_from(&oprf_bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, oprf_bytes);
// If we do have envelope and client pk, the server registration contains
// the whole kit
// 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(&[0, 0]); // empty ciphertext // 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>::try_from(&bytes[..]).unwrap(); let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes(); let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes); assert_eq!(reg_bytes, bytes);
} }
#[test] #[test]
fn register_first_message_roundtrip() { fn registration_request_roundtrip() {
let pt = random_ristretto_point(); let pt = random_ristretto_point();
let pt_bytes = pt.to_arr().to_vec(); let pt_bytes = pt.to_arr().to_vec();
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());
let r1 = RegistrationRequest::<RistrettoPoint>::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]
fn register_second_message_roundtrip() { 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 pubkey_length: usize = pubkey_bytes.len();
let mut input = Vec::new(); let mut input = Vec::new();
input.extend_from_slice(beta_bytes.as_slice()); input.extend_from_slice(beta_bytes.as_slice());
input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
input.extend_from_slice(&pubkey_bytes.as_slice()); input.extend_from_slice(&pubkey_bytes.as_slice());
let r2 = RegistrationResponse::<RistrettoPoint>::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 register_third_message_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 pubkey_length: usize = pubkey_bytes.len();
let mut input = Vec::new(); let mut input = Vec::new();
input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
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::<sha2::Sha512, RistrettoPoint>::deserialize(&input[..]).unwrap(); let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap();
let r3_bytes = r3.serialize(); let r3_bytes = r3.serialize();
assert_eq!(input, r3_bytes); assert_eq!(input, r3_bytes);
} }
#[test] #[test]
fn login_first_message_roundtrip() { fn credential_request_roundtrip() {
let mut rng = OsRng; let mut rng = OsRng;
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);
@@ -189,60 +188,67 @@ fn login_first_message_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]
fn login_second_message_roundtrip() { fn credential_response_roundtrip() {
let pt = random_ristretto_point(); let pt = random_ristretto_point();
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 pubkey_length: usize = pubkey_bytes.len();
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_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]); input.extend_from_slice(&masking_nonce);
input.extend_from_slice(&pubkey_bytes.as_slice()); input.extend_from_slice(&masked_response);
input.extend_from_slice(&envelope.serialize());
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]
@@ -262,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);
@@ -279,8 +285,8 @@ fn client_login_roundtrip() {
&pw[..], &pw[..],
] ]
.concat(); .concat();
let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap(); let reg = ClientLogin::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes(); let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes); assert_eq!(reg_bytes, bytes);
} }
@@ -288,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);
} }
@@ -312,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);
} }
@@ -343,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);
} }
@@ -358,53 +351,53 @@ fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..std::mem::size_of::<usize>()))
} }
#[test] #[test]
fn test_nocrash_register_first_message(bytes in vec(any::<u8>(), 0..200)) { fn test_nocrash_registration_request(bytes in vec(any::<u8>(), 0..200)) {
RegistrationRequest::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true); RegistrationRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_register_second_message(bytes in vec(any::<u8>(), 0..200)) { fn test_nocrash_registration_response(bytes in vec(any::<u8>(), 0..200)) {
RegistrationResponse::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true); RegistrationResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_register_third_message(bytes in vec(any::<u8>(), 0..200)) { fn test_nocrash_registration_upload(bytes in vec(any::<u8>(), 0..200)) {
RegistrationUpload::<sha2::Sha512, RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true); RegistrationUpload::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_login_first_message(bytes in vec(any::<u8>(), 0..500)) { fn test_nocrash_credential_request(bytes in vec(any::<u8>(), 0..500)) {
CredentialRequest::<Default>::try_from(&bytes[..]).map_or(true, |_| true); CredentialRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) { fn test_nocrash_credential_response(bytes in vec(any::<u8>(), 0..500)) {
CredentialResponse::<Default>::try_from(&bytes[..]).map_or(true, |_| true); CredentialResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_login_third_message(bytes in vec(any::<u8>(), 0..500)) { fn test_nocrash_credential_finalization(bytes in vec(any::<u8>(), 0..500)) {
CredentialFinalization::<Default>::try_from(&bytes[..]).map_or(true, |_| true); CredentialFinalization::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) { fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
ClientRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true); ClientRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) { fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
ServerRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true); ServerRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) { fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
ClientLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true); ClientLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
#[test] #[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) { fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
ServerLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true); ServerLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
} }
} }
+10 -4
View File
@@ -31,14 +31,20 @@ impl<D: Hash> SlowHash<D> for NoOpHash {
} }
#[cfg(feature = "slow-hash")] #[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams { impl<D: Hash> SlowHash<D> for argon2::Argon2<'_> {
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(15, 8, 1).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)
} }
+517 -165
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": "2c3247e1d7fcf2bef09a0c6e771c44fe922f36deb5726998c89f7816323bc373", "client_s_pk": "b47c69b4ea5e87139649349516c2842145993a2a00cc6e63d27c57f170475260",
"client_s_sk": "d7dd5397912dc1dce64a26b8d24bee59b7929b15a6b9769ad6357913a13e7f09", "client_s_sk": "60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c06",
"client_e_pk": "3431e8f57ce0c1ae9da2b4f37a2f877a64b244a86bc1384ba70a9c39985b3729", "client_e_pk": "5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c",
"client_e_sk": "8410c0520fd2a37775e54000d9abb5a84825d37649827d8f2721dd655cd64c0c", "client_e_sk": "9f42ca864614d4175e1540e4c56fe18362cb56b778dccf6b0a9446a23735dc03",
"server_s_pk": "6a55f77ffb17741d5dfd404ef7079ef276cb2114a76f507c0ae7172847da647a", "server_s_pk": "8ed3fd51aa5e6931559fa6ae9be9829e609e441efbabb0846933fd5e30a3a268",
"server_s_sk": "f4433f94dea3f4bf877ab760566efa36340e34d516dc4462b2ad5b9600157a06", "server_s_sk": "a514a8842cd760449887fb2f943440b17073b5073691ceeaa0552210e693ea01",
"server_e_pk": "32d56c7e3b882b9ac1a4e38d60456c4942ec6b6aceed91d069ead967a825df74", "server_e_pk": "e8249649f7614f6268df01e54eb992043d49df04c98f8c8cea27c263d95dec4b",
"server_e_sk": "256f9cdddb2c4bed42866e37d5dff019b4a9584b7486e5683d52b56842d82b0d", "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": "c0937fddc5b7a8cbb76301cbe588268d52a9efe6a205af21a0a12b1fadf3a008", "blinding_factor": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a",
"oprf_key": "f386e8710c12c870a0ec74f09364811142050a5266ca53d36c11e369343a5e09", "oprf_seed": "1e7fddf167679cb1e83a179d4275034c09d2d745a1fec311a5e59ed30d0b80e2100ee8e6bbc996dc298f7f9e7dcc03c052853a02e4273d33c2973c7a6128affd",
"envelope_nonce": "2338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c93", "masking_nonce": "2b49d01802a69aacdad4979c503b96d08f44e4c67eaf82bbf6e71c6bb5473aa819359428f408bda29976beb0243c8a91fbeb2ee57840b90c62f4d87f88344db0",
"client_nonce": "09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf", "envelope_nonce": "f4351a2d4f1efb09877fbef82d44bff3a963b08cc727874aa75c5d57d604aa2e",
"server_nonce": "5c11e9abe0d5d677dd1a66cd1c8e9baa1f1a44c7554ffe61978b100e657f79f4", "client_nonce": "e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a",
"info1": "696e666f31", "server_nonce": "9b78591d87600abf26789c0691dd5f760d5620aa58e34181cb24503bf04c936a",
"einfo2": "65696e666f32", "context": "636f6e74657874",
"registration_request": "58e629f68bd59f1d5fcee531a5afeedb4e07d2c6aed98d15cab686d063f2f46e", "registration_request": "0cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832",
"registration_response": "a2227384ed34860b7d879d617f7daa802b6a878eaa5eab33669400f30ad0431d00206a55f77ffb17741d5dfd404ef7079ef276cb2114a76f507c0ae7172847da647a", "registration_response": "e88418f5a9145287062e50b060e6f6790583ec8646430af1bff0a2729bf20d1b8ed3fd51aa5e6931559fa6ae9be9829e609e441efbabb0846933fd5e30a3a268",
"registration_upload": "00202c3247e1d7fcf2bef09a0c6e771c44fe922f36deb5726998c89f7816323bc373022338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c930022576b449b09abeb68d5f6c56082b0c2f560c636bff4f0af3b44b6377045201b545598d319af3122041f06e3ab86371bff6c0727557c77d20efeb9a0b9f24573e3646735eeebed45e741d11d0120741af27b04c2716eb6bdfe950daff937cfcae8569e", "registration_upload": "d6f1486284e595707ae341a4d083d454477933b1bcf770bfc4087127c0a8e844e833f76e997aef5b46d2108811667183d08f0cc0a8465dac277287591cac1e42933ed23a2c9476cfa939854a40fc746c21606535b19f0a48cf8cc565f7c3e6df60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c0654e4188e55b7fe2eed8a7aee79ae6cfefabab86e7b7822f05bc422ac7e7a9acb968001b3dc5ead255a2d7599a7be60aa97ebed89808db20faa445e912f7df2da",
"credential_request": "58e629f68bd59f1d5fcee531a5afeedb4e07d2c6aed98d15cab686d063f2f46e09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf0005696e666f313431e8f57ce0c1ae9da2b4f37a2f877a64b244a86bc1384ba70a9c39985b3729", "credential_request": "0cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c",
"credential_response": "a2227384ed34860b7d879d617f7daa802b6a878eaa5eab33669400f30ad0431d00206a55f77ffb17741d5dfd404ef7079ef276cb2114a76f507c0ae7172847da647a022338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c930022576b449b09abeb68d5f6c56082b0c2f560c636bff4f0af3b44b6377045201b545598d319af3122041f06e3ab86371bff6c0727557c77d20efeb9a0b9f24573e3646735eeebed45e741d11d0120741af27b04c2716eb6bdfe950daff937cfcae8569e5c11e9abe0d5d677dd1a66cd1c8e9baa1f1a44c7554ffe61978b100e657f79f432d56c7e3b882b9ac1a4e38d60456c4942ec6b6aceed91d069ead967a825df740006a0c5112e94c86671fd294468658b8a78375f89fca9fb7c3ee1f63871360d5237e57da81719849a8dbccb974742333a1aed034d63d0bdba0beacfe30196912d561e99c1e79ccf", "credential_response": "e88418f5a9145287062e50b060e6f6790583ec8646430af1bff0a2729bf20d1b2b49d01802a69aacdad4979c503b96d08f44e4c67eaf82bbf6e71c6bb5473aa8718337df372fbb0de1beb29e2f4e6a2419858326ffe3f2a24172cca25e6344edd7db031cac3e206218eda4555d816f341c428317a4d37ed63441a278f78185b202b675b620e6f35056964d400c311cad23a1e6b0d9a91837d9d0021280bf0facf422961c96cffea530a24eb2486d4fa91adadaf7ac9a17d35b329b2add32e368a4b66443250a0cc39ad9baae6ada72c243ddee53b712eb48933993230c13500f2896e6f69e8610ced17584f34c09d872300bac6c99b8157392517ab9e9ed1f4aa163f8040d899cc77cf1f0ca2c4be6aef1616288cd3a6ac21989bdfc07bc4e94a284cf4c588583b2361195feab1ddcd390defde6282db2edc3eb535ede66404b",
"credential_finalization": "b1fad859f343f1a15df14bd7782db9ab6ee4493225a470152e53b3b7601dc954bc80d7b7dadfe70f743b1ab1552a054e7a9d192399ea42073a1d1c5839aabfc2", "credential_finalization": "2f8c71675d7db1b32ed3daaa7f15fc353f6af536ab1199e41e43ece9871d8b69336b8c84c4906810bb87c1a0407bd5f5d780c7d10a1c94016103639e507cf6d0",
"client_registration_state": "c0937fddc5b7a8cbb76301cbe588268d52a9efe6a205af21a0a12b1fadf3a00870617373776f7264", "client_registration_state": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a70617373776f7264",
"client_login_state": "c0937fddc5b7a8cbb76301cbe588268d52a9efe6a205af21a0a12b1fadf3a008006758e629f68bd59f1d5fcee531a5afeedb4e07d2c6aed98d15cab686d063f2f46e09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf0005696e666f313431e8f57ce0c1ae9da2b4f37a2f877a64b244a86bc1384ba70a9c39985b372900408410c0520fd2a37775e54000d9abb5a84825d37649827d8f2721dd655cd64c0c09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf70617373776f7264", "client_login_state": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a00600cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c00409f42ca864614d4175e1540e4c56fe18362cb56b778dccf6b0a9446a23735dc03e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a70617373776f7264",
"server_registration_state": "f386e8710c12c870a0ec74f09364811142050a5266ca53d36c11e369343a5e09", "server_login_state": "a62f305635e341c151f5e51b89307940031337a0ad8f1369ddec9b672dc31f35d59be00eb66d77bda0079d6eda94809c863da359fef3a636704ae3fa1c9b9b2d18eb9b193528fbb392a5eab5da8068b7c276c8fe00814213ddd70d02157902bebfce850b403aaa4c99f8dbd5ff50d4ad3e703fb564a3fc474861e3f69d7c9a90037d3dbf36f215082644d5c5bc91e138f9665e7bc538f4bc70f97c91dfcd029b1c027b03dc99137478b3570d9da27922b88a8784f1c2f07cd04a0db04246531a",
"server_login_state": "761333eea593c396021c3930fb36cf97aad7c7ea00f1ff983e4df9ca002885017161427303fddb4508c9136a67612e01b673ed88b1de49ed6628d0a77e43c590595f53bf0b0766c165b876173c509efc982868d3860df5dad3f0753477ec9bdd103b71dab9540537f2b10964da82032a6339da6d663a409f4e8e5dea03e3653bb6202f265ed814df39f446486197e1c6091ec6b74200f18df5eef59813ff7f245d69b34e8843be496630921601ab784b1f3ccbfe2ff014ea132199c5c76deda7", "password_file": "d6f1486284e595707ae341a4d083d454477933b1bcf770bfc4087127c0a8e844e833f76e997aef5b46d2108811667183d08f0cc0a8465dac277287591cac1e42933ed23a2c9476cfa939854a40fc746c21606535b19f0a48cf8cc565f7c3e6df60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c0654e4188e55b7fe2eed8a7aee79ae6cfefabab86e7b7822f05bc422ac7e7a9acb968001b3dc5ead255a2d7599a7be60aa97ebed89808db20faa445e912f7df2da",
"password_file": "f386e8710c12c870a0ec74f09364811142050a5266ca53d36c11e369343a5e092c3247e1d7fcf2bef09a0c6e771c44fe922f36deb5726998c89f7816323bc373022338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c930022576b449b09abeb68d5f6c56082b0c2f560c636bff4f0af3b44b6377045201b545598d319af3122041f06e3ab86371bff6c0727557c77d20efeb9a0b9f24573e3646735eeebed45e741d11d0120741af27b04c2716eb6bdfe950daff937cfcae8569e", "export_key": "f1abeb7ab0a43ff1924b59d744053b271d999f341eedc740f1f62d785d19bec939479e5e39f2ec25f5ef712ecd10a085653ad1ed9049092cb2a3d44d6cc205ba",
"export_key": "96b95891f06c7f02ab9c508f30e1a82ddea25e2f4fc4ffdafeb199ee65e24e418b2648a201e889eaf84301a1a8d0c3b7fea0ec8d4611686e6daee12430f60c05", "session_key": "037d3dbf36f215082644d5c5bc91e138f9665e7bc538f4bc70f97c91dfcd029b1c027b03dc99137478b3570d9da27922b88a8784f1c2f07cd04a0db04246531a"
"session_key": "b6202f265ed814df39f446486197e1c6091ec6b74200f18df5eef59813ff7f245d69b34e8843be496630921601ab784b1f3ccbfe2ff014ea132199c5c76deda7"
} }
"#; "#;
@@ -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 =
@@ -300,22 +323,18 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
.message .message
.serialize() .serialize()
.to_vec(); .to_vec();
let client_registration_state = client_registration_start_result.state.to_bytes().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.to_bytes().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,11 +356,8 @@ 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 let password_file_bytes = password_file.serialize();
.finish(client_registration_finish_result.message)
.unwrap();
let password_file_bytes = password_file.to_bytes();
let mut client_login_start: Vec<u8> = Vec::new(); let mut client_login_start: Vec<u8> = Vec::new();
client_login_start.extend_from_slice(&blinding_factor_bytes); client_login_start.extend_from_slice(&blinding_factor_bytes);
@@ -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.to_bytes().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,27 +380,30 @@ 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();
let credential_response_bytes = server_login_start_result.message.serialize().to_vec(); let credential_response_bytes = server_login_start_result.message.serialize().to_vec();
let server_login_state = server_login_start_result.state.to_bytes().to_vec(); let server_login_state = server_login_start_result.state.serialize().to_vec();
let client_login_finish_result = client_login_start_result let client_login_finish_result = client_login_start_result
.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.to_bytes().to_vec(); let credential_finalization_bytes = client_login_finish_result.message.serialize();
TestVectorParameters { TestVectorParameters {
client_s_pk: client_s_kp.public().to_arr().to_vec(), client_s_pk: client_s_kp.public().to_arr().to_vec(),
@@ -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,
@@ -440,29 +459,70 @@ fn test_registration_request() -> Result<(), ProtocolError> {
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.client_registration_state), hex::encode(&parameters.client_registration_state),
hex::encode(client_registration_start_result.state.to_bytes()) hex::encode(client_registration_start_result.state.serialize())
); );
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.to_bytes())
);
Ok(()) Ok(())
} }
@@ -473,13 +533,15 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
let client_s_sk_and_nonce: Vec<u8> = let client_s_sk_and_nonce: Vec<u8> =
[parameters.client_s_sk, parameters.envelope_nonce].concat(); [parameters.client_s_sk, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce); let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let result = ClientRegistration::<RistrettoSha5123dhNoSlowHash>::try_from( let result = ClientRegistration::<RistrettoSha5123dhNoSlowHash>::deserialize(
&parameters.client_registration_state[..], &parameters.client_registration_state[..],
)? )?
.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,16 +560,15 @@ 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>::try_from( 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),
hex::encode(password_file.to_bytes()) hex::encode(password_file.serialize())
); );
Ok(()) Ok(())
} }
@@ -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),
@@ -534,7 +594,7 @@ fn test_credential_request() -> Result<(), ProtocolError> {
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.client_login_state), hex::encode(&parameters.client_login_state),
hex::encode(client_login_start_result.state.to_bytes()) hex::encode(client_login_start_result.state.serialize())
); );
Ok(()) Ok(())
} }
@@ -543,32 +603,45 @@ 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::try_from(&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())
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.server_login_state), hex::encode(&parameters.server_login_state),
hex::encode(server_login_start_result.state.to_bytes()) hex::encode(server_login_start_result.state.serialize())
); );
Ok(()) Ok(())
} }
@@ -577,19 +650,19 @@ fn test_credential_response() -> Result<(), ProtocolError> {
fn test_credential_finalization() -> Result<(), ProtocolError> { fn test_credential_finalization() -> 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 client_login_finish_result = let client_login_finish_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::deserialize(
ClientLogin::<RistrettoSha5123dhNoSlowHash>::try_from(&parameters.client_login_state[..])? &parameters.client_login_state[..],
.finish( )?
CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize( .finish(
&parameters.credential_response[..], CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize(
)?, &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())
@@ -600,7 +673,7 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.credential_finalization), hex::encode(&parameters.credential_finalization),
hex::encode(client_login_finish_result.message.to_bytes()) hex::encode(client_login_finish_result.message.serialize())
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.export_key), hex::encode(&parameters.export_key),
@@ -614,15 +687,16 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
fn test_server_login_finish() -> Result<(), ProtocolError> { fn test_server_login_finish() -> 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_login_result = let server_login_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::deserialize(
ServerLogin::<RistrettoSha5123dhNoSlowHash>::try_from(&parameters.server_login_state[..])? &parameters.server_login_state[..],
.finish(CredentialFinalization::try_from( )?
&parameters.credential_finalization[..], .finish(CredentialFinalization::deserialize(
)?)?; &parameters.credential_finalization[..],
)?)?;
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(())
@@ -632,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,
@@ -642,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(),
)?; )?;
@@ -679,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(())
@@ -708,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(())
}
+482 -269
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>,
@@ -60,206 +65,333 @@ pub struct TestVectorParameters {
pub session_key: Vec<u8>, pub session_key: Vec<u8>,
} }
// Pulled from "OPAQUE-3DH Test Vector 1" and "OPAQUE-3DH Test Vector 6"
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
static TEST_VECTORS: &[&str] = &[ static TEST_VECTORS: &[&str] = &[
r#" r#"
Group: ristretto255 ## OPAQUE-3DH Test Vector 1
EnvelopeMode: 01
### Configuration
~~~
OPRF: 0001 OPRF: 0001
SlowHash: Identity
Hash: SHA512 Hash: SHA512
server_nonce: a4997137a8fa0d4baf7052a499bf877057f9404e03c889d641a0d7c MHF: Identity
807b6a518 KDF: HKDF-SHA512
oprf_key: 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be03 MAC: HMAC-SHA512
7e50b EnvelopeMode: 01
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
~~~
### Input Values
~~~
oprf_seed: 5c4f99877d253be5817b4b03f37b6da680b0d5671d1ec5351fa61c5d82
eab28b9de4c4e170f27e433ba377c71c49aa62ad26391ee1cac17011d8a7e9406657c
8
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65 password: 436f7272656374486f72736542617474657279537461706c65
blind_login: ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63 envelope_nonce: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd
e3263503 539c4676775
server_private_keyshare: 31587dff30b8001d9d43584decc22e358fa7f9d6e606 masking_nonce: 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab
29fb1223081c3bae7103 86ff39ed7f
client_nonce: 75a1ad27ab77578bc08b44c4318f09b31d53145c9ba3b42abf0ea08 server_private_key: 16eb9dc74a3df2033cd738bf2cfb7a3670c569d7749f284b2
a781277a6 b241cb237e7d10f
server_info: 6772656574696e677320616c696365 server_public_key: 18d5035fd0a9c1d6412226df037125901a43f4dff660c0549d
client_info: 68656c6c6f20626f62 402f672bcc0933
client_private_keyshare: fbbf4ad24119f08a35bf999f8ae0c779ed7b3e266bf3 server_nonce: f9c5ec75a8cd571370add249e99cb8a8c43f6ef05610ac6e354642b
3f793f6bf9ebf4578005 f4fedbf69
envelope_nonce: 6c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e037 client_nonce: 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f
585783444c0 2e9784f69
server_keyshare: 82be40ef93bf7c6edd43d4ed9f52fa19827649b819de39c52a22 server_keyshare: 6e77d4749eb304c4d74be9457c597546bc22aed699225499910f
43e985b75d62 c913b3e90712
blind_registration: c604c785ada70d77a5256ae21767de8c3304115237d262134 client_keyshare: f67926bd036c5dc4971816b9376e9f64737f361ef8269c18f69f
f5e46e512cf8e03 1ab555e96d4a
client_public_key: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3ef server_private_keyshare: f8e3e31543dd6fc86833296726773d51158291ab9afd
caba4f1c4ae861 666bb55dce83474c1101
client_private_key: f0f56cfb488649fe28691dd9aa5dc9ff4c0e6028075baa3c5 client_private_keyshare: 4230d62ea740b13e178185fc517cf2c313e6908c4cd9
615398a2cb12304 fb42154870ff3490c608
client_keyshare: 484e47e31b3132f4ee512e41805a1690891111a7b885bc526198 blind_registration: c62937d17dc9aa213c9038f84fe8c5bf3d953356db01c4d48
22c14cabf360 acb7cae48e6a504
server_public_key: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdc blind_login: b5f458822ea11c900ad776e38e29d7be361f75b4d79b55ad74923299
eeeb935924a903 bf8d6503
server_private_key: d63d709e3a739a128929a9f289ff263fdcbc457f2f47f7c43 oprf_key: 23d431bab39aea4d2737ac391a50076300210730971788e3a6a8c29ad3c
ccafbbfee72290c 5930e
auth_key: 22198da4ad73b1d35cd8bb875e64ce1a9fc2edeb073d760e114d1d7a2f8 ~~~
6d47411caf1787907e2ff96cd3190b14d101101d74cff234259d9f19a18f2cfe29d0b
server_mac_key: 9de28e2f7107afe266570934c033dd6a403fb2b09a9f1a1357a81 ### Intermediate Values
9fb072d25e6651626638d77bd7f0adf4b2b715d0a0bee2ac531fd0f7da699aba6e9ed
717466 ~~~
envelope: 016c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e0375857 client_public_key: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a33
83444c00022e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92c73838c043 439911e66c3c27
fc316deeb83deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23775aa54633 auth_key: 27972f9b1cf2ce524d50a7afa40a2ee6957904e2bef29976bdbda452a84
ed75665cd88f451e044cac1b282d890269476d1ac18ff9a4c0cb832e1143ffa7d447a fcf01023f3ddd8182e64ea5287f99765dd39b83fa89fe189db227212a144134684783
prk: 732303f65e76c39f30876aec31af1f5bcd8861626c922baa2e842209c7d5bf2e randomized_pwd: 750ef06299c2fb102242fd84e59613616338f83e69c09c1dc3f91
024912db2d6cac00b1260c437d34ec588a664a55fc7a0a40251915d2b15d8ec9 c57ac0642876ccbe785e94aa094262efdc6aed08b3faff7c1bddfa14c434c5a908ad6
client_mac_key: 1536168d48218d08dcbed3438e897d98eff566894240d8d136be0 c5f9d5
4ba46b2c788883fd165ce7614c52a6a9c926bf55d249e6c29bc4a23d5a1775ad294a7 envelope: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c46
858922 76775455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e
pseudorandom_pad: e99274b5cd27d14aeeaf16e7aa8d7e7a03071d58229c5dc96d0 43d4c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
46ed57a761ddccdbc handshake_secret: 02fb23a668b7138b029c95d21f1e0eec9e10377be933bdbf3e5
handshake_encrypt_key: c646def7a8ca75282ac1a0ad15630834cba2772837d7a3 33ea39073d3ce9d1ef16b55a8a8464f3bf6a991cc645d14c1fa3d9d6cfe36c6c0dcc2
a26d00b36923e55b3f5d065679c715e4c799654f261c865f1a55bc94cabc29a6e2e72 691d7109
296dfc494a984 server_mac_key: e75ce46beeebd26f22540d7988de9809a69cf34fec6c050750708
handshake_secret: cc887e128c28064106d4101ac3de633e9096e170f2c4a9913d3 e91232297fdbb51e875cd37167d5ce661ebccf0004dbbf96311daf64ddec7faae04c4
50f306274b1665a5b251f761672c12db4c403a615c22cee96adb3539fa62662a17f2e 8bbd89
18cd5fed client_mac_key: 4bce132daa031fff2a6e5ac29287c4641e3b9dc2560394b8c73f3
registration_response: 1867301bcc67bdf8e640b7d6edcbe2a65488446417b50d b748f1e51e577b932a960b236981217b33bee220b0bce2696638cfb7791f427ade292
30cdba66ccb379e57200205442a6f57333a332b4c6f07308f6fa846bde3ed27425820 d60f55
cdceeeb935924a903 ~~~
export_key: 6ca2c344763e5bc9e3d2bbfe3d982b826b709da597e28e85f9594ec54
2a20c697d55de277ccce1d1af7c48ab7fea1467ac1e3a99c71dcf6326a909d280bd2f ### Output Values
6f
registration_upload: 0020b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9a ~~~
a2c3efcaba4f1c4ae861016c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc5 registration_request: 80576bce33c6ce89f9e1a06d8595cd9d09d9aef46b20dad
56e037585783444c00022e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92 d57a845dc50e7c074
c73838c043fc316deeb83deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23 registration_response: 1a80fdb4f4eb1985587b5b95661d2cff1ef2493cdcdd88
775aa54633ed75665cd88f451e044cac1b282d890269476d1ac18ff9a4c0cb832e114 b5699f39048f0d6c2618d5035fd0a9c1d6412226df037125901a43f4dff660c0549d4
3ffa7d447a 02f672bcc0933
registration_request: 241b621c417c0705b5ea7a8b7cdd5039fd61e6b63effe2a registration_upload: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a
44418164c4d49003e 33439911e66c3c2795014d8fc0c710bd763c981c5b9329c95e149c6717af91bad2cec
session_key: ee9f1ef224d498858f6c9b3a121016a38bad7816055c452b1c7edf3d daf87f2c3c9c11914cb6d44aaee5679e3e61e1b65241fda74902cca908a065495c0b2
d439c42a4cc78cbd672e985a20910df14f8f1af4ce5793303ffe6954ff5f1a264e3fd 8b799e71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c467677
515 5455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e43d4
KE3: e771daf28bc5e8068dead67c3db19f9ad03ee919e52f6c7a6e79cf1085bd7448 c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
1e76512c77f37762578eb2faff8fe98e4185ca2d01957216c556d33a6fba3028 KE1: 60d71c9f5d2a14568807b869e2c251a8e5f7ad8951cd8386c7e32c0634b26b16
KE2: e83812f06568d57b8cdfdcc90fe91454e21bd25dd2a1c32dd1599a2e4a4b6c35 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f2e9784f69f6792
00205442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdceeeb935924a9030 6bd036c5dc4971816b9376e9f64737f361ef8269c18f69f1ab555e96d4a
16c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e037585783444c00022 KE2: 78a428204f552d3532bad040c961324edb22c738d98f1dd770d65caba0bd8966
e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92c73838c043fc316deeb83 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab86ff39ed7fbcbbb
deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23775aa54633ed75665cd88 84a18810b8eb1dc898d9af686f5901a21d0768720b325279fde4931ee52f0d4a0d0d9
f451e044cac1b282d890269476d1ac18ff9a4c0cb832e1143ffa7d447aa4997137a8f cd1cd7c424d4622b1588ba554cd9241352a59ef52bbe85e0f865021404b115ba954f5
a0d4baf7052a499bf877057f9404e03c889d641a0d7c807b6a51882be40ef93bf7c6e 540cf2d811a6566a93876cac1239b1f75f39b070250af5a84a819e08b13e9e437a80f
dd43d4ed9f52fa19827649b819de39c52a2243e985b75d62000f13aed85ae30aee2f8 c25cc130f8475dde43efe6d900c664e9bac300298bb0f9c5ec75a8cd571370add249e
9ac5e1c5dd53609b890267ef2d765bb56000bc704c2ba4e4256107befee6655cdb084 99cb8a8c43f6ef05610ac6e354642bf4fedbf696e77d4749eb304c4d74be9457c5975
64e6c75d8b251642e0d721b3fce38f245568b50e7f6c98825d54cc1a26d6eafadcc1d 46bc22aed699225499910fc913b3e907120485942e3e077f71c1dd2d87053b39f0d31
344 bfe5d5f90df0e85ad9ce771e4f4d1ab697a10a02002cd73916051b887da9554465d58
KE1: b68e0e356f8490fa9c3bed952e16cc02db21eda686b3c484f3d9d912caa41f76 68811fd8b22b8f457ed5a4b0
75a1ad27ab77578bc08b44c4318f09b31d53145c9ba3b42abf0ea08a781277a600096 KE3: b4f8aece9fb4f6b7b5ffe1c98747a91f4ec7bf5481fe5719ba4baad668e3fd4e
8656c6c6f20626f62484e47e31b3132f4ee512e41805a1690891111a7b885bc526198 8aba4fa227bd4c688ed9e17f6c6d28ab5e5617a883207d80979dc4797ca89304
22c14cabf360 export_key: 045f61f4baa0a945c2e85dfb7a85fe4df8a49e6c31344920e863c286b
c8a17fe25fc16c84836335b4b5ecc9743c5d3a221101ab004aa99ce65026b6953ad6c
c0
session_key: 91187690e5ea0da3110a1dd7d5ffd7c4c3111950c587d9fcf3b9f34b
f73b86dbeafed42a05024fa875a32415c6143d20c39cd732eb0e31db5e60ea3fb2551
cf7
~~~
"#, "#,
r#" r#"
Group: ristretto255 ## OPAQUE-3DH Test Vector 2
EnvelopeMode: 02
### Configuration
~~~
OPRF: 0001 OPRF: 0001
SlowHash: Identity
Hash: SHA512 Hash: SHA512
server_nonce: 0f3a6da8b667bc7a383c987586bee749c5f2787691baca68757e78b MHF: Identity
6128b0a0f KDF: HKDF-SHA512
oprf_key: 89c61a42c8191a5ca41f2fe959843d333bcf43173b7de4c5c119e0e0d8b MAC: HMAC-SHA512
0e707 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: db5c1c16e264b8933d5da56439e7cfed23ab7287b474fe3cdcd58df089
a365a426ea849258d9f4bc13573601f2e727c90ecc19d448cf3145a662e0065f157ba
5
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65 password: 436f7272656374486f72736542617474657279537461706c65
blind_login: e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd envelope_nonce: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf
4171ea02 2747829b2d2
server_private_keyshare: 70c944dcb7f4dddde168ecb48dd9488c62b6fc7e9bb4 masking_nonce: 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b
2a16d291afca9dd25b07 5def228c85
client_nonce: 480917b09c6720680b4a7a0ba9f54b69d870f640a4a7994b47ad07d server_private_key: eeb2fcc794f98501b16139771720a0713a2750b9e528adfd3
1a95c984f 662ad56a7e19b04
server_info: 6772656574696e677320616c696365 server_public_key: 8aa90cb321a38759fc253c444f317782962ca18d33101eab2c
server_identity: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdcee 8cda04405a181f
eb935924a903 server_nonce: 3fa57f7ef652185f89114109f5a61cc8c9216fdd7398246bb7a0c20
client_info: 68656c6c6f20626f62 e2fbca2d8
client_private_keyshare: a4ba6cb7e16ab76eccdb4c0b9261eedd426d7863f00b client_nonce: a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e608
fc4a0e09476d3121e70c 5d8c1187a
envelope_nonce: e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7dee server_keyshare: ae070cdffe5bb4b1c373e71be8e7d8f356ee5de37881533f1039
b8cfb42d13f 7bcd84d35445
server_public_key: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdc client_keyshare: 642e7eecf19b804a62817486663d6c6c239396f709b663a4350c
eeeb935924a903 da67d025687a
client_identity: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3efca server_private_keyshare: 0974010a8528b813f5b33ae0d791df88516c8839c152
ba4f1c4ae861 b030697637878b2d8b0a
blind_registration: 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9db client_private_keyshare: 03b52f066898929f4aca48014b2b97365205ce691ee3
cec831b8c681a09 444b0a7cecec3c7efb01
client_public_key: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3ef blind_registration: a66ffb41ccf1194a8d7dda900f8b6b0652e4c7fac4610066f
caba4f1c4ae861 e0489a804d3bb05
client_private_key: f0f56cfb488649fe28691dd9aa5dc9ff4c0e6028075baa3c5 blind_login: e6f161ac189e6873a19a54efca4baa0719e801e336d929d35ca28b5b
615398a2cb12304 4f60560e
client_keyshare: 4ae7d50bb80cc8f5034d36c1c27edc30caca0983677a941bc0ac oprf_key: 1e0550d2dbb9ce5dd9bdbb5f808afbb724c573dc03306dcfc7217796465
e5e10b18300a ce607
server_keyshare: a2e9e0809b270a1d5c8208f3498a3188538265e6a9e6b274cb38 ~~~
c4c5d9b1792d
server_private_key: d63d709e3a739a128929a9f289ff263fdcbc457f2f47f7c43 ### Intermediate Values
ccafbbfee72290c
auth_key: 4c7c0ae950ad7e9c518266f953d4a01bd2232695f92fb028aa6c124996e ~~~
31a205f621305fd4997edf8a5fce04a51252ba8430227c134b81d7093a58e01c70752 client_public_key: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7e1
server_mac_key: 974bde939ef1d30ba29f9ec2addcaff1eeb105a1f534e04113f9f c9004f20d5db71
0b5c3b9a0797e3fa5f7006e63dfb2b0ce74d002bd1161767c361507bfb1fa0fed5063 auth_key: 5142ae6f6bd80686039656fd7a03cdd7e39cc6e869aa637220d4b5fb64f
1cdcb7 afee2f284a1581fff95ad3a5261b413c5e5b91115f78a3c35486fa56023c300d1726b
envelope: 02e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7deeb8cf randomized_pwd: cea240b632b9c1d704034920cc3dc3c664ed8cd82cf5c0339af76
b42d13f00227ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26ecd566d7688 4d6350d2ee9ba1f675ce8df7b6cf8692d1efb158bafa3c2695ac03a2d92346c19810c
095f9478a721e5a592be9dd3bda76ed97421819aca4a30752813223d33bc7ea443be9 1a698b
76754605116278c7fffda4de142674ed154f540bd3285080637eb4b929e1378336b11 envelope: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf274782
prk: e3bb74ca5f88a95571578e921489e1b6119b438e4efce6e955ae9b6453f24aa4 9b2d26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c
e3a34fa22bc5f470cfc134ca0784a9cd7df64be46ff3b325fc19f4009979bf2e 1517b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
client_mac_key: 1c55ecba64d98dc2db8f45faa72b8fdd63cad8677b8665cc575cf handshake_secret: 7925c12d7bf3050e62fe5c8caaece3c85737754c5df79bc59a6
8ad36ef38ec63c9dfc5552007573215983f55d8f5cfa1651da8417e3f27094dde5254 0fa87929ab1f4a4730f903b87be8b7d89ded8ec97aaec97bc8e7d53a555fd4ad74c4f
7f5b89 33b9bc83
pseudorandom_pad: 7ef495b828a97c896f381824d3371ba012eb63c3f19bb535676 server_mac_key: 27d6036335c5654132fb08cc81d95b3067ef7fe795f017531231a
a3b63b18373255ba3 e3fa03cd3ab72f1f5e81473318f9c01f990263d885dfce4b6ac8630fdc8ee8abc6a36
handshake_encrypt_key: 0852fb826109073be6e7c065b6e2d1f872c16f9977c177 7c2339
cd2f945b26bb933fe6252f226f40483f7aa52f545f801d1e4430f30f80fd42070494e client_mac_key: ebb3693bac6310075a89922c7a40599d14d03d9104b7a331106e8
a77fdcecd6595 a578a32a4944751f9d3c230a6690a5747137388a86159cf587969d13dadc0a3830218
handshake_secret: bfd3bfbe451520880975d0e568cbd3b5155b23c02de504fccb9 dfbca5
dd5a8195266cd94d49d040530b3d6b0a585d542eb24da708a2b6f6dc34dee4652d0c1 ~~~
c62c4e59
registration_response: 088ac01ebf5700f0c96bc2988509343cb7e2dd6f0df820 ### Output Values
d0fb807faa11a26f5600205442a6f57333a332b4c6f07308f6fa846bde3ed27425820
cdceeeb935924a903 ~~~
export_key: 0effa605dc47ba4fe565c423b782b8b6697b26ee2ede7059b0e17510d registration_request: f841cbb85844967568c7405f3831a58c4f5f37ccddb0baa
f8b11554ce053409671480a56ffbe77b91edc95205c213caeaf9dcb0841790ff834a0 4972ea912c960ae66
09 registration_response: 0256257cc6e2b04444edc076b9ad44d8b31593e050bea8
registration_upload: 0020b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9a 06485707a818f8a93f8aa90cb321a38759fc253c444f317782962ca18d33101eab2c8
a2c3efcaba4f1c4ae86102e38fb444afe3df13ae05e6876d10eca7661196375518eb6 cda04405a181f
6d7deeb8cfb42d13f00227ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26e registration_upload: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7
cd566d7688095f9478a721e5a592be9dd3bda76ed97421819aca4a30752813223d33b e1c9004f20d5db71146e42585d25fa19913876edce4b5ee99b638eb37b1d8a8a76607
c7ea443be976754605116278c7fffda4de142674ed154f540bd3285080637eb4b929e efaa12299e828641ba4fbf1c46fc2c3776e0a0c9791f88a15b9ddfb5495d63ce92d8f
1378336b11 58823bd0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf2747829b2d
registration_request: c8d2e9ba503bf3f8821226653314427edb1ec8a3ecc94a5 26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c1517
dfbbe33d59d07b645 b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
session_key: 8dc21ff264f2774de95955d35544ba314e92d07f4a3b32c89ead5e70 KE1: 14cc586d982b6db9846c78e0b3c543591e95fbf2fc877fa0e5eff89897dd3050
c83ac4c0221deadd34ed11d43fc4d3651aec612d696c63979c96bf1ddd1ee44da5d0c a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e6085d8c1187a642e7
d68 eecf19b804a62817486663d6c6c239396f709b663a4350cda67d025687a
KE3: c178e45ed9b314653685cdbf5f7730e3e40f8652ceb9b10f47d1c784fdd75dd1 KE2: 8ab71c17547f376ae787741c367142790087090cdde6327dabb2581197bffa59
07c4ae3f7683de5a692359178c8f13f41a043fc1dcfc14b1fb7cb411514efc6c 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b5def228c85dd973
KE2: 5079b16709b195b3b63257b419efb752bd0603170160fa72b828ce9ff9209c0c a1ac59244f674da4a1c057961886661bd29e0c1346f0fcf75bf1c78d4781815c2f9f6
00205442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdceeeb935924a9030 f2f9fe0e370b256f6e82fb2e14c7ffc374d42caf26abf13dca169a6faafd5cff8baa9
2e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7deeb8cfb42d13f0022 717090bc1fc5e1ba56acb93492d1a8b789f33ff29b6004c4be9a755ff590d7d00d6e8
7ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26ecd566d7688095f9478a72 893e7e54e639aebf69d18f2182a9bb0f2e1c27c81ba73fa57f7ef652185f89114109f
1e5a592be9dd3bda76ed97421819aca4a30752813223d33bc7ea443be976754605116 5a61cc8c9216fdd7398246bb7a0c20e2fbca2d8ae070cdffe5bb4b1c373e71be8e7d8
278c7fffda4de142674ed154f540bd3285080637eb4b929e1378336b110f3a6da8b66 f356ee5de37881533f10397bcd84d35445401c619d464ab3a134c71da4d9874f2f736
7bc7a383c987586bee749c5f2787691baca68757e78b6128b0a0fa2e9e0809b270a1d 189b8bbb659c28f8db25a58b9f089272132e3091efa87d6b07d10321ba464047be011
5c8208f3498a3188538265e6a9e6b274cb38c4c5d9b1792d000f5a1a0b34573bf728b 3e91514aba299fd1553bcebb
14f53485f3bf62fd91154dc9ca01b21945b2204f96adc87bd80e8283ecf3522b6d893 KE3: c4a0d5b8148f3ac0f8611b38de38bda085d4eb00d561397ae59676f36dc705be
7527f2e9b9782a94fedab0fa304590d1d4f03d72fb1664fca1523a3be95f6c3a97d0a 1c939e7bfdd7301103af5eb164bdfb70298aab889bd2ac797e419a82bfb442e6
2ed export_key: 6b50ae4dba956930c0465b4a26c3cee58e05afcab623c1c254ae34acc
KE1: 7024ca0d5423176294fbb9ca968d8ce3fc879a231f1ceef69e672c89e02ded59 38babf954530a53475672ff46a1cf7fd53ef9e808f85b08793d021bb5c6d2a1bb9204
480917b09c6720680b4a7a0ba9f54b69d870f640a4a7994b47ad07d1a95c984f00096 f6
8656c6c6f20626f624ae7d50bb80cc8f5034d36c1c27edc30caca0983677a941bc0ac session_key: c9bc2b7e2237f6fbeccd92dc6ec6d51faeb886492f8d23f21743a967
e5e10b18300a 597025215df02a4afb75349acbafeef9dfd4f19e6d38da8bea4912f7b691b70849b0d
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,
} }
}; };
} }
@@ -276,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);
} }
@@ -306,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"),
@@ -342,19 +486,23 @@ 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 Ok(password_file.serialize())
.state }
.finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
Ok(password_file.to_bytes()) 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]
@@ -377,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),
@@ -402,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()
}, },
)?; )?;
@@ -444,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),
@@ -457,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::try_from(&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())
@@ -502,32 +662,25 @@ 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)
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.KE3), hex::encode(&parameters.KE3),
hex::encode(client_login_finish_result.message.to_bytes()) hex::encode(client_login_finish_result.message.serialize())
); );
assert_eq!( assert_eq!(
hex::encode(&parameters.export_key), hex::encode(&parameters.export_key),
@@ -540,34 +693,94 @@ 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::try_from(&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())
}, },
)?; )?;
let server_login_result = server_login_start_result let server_login_result = server_login_start_result
.state .state
.finish(CredentialFinalization::try_from(&parameters.KE3[..])?)?; .finish(CredentialFinalization::deserialize(&parameters.KE3[..])?)?;
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(())
+16 -49
View File
@@ -16,36 +16,30 @@ struct VOPRFTestVectorParameters {
blind: Vec<u8>, blind: Vec<u8>,
blinded_element: Vec<u8>, blinded_element: Vec<u8>,
evaluation_element: Vec<u8>, evaluation_element: Vec<u8>,
unblinded_element: Vec<u8>,
info: Vec<u8>,
output: Vec<u8>, output: Vec<u8>,
} }
// 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": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03", "sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "00", "input": "00",
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b", "blind": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"blinded_element": "5cccd309ec729aebe398c53e19c0ab09c24a29f01036960bdad109852e7bdb44", "blinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
"evaluation_element": "86bd5eeabf29a87cb4a5c7207cb3ade5297e65f9b74c979bd3551891f4b21515", "evaluation_element": "fc6c2b854553bf1ed6674072ed0bde1a9911e02b4bd64aa02cfb428f30251e77",
"unblinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348", "output": "d8ed12382086c74564ae19b7a2b5ed9bdc52656d1fc151faaae51aaba86291e8df0b2143a92f24d44d5efd0892e2e26721d27d88745343493634a66d3a925e3a"
"info": "4f505246207465737420766563746f7273",
"output": "0bb570873cc0402ca38f1a2c395301f2a3627616e305f2bc54bb08c3f6ea9871eb71074e52e36b90778ba7c3e3429ef7170245c9e01647f3827fdef84d3ba930"
} }
"#, "#,
r#" r#"
{ {
"sksm": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03", "sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a", "input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e3263503", "blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
"blinded_element": "227d63ca69e93bd062193c1e97fff3d5ebf628f646009d77c4e22ba6429be154", "blinded_element": "28a5e797b710f76d20a52507145fbf320a574ec2c8ab0e33e65dd2c277d0ee56",
"evaluation_element": "063b91a12e7cbb98dfeb75d8a7eeb83aacf9fd6df7e0b4197466fb77a27fa631", "evaluation_element": "345e140b707257ae83d4911f7ead3177891e7a62c54097732802c4c7a98ab25a",
"unblinded_element": "804ec6774764ed50a0bbad0a5f477aa04df7323acab8f98ca6e468b7790bca4c", "output": "4d5f4221b5ebfd4d1a9dd54830e1ed0bce5a8f30a792723a6fddfe6cfe9f86bb1d95a3725818aeb725eb0b1b52e01ee9a72f47042372ef66c307770054d674fc"
"info": "4f505246207465737420766563746f7273",
"output": "af7cc264dbc96a6b898ba0fa33bfa9e1407bf1dcfbf8772204d470d4458b8f047806679dbfa251f656b906edf9fa638e268adf979bd0e2380a092047d61f9db9"
} }
"#, "#,
]; ];
@@ -63,8 +57,6 @@ fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
blind: decode(&values, "blind").unwrap(), blind: decode(&values, "blind").unwrap(),
blinded_element: decode(&values, "blinded_element").unwrap(), blinded_element: decode(&values, "blinded_element").unwrap(),
evaluation_element: decode(&values, "evaluation_element").unwrap(), evaluation_element: decode(&values, "evaluation_element").unwrap(),
unblinded_element: decode(&values, "unblinded_element").unwrap(),
info: decode(&values, "info").unwrap(),
output: decode(&values, "output").unwrap(), output: decode(&values, "output").unwrap(),
} }
} }
@@ -112,34 +104,7 @@ fn test_evaluate() -> Result<(), PakeError> {
Ok(()) Ok(())
} }
// Tests sksm, evaluation_element -> evaluation_element // Tests input, blind, evaluation_element -> output
#[test]
fn test_unblind() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let token = oprf::Token {
data: parameters.input,
blind: RistrettoPoint::from_scalar_slice(GenericArray::from_slice(
&parameters.blind[..],
))
.unwrap(),
};
let unblinded_element = oprf::unblind::<RistrettoPoint>(
&token,
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.evaluation_element,
))
.unwrap(),
);
assert_eq!(&parameters.unblinded_element, &unblinded_element);
}
Ok(())
}
// Tests input, unblinded_element, info -> output
#[test] #[test]
fn test_finalize() -> Result<(), PakeError> { fn test_finalize() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 { for tv in OPRF_RISTRETTO255_SHA512 {
@@ -147,8 +112,10 @@ fn test_finalize() -> Result<(), PakeError> {
let output = oprf::finalize::<RistrettoPoint, Sha512>( let output = oprf::finalize::<RistrettoPoint, Sha512>(
&parameters.input, &parameters.input,
&parameters.unblinded_element, &RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&parameters.blind))?,
&parameters.info, RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.evaluation_element,
))?,
); );
assert_eq!(&parameters.output, &output.to_vec()); assert_eq!(&parameters.output, &output.to_vec());