29 Commits
Author SHA1 Message Date
8bb96820d9 Fix zeroize dependency in a more future-proof fashion (#321)
* cargo: make zeroize dependency more generous

* Release v0.6.2

* Updating MSRV and changelog

---------

Co-authored-by: Kevin Lewi <[email protected]>
2023-05-13 14:14:02 -07:00
902a605b9c Upgrade zeroize to 1.5 (#286)
* Upgrade zeroize to 1.5

* Update the MSRV to 1.51

* Fix clippy warnings

Co-authored-by: Valentin Tolmer <[email protected]>
2022-11-25 11:31:14 -08:00
daxpeddaandGitHub 1012439d2f Backport zeroize fix (#266)
* Backport `zeroize` fix

* Update version

* Fix CI

* Remove bench for CI MSRV

* Downgrade rustyline for MSRV

* Downgrade proptest for MSRV

* Downgrade zeroize for MSRV
2022-01-30 16:05:19 -08:00
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
29 changed files with 2455 additions and 3474 deletions
+24 -30
View File
@@ -16,8 +16,8 @@ jobs:
- u64_backend
- u32_backend
toolchain:
- stable
- 1.41.0
- nightly
- 1.56.0
name: test
steps:
- name: Checkout sources
@@ -72,6 +72,20 @@ jobs:
- uses: hecrj/setup-rust-action@v1
- 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:
runs-on: ubuntu-latest
@@ -79,8 +93,8 @@ jobs:
fail-fast: false
matrix:
toolchain:
- stable
- 1.41.0
- nightly
- 1.56.0
name: test simple_login command-line example
steps:
- name: install expect
@@ -103,8 +117,8 @@ jobs:
fail-fast: false
matrix:
toolchain:
- stable
- 1.41.0
- nightly
- 1.51.0
name: test digital_locker command-line example
steps:
- name: install expect
@@ -121,26 +135,6 @@ jobs:
- name: Run expect (which then runs cargo run)
run: expect -f scripts/digital_locker.exp
benches:
name: cargo bench compilation
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo bench --no-run
uses: actions-rs/cargo@v1
with:
command: bench
args: --features "bench" --no-run
clippy:
name: cargo clippy
runs-on: ubuntu-latest
@@ -148,11 +142,11 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: nightly
override: true
components: rustfmt, clippy
@@ -170,11 +164,11 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
toolchain: nightly
override: true
components: rustfmt, clippy
+1
View File
@@ -2,4 +2,5 @@
.vscode/
src/.DS_Store
/target
Cargo.lock
**/*.rs.bk
+23 -7
View File
@@ -1,16 +1,32 @@
# Changelog
## 0.5.1 (July 16, 2021)
## 0.7.0 (May 13, 2023)
* Update zeroize dependency to allow for beyond version 1.5
* Increase MSRV to 1.56
## 0.6.1 (January 25, 2022)
* Fix `zeroize` implementing `Drop` on `enum`s now
## 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, reflected value check, and adding an
i2osp error condition
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, 2020)
## 0.5.0 (March 1, 2021)
* Removed dependency on generic-bytes-derive package
## 0.4.0 (February 26, 2020)
## 0.4.0 (February 26, 2021)
* Adherence to protocol format described in
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
@@ -19,13 +35,13 @@
* Conformed all message type parameters to be parameterized in the
Ciphersuite object
## 0.3.1 (February 11, 2020)
## 0.3.1 (February 11, 2021)
* Re-exporting the rand library (and including it as a dependency instead of
just rand_core)
* 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
identifiers, optional result parameters, and the use of the export key
Generated
-1284
View File
File diff suppressed because it is too large Load Diff
+14 -17
View File
@@ -1,6 +1,6 @@
[package]
name = "opaque-ke"
version = "0.5.1"
version = "0.7.0"
repository = "https://github.com/novifinancial/opaque-ke"
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
@@ -10,40 +10,37 @@ edition = "2018"
readme = "README.md"
[features]
default = ["u64_backend"]
slow-hash = ["scrypt"]
default = ["u64_backend", "serialize"]
slow-hash = ["argon2"]
bench = []
u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"]
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"]
[dependencies]
constant_time_eq = "0.1.5"
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"
displaydoc = "0.1.7"
generic-array = "0.14.4"
generic-bytes = { version = "0.1.0" }
hkdf = "0.10.0"
hmac = "0.10.1"
hkdf = "0.11.0"
hmac = "0.11.0"
rand = "0.8"
scrypt = { version = "0.5.0", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
subtle = { version = "2.3.0", default-features = false }
thiserror = "1.0.22"
zeroize = { version = "1.1.1", features = ["zeroize_derive"] }
zeroize = { version = "1.5", features = ["zeroize_derive"] }
[dev-dependencies]
anyhow = "1.0.35"
base64 = "0.13.0"
bincode = "1"
chacha20poly1305 = "0.7.1"
criterion = "0.3.3"
hex = "0.4.2"
lazy_static = "1.4.0"
serde_json = "1.0.60"
sha2 = "0.9.2"
proptest = "0.10.1"
rustyline = "6.3.0"
[[bench]]
name = "oprf"
harness = false
required-features = ["bench"]
proptest = "0.3"
rustyline = "1"
+4 -3
View File
@@ -14,7 +14,7 @@ OPAQUE is a PKI-free aPAKE that is secure against pre-computation attacks and ca
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
------------
@@ -22,15 +22,16 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
opaque-ke = "0.5.1"
opaque-ke = "0.7.0"
```
Resources
---------
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
- [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html), containing a detailed (byte-level) specification for OPAQUE
- [draft-irtf-cfrg-opaque-05](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-05.html), containing a detailed (byte-level) specification for OPAQUE
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
- [opaque-wasm](https://github.com/marucjmar/opaque-wasm), a WebAssembly package for this library
Contributors
------------
-68
View File
@@ -1,68 +0,0 @@
// 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_use]
extern crate criterion;
use criterion::Criterion;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::arr;
use opaque_ke::{
group::Group,
oprf::{blind_shim, evaluate_shim, finalize_shim},
};
use rand::{prelude::ThreadRng, thread_rng};
use sha2::Sha512;
fn oprf1(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
c.bench_function("blind with Ristretto", move |b| {
b.iter(|| {
blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
})
});
}
fn oprf2(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let (_, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
c.bench_function("evaluate with Ristretto", move |b| {
b.iter(|| {
let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
})
});
}
fn oprf3(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let (token, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
c.bench_function("finalize with Ristretto", move |b| {
b.iter(|| {
let _res = finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
})
});
}
criterion_group!(oprf_benches, oprf1, oprf2, oprf3);
criterion_main!(oprf_benches);
+29 -26
View File
@@ -33,10 +33,10 @@ use std::process::exit;
use opaque_ke::{
ciphersuite::CipherSuite,
rand::{rngs::OsRng, RngCore},
ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistration,
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration,
ServerLoginStartParameters, ServerRegistration, ServerSetup,
};
// The ciphersuite trait allows to specify the underlying primitives
@@ -81,7 +81,8 @@ fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
// Password-based registration and encryption of client secret message between a client and server
fn register_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<Default>,
locker_id: usize,
password: String,
secret_message: String,
) -> Locker {
@@ -91,12 +92,10 @@ fn register_locker(
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng,
&server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(),
&locker_id.to_be_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -121,10 +120,9 @@ fn register_locker(
// Client sends message_bytes to server
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
.unwrap();
let password_file = ServerRegistration::finish(
RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
);
Locker {
contents: ciphertext,
@@ -134,18 +132,15 @@ fn register_locker(
// Open the contents of a locker with a password between a client and server
fn open_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<Default>,
locker_id: usize,
password: String,
locker: &Locker,
) -> Result<String, String> {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
&mut client_rng,
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize().unwrap();
let client_login_start_result =
ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
@@ -154,13 +149,14 @@ fn open_locker(
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
password_file,
&server_kp.private(),
&server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize().unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
// Server sends credential_response_bytes to client
@@ -174,7 +170,7 @@ fn open_locker(
return Err(String::from("Incorrect password, please try again."));
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize();
// Client sends credential_finalization_bytes to server
@@ -200,7 +196,7 @@ fn open_locker(
fn main() {
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 registered_lockers: Vec<Locker> = vec![];
@@ -225,8 +221,10 @@ fn main() {
&mut rl,
None,
);
let locker_id = registered_lockers.len();
registered_lockers.push(register_locker(
&server_kp,
&server_setup,
locker_id,
password,
secret_message,
));
@@ -252,7 +250,12 @@ fn main() {
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) => {
println!("\n\nSuccess! Contents: {}\n\n", contents);
}
+27 -28
View File
@@ -27,10 +27,9 @@ use std::process::exit;
use opaque_ke::{
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters,
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload, ServerLogin, ServerLoginStartParameters,
ServerRegistration,
ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization,
CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration, ServerSetup,
};
// The ciphersuite trait allows to specify the underlying primitives
@@ -46,7 +45,8 @@ impl CipherSuite for Default {
// Password-based registration between a client and server
fn account_registration(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<Default>,
username: String,
password: String,
) -> Vec<u8> {
let mut client_rng = OsRng;
@@ -56,11 +56,10 @@ fn account_registration(
// Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng,
&server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(),
username.as_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -79,27 +78,23 @@ fn account_registration(
// Client sends message_bytes to server
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
.unwrap();
let password_file = ServerRegistration::finish(
RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
);
password_file.serialize()
}
// Password-based login between a client and server
fn account_login(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<Default>,
username: String,
password: String,
password_file_bytes: &[u8],
) -> bool {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
&mut client_rng,
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize().unwrap();
let client_login_start_result =
ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
@@ -107,13 +102,14 @@ fn account_login(
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
password_file,
&server_kp.private(),
&server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
username.as_bytes(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize().unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
// Server sends credential_response_bytes to client
@@ -127,7 +123,7 @@ fn account_login(
return false;
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize();
// Client sends credential_finalization_bytes to server
@@ -141,7 +137,7 @@ fn account_login(
fn main() {
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 registered_users = HashMap::<String, Vec<u8>>::new();
@@ -164,13 +160,16 @@ fn main() {
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
match line.as_ref() {
"1" => {
registered_users
.insert(username, account_registration(&server_kp, password));
registered_users.insert(
username.clone(),
account_registration(&server_setup, username, password),
);
continue;
}
"2" => match registered_users.get(&username) {
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!");
} else {
// 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
use crate::{
hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
hash::Hash, key_exchange::traits::KeyExchange, map_to_curve::GroupWithMapToCurve,
slow_hash::SlowHash,
};
use digest::Digest;
use rand::{CryptoRng, RngCore};
/// Configures the underlying primitives used in OPAQUE
/// * `Group`: a finite cyclic group along with a point representation, along
/// with an extension trait PasswordToCurve that allows some customization on
@@ -33,9 +31,4 @@ pub trait CipherSuite {
type Hash: Hash;
/// A slow hashing function, typically used for password hashing
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)
}
}
+175 -217
View File
@@ -4,10 +4,13 @@
// LICENSE file in the root directory of this source tree.
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,
keypair::Key,
serialization::serialize,
keypair::{KeyPair, PrivateKey, PublicKey},
map_to_curve::GroupWithMapToCurve,
opaque::{bytestrings_from_identifiers, Identifiers},
};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
@@ -19,80 +22,62 @@ use std::convert::TryFrom;
use zeroize::Zeroize;
// Constant string used as salt for HKDF computation
const STR_PAD: &[u8] = b"Pad";
const STR_AUTH_KEY: &[u8] = b"AuthKey";
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;
#[derive(Clone, Copy, PartialEq, Zeroize)]
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, Debug, Eq, Hash, PartialEq, Zeroize)]
#[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode {
Base = 1,
CustomIdentifier = 2,
Zero = 0,
Internal = 1,
}
impl TryFrom<u8> for InnerEnvelopeMode {
type Error = PakeError;
fn try_from(x: u8) -> Result<Self, Self::Error> {
match x {
1 => Ok(InnerEnvelopeMode::Base),
2 => Ok(InnerEnvelopeMode::CustomIdentifier),
1 => Ok(InnerEnvelopeMode::Internal),
_ => Err(PakeError::SerializationError),
}
}
}
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub(crate) struct InnerEnvelope {
mode: InnerEnvelopeMode,
nonce: Vec<u8>,
ciphertext: Vec<u8>,
}
impl InnerEnvelope {
pub(crate) fn serialize(&self) -> Vec<u8> {
[&[self.mode as u8], &self.nonce[..], &self.ciphertext[..]].concat()
}
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
if input.is_empty() {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let mode = InnerEnvelopeMode::try_from(input[0])?;
let key_len = <Key as SizedBytes>::Len::to_usize();
let bytes = &input[1..];
if bytes.len() < NONCE_LEN + key_len {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
Ok((
Self {
mode,
nonce: bytes[..NONCE_LEN].to_vec(),
ciphertext: bytes[NONCE_LEN..NONCE_LEN + key_len].to_vec(),
},
bytes[NONCE_LEN + key_len..].to_vec(),
))
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
/* Cannot easily get raw pointer of enum value, otherwise would do self.mode.as_ptr() */
(self.nonce.as_ptr(), self.nonce.len()),
(self.ciphertext.as_ptr(), self.ciphertext.len()),
]
}
}
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
///
@@ -103,131 +88,153 @@ impl InnerEnvelope {
/// The specification update has simplified this assumption by taking
/// an XOR-based approach without compromising on security, and to avoid
/// the confusion around the implementation of an RKR-secure encryption.
#[derive(Clone)]
pub(crate) struct Envelope<D: Hash> {
inner_envelope: InnerEnvelope,
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
pub(crate) struct Envelope<CS: CipherSuite> {
mode: InnerEnvelopeMode,
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.clone(),
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
// key. This key is also used to derive the export_key parameter, which is technically
// unrelated to the envelope's encrypted and authenticated contents.
pub(crate) struct OpenedEnvelope<D: Hash> {
pub(crate) client_s_sk: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
pub(crate) struct OpenedEnvelope<CS: CipherSuite> {
pub(crate) client_static_keypair: KeyPair<CS::Group>,
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) plaintext: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
}
impl<D: Hash> Envelope<D> {
impl<CS: CipherSuite> Envelope<CS> {
fn hmac_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
<CS::Hash as Digest>::OutputSize::to_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 {
self.inner_envelope.mode
}
/// 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 len() -> usize {
<CS::Hash as Digest>::OutputSize::to_usize() + NONCE_LEN
}
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> {
let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?;
if bytes.len() < NONCE_LEN {
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_and_remainder =
check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
Ok((
Self {
inner_envelope,
hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]),
},
hmac_and_remainder[hmac_key_size..].to_vec(),
))
Ok(Self {
mode,
nonce,
hmac: GenericArray::clone_from_slice(hmac),
})
}
// 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>(
rng: &mut R,
key: &[u8],
client_s_sk: &[u8],
server_s_pk: &[u8],
optional_ids: Option<(Vec<u8>, Vec<u8>)>,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), ProtocolError> {
let aad = construct_aad(server_s_pk, &optional_ids)?;
Self::seal_raw(rng, key, &client_s_sk, &aad, mode_from_ids(&optional_ids))
optional_ids: Option<Identifiers>,
) -> Result<
(
Self,
PublicKey,
GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
),
InternalPakeError,
> {
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let (mode, client_s_pk) = (
InnerEnvelopeMode::Internal,
build_inner_envelope_internal::<CS>(key, &nonce)?,
);
let (id_u, id_s) =
bytestrings_from_identifiers(&optional_ids, &client_s_pk.to_arr(), server_s_pk);
let aad = construct_aad(&id_u, &id_s, server_s_pk);
let (envelope, export_key) = Self::seal_raw(key, &nonce, &aad, mode)?;
Ok((envelope, client_s_pk, export_key))
}
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
/// Note that a new nonce is sampled for each call to seal.
pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
rng: &mut R,
#[allow(clippy::type_complexity)]
pub(crate) fn seal_raw(
key: &[u8],
plaintext: &[u8],
nonce: &[u8],
aad: &[u8],
mode: InnerEnvelopeMode,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), ProtocolError> {
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let h = Hkdf::<D>::new(Some(&nonce), &key);
let mut xor_key = vec![0u8; plaintext.len()];
) -> Result<(Self, GenericArray<u8, <CS::Hash as Digest>::OutputSize>), InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, key);
let mut hmac_key = vec![0u8; Self::hmac_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)?;
h.expand(STR_AUTH_KEY, &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
h.expand(&[nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = xor_key
.iter()
.zip(plaintext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.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 mut hmac = Hmac::<CS::Hash>::new_from_slice(&hmac_key)
.map_err(|_| InternalPakeError::HmacError)?;
hmac.update(nonce);
hmac.update(aad);
let hmac_bytes = hmac.finalize().into_bytes();
Ok((
Self {
inner_envelope,
mode,
nonce: nonce.to_vec(),
hmac: hmac_bytes,
},
GenericArray::clone_from_slice(&export_key),
@@ -238,24 +245,29 @@ impl<D: Hash> Envelope<D> {
&self,
key: &[u8],
server_s_pk: &[u8],
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
) -> Result<OpenedEnvelope<D>, ProtocolError> {
// First, check that mode matches
if self.inner_envelope.mode != mode_from_ids(optional_ids) {
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into());
}
optional_ids: &Option<Identifiers>,
) -> Result<OpenedEnvelope<CS>, InternalPakeError> {
let client_static_keypair = match self.mode {
InnerEnvelopeMode::Zero => {
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)?;
if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
// Plaintext should consist of a single key
return Err(InternalPakeError::UnexpectedEnvelopeContentsError.into());
}
Ok(OpenedEnvelope {
client_s_sk: opened.plaintext,
client_static_keypair,
export_key: opened.export_key,
id_u,
id_s,
})
}
@@ -265,35 +277,26 @@ impl<D: Hash> Envelope<D> {
&self,
key: &[u8],
aad: &[u8],
) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> {
let h = Hkdf::<D>::new(Some(&self.inner_envelope.nonce), &key);
let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, key);
let mut hmac_key = vec![0u8; Self::hmac_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)?;
h.expand(STR_AUTH_KEY, &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
h.expand(&[&self.nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.inner_envelope.serialize());
let mut hmac = Hmac::<CS::Hash>::new_from_slice(&hmac_key)
.map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.nonce);
hmac.update(aad);
if hmac.verify(&self.hmac).is_err() {
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 {
plaintext,
export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
export_key: GenericArray::<u8, <CS::Hash as Digest>::OutputSize>::clone_from_slice(
&export_key,
),
})
@@ -301,23 +304,20 @@ impl<D: Hash> Envelope<D> {
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
[
self.inner_envelope.as_byte_ptrs(),
vec![(self.hmac.as_ptr(), self.hmac.len())],
]
.concat()
vec![(self.hmac.as_ptr(), self.hmac.len())]
}
}
// This can't be derived because of the use of a phantom parameter
impl<D: Hash> Zeroize for Envelope<D> {
impl<CS: CipherSuite> Zeroize for Envelope<CS> {
fn zeroize(&mut self) {
self.inner_envelope.zeroize();
self.mode.zeroize();
self.nonce.zeroize();
self.hmac.zeroize();
}
}
impl<D: Hash> Drop for Envelope<D> {
impl<CS: CipherSuite> Drop for Envelope<CS> {
fn drop(&mut self) {
self.zeroize();
}
@@ -325,48 +325,6 @@ impl<D: Hash> Drop for Envelope<D> {
// Helper functions
fn construct_aad(
server_s_pk: &[u8],
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
) -> Result<Vec<u8>, ProtocolError> {
let ids = match optional_ids {
Some((l, r)) => [serialize(l, 2)?, serialize(r, 2)?].concat(),
None => vec![],
};
Ok([server_s_pk.to_vec(), ids].concat())
}
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
match optional_ids {
Some(_) => InnerEnvelopeMode::CustomIdentifier,
None => InnerEnvelopeMode::Base,
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn seal_and_open() {
let mut rng = OsRng;
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
&mut rng,
&key,
&msg,
b"aad",
InnerEnvelopeMode::Base,
)
.unwrap();
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
}
fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> {
[server_s_pk, id_s, id_u].concat()
}
+5 -6
View File
@@ -8,7 +8,7 @@ use displaydoc::Display;
use thiserror::Error;
/// 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 {
/// Deserializing from a byte sequence failed
InvalidByteSequence,
@@ -56,7 +56,7 @@ pub enum InternalPakeError {
}
/// Represents an error in password checking
#[derive(Debug, Display, Error)]
#[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum PakeError {
/// This error results from an internal error during PRF construction
///
@@ -86,12 +86,14 @@ impl From<InternalPakeError> for PakeError {
}
/// Represents an error in protocol handling
#[derive(Debug, Display, Error)]
#[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum ProtocolError {
/// This error results from an error during password verification
///
/// Internal error during password verification: {0}
VerificationError(PakeError),
/// This error occurs when the inner envelope is malformed
InvalidInnerEnvelopeError,
/// This error occurs when the server answer cannot be handled
/// Server response cannot be handled.
ServerError,
@@ -101,9 +103,6 @@ pub enum ProtocolError {
/// This error occurs when the client request cannot be handled
/// Client request cannot be handled.
ClientError,
/// This error occurs when the client detects that the server has
/// reflected the OPRF value (beta == alpha)
ReflectedValueError,
}
// This is meant to express future(ly) non-trivial ways of converting the
-7
View File
@@ -68,9 +68,6 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool;
/// Compares in constant time if the group elements are equal
fn ct_equal(&self, other: &Self) -> bool;
}
/// The implementation of such a subgroup for Ristretto
@@ -154,8 +151,4 @@ impl Group for RistrettoPoint {
fn is_identity(&self) -> bool {
self == &Self::identity()
}
fn ct_equal(&self, other: &Self) -> bool {
constant_time_eq::constant_time_eq(&self.to_arr(), &other.to_arr())
}
}
+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(),)*
)
}
}
};
}
+21 -18
View File
@@ -4,25 +4,23 @@
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::{PakeError, ProtocolError},
group::Group,
hash::Hash,
keypair::Key,
keypair::{PrivateKey, PublicKey},
};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use zeroize::Zeroize;
pub trait KeyExchange<D: Hash, G: Group> {
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytesWithPointers + Zeroize;
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytesWithPointers + Zeroize;
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes + Clone;
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE1State: FromBytes + ToBytesWithPointers + Zeroize + Clone;
type KE2State: FromBytes + ToBytesWithPointers + Zeroize + Clone;
type KE1Message: FromBytes + ToBytes + Clone;
type KE2Message: FromBytes + ToBytes + Clone;
type KE3Message: FromBytes + ToBytes + Clone;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
@@ -32,12 +30,12 @@ pub trait KeyExchange<D: Hash, G: Group> {
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
client_s_pk: PublicKey,
server_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
context: Vec<u8>,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3(
@@ -45,11 +43,12 @@ pub trait KeyExchange<D: Hash, G: Group> {
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
server_s_pk: PublicKey,
client_s_sk: PrivateKey,
id_u: 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)]
fn finish_ke(
@@ -60,12 +59,16 @@ pub trait KeyExchange<D: Hash, G: Group> {
fn ke2_message_size() -> usize;
}
pub trait FromBytes: Sized {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError>;
}
pub trait ToBytes {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
fn to_bytes(&self) -> Vec<u8>;
}
pub trait ToBytesWithPointers {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
fn to_bytes(&self) -> Vec<u8>;
// Only used for tests to grab raw pointers to data
#[cfg(test)]
+111 -157
View File
@@ -5,15 +5,16 @@
//! An implementation of the Triple Diffie-Hellman key exchange protocol
use crate::{
ciphersuite::CipherSuite,
errors::{
utils::{check_slice_size, check_slice_size_atleast},
InternalPakeError, PakeError, ProtocolError,
},
group::Group,
hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes, ToBytesWithPointers},
keypair::{Key, KeyPair, SizedBytesExt},
serialization::{serialize, tokenize},
key_exchange::traits::{FromBytes, KeyExchange, ToBytes, ToBytesWithPointers},
keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
serialization::serialize,
};
use digest::{Digest, FixedOutput};
use generic_array::{
@@ -30,14 +31,12 @@ use zeroize::Zeroize;
const KEY_LEN: usize = 32;
pub(crate) type NonceLen = U32;
static STR_3DH: &[u8] = b"3DH";
static STR_CLIENT_MAC: &[u8] = b"client mac";
static STR_HANDSHAKE_SECRET: &[u8] = b"handshake secret";
static STR_SERVER_MAC: &[u8] = b"server mac";
static STR_HANDSHAKE_ENC: &[u8] = b"handshake enc";
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
static STR_SESSION_SECRET: &[u8] = b"session secret";
static STR_OPAQUE: &[u8] = b"OPAQUE ";
static STR_RFC: &[u8] = b"RFCXXXX";
static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
static STR_SERVER_MAC: &[u8] = b"ServerMAC";
static STR_SESSION_KEY: &[u8] = b"SessionKey";
static STR_OPAQUE: &[u8] = b"OPAQUE-";
#[allow(clippy::upper_case_acronyms)]
/// The Triple Diffie-Hellman key exchange implementation
@@ -51,7 +50,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyPair::<G>::generate_random(rng);
@@ -59,7 +57,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
let ke1_message = Ke1Message {
client_nonce,
info,
client_e_pk: client_e_kp.public().clone(),
};
@@ -78,25 +75,26 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
serialized_credential_request: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
client_s_pk: PublicKey,
server_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> {
context: Vec<u8>,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyPair::<G>::generate_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let mut transcript_hasher = D::new()
.chain(STR_3DH)
.chain(&serialize(&id_u, 2)?)
.chain(STR_RFC)
.chain(serialize(&context, 2))
.chain(id_u)
.chain(&serialized_credential_request[..])
.chain(&serialize(&id_s, 2)?)
.chain(id_s)
.chain(&l2_bytes[..])
.chain(&server_nonce[..])
.chain(&server_e_kp.public().to_arr());
.chain(server_e_kp.public().to_arr());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke1_message.client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
@@ -108,28 +106,14 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
&transcript_hasher.clone().finalize(),
)?;
// Compute encryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = encryption_pad
.iter()
.zip(e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
transcript_hasher.update(&serialize(&ciphertext, 2)?);
let mut mac_hasher =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
Hmac::<D>::new_from_slice(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac_hasher.update(&transcript_hasher.clone().finalize());
let mac = mac_hasher.finalize().into_bytes();
transcript_hasher.update(&mac);
Ok((
ke1_message.info,
Ke2State {
km3,
hashed_transcript: transcript_hasher.finalize(),
@@ -138,7 +122,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
Ke2Message {
server_nonce,
server_e_pk: server_e_kp.public().clone(),
e_info: ciphertext,
mac,
},
))
@@ -150,20 +133,22 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
server_s_pk: PublicKey,
client_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
let mut transcript_hasher = D::new()
.chain(STR_3DH)
.chain(&serialize(&id_u, 2)?)
.chain(&serialized_credential_request)
.chain(&serialize(&id_s, 2)?)
.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());
.chain(ke2_message.to_bytes_without_info_or_mac());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
@@ -175,10 +160,8 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
&transcript_hasher.clone().finalize(),
)?;
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2)?);
let mut server_mac =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
Hmac::<D>::new_from_slice(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&transcript_hasher.clone().finalize());
if server_mac.verify(&ke2_message.mac).is_err() {
@@ -187,25 +170,13 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
));
}
transcript_hasher.update(ke2_message.mac.to_vec());
transcript_hasher.update(ke2_message.mac);
let mut client_mac =
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
Hmac::<D>::new_from_slice(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&transcript_hasher.finalize());
// 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((
plaintext,
session_key.to_vec(),
Ke3Message {
mac: client_mac.finalize().into_bytes(),
@@ -219,7 +190,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> {
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);
if client_mac.verify(&ke3_message.mac).is_err() {
@@ -237,30 +208,29 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
}
/// The client state produced after the first key exchange message
#[derive(PartialEq, Eq, Zeroize)]
#[derive(PartialEq, Eq, Debug, Hash, Zeroize, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[zeroize(drop)]
pub struct Ke1State {
client_e_sk: Key,
client_e_sk: PrivateKey,
client_nonce: GenericArray<u8, NonceLen>,
}
/// 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(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) info: Vec<u8>,
pub(crate) client_e_pk: Key,
pub(crate) client_e_pk: PublicKey,
}
impl TryFrom<&[u8]> for Ke1State {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
impl FromBytes for Ke1State {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
let nonce_len = NonceLen::to_usize();
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
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(
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
),
@@ -269,9 +239,9 @@ impl TryFrom<&[u8]> for Ke1State {
}
impl ToBytesWithPointers for Ke1State {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
Ok(output)
output
}
#[cfg(test)]
@@ -279,7 +249,7 @@ impl ToBytesWithPointers for Ke1State {
vec![
(
self.client_e_sk.as_ptr(),
<Key as SizedBytes>::Len::to_usize(),
<PrivateKey as SizedBytes>::Len::to_usize(),
),
(self.client_nonce.as_ptr(), NonceLen::to_usize()),
]
@@ -287,36 +257,27 @@ impl ToBytesWithPointers for Ke1State {
}
impl ToBytes for Ke1Message {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
&self.client_nonce[..],
&serialize(&self.info, 2)?,
&self.client_e_pk.to_arr(),
]
.concat())
fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl TryFrom<&[u8]> for Ke1Message {
type Error = PakeError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
impl FromBytes for Ke1Message {
fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, PakeError> {
let nonce_len = NonceLen::to_usize();
let checked_nonce =
check_slice_size_atleast(ke1_message_bytes, nonce_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")?;
check_slice_size(ke1_message_bytes, nonce_len + KEY_LEN, "ke1_message nonce")?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
info,
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
})
}
}
/// 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>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
@@ -339,13 +300,13 @@ impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
}
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat())
.concat()
}
#[cfg(test)]
@@ -359,18 +320,17 @@ impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
}
/// The second key exchange message
#[derive(Clone)]
#[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: Key,
e_info: Vec<u8>,
server_e_pk: PublicKey,
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
type Error = PakeError;
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 checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
@@ -385,13 +345,8 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
&self.to_bytes_without_info_or_mac(),
&serialize(&self.e_info, 2)?,
&self.mac[..],
]
.concat())
fn to_bytes(&self) -> Vec<u8> {
[&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
}
}
@@ -401,25 +356,31 @@ impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2Message<HashLen> {
type Error = PakeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError> {
let nonce_len = NonceLen::to_usize();
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..],
KEY_LEN,
"ke2_message server_e_pk",
)?;
let (e_info, remainder) = tokenize(&checked_server_e_pk[KEY_LEN..], 2)?;
let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?;
let checked_mac = check_slice_size(
&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 {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: Key::from_bytes(&checked_server_e_pk[..KEY_LEN])?,
e_info,
mac: GenericArray::clone_from_slice(&checked_mac),
server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
mac: GenericArray::clone_from_slice(checked_mac),
})
}
}
@@ -427,42 +388,42 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2Message<HashLen> {
#[allow(clippy::upper_case_acronyms)]
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: Key,
sk1: Key,
pk2: Key,
sk2: Key,
pk3: Key,
sk3: Key,
pk1: PublicKey,
sk1: PrivateKey,
pk2: PublicKey,
sk2: PrivateKey,
pk3: PublicKey,
sk3: PrivateKey,
}
#[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> = (
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
#[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>> {
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok(self.mac.to_vec())
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke3Message<HashLen> {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&bytes, HashLen::to_usize(), "ke3_message")?;
impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
let checked_bytes = check_slice_size(bytes, HashLen::to_usize(), "ke3_message")?;
Ok(Self {
mac: GenericArray::clone_from_slice(&checked_bytes),
mac: GenericArray::clone_from_slice(checked_bytes),
})
}
}
@@ -485,30 +446,24 @@ fn derive_3dh_keys<D: Hash, G: Group>(
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>(
&extracted_ikm,
&STR_HANDSHAKE_SECRET,
&hashed_derivation_transcript,
STR_HANDSHAKE_SECRET,
hashed_derivation_transcript,
)?;
let session_key = derive_secrets::<D>(
&extracted_ikm,
&STR_SESSION_SECRET,
&hashed_derivation_transcript,
STR_SESSION_KEY,
hashed_derivation_transcript,
)?;
let km2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let ke2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_HANDSHAKE_ENC,
STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let km3 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_CLIENT_MAC,
STR_CLIENT_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
@@ -516,7 +471,6 @@ fn derive_3dh_keys<D: Hash, G: Group>(
Ok((
GenericArray::clone_from_slice(&session_key),
GenericArray::clone_from_slice(&km2),
GenericArray::clone_from_slice(&ke2),
GenericArray::clone_from_slice(&km3),
))
}
@@ -545,11 +499,11 @@ fn hkdf_expand_label_extracted<D: Hash>(
hkdf_label.extend_from_slice(&length_u16.to_be_bytes());
let mut opaque_label: Vec<u8> = Vec::new();
opaque_label.extend_from_slice(&STR_OPAQUE);
opaque_label.extend_from_slice(&label);
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1)?);
opaque_label.extend_from_slice(STR_OPAQUE);
opaque_label.extend_from_slice(label);
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)
.map_err(|_| InternalPakeError::HkdfError)?;
@@ -564,7 +518,7 @@ fn derive_secrets<D: Hash>(
hkdf_expand_label_extracted::<D>(
hkdf,
label,
&hashed_derivation_transcript,
hashed_derivation_transcript,
<D as Digest>::OutputSize::to_usize(),
)
}
+106 -32
View File
@@ -35,13 +35,22 @@ pub trait SizedBytesExt: SizedBytes {
impl<T> SizedBytesExt for T where T: SizedBytes {}
/// 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> {
pk: Key,
sk: Key,
pk: PublicKey,
sk: PrivateKey,
_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) {
@@ -58,12 +67,12 @@ impl<G> Drop for KeyPair<G> {
impl<G: Group> KeyPair<G> {
/// The public key component
pub fn public(&self) -> &Key {
pub fn public(&self) -> &PublicKey {
&self.pk
}
/// The private key component
pub fn private(&self) -> &Key {
pub fn private(&self) -> &PrivateKey {
&self.sk
}
@@ -71,40 +80,46 @@ impl<G: Group> KeyPair<G> {
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_nonzero_scalar(rng);
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 {
pk: Key(pk.to_arr().to_vec()),
sk: Key(sk_bytes.to_vec()),
pk: PublicKey(Key(pk.to_arr().to_vec())),
sk: PrivateKey(Key(sk_bytes.to_vec())),
_g: PhantomData,
}
}
/// Obtaining a public key from secret bytes. At all times, we should have
/// &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[..]);
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
/// material provided through the network which fits the key
/// representation (i.e. can be mapped to a curve point), but presents
/// 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)
}
/// 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 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[..]);
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
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);
Ok(Self {
pk,
@@ -116,8 +131,8 @@ impl<G: Group> KeyPair<G> {
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.pk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
(self.sk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
(self.pk.as_ptr(), KeyLen::to_usize()),
(self.sk.as_ptr(), KeyLen::to_usize()),
]
}
}
@@ -129,18 +144,21 @@ impl<G: Group + Debug> KeyPair<G> {
fn uniform_keypair_strategy() -> BoxedStrategy<Self> {
// The no_shrink is because keypairs should be fixed -- shrinking would cause a different
// keypair to be generated, which appears to not be very useful.
any::<[u8; 32]>()
.prop_filter_map("valid random keypair", |seed| {
prop::array::uniform32(0_u8..)
.prop_map(|seed| {
let mut rng = StdRng::from_seed(seed);
Some(Self::generate_random(&mut rng))
Self::generate_random(&mut rng)
})
.no_shrink()
.boxed()
}
}
type KeyLen = U32;
/// A minimalist key type built around a \[u8; 32\]
#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
#[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)]
@@ -154,18 +172,74 @@ impl Deref for Key {
}
}
impl SizedBytes for Key {
type Len = U32;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
// Don't make it implement SizedBytes so that it's not constructible outside of this module.
impl Key {
fn to_arr(&self) -> GenericArray<u8, KeyLen> {
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()))
}
}
/// 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)]
mod tests {
use super::*;
@@ -177,7 +251,7 @@ mod tests {
#[test]
fn test_zeroize_key() -> Result<(), ProtocolError> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let key_len = KeyLen::to_usize();
let mut key = Key(vec![1u8; key_len]);
let ptr = key.as_ptr();
@@ -207,21 +281,21 @@ mod tests {
proptest! {
#[test]
fn test_ristretto_check(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
fn test_ristretto_check(ref kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
}
#[test]
fn test_ristretto_pub_from_priv(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
fn test_ristretto_pub_from_priv(ref kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&KeyPair::<RistrettoPoint>::public_from_private(sk), pk);
}
#[test]
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
fn test_ristretto_dh(ref kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
ref kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
@@ -230,7 +304,7 @@ mod tests {
}
#[test]
fn test_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
fn test_private_key_slice(ref kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
+178 -130
View File
@@ -5,7 +5,7 @@
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
//!
//! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html),
//! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-05](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-05.html),
//! but this specification is subject to change, until the final version published by the IETF.
//!
//! # Overview
@@ -19,7 +19,7 @@
//!
//! We will use the following choices in this example:
//! ```
//! use opaque_ke::ciphersuite::CipherSuite;
//! use opaque_ke::CipherSuite;
//! struct Default;
//! impl CipherSuite for Default {
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -32,14 +32,15 @@
//! 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
//! that the tests execute quickly. A real application should use an actual slow hashing function, such as `scrypt`,
//! which can be enabled through the `slow-hash` feature.
//! 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. See more details in the [features](#features) section.
//!
//! ## 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::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # use opaque_ke::ServerSetup;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -49,17 +50,17 @@
//! # }
//! use rand::{rngs::OsRng, RngCore};
//! let mut rng = OsRng;
//! let server_kp = Default::generate_random_keypair(&mut rng);
//! let server_setup = ServerSetup::<Default>::new(&mut rng);
//! # Ok::<(), ProtocolError>(())
//! ```
//! The server must persist this keypair for the registration and login steps, where the public component will be
//! used by the client during both registration and login, and the private component will be used by the server during login.
//! The server must persist an instance of [ServerSetup] for the registration and login steps.
//!
//! ## Registration
//! 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
//! server producing a password file corresponding to the password provided by
//! the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.
//! 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 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
//! In the first step of registration, the client chooses as input a registration password. The client runs [ClientRegistration::start]
@@ -71,7 +72,7 @@
//! # ServerRegistration,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -90,18 +91,18 @@
//! ```
//!
//! ### Server Registration Start
//! In the second step of registration, the server takes as input the instance of [RegistrationRequest] from the client, and
//! the server's public key `server_kp.public()`.
//! The server runs [ServerRegistration::start] to produce an a [ServerRegistrationStartResult], which consists of
//! a [RegistrationResponse] to be returned to the client and
//! a [ServerRegistration] which must be persisted on the server for the final step of server registration.
//! In the second step of registration, the server takes as input a persisted instance of [ServerSetup], a [RegistrationRequest] from the client, and
//! a server-side identifier for the client.
//! The server runs [ServerRegistration::start] to produce a [ServerRegistrationStartResult], which consists of
//! a [RegistrationResponse] to be returned to the client.
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration,
//! # ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -116,12 +117,12 @@
//! # b"password",
//! # )?;
//! use opaque_ke::ServerRegistration;
//! let mut server_rng = OsRng;
//! let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! let server_registration_start_result = ServerRegistration::<Default>::start(
//! &mut server_rng,
//! &server_setup,
//! client_registration_start_result.message,
//! server_kp.public(),
//! b"[email protected]",
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
@@ -135,10 +136,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -153,8 +154,8 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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,
@@ -173,10 +174,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -191,19 +192,20 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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 password_file = server_registration_start_result.state.finish(
//! let password_file = ServerRegistration::<Default>::finish(
//! client_registration_finish_result.message,
//! )?;
//! );
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! ## Login
//! 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
//! 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.
//!
//! ### Client Login Start
@@ -217,7 +219,7 @@
//! # ClientRegistration, ServerRegistration, ServerLogin, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -226,31 +228,31 @@
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! use opaque_ke::{ClientLogin, ClientLoginStartParameters};
//! use opaque_ke::ClientLogin;
//! let mut client_rng = OsRng;
//! let client_login_start_result = ClientLogin::<Default>::start(
//! &mut client_rng,
//! b"password",
//! ClientLoginStartParameters::default(),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! ### Server Login Start
//! In the second step of login, the server takes as input
//! a [CredentialRequest] from the client,
//! the server's private key `server_kp.private()`, and
//! the password file output from registration.
//! a persisted instance of [ServerSetup],
//! 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
//! a [CredentialResponse] which is returned to the client, and
//! a [ServerLogin] which must be persisted on the server for the final step of login.
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -265,27 +267,32 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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 password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
//! &mut server_rng,
//! password_file,
//! &server_kp.private(),
//! &server_setup,
//! Some(password_file),
//! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters::default(),
//! )?;
//! # 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
//! In the third step of login, the client takes as input a [CredentialResponse] from the server.
@@ -295,10 +302,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -313,21 +320,20 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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 password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # 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(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
@@ -341,10 +347,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -359,21 +365,20 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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 password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # 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(
//! # server_login_start_result.message,
//! # ClientLoginFinishParameters::default(),
@@ -412,14 +417,15 @@
//!
//! A [ClientLoginFinishResult] contains the `server_s_pk` field, which is represents the static public key of the server that is established
//! 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::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -434,25 +440,26 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! // During setup, server generates its static keypair
//! let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//!
//! // During setup or registration, the server transmits its static public key to the client
//! let server_s_pk = server_kp.public(); // obtained from the server
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! // During registration, the client obtains a ClientRegistrationFinishResult with
//! // a server_s_pk field
//! 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 = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # 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...
//! let client_login_finish_result = client_login_start_result.state.finish(
@@ -460,11 +467,11 @@
//! ClientLoginFinishParameters::default(),
//! )?;
//!
//! // Check that the server's static public key matches what was obtained during
//! // setup or registration
//! // Check that the server's static public key obtained from login matches what
//! // was obtained during registration
//! assert_eq!(
//! &client_registration_finish_result.server_s_pk,
//! &client_login_finish_result.server_s_pk,
//! server_s_pk,
//! );
//! # Ok::<(), ProtocolError>(())
//! ```
@@ -492,10 +499,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -510,26 +517,25 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! // During registration...
//! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng,
//! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::default()
//! )?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # 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...
//! let client_login_finish_result = client_login_start_result.state.finish(
@@ -546,8 +552,10 @@
//!
//! ## 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
//! identify the corresponding password file established during registration. The server may also have an identifier corresponding to an entity (e.g. facebook.com).
//! 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. 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.
//!
//! But, for applications that wish to cryptographically bind these identities to
@@ -556,10 +564,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
//! # ClientRegistration, ClientRegistrationFinishParameters, Identifiers, ServerRegistration, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -574,14 +582,16 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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(),
//! Identifiers::ClientAndServerIdentifiers(
//! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! ),
//! ),
//! )?;
//! # Ok::<(), ProtocolError>(())
@@ -591,10 +601,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, CredentialFinalization, Identifiers, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -609,26 +619,28 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # 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 password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! # let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
//! &mut server_rng,
//! password_file,
//! &server_kp.private(),
//! &server_setup,
//! Some(password_file),
//! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters::WithIdentifiers(
//! b"username".to_vec(),
//! b"facebook.com".to_vec(),
//! Identifiers::ClientAndServerIdentifiers(
//! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! ),
//! ),
//! )?;
//! # Ok::<(), ProtocolError>(())
@@ -638,10 +650,10 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, Identifiers, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -656,48 +668,74 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # 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 password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # 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(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # 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(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::WithIdentifiers(
//! b"username".to_vec(),
//! b"facebook.com".to_vec(),
//! Identifiers::ClientAndServerIdentifiers(
//! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! ),
//! ),
//! )?;
//!
//! # 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
//! 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
//! 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.
//! ## Key Exchange Context
//!
//! The following three messages support passing of additional data:
//! - The first login message, where the client can populate [ClientLoginStartParameters::WithInfo] with plaintext additional data, and
//! the server can retrieve using the `plain_info` field of [ServerLoginStartResult].
//! - The second login message, where the server can populate [ServerLoginStartParameters::WithInfo] with confidential additional data,
//! and the client can retrieve using the `confidential_info` field of [ClientLoginFinishResult].
//! A key exchange protocol typically allows for the specifying of shared "context" information between the two parties before the exchange is complete,
//! so as to bind the integrity of application-specific data or configuration parameters to the security of the key exchange.
//! During the login phase, the client and server can specify this context using:
//! - The second login message, where the server can populate [ServerLoginStartParameters::WithContext], and
//! - 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
//! [custom identifiers](#custom-identifiers), with the ordering of the fields as `WithInfoAndIdentifiers(confidential_info, username, server_name)`.
//! 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
//! `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))]
@@ -709,9 +747,18 @@ compile_error!(
please enable one of: u64_backend, u32_backend"
);
#[cfg(test)]
#[macro_use]
extern crate proptest;
// Error types
pub mod errors;
#[macro_use]
mod impls;
#[macro_use]
mod serialization;
// High-level API
mod opaque;
@@ -735,8 +782,6 @@ mod oprf;
pub mod slow_hash;
mod serialization;
#[cfg(test)]
mod tests;
@@ -744,17 +789,20 @@ mod tests;
pub use rand;
pub use ciphersuite::CipherSuite;
pub use crate::messages::{
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload,
};
pub use crate::opaque::{ClientLogin, ClientRegistration, ServerLogin, ServerRegistration};
pub use crate::opaque::{
ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistrationFinishParameters,
ServerLoginStartParameters,
ClientLogin, ClientRegistration, ServerLogin, ServerRegistration, ServerSetup,
};
pub use crate::opaque::{
ClientLoginFinishParameters, ClientRegistrationFinishParameters, ServerLoginStartParameters,
};
pub use crate::opaque::{
ClientLoginFinishResult, ClientLoginStartResult, ClientRegistrationFinishResult,
ClientRegistrationStartResult, ServerLoginFinishResult, ServerLoginStartResult,
ClientRegistrationStartResult, Identifiers, ServerLoginFinishResult, ServerLoginStartResult,
ServerRegistrationStartResult,
};
+29 -13
View File
@@ -6,7 +6,7 @@
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
//! curve point
use crate::errors::{InternalPakeError, ProtocolError};
use crate::errors::InternalPakeError;
use crate::group::Group;
use crate::hash::Hash;
use crate::serialization::i2osp;
@@ -22,12 +22,16 @@ pub trait GroupWithMapToCurve: Group {
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError>;
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
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
fn get_context_string(mode: u8) -> Result<Vec<u8>, ProtocolError> {
Ok([i2osp(mode as usize, 1)?, i2osp(Self::SUITE_ID, 2)?].concat())
fn get_context_string(mode: u8) -> Vec<u8> {
[i2osp(mode as usize, 1), i2osp(Self::SUITE_ID, 2)].concat()
}
}
@@ -36,13 +40,25 @@ impl GroupWithMapToCurve for RistrettoPoint {
// Implements the hash_to_ristretto255() function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError> {
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError> {
let uniform_bytes =
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
Ok(<Self as Group>::hash_to_curve(
&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)
@@ -65,24 +81,24 @@ pub(crate) fn expand_message_xmd<H: Hash>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, ProtocolError> {
) -> Result<Vec<u8>, InternalPakeError> {
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
let ell = div_ceil(len_in_bytes, b_in_bytes);
if ell > 255 {
return Err(InternalPakeError::HashToCurveError.into());
return Err(InternalPakeError::HashToCurveError);
}
let dst_prime = [dst, &i2osp(dst.len(), 1)?].concat();
let z_pad = i2osp(0, r_in_bytes)?;
let l_i_b_str = i2osp(len_in_bytes, 2)?;
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1)?, &dst_prime].concat();
let dst_prime = [dst, &i2osp(dst.len(), 1)].concat();
let z_pad = i2osp(0, r_in_bytes);
let l_i_b_str = i2osp(len_in_bytes, 2);
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1), &dst_prime].concat();
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1)?);
h.update(&i2osp(1, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
@@ -91,7 +107,7 @@ pub(crate) fn expand_message_xmd<H: Hash>(
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1)?);
h.update(&i2osp(i, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
+166 -97
View File
@@ -13,12 +13,14 @@ use crate::{
PakeError, ProtocolError,
},
group::Group,
key_exchange::traits::{KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytesExt},
key_exchange::traits::{FromBytes, KeyExchange, ToBytes},
keypair::{KeyPair, PublicKey, SizedBytesExt},
opaque::ServerSetup,
};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use std::convert::TryFrom;
use rand::{CryptoRng, RngCore};
// Messages
// =========
@@ -44,6 +46,8 @@ impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
}
}
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [alpha], [CS::Group]);
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
@@ -53,7 +57,7 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = check_slice_size(&input, elem_len, "first_message_bytes")?;
let checked_slice = check_slice_size(input, elem_len, "first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(checked_slice);
@@ -67,27 +71,45 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
}
}
impl_serialize_and_deserialize_for!(RegistrationRequest);
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) beta: CS::Group,
/// Server's static public key
pub(crate) server_s_pk: Vec<u8>,
pub(crate) server_s_pk: PublicKey,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationResponse<CS> {
fn clone(&self) -> Self {
Self {
beta: self.beta,
server_s_pk: self.server_s_pk.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct RegistrationResponse<CS: CipherSuite>,
[beta, server_s_pk],
[CS::Group],
);
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[self.beta.to_arr().to_vec(), self.server_s_pk.clone()].concat()
[self.beta.to_arr().to_vec(), self.server_s_pk.to_vec()].concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize();
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size(&input, elem_len + key_len, "registration_response_bytes")?;
check_slice_size(input, elem_len + key_len, "registration_response_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
@@ -100,41 +122,43 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
}
// Ensure that public key is valid
let server_s_pk =
KeyPair::<CS::Group>::check_public_key(Key::from_bytes(&checked_slice[elem_len..])?)?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[elem_len..],
)?)?;
Ok(Self {
beta,
server_s_pk: server_s_pk.to_arr().to_vec(),
})
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the reflection
/// error case
pub fn set_beta_for_testing(&self, new_beta: CS::Group) -> Self {
Self {
beta: new_beta,
server_s_pk: self.server_s_pk.clone(),
}
Ok(Self { server_s_pk, beta })
}
}
impl_serialize_and_deserialize_for!(RegistrationResponse);
/// The final message from the client, containing sealed cryptographic
/// identifiers
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
pub(crate) envelope: Envelope<CS::Hash>,
pub(crate) envelope: Envelope<CS>,
/// The masking key used to mask the envelope
pub(crate) masking_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The user's public key
pub(crate) client_s_pk: Key,
pub(crate) client_s_pk: PublicKey,
}
impl_clone_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
self.client_s_pk.to_arr().to_vec(),
self.masking_key.to_vec(),
self.envelope.serialize(),
]
.concat()
@@ -142,25 +166,40 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice = check_slice_size_atleast(&input, key_len, "registration_upload_bytes")?;
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[key_len..])?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.into());
}
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let hash_len = <CS::Hash as Digest>::OutputSize::to_usize();
let checked_slice =
check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
Ok(Self {
envelope,
client_s_pk: KeyPair::<CS::Group>::check_public_key(Key::from_bytes(
masking_key: GenericArray::clone_from_slice(
&checked_slice[key_len..key_len + hash_len],
),
client_s_pk: KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[..key_len],
)?)?,
})
}
// 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
pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information
@@ -168,21 +207,36 @@ pub struct CredentialRequest<CS: CipherSuite> {
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialRequest<CS> {
fn clone(&self) -> Self {
Self {
alpha: self.alpha,
ke1_message: self.ke1_message.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct CredentialRequest<CS: CipherSuite>,
[alpha, ke1_message],
[
CS::Group,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message
],
);
impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
let mut credential_request: Vec<u8> = Vec::new();
credential_request.extend_from_slice(&self.alpha.to_arr());
credential_request.extend_from_slice(&self.ke1_message.to_bytes()?);
Ok(credential_request)
pub fn serialize(&self) -> Vec<u8> {
[self.alpha.to_arr().to_vec(), self.ke1_message.to_bytes()].concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice =
check_slice_size_atleast(&input, elem_len, "login_first_message_bytes")?;
let checked_slice = check_slice_size_atleast(input, elem_len, "login_first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
@@ -195,60 +249,79 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
}
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..],
)?;
Ok(Self { alpha, ke1_message })
}
/// Only used for testing purposes
#[cfg(test)]
pub fn get_alpha_for_testing(&self) -> CS::Group {
self.alpha
}
}
impl_serialize_and_deserialize_for!(CredentialRequest);
/// The answer sent by the server to the user, upon reception of the
/// login attempt
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) beta: CS::Group,
pub(crate) server_s_pk: Key,
/// the user's sealed information,
pub(crate) envelope: Envelope<CS::Hash>,
pub(crate) masking_nonce: Vec<u8>,
pub(crate) masked_response: Vec<u8>,
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> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope),
self.ke2_message.to_bytes()?,
pub fn serialize(&self) -> Vec<u8> {
[
Self::serialize_without_ke(&self.beta, &self.masking_nonce, &self.masked_response),
self.ke2_message.to_bytes(),
]
.concat())
.concat()
}
pub(crate) fn serialize_without_ke(
beta: &CS::Group,
server_s_pk: &Key,
envelope: &Envelope<CS::Hash>,
masking_nonce: &[u8],
masked_response: &[u8],
) -> Vec<u8> {
[
&beta.to_arr(),
&server_s_pk.to_arr()[..],
&envelope.to_bytes(),
]
.concat()
[&beta.to_arr(), masking_nonce, masked_response].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 = <Key as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?;
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
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
// correct subgroup
@@ -261,57 +334,53 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
return Err(PakeError::IdentityGroupElementError.into());
}
let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
let (envelope, remainder) =
Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len + key_len..])?;
let ke2_message_size = CS::KeyExchange::ke2_message_size();
let checked_remainder =
check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
let masking_nonce = checked_slice[elem_len..elem_len + nonce_len].to_vec();
let masked_response = checked_slice
[elem_len + nonce_len..elem_len + nonce_len + masked_response_len]
.to_vec();
let ke2_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::try_from(
&checked_remainder,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::from_bytes::<CS>(
&checked_slice[elem_len + nonce_len + masked_response_len..],
)?;
Ok(Self {
beta,
server_s_pk,
envelope,
masking_nonce,
masked_response,
ke2_message,
})
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the reflection
/// error case
pub fn set_beta_for_testing(&self, new_beta: CS::Group) -> Self {
Self {
beta: new_beta,
server_s_pk: self.server_s_pk.clone(),
envelope: self.envelope.clone(),
ke2_message: self.ke2_message.clone(),
}
}
}
impl_serialize_and_deserialize_for!(CredentialResponse);
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message,
}
impl_clone_for!(struct CredentialFinalization<CS: CipherSuite>, [ke3_message]);
impl_debug_eq_hash_for!(
struct CredentialFinalization<CS: CipherSuite>,
[ke3_message],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message],
);
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
pub fn serialize(&self) -> Vec<u8> {
self.ke3_message.to_bytes()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let ke3_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(input)?;
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::from_bytes::<CS>(
input,
)?;
Ok(Self { ke3_message })
}
}
impl_serialize_and_deserialize_for!(CredentialFinalization);
+468 -461
View File
File diff suppressed because it is too large Load Diff
+28 -33
View File
@@ -4,7 +4,7 @@
// LICENSE file in the root directory of this source tree.
use crate::{
errors::ProtocolError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
serialization::serialize,
};
use digest::Digest;
@@ -12,11 +12,15 @@ use generic_array::GenericArray;
use rand::{CryptoRng, RngCore};
/// Used to store the OPRF input and blinding factor
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Token<Grp: Group> {
pub(crate) data: Vec<u8>,
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_FINALIZE: &[u8] = b"VOPRF06-Finalize-";
static MODE_BASE: u8 = 0x00;
@@ -28,10 +32,10 @@ static MODE_BASE: u8 = 0x00;
pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), ProtocolError> {
) -> Result<(Token<G>, G), InternalPakeError> {
// 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 blind_token = mapped_point * &blind;
Ok((
@@ -55,7 +59,7 @@ pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blind: &G::Scalar,
evaluated_element: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
) -> GenericArray<u8, <H as Digest>::OutputSize> {
let unblinded_element = evaluated_element * &G::scalar_invert(blind);
finalize_after_unblind::<G, H>(input, unblinded_element)
}
@@ -63,15 +67,15 @@ pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
unblinded_element: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)?].concat();
) -> GenericArray<u8, <H as Digest>::OutputSize> {
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
let hash_input = [
serialize(input, 2)?,
serialize(&unblinded_element.to_arr().to_vec(), 2)?,
serialize(&finalize_dst, 2)?,
serialize(input, 2),
serialize(&unblinded_element.to_arr(), 2),
serialize(&finalize_dst, 2),
]
.concat();
Ok(<H as Digest>::digest(&hash_input))
<H as Digest>::digest(&hash_input)
}
////////////////////////
@@ -84,7 +88,7 @@ fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), ProtocolError> {
) -> Result<(Token<G>, G), InternalPakeError> {
blind::<R, G, H>(input, blinding_factor_rng)
}
@@ -101,8 +105,8 @@ pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
pub fn finalize_shim<G: GroupWithMapToCurve, H: Hash>(
token: &Token<G>,
point: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
finalize::<G, H>(&token.data, &token.blind, point)
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
Ok(finalize::<G, H>(&token.data, &token.blind, point))
}
///////////
@@ -120,34 +124,30 @@ mod tests {
use sha2::Sha512;
fn prf(input: &[u8], oprf_key: &[u8; 32]) -> GenericArray<u8, <Sha512 as Digest>::OutputSize> {
let dst = [
STR_VOPRF,
&RistrettoPoint::get_context_string(MODE_BASE).unwrap(),
]
.concat();
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(input, &dst).unwrap();
let scalar =
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar;
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res).unwrap()
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(input, res)
}
#[test]
fn oprf_retrieval() {
fn oprf_retrieval() -> Result<(), InternalPakeError> {
let input = b"hunter2";
let mut rng = OsRng;
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng).unwrap();
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng)?;
let oprf_key_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes).unwrap();
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
let res =
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta).unwrap();
let res2 = prf(&input[..], &oprf_key.as_bytes());
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta);
let res2 = prf(&input[..], oprf_key.as_bytes());
assert_eq!(res, res2);
Ok(())
}
#[test]
@@ -156,16 +156,11 @@ mod tests {
let mut input = vec![0u8; 64];
rng.fill_bytes(&mut input);
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
let res =
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha).unwrap();
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha);
let dst = [
STR_VOPRF,
&RistrettoPoint::get_context_string(MODE_BASE).unwrap(),
]
.concat();
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point).unwrap();
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point);
assert_eq!(res, res2);
}
+74 -33
View File
@@ -6,24 +6,17 @@
use crate::errors::PakeError;
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp(input: usize, length: usize) -> Result<Vec<u8>, PakeError> {
let sizeof_usize = std::mem::size_of::<usize>();
// Check if input >= 256^length
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
return Err(PakeError::SerializationError);
}
if length <= sizeof_usize {
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
pub(crate) fn i2osp(input: usize, length: usize) -> Vec<u8> {
if length <= std::mem::size_of::<usize>() {
return input.to_be_bytes()[std::mem::size_of::<usize>() - length..].to_vec();
}
let mut output = vec![0u8; length];
output.splice(
length - sizeof_usize..length,
length - std::mem::size_of::<usize>()..length,
input.to_be_bytes().iter().cloned(),
);
Ok(output)
output
}
// Corresponds to the OS2IP() function from RFC8017
@@ -38,8 +31,8 @@ pub(crate) fn os2ip(input: &[u8]) -> Result<usize, PakeError> {
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result<Vec<u8>, PakeError> {
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Vec<u8> {
[&i2osp(input.len(), max_bytes), input].concat()
}
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
@@ -60,24 +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)]
mod tests;
#[cfg(test)]
mod unit_tests {
use super::*;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp(0, 1).is_ok());
assert!(i2osp(255, 1).is_ok());
assert!(i2osp(256, 1).is_err());
assert!(i2osp(257, 1).is_err());
assert!(i2osp(256 * 256 - 1, 2).is_ok());
assert!(i2osp(256 * 256, 2).is_err());
assert!(i2osp(256 * 256 + 1, 2).is_err());
}
}
+104 -154
View File
@@ -9,10 +9,10 @@ use crate::{
errors::*,
group::Group,
key_exchange::{
traits::{KeyExchange, ToBytes},
traits::{FromBytes, KeyExchange, ToBytes},
tripledh::{NonceLen, TripleDH},
},
opaque::*,
keypair::{KeyPair, PublicKey},
serialization::{i2osp, os2ip, serialize},
*,
};
@@ -24,7 +24,6 @@ use proptest::{collection::vec, prelude::*};
use rand::{rngs::OsRng, RngCore};
use sha2::Digest;
use std::convert::TryFrom;
struct Default;
impl CipherSuite for Default {
@@ -34,7 +33,7 @@ impl CipherSuite for Default {
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
fn random_ristretto_point() -> RistrettoPoint {
@@ -56,10 +55,9 @@ fn client_registration_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let elem = <RistrettoPoint as Group>::base_point() * sc;
// serialization order: scalar, password, group element
let bytes: Vec<u8> = [&elem.to_arr(), &sc.as_bytes()[..], &pw[..]].concat();
// serialization order: scalar, password
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
let reg = ClientRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes);
@@ -70,28 +68,20 @@ fn server_registration_roundtrip() {
// If we don't have envelope and client_pk, the server registration just
// contains the prf key
let mut rng = OsRng;
let oprf_key = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(oprf_key.as_bytes());
let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, oprf_bytes);
let mut ciphertext = [0u8; 32];
rng.fill_bytes(&mut ciphertext);
let mut masking_key = [0u8; HASH_SIZE];
rng.fill_bytes(&mut masking_key);
// Construct a mock envelope
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(&ciphertext); // ciphertext which is an encrypted private key
// mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
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
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(&masking_key);
bytes.extend_from_slice(&mock_envelope_bytes);
let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize();
@@ -114,12 +104,12 @@ fn registration_request_roundtrip() {
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,
}
);
assert!(matches!(
RegistrationRequest::<Default>::deserialize(identity_bytes.as_slice()),
Err(ProtocolError::VerificationError(
PakeError::IdentityGroupElementError
))
));
}
#[test]
@@ -127,12 +117,12 @@ fn registration_response_roundtrip() {
let pt = random_ristretto_point();
let beta_bytes = pt.to_arr();
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 mut input = Vec::new();
input.extend_from_slice(beta_bytes.as_slice());
input.extend_from_slice(&pubkey_bytes.as_slice());
input.extend_from_slice(pubkey_bytes.as_slice());
let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap();
let r2_bytes = r2.serialize();
@@ -142,38 +132,38 @@ fn registration_response_roundtrip() {
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,
});
assert!(matches!(
RegistrationResponse::<Default>::deserialize(
&[identity_bytes, pubkey_bytes.to_vec()].concat()
),
Err(ProtocolError::VerificationError(
PakeError::IdentityGroupElementError
))
));
}
#[test]
fn registration_upload_roundtrip() {
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 mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut nonce = [0u8; 32];
rng.fill_bytes(&mut nonce);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let mut masking_key = vec![0u8; <sha2::Sha512 as Digest>::OutputSize::to_usize()];
rng.fill_bytes(&mut masking_key);
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
&mut rng,
&key,
&msg,
&pubkey_bytes,
InnerEnvelopeMode::Base,
)
.unwrap();
let (envelope, _) =
Envelope::<Default>::seal_raw(&key, &nonce, &pubkey_bytes, InnerEnvelopeMode::Internal)
.unwrap();
let envelope_bytes = envelope.serialize();
let mut input = Vec::new();
input.extend_from_slice(&pubkey_bytes[..]);
input.extend_from_slice(&masking_key[..]);
input.extend_from_slice(&envelope_bytes);
let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap();
@@ -187,38 +177,30 @@ fn credential_request_roundtrip() {
let alpha = random_ristretto_point();
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()];
rng.fill_bytes(&mut client_nonce);
let mut info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut info);
let ke1m: Vec<u8> = [
&client_nonce[..],
&serialize(&info.to_vec(), 2).unwrap(),
&client_e_kp.public(),
]
.concat();
let ke1m: Vec<u8> = [&client_nonce[..], client_e_kp.public()].concat();
let mut input = Vec::new();
input.extend_from_slice(&alpha_bytes);
input.extend_from_slice(&ke1m[..]);
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap();
let l1_bytes = l1.serialize().unwrap();
let l1_bytes = l1.serialize();
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,
});
assert!(matches!(
CredentialRequest::<Default>::deserialize(&[identity_bytes, ke1m.to_vec()].concat()),
Err(ProtocolError::VerificationError(
PakeError::IdentityGroupElementError
))
));
}
#[test]
@@ -227,69 +209,50 @@ fn credential_response_roundtrip() {
let pt_bytes = pt.to_arr().to_vec();
let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng);
let pubkey_bytes = skp.public().to_arr();
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut masking_nonce = vec![0u8; 32];
rng.fill_bytes(&mut masking_nonce);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let mut masked_response =
vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<Default>::len()];
rng.fill_bytes(&mut masked_response);
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
&mut rng,
&key,
&msg,
&pubkey_bytes,
InnerEnvelopeMode::Base,
)
.unwrap();
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];
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce);
let mut e_info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut e_info);
let ke2m: Vec<u8> = [
&server_nonce[..],
&server_e_kp.public(),
&serialize(&e_info.to_vec(), 2).unwrap(),
&mac[..],
]
.concat();
let serialized_envelope = envelope.serialize();
let ke2m: Vec<u8> = [&server_nonce[..], server_e_kp.public(), &mac[..]].concat();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
input.extend_from_slice(&pubkey_bytes.as_slice());
input.extend_from_slice(&serialized_envelope);
input.extend_from_slice(&masking_nonce);
input.extend_from_slice(&masked_response);
input.extend_from_slice(&ke2m[..]);
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
let l2_bytes = l2.serialize().unwrap();
let l2_bytes = l2.serialize();
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,
pubkey_bytes.to_vec(),
serialized_envelope,
ke2m.to_vec()
]
.concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
assert!(matches!(
CredentialResponse::<Default>::deserialize(
&[
identity_bytes,
masking_nonce.to_vec(),
masked_response,
ke2m.to_vec()
]
.concat()
),
Err(ProtocolError::VerificationError(
PakeError::IdentityGroupElementError
))
));
}
#[test]
@@ -301,7 +264,7 @@ fn login_third_message_roundtrip() {
let input: Vec<u8> = [&mac[..]].concat();
let l3 = CredentialFinalization::<Default>::deserialize(&input).unwrap();
let l3_bytes = l3.serialize().unwrap();
let l3_bytes = l3.serialize();
assert_eq!(input, l3_bytes);
}
@@ -311,7 +274,7 @@ fn client_login_roundtrip() {
let mut rng = OsRng;
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()];
rng.fill_bytes(&mut client_nonce);
@@ -321,13 +284,13 @@ fn client_login_roundtrip() {
// serialization order: scalar, credential_request, ke1_state, password
let bytes: Vec<u8> = [
&sc.as_bytes()[..],
&serialize(&serialized_credential_request, 2).unwrap(),
&serialize(&l1_data, 2).unwrap(),
&serialize(&serialized_credential_request, 2),
&serialize(&l1_data, 2),
&pw[..],
]
.concat();
let reg = ClientLogin::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize().unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes);
}
@@ -335,23 +298,16 @@ fn client_login_roundtrip() {
fn ke1_message_roundtrip() {
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()];
rng.fill_bytes(&mut client_nonce);
let mut info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut info);
let ke1m: Vec<u8> = [
&client_nonce[..],
&serialize(&info.to_vec(), 2).unwrap(),
&client_e_kp.public(),
]
.concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::try_from(&ke1m[..])
.unwrap();
let reg_bytes = reg.to_bytes().unwrap();
let ke1m: Vec<u8> = [&client_nonce[..], client_e_kp.public()].concat();
let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::from_bytes::<
Default,
>(&ke1m[..])
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke1m);
}
@@ -359,26 +315,19 @@ fn ke1_message_roundtrip() {
fn ke2_message_roundtrip() {
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];
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce);
let mut e_info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut e_info);
let ke2m: Vec<u8> = [
&server_nonce[..],
&server_e_kp.public(),
&serialize(&e_info.to_vec(), 2).unwrap(),
&mac[..],
]
.concat();
let ke2m: Vec<u8> = [&server_nonce[..], server_e_kp.public(), &mac[..]].concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::try_from(&ke2m[..])
.unwrap();
let reg_bytes = reg.to_bytes().unwrap();
let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::from_bytes::<
Default,
>(&ke2m[..])
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke2m);
}
@@ -390,67 +339,68 @@ fn ke3_message_roundtrip() {
let ke3m: Vec<u8> = [&mac[..]].concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::try_from(&ke3m[..])
.unwrap();
let reg_bytes = reg.to_bytes().unwrap();
let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::from_bytes::<
Default,
>(&ke3m[..])
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke3m);
}
proptest! {
#[test]
fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..std::mem::size_of::<usize>())) {
assert_eq!(i2osp(os2ip(&bytes)?, bytes.len())?, bytes);
fn test_i2osp_os2ip(ref bytes in vec(prop::num::u8::ANY, 0..std::mem::size_of::<usize>())) {
assert_eq!(&i2osp(os2ip(bytes)?, bytes.len()), bytes);
}
#[test]
fn test_nocrash_registration_request(bytes in vec(any::<u8>(), 0..200)) {
fn test_nocrash_registration_request(ref bytes in vec(prop::num::u8::ANY, 0..200)) {
RegistrationRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_response(bytes in vec(any::<u8>(), 0..200)) {
fn test_nocrash_registration_response(ref bytes in vec(prop::num::u8::ANY, 0..200)) {
RegistrationResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_upload(bytes in vec(any::<u8>(), 0..200)) {
fn test_nocrash_registration_upload(ref bytes in vec(prop::num::u8::ANY, 0..200)) {
RegistrationUpload::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_request(bytes in vec(any::<u8>(), 0..500)) {
fn test_nocrash_credential_request(ref bytes in vec(prop::num::u8::ANY, 0..500)) {
CredentialRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_response(bytes in vec(any::<u8>(), 0..500)) {
fn test_nocrash_credential_response(ref bytes in vec(prop::num::u8::ANY, 0..500)) {
CredentialResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_finalization(bytes in vec(any::<u8>(), 0..500)) {
fn test_nocrash_credential_finalization(ref bytes in vec(prop::num::u8::ANY, 0..500)) {
CredentialFinalization::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
fn test_nocrash_client_registration(ref bytes in vec(prop::num::u8::ANY, 0..700)) {
ClientRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
fn test_nocrash_server_registration(ref bytes in vec(prop::num::u8::ANY, 0..700)) {
ServerRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
fn test_nocrash_client_login(ref bytes in vec(prop::num::u8::ANY, 0..700)) {
ClientLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
fn test_nocrash_server_login(ref bytes in vec(prop::num::u8::ANY, 0..700)) {
ServerLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
+10 -12
View File
@@ -31,22 +31,20 @@ impl<D: Hash> SlowHash<D> for NoOpHash {
}
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_LOG_N: u8 = 15u8;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_R: u32 = 8u32;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_P: u32 = 1u32;
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
impl<D: Hash> SlowHash<D> for argon2::Argon2<'_> {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
let params =
scrypt::ScryptParams::new(DEFAULT_SCRYPT_LOG_N, DEFAULT_SCRYPT_R, DEFAULT_SCRYPT_P)
.map_err(|_| InternalPakeError::SlowHashError)?;
let params = argon2::Argon2::default();
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)?;
Ok(output)
}
+268 -346
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -48,7 +48,7 @@ impl RngCore for CycleRng {
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
dest[..len].copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
}
+474 -272
View File
@@ -4,16 +4,12 @@
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::*,
key_exchange::tripledh::TripleDH,
keypair::{Key, SizedBytesExt},
opaque::*,
slow_hash::NoOpHash,
tests::mock_rng::CycleRng,
*,
ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::PrivateKey,
opaque::*, slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes;
use serde_json::Value;
// Tests
@@ -27,7 +23,7 @@ impl CipherSuite for Ristretto255Sha512NoSlowHash {
type SlowHash = NoOpHash;
}
#[derive(PartialEq)]
#[derive(PartialEq, Eq)]
pub enum EnvelopeMode {
Base,
CustomIdentifier,
@@ -35,20 +31,24 @@ pub enum EnvelopeMode {
#[allow(non_snake_case)]
pub struct TestVectorParameters {
pub dummy_private_key: Vec<u8>,
pub dummy_masking_key: Vec<u8>,
pub context: Vec<u8>,
pub envelope_mode: EnvelopeMode,
pub client_public_key: Vec<u8>,
pub client_private_key: Vec<u8>,
pub client_private_key: Option<Vec<u8>>,
pub client_keyshare: Vec<u8>,
pub client_private_keyshare: Vec<u8>,
pub server_public_key: Vec<u8>,
pub server_private_key: Vec<u8>,
pub server_keyshare: Vec<u8>,
pub server_private_keyshare: Vec<u8>,
pub client_identity: Vec<u8>,
pub server_identity: Vec<u8>,
pub client_identity: Option<Vec<u8>>,
pub server_identity: Option<Vec<u8>>,
pub credential_identifier: Vec<u8>,
pub password: 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 client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
@@ -69,206 +69,329 @@ pub struct TestVectorParameters {
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
static TEST_VECTORS: &[&str] = &[
r#"
## OPAQUE-3DH Test Vector 1
### Configuration
~~~
OPRF: 0001
Hash: SHA512
SlowHash: Identity
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
~~~
oprf_seed: 5c4f99877d253be5817b4b03f37b6da680b0d5671d1ec5351fa61c5d82
eab28b9de4c4e170f27e433ba377c71c49aa62ad26391ee1cac17011d8a7e9406657c
8
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da54
20d3eebcd2b
client_private_key: 8bcb0b70dac18de24eef12e737d6b28724d3e37774e0b092f
9f70b255defaf04
client_public_key: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa1905
df6ea129343b09
server_private_key: f3a0829898a89239dce29ccc98ec8b449a34b255ba1e6f944
829d18e0d589b0f
server_public_key: 66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb74
9c22c762389c3c
client_info: 68656c6c6f20626f62
server_info: 6772656574696e677320616c696365
server_nonce: 98b8081059f60ffed9336f026fd8e124737205ac73f5348ae5bebdb
49456c70f
client_nonce: 58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e
36427ca44
server_keyshare: 5214e3ddc73db786480b79fa2da787f2080b82cbe922c2a9592b
44597d9a702e
client_keyshare: a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
b98265a5e651
server_private_keyshare: c4d002aa4cfcf281657cf36fe562bc60d9133e0e72a7
4432f685b2b6a4b42a0c
client_private_keyshare: de2e98f422bf7b99be19f7da7cac62f1599d35a225ec
6340149a0aaff3102003
blind_registration: 7e5bcbf82a46109ee0d24e9bcab41fc830a6ce8b82fc1e921
3a043b743b95800
blind_login: c4d5a15f0d5ffc354e340454ec779f575e4573a3886ab5e57e4da298
4bdd5306
oprf_key: 080d0a4d352de92672ab709b1ae1888cb48dfabc2d6ca5b914b335512fe
70508
auth_key: 7bb7f2b831ee30d3e5cc4012c8f721a4d8f9dd494932d53776e043df9bd
2aa284025b8b006fd8449536446ff50698f46c73fccb53f20d80898f185307d1d39e5
prk: b0aefddbb21d1b97bc40c07b172e0bf172ec740de4f6274f69d46350a447e9b1
b3fb1e4cefc7d8e393ff58a5c45c74d0615ee0eecde116f3d4e744142eb2ee89
pseudorandom_pad: 36a828b3b57bf242c4c47ccd9cb84e5b3cefaffe09629c6b94d
eba0ccec5fa39
envelope: 01cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3
eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a55
3d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e
906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
handshake_secret: 2b041dcf12ac9b75dded88f891c25d76746ce9e2c1a43118ac4
aa5721cdc1bc2f0691e6c012a1ea9eb95ab4899b3e7058d37fe9546c46b0511877e40
f55aac6c
handshake_encrypt_key: ceef10f15d869a4cea8174fa98d0d96c7aaf8602d006fe
0c5274a40173db76cac820138c5890bb63fb974d1e3e925850cc2464e2c10f0a9a776
9a45e80889b1e
server_mac_key: f8fd7fdc349b5ae1339515e05912c89a795f561a117cdc84d8d8b
5f05b05751abfb87fa01c799c5d367244d1e32eab67ff926833c6025c556acffa4af1
f3871a
client_mac_key: 92a30cc82c374c06895aa07e81f0cf5f25309a24b595faefcd225
1f9219b47e47d17da4fe8b572dedefa350ed365f87b217973e90d0b647a2ccf1d796a
8970f6
registration_request: ec9027daa5e9a901d641286a7ded51364142936ac7636e1
42e3f4368b4bd8124
registration_response: 8867d7c8c2c576a6322d49d46078ea32f479aed917c70a
636d3ada4397ea1c0e66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749
c22c762389c3c
registration_upload: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa19
05df6ea129343b0901cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da
5420d3eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129
932a553d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3cc
d8699e906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
KE1: e06a32011e1b1704eb686b263e5d132fff4e9f6429cd93b98db107485006792c
58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e36427ca4400096
8656c6c6f20626f62a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
b98265a5e651
KE2: 66f6b5fa1a4eb6bd7a0c93ed2639a31cba0d02e2df744003641d5a30a4a12364
66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749c22c762389c3c01cc7
abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3eebcd2bbd6323c3
6fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a553d469ffa9999fcb
d37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e906027842694b62
93b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e98b8081059f60ffed9336f0
26fd8e124737205ac73f5348ae5bebdb49456c70f5214e3ddc73db786480b79fa2da7
87f2080b82cbe922c2a9592b44597d9a702e000f72f38a0945819089c44c86820c51d
89cc35f77df03d330101bbed3b2f69066112f32529bdda0998657350fc9f8da4cde73
408ad931f4c2ea6237ccae4696483388b174f50cf96d439139b0f8680c3b
KE3: 9f0e4f73455ca9fe06bb52ad02670b09be5a03db11a73be4422f19963be082b0
eb55871022e8d1d87adc3ab50de7c738058eb659866d091648f2fed12e23fd53
export_key: 66c0b72aa829f13a166fb1a1168f1e26023921f0eed1126def4f81ba0
4924ad6012e42b63656ec199ba27670d1e7f23dc0a927714edc140134dde5a5d2063d
fc
session_key: 951c2bb1b876725fa7d3829db791dddd406a688507b47e24101bd0cc
5d071760b6fba59e8758a6ea6d7e5f51a715b49a47c50fee9a7c8a0451243c3ee837f
d30
envelope_nonce: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd
539c4676775
masking_nonce: 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab
86ff39ed7f
server_private_key: 16eb9dc74a3df2033cd738bf2cfb7a3670c569d7749f284b2
b241cb237e7d10f
server_public_key: 18d5035fd0a9c1d6412226df037125901a43f4dff660c0549d
402f672bcc0933
server_nonce: f9c5ec75a8cd571370add249e99cb8a8c43f6ef05610ac6e354642b
f4fedbf69
client_nonce: 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f
2e9784f69
server_keyshare: 6e77d4749eb304c4d74be9457c597546bc22aed699225499910f
c913b3e90712
client_keyshare: f67926bd036c5dc4971816b9376e9f64737f361ef8269c18f69f
1ab555e96d4a
server_private_keyshare: f8e3e31543dd6fc86833296726773d51158291ab9afd
666bb55dce83474c1101
client_private_keyshare: 4230d62ea740b13e178185fc517cf2c313e6908c4cd9
fb42154870ff3490c608
blind_registration: c62937d17dc9aa213c9038f84fe8c5bf3d953356db01c4d48
acb7cae48e6a504
blind_login: b5f458822ea11c900ad776e38e29d7be361f75b4d79b55ad74923299
bf8d6503
oprf_key: 23d431bab39aea4d2737ac391a50076300210730971788e3a6a8c29ad3c
5930e
~~~
### Intermediate Values
~~~
client_public_key: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a33
439911e66c3c27
auth_key: 27972f9b1cf2ce524d50a7afa40a2ee6957904e2bef29976bdbda452a84
fcf01023f3ddd8182e64ea5287f99765dd39b83fa89fe189db227212a144134684783
randomized_pwd: 750ef06299c2fb102242fd84e59613616338f83e69c09c1dc3f91
c57ac0642876ccbe785e94aa094262efdc6aed08b3faff7c1bddfa14c434c5a908ad6
c5f9d5
envelope: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c46
76775455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e
43d4c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
handshake_secret: 02fb23a668b7138b029c95d21f1e0eec9e10377be933bdbf3e5
33ea39073d3ce9d1ef16b55a8a8464f3bf6a991cc645d14c1fa3d9d6cfe36c6c0dcc2
691d7109
server_mac_key: e75ce46beeebd26f22540d7988de9809a69cf34fec6c050750708
e91232297fdbb51e875cd37167d5ce661ebccf0004dbbf96311daf64ddec7faae04c4
8bbd89
client_mac_key: 4bce132daa031fff2a6e5ac29287c4641e3b9dc2560394b8c73f3
b748f1e51e577b932a960b236981217b33bee220b0bce2696638cfb7791f427ade292
d60f55
~~~
### Output Values
~~~
registration_request: 80576bce33c6ce89f9e1a06d8595cd9d09d9aef46b20dad
d57a845dc50e7c074
registration_response: 1a80fdb4f4eb1985587b5b95661d2cff1ef2493cdcdd88
b5699f39048f0d6c2618d5035fd0a9c1d6412226df037125901a43f4dff660c0549d4
02f672bcc0933
registration_upload: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a
33439911e66c3c2795014d8fc0c710bd763c981c5b9329c95e149c6717af91bad2cec
daf87f2c3c9c11914cb6d44aaee5679e3e61e1b65241fda74902cca908a065495c0b2
8b799e71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c467677
5455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e43d4
c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
KE1: 60d71c9f5d2a14568807b869e2c251a8e5f7ad8951cd8386c7e32c0634b26b16
804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f2e9784f69f6792
6bd036c5dc4971816b9376e9f64737f361ef8269c18f69f1ab555e96d4a
KE2: 78a428204f552d3532bad040c961324edb22c738d98f1dd770d65caba0bd8966
54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab86ff39ed7fbcbbb
84a18810b8eb1dc898d9af686f5901a21d0768720b325279fde4931ee52f0d4a0d0d9
cd1cd7c424d4622b1588ba554cd9241352a59ef52bbe85e0f865021404b115ba954f5
540cf2d811a6566a93876cac1239b1f75f39b070250af5a84a819e08b13e9e437a80f
c25cc130f8475dde43efe6d900c664e9bac300298bb0f9c5ec75a8cd571370add249e
99cb8a8c43f6ef05610ac6e354642bf4fedbf696e77d4749eb304c4d74be9457c5975
46bc22aed699225499910fc913b3e907120485942e3e077f71c1dd2d87053b39f0d31
bfe5d5f90df0e85ad9ce771e4f4d1ab697a10a02002cd73916051b887da9554465d58
68811fd8b22b8f457ed5a4b0
KE3: b4f8aece9fb4f6b7b5ffe1c98747a91f4ec7bf5481fe5719ba4baad668e3fd4e
8aba4fa227bd4c688ed9e17f6c6d28ab5e5617a883207d80979dc4797ca89304
export_key: 045f61f4baa0a945c2e85dfb7a85fe4df8a49e6c31344920e863c286b
c8a17fe25fc16c84836335b4b5ecc9743c5d3a221101ab004aa99ce65026b6953ad6c
c0
session_key: 91187690e5ea0da3110a1dd7d5ffd7c4c3111950c587d9fcf3b9f34b
f73b86dbeafed42a05024fa875a32415c6143d20c39cd732eb0e31db5e60ea3fb2551
cf7
~~~
"#,
r#"
## OPAQUE-3DH Test Vector 2
### Configuration
~~~
OPRF: 0001
Hash: SHA512
SlowHash: Identity
EnvelopeMode: 02
MHF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
client_identity: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613b2
e16a6dde6b05
server_identity: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8e
de8d6d272c65
Nm: 64
Nx: 64
Nok: 32
~~~
### Input Values
~~~
client_identity: 616c696365
server_identity: 626f62
oprf_seed: db5c1c16e264b8933d5da56439e7cfed23ab7287b474fe3cdcd58df089
a365a426ea849258d9f4bc13573601f2e727c90ecc19d448cf3145a662e0065f157ba
5
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb241809
0438877888c
client_private_key: dc70a99bbabf1ebe98b192e93cedceb9c0164e95b891bd8bc
81721b83d66b00b
client_public_key: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613
b2e16a6dde6b05
server_private_key: 709687a36c94592ab76579f42ce1be6961f0700496e71df80
6ebd5320554720d
server_public_key: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c
8ede8d6d272c65
client_info: 68656c6c6f20626f62
server_info: 6772656574696e677320616c696365
server_nonce: ef49d83cef5f1411ea30abb82b08bd85423aadb86e2c19df5930b3c
8498b9f97
client_nonce: 4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d6
77165cd3b
server_keyshare: 96a9587e233e67f2397f10fec6355b68102534f1f1b115b4ddf7
485840efcd7c
client_keyshare: 54f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
e5033632611a
server_private_keyshare: 6650d64df70618a878504ce73dcca27b1af125c67e48
1e7bd49d0b24709b200f
client_private_keyshare: ebb01c59f99bc955df622548e247f7ef180732909ff3
c5f87ff8c7867b8be704
blind_registration: 308f1d3fa1fea402f3c90b04601274050a3c6f467387c2f48
878823949b0e109
blind_login: 141e21373228a44b09d4c00da9a6bbaf9a5e54a1687c07f327833643
4245510b
oprf_key: b7126967aa0cb69c311b71343843ea041bae30e2bde41b548b8fbd8bced
97604
auth_key: 9f761a5a56a74269b382403aeba47c1d24b9e200e1839efcb616fb280da
1b6ba7bd71d6455dd3cd979c545608cfbfc1c4e9ba677e1d40848054a00696c4b2589
prk: 30bc3f37a757890ac17ce46043f3c5ed30c96fb8743205e77e84dc167d98e114
6093150ff7b4d002f793bfe717e88d174ed2669abdd9e96af473a7ac82973b0a
pseudorandom_pad: b909f990c94b9c5949a2b8d0874d602f846b7981b331fd79978
b530cc46c8670
envelope: 02f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438
877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a36
7bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc
439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
handshake_secret: ff89f264f8c3974238f4c8d736af7b0a55f2e4edc487cbf3e5c
7b4bbf21acd7c1d28354c2c8555fba57c4d4b1fbb4b772bfdf909881f67dd517cc9f4
f6ebaeac
handshake_encrypt_key: 3071b181f639062cf70b74d0ffe5ec8fa695da13cd2f00
e74b8b7ef348ae7a5df9c3a32c9f7aeaad5a28379712cf849b9707e221dce124abfad
d0225a8e8e045
server_mac_key: 7c37b344d189cbbeff80bbb4b78e2703d1a80dc28239923094287
62f7ce2a93b11f6e85dd45c02809afe8583d4aad6377e72788773af92eef33c690692
20ae76
client_mac_key: 2c4aff12ba7aa911a51f9e5b7a7c01439d854c97e4b8ec842a9db
d78345760328fd5a72424e49e25ec8fe1b6d9d42f774516f400948bd5a105d995d000
2fc83b
registration_request: 3c8b89966e261a5aaf7aeb6dcdd94c87ce311bf197221b8
7ef44632d58f18a05
registration_response: caf9243d7ef3e267815632bf79c85a27a23f218a438815
2a523f6a310949807beae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8
ede8d6d272c65
registration_upload: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc6
13b2e16a6dde6b0502f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418
090438877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4
f90a367bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d
2566cc439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
KE1: 8261a1efd78bea73faf256a23c200d729259886530fa43b875c1ca124b09bc7e
4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d677165cd3b00096
8656c6c6f20626f6254f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
e5033632611a
KE2: fa1f33a43a03123ebe35345ef93aa23b57ea8bfbee7022b05a179d60768ba02e
eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8ede8d6d272c6502f41
e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438877888c6579500b
73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a367bdb425fa1dd4c4
9e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc439c83030464eff
ecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282ef49d83cef5f1411ea30abb
82b08bd85423aadb86e2c19df5930b3c8498b9f9796a9587e233e67f2397f10fec635
5b68102534f1f1b115b4ddf7485840efcd7c000f7ebe71d4ab326006a3aeca802435d
c995a38ac6662221f974cb920992d82b8ef8d147c77e29b628a82b5ccb01ea2f7bb60
af94cd1860e1bd974a11a1c9bd827789f663c4758eb71058c244138de0c2
KE3: d81f93397cdba85a43993d4d9afbdc67f147adfa2b223213b19692cb820eef48
5073eda4c8236b2f47702404ad60d9a875d189626fc7b7cc861825385470ae54
export_key: 03192555940b5b42e64e6200bf55cc701f1bace3d402a2f8d83977843
51a1e3fa1f07a471b783b208acb1d92be47903b6fa3a0df9f4d4b7956ee4f431e2950
envelope_nonce: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf
2747829b2d2
masking_nonce: 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b
5def228c85
server_private_key: eeb2fcc794f98501b16139771720a0713a2750b9e528adfd3
662ad56a7e19b04
server_public_key: 8aa90cb321a38759fc253c444f317782962ca18d33101eab2c
8cda04405a181f
server_nonce: 3fa57f7ef652185f89114109f5a61cc8c9216fdd7398246bb7a0c20
e2fbca2d8
client_nonce: a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e608
5d8c1187a
server_keyshare: ae070cdffe5bb4b1c373e71be8e7d8f356ee5de37881533f1039
7bcd84d35445
client_keyshare: 642e7eecf19b804a62817486663d6c6c239396f709b663a4350c
da67d025687a
server_private_keyshare: 0974010a8528b813f5b33ae0d791df88516c8839c152
b030697637878b2d8b0a
client_private_keyshare: 03b52f066898929f4aca48014b2b97365205ce691ee3
444b0a7cecec3c7efb01
blind_registration: a66ffb41ccf1194a8d7dda900f8b6b0652e4c7fac4610066f
e0489a804d3bb05
blind_login: e6f161ac189e6873a19a54efca4baa0719e801e336d929d35ca28b5b
4f60560e
oprf_key: 1e0550d2dbb9ce5dd9bdbb5f808afbb724c573dc03306dcfc7217796465
ce607
~~~
### Intermediate Values
~~~
client_public_key: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7e1
c9004f20d5db71
auth_key: 5142ae6f6bd80686039656fd7a03cdd7e39cc6e869aa637220d4b5fb64f
afee2f284a1581fff95ad3a5261b413c5e5b91115f78a3c35486fa56023c300d1726b
randomized_pwd: cea240b632b9c1d704034920cc3dc3c664ed8cd82cf5c0339af76
4d6350d2ee9ba1f675ce8df7b6cf8692d1efb158bafa3c2695ac03a2d92346c19810c
1a698b
envelope: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf274782
9b2d26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c
1517b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
handshake_secret: 7925c12d7bf3050e62fe5c8caaece3c85737754c5df79bc59a6
0fa87929ab1f4a4730f903b87be8b7d89ded8ec97aaec97bc8e7d53a555fd4ad74c4f
33b9bc83
server_mac_key: 27d6036335c5654132fb08cc81d95b3067ef7fe795f017531231a
e3fa03cd3ab72f1f5e81473318f9c01f990263d885dfce4b6ac8630fdc8ee8abc6a36
7c2339
client_mac_key: ebb3693bac6310075a89922c7a40599d14d03d9104b7a331106e8
a578a32a4944751f9d3c230a6690a5747137388a86159cf587969d13dadc0a3830218
dfbca5
~~~
### Output Values
~~~
registration_request: f841cbb85844967568c7405f3831a58c4f5f37ccddb0baa
4972ea912c960ae66
registration_response: 0256257cc6e2b04444edc076b9ad44d8b31593e050bea8
06485707a818f8a93f8aa90cb321a38759fc253c444f317782962ca18d33101eab2c8
cda04405a181f
registration_upload: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7
e1c9004f20d5db71146e42585d25fa19913876edce4b5ee99b638eb37b1d8a8a76607
efaa12299e828641ba4fbf1c46fc2c3776e0a0c9791f88a15b9ddfb5495d63ce92d8f
58823bd0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf2747829b2d
26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c1517
b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
KE1: 14cc586d982b6db9846c78e0b3c543591e95fbf2fc877fa0e5eff89897dd3050
a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e6085d8c1187a642e7
eecf19b804a62817486663d6c6c239396f709b663a4350cda67d025687a
KE2: 8ab71c17547f376ae787741c367142790087090cdde6327dabb2581197bffa59
30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b5def228c85dd973
a1ac59244f674da4a1c057961886661bd29e0c1346f0fcf75bf1c78d4781815c2f9f6
f2f9fe0e370b256f6e82fb2e14c7ffc374d42caf26abf13dca169a6faafd5cff8baa9
717090bc1fc5e1ba56acb93492d1a8b789f33ff29b6004c4be9a755ff590d7d00d6e8
893e7e54e639aebf69d18f2182a9bb0f2e1c27c81ba73fa57f7ef652185f89114109f
5a61cc8c9216fdd7398246bb7a0c20e2fbca2d8ae070cdffe5bb4b1c373e71be8e7d8
f356ee5de37881533f10397bcd84d35445401c619d464ab3a134c71da4d9874f2f736
189b8bbb659c28f8db25a58b9f089272132e3091efa87d6b07d10321ba464047be011
3e91514aba299fd1553bcebb
KE3: c4a0d5b8148f3ac0f8611b38de38bda085d4eb00d561397ae59676f36dc705be
1c939e7bfdd7301103af5eb164bdfb70298aab889bd2ac797e419a82bfb442e6
export_key: 6b50ae4dba956930c0465b4a26c3cee58e05afcab623c1c254ae34acc
38babf954530a53475672ff46a1cf7fd53ef9e808f85b08793d021bb5c6d2a1bb9204
f6
session_key: 58a7fa98bf3b7b52da21406abfb11d98734354edd47d7b32462c0513
f0617c89824ea6031d4147a86fc9f6c6837ce640c12fb937d764f296d1a9421ad1b2a
5d5
session_key: c9bc2b7e2237f6fbeccd92dc6ec6d51faeb886492f8d23f21743a967
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 {
( $v:ident, $s:expr ) => {
parse_default!($v, $s, vec![])
};
}
macro_rules! parse_default {
( $v:ident, $s:expr, $d:expr ) => {
match decode(&$v, $s) {
Some(x) => x,
None => vec![],
None => $d,
}
};
}
@@ -285,20 +408,24 @@ fn rfc_to_json(input: &str) -> String {
let mut json = vec![];
for line in input.lines() {
// If line contains colon, then
if line.contains(":") {
if json.len() > 0 {
if line.contains(':') {
if !json.is_empty() {
// Adding closing quote for previous line, comma, and newline
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 val = iter.next().unwrap().split_whitespace().next().unwrap();
json.push(format!(" \"{}\": \"{}", key, val));
} else {
let s = line.trim().to_string();
if s.len() > 0 {
if s.contains('~') || s.contains('#') {
// Ignore comment lines
continue;
}
if !s.is_empty() {
json.push(s);
}
}
@@ -308,31 +435,37 @@ fn rfc_to_json(input: &str) -> String {
}
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
values[key].as_str().and_then(|s| hex::decode(s).ok())
}
fn populate_test_vectors(values: &Value) -> 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() {
Some("01") => EnvelopeMode::Base,
Some("02") => EnvelopeMode::CustomIdentifier,
_ => panic!("Could not match envelope mode"),
},
client_public_key: parse!(values, "client_public_key"),
client_private_key: parse!(values, "client_private_key"),
client_private_key: decode(values, "client_private_key"),
client_keyshare: parse!(values, "client_keyshare"),
client_private_keyshare: parse!(values, "client_private_keyshare"),
server_public_key: parse!(values, "server_public_key"),
server_private_key: parse!(values, "server_private_key"),
server_keyshare: parse!(values, "server_keyshare"),
server_private_keyshare: parse!(values, "server_private_keyshare"),
client_identity: parse!(values, "client_identity"),
server_identity: parse!(values, "server_identity"),
client_identity: decode(values, "client_identity"),
server_identity: decode(values, "server_identity"),
credential_identifier: parse!(values, "credential_identifier"),
password: parse!(values, "password"),
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"),
client_nonce: parse!(values, "client_nonce"),
server_nonce: parse!(values, "server_nonce"),
@@ -351,21 +484,25 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
}
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key.clone());
let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
)?;
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
let password_file = ServerRegistration::<Ristretto255Sha512NoSlowHash>::finish(
RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap(),
);
Ok(password_file.serialize())
}
fn parse_identifiers(
client_identity: Option<Vec<u8>>,
server_identity: Option<Vec<u8>>,
) -> Option<Identifiers> {
match (client_identity, server_identity) {
(None, None) => None,
(Some(x), None) => Some(Identifiers::ClientIdentifier(x)),
(None, Some(y)) => Some(Identifiers::ServerIdentifier(y)),
(Some(x), Some(y)) => Some(Identifiers::ClientAndServerIdentifiers(x, y)),
}
}
#[test]
fn test_registration_request() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
@@ -386,12 +523,19 @@ fn test_registration_request() -> Result<(), ProtocolError> {
#[test]
fn test_registration_response() -> Result<(), ProtocolError> {
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 =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng,
&server_setup,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
&parameters.credential_identifier,
)?;
assert_eq!(
hex::encode(parameters.registration_response),
@@ -411,19 +555,13 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
&parameters.password,
)?;
let sk_u_and_nonce: Vec<u8> =
[parameters.client_private_key, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce);
let result = client_registration_start_result.state.finish(
&mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ClientRegistrationFinishParameters::WithIdentifiers(
parameters.client_identity,
parameters.server_identity,
)
} else {
ClientRegistrationFinishParameters::default()
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ClientRegistrationFinishParameters::Default,
Some(ids) => ClientRegistrationFinishParameters::WithIdentifiers(ids),
},
)?;
@@ -433,7 +571,7 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
);
assert_eq!(
hex::encode(parameters.export_key),
hex::encode(result.export_key.to_vec())
hex::encode(result.export_key)
);
}
@@ -453,11 +591,10 @@ fn test_ke1() -> Result<(), ProtocolError> {
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?;
assert_eq!(
hex::encode(&parameters.KE1),
hex::encode(client_login_start_result.message.serialize()?)
hex::encode(client_login_start_result.message.serialize())
);
}
Ok(())
@@ -466,33 +603,45 @@ fn test_ke1() -> Result<(), ProtocolError> {
#[test]
fn test_ke2() -> Result<(), ProtocolError> {
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 =
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
&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(
&mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
&Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
&server_setup,
Some(record),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.server_info.to_vec(),
parameters.client_identity,
parameters.server_identity,
)
} else {
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
&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.client_info),
hex::encode(server_login_start_result.plain_info),
);
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize()?)
hex::encode(server_login_start_result.message.serialize())
);
}
Ok(())
@@ -511,32 +660,25 @@ fn test_ke3() -> Result<(), ProtocolError> {
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?;
let client_login_finish_result = client_login_start_result.state.finish(
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?,
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ClientLoginFinishParameters::WithIdentifiers(
parameters.client_identity,
parameters.server_identity,
)
} else {
ClientLoginFinishParameters::default()
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ClientLoginFinishParameters::WithContext(parameters.context),
Some(ids) => {
ClientLoginFinishParameters::WithContextAndIdentifiers(parameters.context, ids)
}
},
)?;
assert_eq!(
hex::encode(&parameters.server_info),
hex::encode(&client_login_finish_result.confidential_info)
);
assert_eq!(
hex::encode(&parameters.session_key),
hex::encode(&client_login_finish_result.session_key)
);
assert_eq!(
hex::encode(&parameters.KE3),
hex::encode(client_login_finish_result.message.serialize()?)
hex::encode(client_login_finish_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.export_key),
@@ -549,24 +691,40 @@ fn test_ke3() -> Result<(), ProtocolError> {
#[test]
fn test_server_login_finish() -> Result<(), ProtocolError> {
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 =
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
&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(
&mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
&Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
&server_setup,
Some(record),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.server_info.to_vec(),
parameters.client_identity,
parameters.server_identity,
)
} else {
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
&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,
),
},
)?;
@@ -581,3 +739,47 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
}
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(())
}
+11 -13
View File
@@ -21,7 +21,7 @@ struct VOPRFTestVectorParameters {
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
// in base mode
static OPRF_RISTRETTO255_SHA512: &'static [&str] = &[
static OPRF_RISTRETTO255_SHA512: &[&str] = &[
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
@@ -45,25 +45,23 @@ static OPRF_RISTRETTO255_SHA512: &'static [&str] = &[
];
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
values[key].as_str().and_then(|s| hex::decode(s).ok())
}
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
sksm: decode(&values, "sksm").unwrap(),
input: decode(&values, "input").unwrap(),
blind: decode(&values, "blind").unwrap(),
blinded_element: decode(&values, "blinded_element").unwrap(),
evaluation_element: decode(&values, "evaluation_element").unwrap(),
output: decode(&values, "output").unwrap(),
sksm: decode(values, "sksm").unwrap(),
input: decode(values, "input").unwrap(),
blind: decode(values, "blind").unwrap(),
blinded_element: decode(values, "blinded_element").unwrap(),
evaluation_element: decode(values, "evaluation_element").unwrap(),
output: decode(values, "output").unwrap(),
}
}
// Tests input -> blind, blinded_element
#[test]
fn test_blind() -> Result<(), ProtocolError> {
fn test_blind() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let mut rng = CycleRng::new(parameters.blind.to_vec());
@@ -106,7 +104,7 @@ fn test_evaluate() -> Result<(), PakeError> {
// Tests input, blind, evaluation_element -> output
#[test]
fn test_finalize() -> Result<(), ProtocolError> {
fn test_finalize() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
@@ -116,7 +114,7 @@ fn test_finalize() -> Result<(), ProtocolError> {
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.evaluation_element,
))?,
)?;
);
assert_eq!(&parameters.output, &output.to_vec());
}