General improvements (#250)

* Remove unnecessary constraints on hash

* Remove unnecessary `Result` on `KeyPair::generate_random`

* Fix de-serialization issue on `Ke1State`

* Fix rustfmt

* Remove allocations in `envelope`

* Run Clippy for tests and rustdoc lints too

* Fix `Debug` implementation

* Fix missing constraints on `ClientRegistration`

* Fix de-serialization

* Pin temporary dependency

* Update dependencies

* Replace macro with derive-where

* Remove unnecessary installation of Rust components

* Improve macro naming

* Implement `Copy`, `Debug`, `Ord` and `PartialOrd` for high-level items

* Add `rust-version` field to `Cargo.toml`

* Remove unnecessary allocations

* Fix MSRV

* Fix no_std

* Remove unnecessary allocations

* Remove unnecessary allocations

* Not importing items from voprf helps readability

* Fix rustdoc

* Remove unnecessary allocations

* Remove unnecessary allocations

* Replace `Vec` from `diffie_hellman` with `GenericArray`

* Remove unnecessary allocations

* Remove unnecessary allocations

* Remove `cfg(feature = bench)` guard for `missing_docs`

* Fix documentation

* Remove all remaining allocations from `KeyExchange`

* Improve type-safety

* Remove all remaining allocations in `keypair`

* Remove last remaining allocations except `NonVerifiableClient` input

* Remove base64 encoding in Serde implementation

* Remove unnecessary Serde `alloc` feature

* Make curve25519-dalek optional

* Rename `serialize` crate feature to `serde`

* Switch `KeGroup` implementations to higher-level libraries

- Fixes missing clamping in X25519
- X25519 is now a separate crate feature

* Fix typo
This commit is contained in:
daxpedda
2022-01-03 15:50:40 -08:00
committed by GitHub
parent d59d0b775b
commit 82e4436d39
25 changed files with 3604 additions and 2606 deletions
+40 -26
View File
@@ -4,7 +4,7 @@ on:
branches:
- main
pull_request:
types: [opened, repoened, synchronize]
types: [opened, reopened, synchronize]
jobs:
test:
@@ -13,14 +13,15 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- ristretto255_u64
- ristretto255_u32
- p256
- x25519_u64,ristretto255_u64
toolchain:
- stable
- 1.51.0
exclude:
- backend_feature: p256,u64_backend
- backend_feature: p256
toolchain: 1.51.0
name: test
steps:
@@ -33,7 +34,6 @@ jobs:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
@@ -57,9 +57,10 @@ jobs:
# 32-bit x86
- i686-unknown-linux-gnu
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- ristretto255_u64
- ristretto255_u32
- p256
- x25519_u64,ristretto255_u64
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
@@ -76,12 +77,18 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- ristretto255_u64
- ristretto255_u32
- p256
- ristretto255_u64,p256
- x25519_u64,ristretto255_u64
- x25519_u32,ristretto255_u32
- x25519_u64,p256
- x25519_u32,p256
- x25519_u64,ristretto255_u64,p256
frontend_feature:
- slow-hash
- serialize
- serde
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
@@ -108,7 +115,6 @@ jobs:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/simple_login.exp
@@ -132,7 +138,6 @@ jobs:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/digital_locker.exp
@@ -148,12 +153,13 @@ jobs:
# for any no_std target
- thumbv6m-none-eabi
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- ristretto255_u64
- ristretto255_u32
- p256
- x25519_u64,ristretto255_u64
frontend_feature:
- slow-hash
- serialize
- serde
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
@@ -167,9 +173,10 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
- p256,u64_backend
- ristretto255_u64
- ristretto255_u32
- p256
- x25519_u64,ristretto255_u64
steps:
- name: Checkout sources
uses: actions/checkout@v2
@@ -185,7 +192,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: bench
args: --no-default-features --features bench --features ${{ matrix.backend_feature }} --no-run
args: --no-default-features --features ${{ matrix.backend_feature }} --no-run
clippy:
name: cargo clippy
@@ -200,14 +207,21 @@ jobs:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
components: clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
args: --all-targets -- -D warnings
- name: Run cargo doc
uses: actions-rs/cargo@v1
env:
RUSTDOCFLAGS: -D warnings
with:
command: doc
args: --no-deps --document-private-items --features p256,slow-hash,std
format:
name: cargo fmt
@@ -222,7 +236,7 @@ jobs:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
components: rustfmt
- name: Run cargo fmt
uses: actions-rs/cargo@v1
Regular → Executable
+25 -13
View File
@@ -10,22 +10,33 @@ license = "Apache-2.0 OR MIT"
edition = "2018"
readme = "README.md"
resolver = "2"
rust-version = "1.51"
[features]
default = ["u64_backend", "serialize"]
default = ["ristretto255_u64", "serde"]
slow-hash = ["argon2"]
p256 = ["p256_", "voprf/p256"]
bench = []
u64_backend = ["curve25519-dalek/u64_backend", "voprf/ristretto255_u64"]
u32_backend = ["curve25519-dalek/u32_backend", "voprf/ristretto255_u32"]
std = ["curve25519-dalek/std", "getrandom", "rand/std", "rand/std_rng", "voprf/std"]
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde", "voprf/serde"]
ristretto255 = []
ristretto255_u64 = ["curve25519-dalek/u64_backend", "ristretto255", "voprf/ristretto255_u64"]
ristretto255_u32 = ["curve25519-dalek/u32_backend", "ristretto255", "voprf/ristretto255_u32"]
ristretto255_fiat_u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255", "voprf/ristretto255_fiat_u64"]
ristretto255_fiat_u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255", "voprf/ristretto255_fiat_u32"]
ristretto255_simd = ["curve25519-dalek/simd_backend", "ristretto255", "voprf/ristretto255_simd"]
x25519 = []
x25519_u64 = ["x25519", "x25519-dalek/u64_backend"]
x25519_u32 = ["x25519", "x25519-dalek/u32_backend"]
x25519_fiat_u64 = ["x25519", "x25519-dalek/fiat_u64_backend"]
x25519_fiat_u32 = ["x25519", "x25519-dalek/fiat_u32_backend"]
# x25519-dalek isn't properly re-exposing `simd_backend`.
x25519_simd = ["curve25519-dalek/simd_backend", "x25519", "x25519-dalek/nightly"]
std = ["getrandom", "rand/std", "rand/std_rng", "voprf/std"]
serde = ["serde_", "generic-array/serde", "voprf/serde"]
[dependencies]
argon2 = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true }
constant_time_eq = "0.1"
curve25519-dalek = { version = "3", default-features = false }
curve25519-dalek = { version = "3", default-features = false, optional = true }
derive-where = { version = "1.0.0-rc.1", features = ["zeroize"] }
digest = "0.9"
displaydoc = { version = "0.2", default-features = false }
generic-array = "0.14"
@@ -34,9 +45,10 @@ hkdf = "0.11"
hmac = "0.11"
p256_ = { package = "p256", version = "0.9", default-features = false, features = ["arithmetic", "zeroize"], optional = true }
rand = { version = "0.8", default-features = false }
serde = { version = "1", default-features = false, features = ["alloc", "derive"], optional = true }
serde_ = { version = "1", package = "serde", default-features = false, features = ["derive"], optional = true }
subtle = { version = "2.3", default-features = false }
voprf = { version = "0.2", default-features = false, features = ["danger"] }
voprf = { git = "https://github.com/khonsulabs/voprf", rev = "f8c19eab4ecc9e7a2a5ae26c59661ce797229566", default-features = false, features = ["danger"] }
x25519-dalek = { version = "1", default-features = false, optional = true }
zeroize = { version = "1", features = ["zeroize_derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
@@ -45,7 +57,7 @@ getrandom = { version = "0.2", features = ["js"], optional = true }
[dev-dependencies]
base64 = "0.13"
bincode = "1"
chacha20poly1305 = "0.8"
chacha20poly1305 = "0.9"
criterion = "0.3"
hex = "0.4"
json = "0.12"
@@ -55,9 +67,9 @@ serde_json = "1"
sha2 = "0.9"
proptest = "1"
regex = "1"
rustyline = "8"
# Version 9.1 requires an MSRV of 1.56
rustyline = "~9.0"
[[bench]]
name = "opaque"
harness = false
required-features = ["bench"]
+48 -49
View File
@@ -12,16 +12,20 @@ use criterion::Criterion;
use opaque_ke::*;
use rand::rngs::OsRng;
#[cfg(all(not(feature = "p256"), feature = "u64_backend"))]
static SUFFIX: &str = "u64_backend";
#[cfg(all(not(feature = "p256"), feature = "u32_backend"))]
static SUFFIX: &str = "u32_backend";
#[cfg(feature = "p256")]
#[cfg(feature = "ristretto255_u64")]
static SUFFIX: &str = "ristretto255_u64";
#[cfg(feature = "ristretto255_u32")]
static SUFFIX: &str = "ristretto255_u32";
#[cfg(feature = "ristretto255_fiat_u64")]
static SUFFIX: &str = "ristretto255_fiat_u64";
#[cfg(feature = "ristretto255_fiat_u32")]
static SUFFIX: &str = "ristretto255_fiat_u32";
#[cfg(all(not(feature = "ristretto255"), feature = "p256"))]
static SUFFIX: &str = "p256";
struct Default;
#[cfg(not(feature = "p256"))]
#[cfg(feature = "ristretto255")]
impl CipherSuite for Default {
type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -30,10 +34,10 @@ impl CipherSuite for Default {
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
#[cfg(feature = "p256")]
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for Default {
type OprfGroup = p256_::ProjectivePoint;
type KeGroup = p256_::ProjectivePoint;
type KeGroup = p256_::PublicKey;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha256;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
@@ -44,7 +48,7 @@ fn server_setup(c: &mut Criterion) {
c.bench_function(&format!("server setup ({})", SUFFIX), move |b| {
b.iter(|| {
ServerSetup::<Default>::new(&mut rng).unwrap();
ServerSetup::<Default>::new(&mut rng);
})
});
}
@@ -57,7 +61,7 @@ fn client_registration_start(c: &mut Criterion) {
&format!("client registration start ({})", SUFFIX),
move |b| {
b.iter(|| {
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
})
},
);
@@ -67,9 +71,9 @@ fn server_registration_start(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
c.bench_function(
&format!("server registration start ({})", SUFFIX),
@@ -78,7 +82,7 @@ fn server_registration_start(c: &mut Criterion) {
ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
&username[..],
username,
)
.unwrap();
})
@@ -90,13 +94,13 @@ fn client_registration_finish(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
&username[..],
username,
)
.unwrap();
@@ -122,21 +126,20 @@ fn server_registration_finish(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
&username[..],
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.clone()
.state
.finish(
&mut rng,
server_registration_start_result.message.clone(),
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
@@ -157,7 +160,7 @@ fn client_login_start(c: &mut Criterion) {
c.bench_function(&format!("client login start ({})", SUFFIX), move |b| {
b.iter(|| {
ClientLogin::<Default>::start(&mut rng, &password[..]).unwrap();
ClientLogin::<Default>::start(&mut rng, password).unwrap();
})
});
}
@@ -166,26 +169,25 @@ fn server_login_start_real(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
&username[..],
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.clone()
.state
.finish(
&mut rng,
server_registration_start_result.message.clone(),
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, &password[..]).unwrap();
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
c.bench_function(
&format!("server login start (real) ({})", SUFFIX),
@@ -196,7 +198,7 @@ fn server_login_start_real(c: &mut Criterion) {
&server_setup,
Some(password_file.clone()),
client_login_start_result.clone().message,
&username[..],
username,
ServerLoginStartParameters::default(),
)
.unwrap();
@@ -209,8 +211,8 @@ fn server_login_start_fake(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, &password[..]).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
c.bench_function(
&format!("server login start (fake) ({})", SUFFIX),
@@ -221,7 +223,7 @@ fn server_login_start_fake(c: &mut Criterion) {
&server_setup,
None,
client_login_start_result.clone().message,
&username[..],
username,
ServerLoginStartParameters::default(),
)
.unwrap();
@@ -234,32 +236,31 @@ fn client_login_finish(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
&username[..],
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.clone()
.state
.finish(
&mut rng,
server_registration_start_result.message.clone(),
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, &password[..]).unwrap();
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
let server_login_start = ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file.clone()),
Some(password_file),
client_login_start_result.clone().message,
&username[..],
username,
ServerLoginStartParameters::default(),
)
.unwrap();
@@ -282,37 +283,35 @@ fn server_login_finish(c: &mut Criterion) {
let mut rng = OsRng;
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, &password[..]).unwrap();
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
&username[..],
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.clone()
.state
.finish(
&mut rng,
server_registration_start_result.message.clone(),
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, &password[..]).unwrap();
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
let server_login_start_result = ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file.clone()),
Some(password_file),
client_login_start_result.clone().message,
&username[..],
username,
ServerLoginStartParameters::default(),
)
.unwrap();
let client_login_finish_result = client_login_start_result
.clone()
.state
.finish(
server_login_start_result.clone().message,
+32 -29
View File
@@ -28,6 +28,8 @@
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use generic_array::GenericArray;
use opaque_ke::ServerRegistrationLen;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::process::exit;
@@ -45,6 +47,8 @@ use opaque_ke::{
// that will be used in the OPAQUE protocol
#[allow(dead_code)]
struct Default;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Default {
type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -53,9 +57,18 @@ impl CipherSuite for Default {
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for Default {
type OprfGroup = p256_::ProjectivePoint;
type KeGroup = p256_::PublicKey;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha256;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
struct Locker {
contents: Vec<u8>,
password_file: Vec<u8>,
password_file: GenericArray<u8, ServerRegistrationLen<Default>>,
}
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
@@ -92,22 +105,16 @@ fn register_locker(
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let registration_request_bytes = client_registration_start_result
.message
.serialize()
.unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_setup,
RegistrationRequest::deserialize(&registration_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result
.message
.serialize()
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
// Server sends registration_response_bytes to client
@@ -115,14 +122,11 @@ fn register_locker(
.state
.finish(
&mut client_rng,
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
RegistrationResponse::deserialize(&registration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result
.message
.serialize()
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
// Client encrypts secret message using export key
let ciphertext = encrypt(
@@ -133,12 +137,12 @@ fn register_locker(
// Client sends message_bytes to server
let password_file = ServerRegistration::finish(
RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
RegistrationUpload::<Default>::deserialize(&message_bytes).unwrap(),
);
Locker {
contents: ciphertext,
password_file: password_file.serialize().unwrap(),
password_file: password_file.serialize(),
}
}
@@ -152,28 +156,27 @@ fn open_locker(
let mut client_rng = OsRng;
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().unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<Default>::deserialize(&locker.password_file[..]).unwrap();
let password_file = ServerRegistration::<Default>::deserialize(&locker.password_file).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
&server_setup,
server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
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
let result = client_login_start_result.state.finish(
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
@@ -182,13 +185,13 @@ 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
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.unwrap();
// Server sends locker contents, encrypted under the session key, to the client
@@ -208,7 +211,7 @@ fn open_locker(
fn main() {
let mut rng = OsRng;
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let mut rl = Editor::<()>::new();
let mut registered_lockers: Vec<Locker> = vec![];
@@ -292,7 +295,7 @@ fn main() {
// Helper functions
fn display_lockers(lockers: &Vec<Locker>) {
fn display_lockers(lockers: &[Locker]) {
let mut locker_numbers = vec![];
for (i, _) in lockers.iter().enumerate() {
locker_numbers.push(i);
+33 -28
View File
@@ -22,6 +22,7 @@
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
use generic_array::GenericArray;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::collections::HashMap;
@@ -31,13 +32,16 @@ use opaque_ke::{
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization,
CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration, ServerSetup,
RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration,
ServerRegistrationLen, ServerSetup,
};
// The ciphersuite trait allows to specify the underlying primitives
// that will be used in the OPAQUE protocol
#[allow(dead_code)]
struct Default;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Default {
type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -46,32 +50,35 @@ impl CipherSuite for Default {
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for Default {
type OprfGroup = p256_::ProjectivePoint;
type KeGroup = p256_::PublicKey;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha256;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
// Password-based registration between a client and server
fn account_registration(
server_setup: &ServerSetup<Default>,
username: String,
password: String,
) -> Vec<u8> {
) -> GenericArray<u8, ServerRegistrationLen<Default>> {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let registration_request_bytes = client_registration_start_result
.message
.serialize()
.unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_setup,
RegistrationRequest::deserialize(&registration_request_bytes).unwrap(),
username.as_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result
.message
.serialize()
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
// Server sends registration_response_bytes to client
@@ -79,21 +86,18 @@ fn account_registration(
.state
.finish(
&mut client_rng,
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
RegistrationResponse::deserialize(&registration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result
.message
.serialize()
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
// Client sends message_bytes to server
let password_file = ServerRegistration::finish(
RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
RegistrationUpload::<Default>::deserialize(&message_bytes).unwrap(),
);
password_file.serialize().unwrap()
password_file.serialize()
}
// Password-based login between a client and server
@@ -106,7 +110,7 @@ fn account_login(
let mut client_rng = OsRng;
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().unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
@@ -114,19 +118,19 @@ fn account_login(
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
&server_setup,
server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
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
let result = client_login_start_result.state.finish(
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
@@ -135,13 +139,13 @@ 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
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.unwrap();
client_login_finish_result.session_key == server_login_finish_result.session_key
@@ -149,10 +153,11 @@ fn account_login(
fn main() {
let mut rng = OsRng;
let server_setup = ServerSetup::<Default>::new(&mut rng).unwrap();
let server_setup = ServerSetup::<Default>::new(&mut rng);
let mut rl = Editor::<()>::new();
let mut registered_users = HashMap::<String, Vec<u8>>::new();
let mut registered_users =
HashMap::<String, GenericArray<u8, ServerRegistrationLen<Default>>>::new();
loop {
println!(
"\nCurrently registered usernames: {:?}\n",
Regular → Executable
+101 -124
View File
@@ -12,12 +12,17 @@ use crate::{
key_exchange::group::KeGroup,
keypair::{KeyPair, PublicKey},
opaque::{bytestrings_from_identifiers, Identifiers},
serialization::{MacExt, Serialize},
};
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryFrom;
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use core::ops::Add;
use derive_where::DeriveWhere;
use digest::{Digest, FixedOutput};
use generic_array::{
sequence::Concat,
typenum::{Sum, Unsigned, U2, U32},
ArrayLength, GenericArray,
};
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
@@ -25,13 +30,13 @@ use voprf::group::Group;
use zeroize::Zeroize;
// Constant string used as salt for HKDF computation
const STR_AUTH_KEY: &[u8; 7] = b"AuthKey";
const STR_EXPORT_KEY: &[u8; 9] = b"ExportKey";
const STR_PRIVATE_KEY: &[u8; 10] = b"PrivateKey";
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: &[u8; 24] = b"OPAQUE-DeriveAuthKeyPair";
const NONCE_LEN: usize = 32;
const STR_AUTH_KEY: [u8; 7] = *b"AuthKey";
const STR_EXPORT_KEY: [u8; 9] = *b"ExportKey";
const STR_PRIVATE_KEY: [u8; 10] = *b"PrivateKey";
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 24] = *b"OPAQUE-DeriveAuthKeyPair";
type NonceLen = U32;
#[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
#[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode {
Zero = 0,
@@ -49,42 +54,31 @@ impl TryFrom<u8> for InnerEnvelopeMode {
}
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
/// <https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4>
///
/// Note that earlier versions of this specification described an
/// implementation of this envelope using an encryption scheme that
/// satisfied random-key robustness
/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4).
/// (<https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4>).
/// 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(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub(crate) struct Envelope<CS: CipherSuite> {
mode: InnerEnvelopeMode,
nonce: Vec<u8>,
nonce: GenericArray<u8, NonceLen>,
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<CS: CipherSuite> {
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
pub(crate) client_static_keypair: KeyPair<CS::KeGroup>,
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) id_u: Serialize<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
pub(crate) id_s: Serialize<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
}
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
@@ -100,7 +94,7 @@ type SealRawResult<CS> = (
type SealRawResult<CS> = (
Envelope<CS>,
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
Vec<u8>,
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
);
#[cfg(not(test))]
type SealResult<CS> = (
@@ -113,30 +107,36 @@ type SealResult<CS> = (
Envelope<CS>,
PublicKey<<CS as CipherSuite>::KeGroup>,
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
Vec<u8>,
GenericArray<u8, <<CS as CipherSuite>::Hash as Digest>::OutputSize>,
);
#[allow(type_alias_bounds)]
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>;
impl<CS: CipherSuite> Envelope<CS> {
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R,
randomized_pwd_hasher: Hkdf<CS::Hash>,
server_s_pk: &[u8],
optional_ids: Option<Identifiers>,
server_s_pk: &PublicKey<CS::KeGroup>,
ids: Identifiers,
) -> Result<SealResult<CS>, ProtocolError> {
let mut nonce = vec![0u8; NONCE_LEN];
let mut nonce = GenericArray::default();
rng.fill_bytes(&mut nonce);
let (mode, client_s_pk) = (
InnerEnvelopeMode::Internal,
build_inner_envelope_internal::<CS>(randomized_pwd_hasher.clone(), &nonce)?,
build_inner_envelope_internal::<CS>(randomized_pwd_hasher.clone(), 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 (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
ids,
client_s_pk.to_arr(),
server_s_pk.to_arr(),
)?;
let aad = construct_aad(id_u.iter(), id_s.iter(), server_s_pk);
let result = Self::seal_raw(randomized_pwd_hasher, &nonce, &aad, mode)?;
let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
Ok((
result.0,
client_s_pk,
@@ -149,64 +149,64 @@ impl<CS: CipherSuite> Envelope<CS> {
/// 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.
#[allow(clippy::type_complexity)]
pub(crate) fn seal_raw(
pub(crate) fn seal_raw<'a>(
randomized_pwd_hasher: Hkdf<CS::Hash>,
nonce: &[u8],
aad: &[u8],
nonce: GenericArray<u8, NonceLen>,
aad: impl Iterator<Item = &'a [u8]>,
mode: InnerEnvelopeMode,
) -> Result<SealRawResult<CS>, InternalError> {
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
let mut hmac_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
let mut export_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
randomized_pwd_hasher
.expand(&[nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
.expand_multi_info(&[&nonce, &STR_AUTH_KEY], &mut hmac_key)
.map_err(|_| InternalError::HkdfError)?;
randomized_pwd_hasher
.expand(&[nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.expand_multi_info(&[&nonce, &STR_EXPORT_KEY], &mut export_key)
.map_err(|_| InternalError::HkdfError)?;
let mut hmac =
Hmac::<CS::Hash>::new_from_slice(&hmac_key).map_err(|_| InternalError::HmacError)?;
hmac.update(nonce);
hmac.update(aad);
hmac.update(&nonce);
hmac.update_iter(aad);
let hmac_bytes = hmac.finalize().into_bytes();
Ok((
Self {
mode,
nonce: nonce.to_vec(),
nonce,
hmac: hmac_bytes,
},
GenericArray::clone_from_slice(&export_key),
export_key,
#[cfg(test)]
hmac_key,
))
}
pub(crate) fn open(
pub(crate) fn open<'a>(
&self,
randomized_pwd_hasher: Hkdf<CS::Hash>,
server_s_pk: &[u8],
optional_ids: &Option<Identifiers>,
) -> Result<OpenedEnvelope<CS>, ProtocolError> {
server_s_pk: PublicKey<CS::KeGroup>,
optional_ids: Identifiers<'a>,
) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
let client_static_keypair = match self.mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => {
recover_keys_internal::<CS>(randomized_pwd_hasher.clone(), &self.nonce)?
recover_keys_internal::<CS>(randomized_pwd_hasher.clone(), self.nonce)?
}
};
let (id_u, id_s) = bytestrings_from_identifiers(
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
optional_ids,
&client_static_keypair.public().to_arr(),
server_s_pk,
client_static_keypair.public().to_arr(),
server_s_pk.to_arr(),
)?;
let aad = construct_aad(&id_u, &id_s, server_s_pk);
let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk);
let opened = self.open_raw(randomized_pwd_hasher, &aad)?;
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
Ok(OpenedEnvelope {
client_static_keypair,
@@ -218,51 +218,37 @@ impl<CS: CipherSuite> Envelope<CS> {
/// Attempts to decrypt the envelope using a key, which is successful only if the key and
/// aad used to construct the envelope are the same.
pub(crate) fn open_raw(
pub(crate) fn open_raw<'a>(
&self,
randomized_pwd_hasher: Hkdf<CS::Hash>,
aad: &[u8],
aad: impl Iterator<Item = &'a [u8]>,
) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalError> {
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
let mut hmac_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
let mut export_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
randomized_pwd_hasher
.expand(
&[self.nonce.clone(), STR_AUTH_KEY.to_vec()].concat(),
&mut hmac_key,
)
.expand(&self.nonce.concat(STR_AUTH_KEY.into()), &mut hmac_key)
.map_err(|_| InternalError::HkdfError)?;
randomized_pwd_hasher
.expand(
&[self.nonce.clone(), STR_EXPORT_KEY.to_vec()].concat(),
&mut export_key,
)
.expand(&self.nonce.concat(STR_EXPORT_KEY.into()), &mut export_key)
.map_err(|_| InternalError::HkdfError)?;
let mut hmac =
Hmac::<CS::Hash>::new_from_slice(&hmac_key).map_err(|_| InternalError::HmacError)?;
hmac.update(&self.nonce);
hmac.update(aad);
if hmac.verify(&self.hmac).is_err() {
return Err(InternalError::SealOpenHmacError);
}
hmac.update_iter(aad);
hmac.verify(&self.hmac)
.map_err(|_| InternalError::SealOpenHmacError)?;
Ok(OpenedInnerEnvelope {
export_key: GenericArray::<u8, <CS::Hash as Digest>::OutputSize>::clone_from_slice(
&export_key,
),
})
Ok(OpenedInnerEnvelope { export_key })
}
// 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::USIZE
]),
nonce: GenericArray::default(),
hmac: GenericArray::default(),
}
}
@@ -270,34 +256,36 @@ impl<CS: CipherSuite> Envelope<CS> {
<CS::Hash as Digest>::OutputSize::USIZE
}
fn export_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE
}
pub(crate) fn len() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE + NONCE_LEN
<CS::Hash as Digest>::OutputSize::USIZE + NonceLen::USIZE
}
pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.nonce[..], &self.hmac[..]].concat()
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
{
self.nonce.concat(self.hmac.clone())
}
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
if bytes.len() < NONCE_LEN {
if bytes.len() < NonceLen::USIZE {
return Err(ProtocolError::SerializationError);
}
let nonce = bytes[..NONCE_LEN].to_vec();
let nonce = GenericArray::clone_from_slice(&bytes[..NonceLen::USIZE]);
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(),
InnerEnvelopeMode::Internal => &bytes[NonceLen::USIZE..],
};
let hmac_key_size = Self::hmac_key_size();
let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
let hmac = check_slice_size(remainder, hmac_key_size, "hmac_key_size")?;
Ok(Self {
mode,
@@ -307,35 +295,20 @@ impl<CS: CipherSuite> Envelope<CS> {
}
}
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for Envelope<CS> {
fn zeroize(&mut self) {
self.mode.zeroize();
self.nonce.zeroize();
self.hmac.zeroize();
}
}
impl<CS: CipherSuite> Drop for Envelope<CS> {
fn drop(&mut self) {
self.zeroize();
}
}
// Helper functions
fn build_inner_envelope_internal<CS: CipherSuite>(
randomized_pwd_hasher: Hkdf<CS::Hash>,
nonce: &[u8],
nonce: GenericArray<u8, NonceLen>,
) -> Result<PublicKey<CS::KeGroup>, ProtocolError> {
let mut keypair_seed = vec![0u8; <CS::KeGroup as KeGroup>::SkLen::USIZE];
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
randomized_pwd_hasher
.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash, _, _>(
Some(&keypair_seed[..]),
GenericArray::from(*STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
Some(keypair_seed.as_slice()),
GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
)?),
)?;
@@ -344,22 +317,26 @@ fn build_inner_envelope_internal<CS: CipherSuite>(
fn recover_keys_internal<CS: CipherSuite>(
randomized_pwd_hasher: Hkdf<CS::Hash>,
nonce: &[u8],
nonce: GenericArray<u8, NonceLen>,
) -> Result<KeyPair<CS::KeGroup>, ProtocolError> {
let mut keypair_seed = vec![0u8; <CS::KeGroup as KeGroup>::SkLen::USIZE];
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
randomized_pwd_hasher
.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash, _, _>(
Some(&keypair_seed[..]),
GenericArray::from(*STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
Some(keypair_seed.as_slice()),
GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
)?),
)?;
Ok(client_static_keypair)
}
fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> {
[server_s_pk, id_s, id_u].concat()
fn construct_aad<'a>(
id_u: impl Iterator<Item = &'a [u8]>,
id_s: impl Iterator<Item = &'a [u8]>,
server_s_pk: &'a [u8],
) -> impl Iterator<Item = &'a [u8]> {
chain!(Some(server_s_pk).into_iter(), id_s, id_u)
}
+2 -2
View File
@@ -14,7 +14,7 @@ use std::error::Error;
use displaydoc::Display;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Clone, Display, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum InternalError<T = Infallible> {
/// Custom [`SecretKey`](crate::keypair::SecretKey) error type
Custom(T),
@@ -129,7 +129,7 @@ impl From<voprf::errors::InternalError> for ProtocolError {
}
/// Represents an error in protocol handling
#[derive(Clone, Display, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ProtocolError<T = Infallible> {
/// Internal error encountered
LibraryError(InternalError<T>),
Regular → Executable
+49 -121
View File
@@ -5,139 +5,67 @@
// License, Version 2.0 found in the LICENSE-APACHE 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)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: core::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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)?),+>)? core::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: core::hash::Hash,)+)?
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.$field1, state);
$(core::hash::Hash::hash(&self.$field2, state);)*
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? core::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: core::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::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)?),+>)? core::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: core::hash::Hash,)+)?
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.$field1, state);
$(core::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(),)*
)
}
}
};
}
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
/// 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> {
($item:ident$( where $($path:ty: $bound1:path $(| $bound2:path)*),+$(,)?)?$(; $error:expr)?) => {
#[cfg(feature = "serde")]
impl<CS: CipherSuite> serde_::Serialize for $item<CS>
$(where
$($path: $bound1 $(+ $bound2)*),+
)?
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
S: serde_::Serializer,
{
use serde::ser::Error;
if serializer.is_human_readable() {
serializer
.serialize_str(&base64::encode(&self.serialize().map_err(Error::custom)?))
} else {
serializer.serialize_bytes(&self.serialize().map_err(Error::custom)?)
}
serializer.serialize_bytes(&self.serialize()$(.map_err($error)?)?)
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
#[cfg(feature = "serde")]
impl<'de, CS: CipherSuite> serde_::Deserialize<'de> for $item<CS>
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
D: serde_::Deserializer<'de>,
{
use serde::de::Error;
use serde_::de::Error;
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
Self::deserialize(&base64::decode(s).map_err(Error::custom)?)
} else {
Self::deserialize(<&[u8]>::deserialize(deserializer)?)
struct ByteVisitor<CS: CipherSuite>(core::marker::PhantomData<CS>);
impl<'de, CS: CipherSuite> serde_::de::Visitor<'de> for ByteVisitor<CS>
{
type Value = $item<CS>;
fn expecting(
&self,
formatter: &mut core::fmt::Formatter,
) -> core::fmt::Result {
formatter.write_str(core::concat!(
"the byte representation of a ",
core::stringify!($t)
))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
$item::<CS>::deserialize(value).map_err(|_| {
Error::invalid_value(
serde_::de::Unexpected::Bytes(value),
&core::concat!(
"invalid byte sequence for ",
core::stringify!($t)
),
)
})
}
}
.map_err(Error::custom)
deserializer
.deserialize_bytes(ByteVisitor::<CS>(core::marker::PhantomData))
.map_err(Error::custom)
}
}
};
+2
View File
@@ -37,5 +37,7 @@ pub trait KeGroup: Sized + Clone {
#[cfg(feature = "p256")]
pub mod p256;
#[cfg(feature = "ristretto255")]
pub mod ristretto255;
#[cfg(feature = "x25519")]
pub mod x25519;
+16 -16
View File
@@ -11,39 +11,39 @@ use super::KeGroup;
use crate::errors::InternalError;
use generic_array::typenum::{U32, U33};
use generic_array::GenericArray;
use p256_::elliptic_curve::group::GroupEncoding;
use p256_::elliptic_curve::sec1::ToEncodedPoint;
use p256_::elliptic_curve::{PublicKey, SecretKey};
use p256_::NistP256;
use rand::{CryptoRng, RngCore};
impl KeGroup for p256_::ProjectivePoint {
impl KeGroup for PublicKey<NistP256> {
type PkLen = U33;
type SkLen = U32;
fn from_pk_slice(element_bits: &GenericArray<u8, Self::PkLen>) -> Result<Self, InternalError> {
use p256_::elliptic_curve::group::GroupEncoding;
Option::from(Self::from_bytes(element_bits)).ok_or(InternalError::PointError)
Self::from_sec1_bytes(element_bits).map_err(|_| InternalError::PointError)
}
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Self::SkLen> {
use p256_::elliptic_curve::Field;
p256_::Scalar::random(rng).into()
SecretKey::<NistP256>::random(rng).to_bytes()
}
fn public_key(sk: &GenericArray<u8, Self::SkLen>) -> Self {
Self::generator() * p256_::Scalar::from_bytes_reduced(sk)
SecretKey::<NistP256>::from_bytes(sk).unwrap().public_key()
}
fn to_arr(&self) -> GenericArray<u8, Self::PkLen> {
use p256_::elliptic_curve::sec1::ToEncodedPoint;
let bytes = self.to_affine().to_encoded_point(true);
let bytes = bytes.as_bytes();
let mut result = GenericArray::default();
result[..bytes.len()].copy_from_slice(bytes);
result
GenericArray::clone_from_slice(self.to_encoded_point(true).as_bytes())
}
fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::PkLen> {
(self * &p256_::Scalar::from_bytes_reduced(sk)).to_arr()
(self.to_projective()
* SecretKey::<NistP256>::from_bytes(sk)
.unwrap()
.to_secret_scalar()
.as_ref())
.to_affine()
.to_bytes()
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ impl KeGroup for RistrettoPoint {
self.compress().to_bytes().into()
}
fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::PkLen> {
fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::SkLen> {
(self * Scalar::from_bits(*sk.as_ref())).to_arr()
}
}
+15 -120
View File
@@ -5,152 +5,47 @@
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
//! Key Exchange group implementation for x25519
//! Key Exchange group implementation for X25519
use super::KeGroup;
use crate::errors::InternalError;
use curve25519_dalek::{constants::X25519_BASEPOINT, montgomery::MontgomeryPoint, scalar::Scalar};
use generic_array::{typenum::U32, GenericArray};
use rand::{CryptoRng, RngCore};
use x25519_dalek::{PublicKey, StaticSecret};
/// The implementation of such a subgroup for Ristretto
impl KeGroup for MontgomeryPoint {
impl KeGroup for PublicKey {
type PkLen = U32;
type SkLen = U32;
fn from_pk_slice(element_bits: &GenericArray<u8, Self::PkLen>) -> Result<Self, InternalError> {
Ok(Self(*element_bits.as_ref()))
Ok(Self::from(<[u8; 32]>::from(*element_bits)))
}
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Self::SkLen> {
let mut scalar_bytes = [0u8; 32];
loop {
let scalar = {
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
rng.fill_bytes(&mut scalar_bytes);
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
};
if scalar != Scalar::zero() {
break GenericArray::clone_from_slice(&scalar.to_bytes());
if scalar_bytes != [0u8; 32] {
break StaticSecret::from(scalar_bytes).to_bytes().into();
}
}
}
fn public_key(sk: &GenericArray<u8, Self::SkLen>) -> Self {
X25519_BASEPOINT * Scalar::from_bits(*sk.as_ref())
Self::from(&StaticSecret::from(<[u8; 32]>::from(*sk)))
}
fn to_arr(&self) -> GenericArray<u8, Self::PkLen> {
self.to_bytes().into()
}
fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::PkLen> {
(self * Scalar::from_bits(*sk.as_ref())).to_arr()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::ProtocolError;
#[test]
fn test_x25519() -> Result<(), ProtocolError> {
use crate::{
key_exchange::tripledh::TripleDH, slow_hash::NoOpHash, CipherSuite, ClientLogin,
ClientLoginFinishParameters, ClientLoginFinishResult, ClientLoginStartResult,
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationFinishResult,
ClientRegistrationStartResult, ServerLogin, ServerLoginStartParameters,
ServerLoginStartResult, ServerRegistration, ServerSetup,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use rand::rngs::OsRng;
struct X25519Sha512NoSlowHash;
impl CipherSuite for X25519Sha512NoSlowHash {
type OprfGroup = RistrettoPoint;
type KeGroup = MontgomeryPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
const PASSWORD: &[u8] = b"1234";
let server_setup = ServerSetup::<X25519Sha512NoSlowHash>::new(&mut OsRng)?;
let ClientRegistrationStartResult {
message,
state: client,
} = ClientRegistration::start(&mut OsRng, PASSWORD)?;
let message = ServerRegistration::start(&server_setup, message, &[])?.message;
let ClientRegistrationFinishResult {
message,
export_key: register_export_key,
..
} = client.finish(
&mut OsRng,
message,
ClientRegistrationFinishParameters::default(),
)?;
let server_registration = ServerRegistration::finish(message);
let ClientLoginStartResult {
message,
state: client,
} = ClientLogin::start(&mut OsRng, PASSWORD)?;
let ServerLoginStartResult {
message,
state: server,
..
} = ServerLogin::start(
&mut OsRng,
&server_setup,
Some(server_registration),
message,
&[],
ServerLoginStartParameters::default(),
)?;
let ClientLoginFinishResult {
message,
session_key: client_session_key,
export_key: login_export_key,
..
} = client.finish(message, ClientLoginFinishParameters::default())?;
let server_session_key = server.finish(message)?.session_key;
assert_eq!(register_export_key, login_export_key);
assert_eq!(client_session_key, server_session_key);
let ClientLoginStartResult {
message,
state: client,
} = ClientLogin::start(&mut OsRng, PASSWORD)?;
let ServerLoginStartResult { message, .. } = ServerLogin::start(
&mut OsRng,
&server_setup,
None,
message,
&[],
ServerLoginStartParameters::default(),
)?;
assert!(matches!(
client.finish(message, ClientLoginFinishParameters::default()),
Err(ProtocolError::InvalidLoginError)
));
Ok(())
fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::SkLen> {
StaticSecret::from(<[u8; 32]>::from(*sk))
.diffie_hellman(self)
.to_bytes()
.into()
}
}
Regular → Executable
+46 -23
View File
@@ -12,7 +12,8 @@ use crate::{
hash::Hash,
keypair::{PrivateKey, PublicKey, SecretKey},
};
use alloc::vec::Vec;
use digest::Digest;
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
use zeroize::Zeroize;
@@ -25,23 +26,26 @@ pub type GenerateKe2Result<K, D, G> = (
pub type GenerateKe2Result<K, D, G> = (
<K as KeyExchange<D, G>>::KE2State,
<K as KeyExchange<D, G>>::KE2Message,
Vec<u8>,
generic_array::GenericArray<u8, <D as digest::Digest>::OutputSize>,
GenericArray<u8, <D as Digest>::OutputSize>,
GenericArray<u8, <D as Digest>::OutputSize>,
);
#[cfg(not(test))]
pub type GenerateKe3Result<K, D, G> = (Vec<u8>, <K as KeyExchange<D, G>>::KE3Message);
pub type GenerateKe3Result<K, D, G> = (
GenericArray<u8, <D as Digest>::OutputSize>,
<K as KeyExchange<D, G>>::KE3Message,
);
#[cfg(test)]
pub type GenerateKe3Result<K, D, G> = (
Vec<u8>,
GenericArray<u8, <D as Digest>::OutputSize>,
<K as KeyExchange<D, G>>::KE3Message,
Vec<u8>,
generic_array::GenericArray<u8, <D as digest::Digest>::OutputSize>,
GenericArray<u8, <D as Digest>::OutputSize>,
GenericArray<u8, <D as Digest>::OutputSize>,
);
pub trait KeyExchange<D: Hash, G: KeGroup> {
type KE1State: FromBytes + ToBytes + Zeroize + Clone;
type KE2State: FromBytes + ToBytes + Zeroize + Clone;
type KE1Message: FromBytes + ToBytes + Clone;
type KE1Message: FromBytes + ToBytes + Zeroize + Clone;
type KE2Message: FromBytes + ToBytes + Clone;
type KE3Message: FromBytes + ToBytes + Clone;
@@ -50,44 +54,63 @@ pub trait KeyExchange<D: Hash, G: KeGroup> {
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng, S: SecretKey<G>>(
fn generate_ke2<'a, 'b, 'c, 'd, R: RngCore + CryptoRng, S: SecretKey<G>>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
l1_bytes: impl Iterator<Item = &'a [u8]>,
l2_bytes: impl Iterator<Item = &'b [u8]>,
ke1_message: Self::KE1Message,
client_s_pk: PublicKey<G>,
server_s_sk: S,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
id_u: impl Iterator<Item = &'c [u8]>,
id_s: impl Iterator<Item = &'d [u8]>,
context: &[u8],
) -> Result<GenerateKe2Result<Self, D, G>, ProtocolError<S::Error>>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
fn generate_ke3<'a, 'b, 'c, 'd>(
l2_component: impl Iterator<Item = &'a [u8]>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
serialized_credential_request: impl Iterator<Item = &'b [u8]>,
server_s_pk: PublicKey<G>,
client_s_sk: PrivateKey<G>,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
id_u: impl Iterator<Item = &'c [u8]>,
id_s: impl Iterator<Item = &'d [u8]>,
context: &[u8],
) -> Result<GenerateKe3Result<Self, D, G>, ProtocolError>;
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError>;
) -> Result<GenericArray<u8, D::OutputSize>, ProtocolError>;
fn ke2_message_size() -> usize;
}
pub trait FromBytes: Sized {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError>;
fn from_bytes(input: &[u8]) -> Result<Self, ProtocolError>;
}
pub trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
type Len: ArrayLength<u8>;
fn to_bytes(&self) -> GenericArray<u8, Self::Len>;
}
#[allow(dead_code, type_alias_bounds)]
pub type Ke1StateLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State as ToBytes>::Len;
#[allow(type_alias_bounds)]
pub type Ke1MessageLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message as ToBytes>::Len;
#[allow(type_alias_bounds)]
#[allow(type_alias_bounds)]
pub type Ke2StateLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State as ToBytes>::Len;
#[allow(type_alias_bounds)]
pub type Ke2MessageLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message as ToBytes>::Len;
#[allow(type_alias_bounds)]
pub type Ke3MessageLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message as ToBytes>::Len;
Regular → Executable
+218 -193
View File
@@ -7,7 +7,6 @@
//! An implementation of the Triple Diffie-Hellman key exchange protocol
use crate::{
ciphersuite::CipherSuite,
errors::{
utils::{check_slice_size, check_slice_size_atleast},
InternalError, ProtocolError,
@@ -18,20 +17,21 @@ use crate::{
traits::{FromBytes, GenerateKe2Result, GenerateKe3Result, KeyExchange, ToBytes},
},
keypair::{KeyPair, PrivateKey, PublicKey, SecretKey},
serialization::serialize,
serialization::{Serialize, UpdateExt},
};
use alloc::vec;
use alloc::vec::Vec;
use core::array::IntoIter;
use core::convert::TryFrom;
use core::ops::Add;
use derive_where::DeriveWhere;
use digest::{Digest, FixedOutput};
use generic_array::sequence::Concat;
use generic_array::{
typenum::{Unsigned, U32},
typenum::{Sum, Unsigned, U1, U2, U32},
ArrayLength, GenericArray,
};
use hkdf::Hkdf;
use hkdf::{Hkdf, HkdfExtract};
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use zeroize::Zeroize;
///////////////
// Constants //
@@ -56,55 +56,69 @@ static STR_OPAQUE: &[u8] = b"OPAQUE-";
pub struct TripleDH;
/// The client state produced after the first key exchange message
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub struct Ke1State<KG: KeGroup> {
client_e_sk: PrivateKey<KG>,
client_nonce: GenericArray<u8, NonceLen>,
}
impl_clone_for!(
struct Ke1State<KG: KeGroup>,
[client_e_sk, client_nonce],
);
impl_debug_eq_hash_for!(
struct Ke1State<KG: KeGroup>,
[client_e_sk, client_nonce],
);
/// The first key exchange message
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
pub struct Ke1Message<KG: KeGroup> {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) client_e_pk: PublicKey<KG>,
}
/// 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>,
session_key: GenericArray<u8, HashLen>,
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub struct Ke2State<D: Hash> {
km3: GenericArray<u8, D::OutputSize>,
hashed_transcript: GenericArray<u8, D::OutputSize>,
session_key: GenericArray<u8, D::OutputSize>,
}
/// 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 Ke2Message<KG: KeGroup, HashLen: ArrayLength<u8>> {
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ke2Message<D: Hash, KG: KeGroup> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: PublicKey<KG>,
mac: GenericArray<u8, HashLen>,
mac: GenericArray<u8, D::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>,
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ke3Message<D: Hash> {
mac: GenericArray<u8, D::OutputSize>,
}
////////////////////////////////
@@ -112,17 +126,33 @@ pub struct Ke3Message<HashLen: ArrayLength<u8>> {
// ========================== //
////////////////////////////////
impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDH {
impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDH
where
// Ke1State: KeSk + Nonce
KG::SkLen: Add<NonceLen>,
Sum<KG::SkLen, NonceLen>: ArrayLength<u8>,
// Ke1Message: Nonce + KePk
NonceLen: Add<KG::PkLen>,
Sum<NonceLen, KG::PkLen>: ArrayLength<u8>,
// Ke2State: (Hash + Hash) + Hash
D::OutputSize: Add<D::OutputSize>,
Sum<D::OutputSize, D::OutputSize>: ArrayLength<u8> + Add<D::OutputSize>,
Sum<Sum<D::OutputSize, D::OutputSize>, D::OutputSize>: ArrayLength<u8>,
// Ke2Message: (Nonce + KePk) + Hash
NonceLen: Add<KG::PkLen>,
Sum<NonceLen, KG::PkLen>: ArrayLength<u8> + Add<D::OutputSize>,
Sum<Sum<NonceLen, KG::PkLen>, D::OutputSize>: ArrayLength<u8>,
{
type KE1State = Ke1State<KG>;
type KE2State = Ke2State<<D as FixedOutput>::OutputSize>;
type KE2State = Ke2State<D>;
type KE1Message = Ke1Message<KG>;
type KE2Message = Ke2Message<KG, <D as FixedOutput>::OutputSize>;
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
type KE2Message = Ke2Message<D, KG>;
type KE3Message = Ke3Message<D>;
fn generate_ke1<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyPair::<KG>::generate_random(rng)?;
let client_e_kp = KeyPair::<KG>::generate_random(rng);
let client_nonce = generate_nonce::<R>(rng);
let ke1_message = Ke1Message {
@@ -140,29 +170,32 @@ impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDH {
}
#[allow(clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng, S: SecretKey<KG>>(
fn generate_ke2<'a, 'b, 'c, 'd, R: RngCore + CryptoRng, S: SecretKey<KG>>(
rng: &mut R,
serialized_credential_request: Vec<u8>,
l2_bytes: Vec<u8>,
serialized_credential_request: impl Iterator<Item = &'a [u8]>,
l2_bytes: impl Iterator<Item = &'b [u8]>,
ke1_message: Self::KE1Message,
client_s_pk: PublicKey<KG>,
server_s_sk: S,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
id_u: impl Iterator<Item = &'c [u8]>,
id_s: impl Iterator<Item = &'d [u8]>,
context: &[u8],
) -> Result<GenerateKe2Result<Self, D, KG>, ProtocolError<S::Error>> {
let server_e_kp =
KeyPair::<KG>::generate_random(rng).map_err(|_| InternalError::InvalidKeypairError)?;
let server_e_kp = KeyPair::<KG>::generate_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let mut transcript_hasher = D::new()
.chain(STR_RFC)
.chain(&serialize(&context, 2).map_err(ProtocolError::into_custom)?)
.chain(&id_u)
.chain(&serialized_credential_request[..])
.chain(&id_s)
.chain(&l2_bytes[..])
.chain(&server_nonce[..])
.chain_iter(
Serialize::<U2>::from(context)
.map_err(ProtocolError::into_custom)?
.iter(),
)
.chain_iter(id_u.into_iter())
.chain_iter(serialized_credential_request)
.chain_iter(id_s.into_iter())
.chain_iter(l2_bytes)
.chain(server_nonce)
.chain(&server_e_kp.public().to_arr());
let result = derive_3dh_keys::<D, KG, S>(
@@ -203,25 +236,25 @@ impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDH {
}
#[allow(clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
fn generate_ke3<'a, 'b, 'c, 'd>(
l2_component: impl Iterator<Item = &'a [u8]>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
serialized_credential_request: impl Iterator<Item = &'b [u8]>,
server_s_pk: PublicKey<KG>,
client_s_sk: PrivateKey<KG>,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
id_u: impl Iterator<Item = &'c [u8]>,
id_s: impl Iterator<Item = &'d [u8]>,
context: &[u8],
) -> Result<GenerateKe3Result<Self, D, KG>, ProtocolError> {
let mut transcript_hasher = D::new()
.chain(STR_RFC)
.chain(&serialize(&context, 2)?)
.chain(&id_u)
.chain(&serialized_credential_request)
.chain(&id_s)
.chain(&l2_component[..])
.chain(&ke2_message.to_bytes_without_info_or_mac());
.chain_iter(Serialize::<U2>::from(context)?.iter())
.chain_iter(id_u)
.chain_iter(serialized_credential_request)
.chain_iter(id_s)
.chain_iter(l2_component)
.chain_iter(ke2_message.to_bytes_without_info_or_mac());
let result = derive_3dh_keys::<D, KG, PrivateKey<KG>>(
TripleDHComponents {
@@ -239,18 +272,18 @@ impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDH {
Hmac::<D>::new_from_slice(&result.1).map_err(|_| InternalError::HmacError)?;
server_mac.update(&transcript_hasher.clone().finalize());
if server_mac.verify(&ke2_message.mac).is_err() {
return Err(ProtocolError::InvalidLoginError);
}
server_mac
.verify(&ke2_message.mac)
.map_err(|_| ProtocolError::InvalidLoginError)?;
transcript_hasher.update(ke2_message.mac.to_vec());
transcript_hasher.update(&ke2_message.mac);
let mut client_mac =
Hmac::<D>::new_from_slice(&result.2).map_err(|_| InternalError::HmacError)?;
client_mac.update(&transcript_hasher.finalize());
Ok((
result.0.to_vec(),
result.0,
Ke3Message {
mac: client_mac.finalize().into_bytes(),
},
@@ -265,16 +298,16 @@ impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDH {
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> {
) -> Result<GenericArray<u8, D::OutputSize>, ProtocolError> {
let mut client_mac =
Hmac::<D>::new_from_slice(&ke2_state.km3).map_err(|_| InternalError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript);
if client_mac.verify(&ke3_message.mac).is_err() {
return Err(ProtocolError::InvalidLoginError);
}
client_mac
.verify(&ke3_message.mac)
.map_err(|_| ProtocolError::InvalidLoginError)?;
Ok(ke2_state.session_key.to_vec())
Ok(ke2_state.session_key.clone())
}
fn ke2_message_size() -> usize {
@@ -311,7 +344,7 @@ type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
Vec<u8>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
);
////////////////////////////////////////////////
@@ -327,18 +360,21 @@ fn derive_3dh_keys<D: Hash, KG: KeGroup, S: SecretKey<KG>>(
dh: TripleDHComponents<KG, S>,
hashed_derivation_transcript: &[u8],
) -> Result<TripleDHDerivationResult<D>, ProtocolError<S::Error>> {
let ikm: Vec<u8> = [
let mut hkdf = HkdfExtract::<D>::new(None);
hkdf.input_ikm(
&dh.sk1
.diffie_hellman(dh.pk1)
.map_err(InternalError::into_custom)?[..],
&dh.sk2.diffie_hellman(dh.pk2)?[..],
.map_err(InternalError::into_custom)?,
);
hkdf.input_ikm(&dh.sk2.diffie_hellman(dh.pk2)?);
hkdf.input_ikm(
&dh.sk3
.diffie_hellman(dh.pk3)
.map_err(InternalError::into_custom)?[..],
]
.concat();
.map_err(InternalError::into_custom)?,
);
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let (_, extracted_ikm) = hkdf.finalize();
let handshake_secret = derive_secrets::<D>(
&extracted_ikm,
STR_HANDSHAKE_SECRET,
@@ -352,20 +388,10 @@ fn derive_3dh_keys<D: Hash, KG: KeGroup, S: SecretKey<KG>>(
)
.map_err(ProtocolError::into_custom)?;
let km2 = hkdf_expand_label::<D>(
&handshake_secret,
STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::USIZE,
)
.map_err(ProtocolError::into_custom)?;
let km3 = hkdf_expand_label::<D>(
&handshake_secret,
STR_CLIENT_MAC,
b"",
<D as Digest>::OutputSize::USIZE,
)
.map_err(ProtocolError::into_custom)?;
let km2 = hkdf_expand_label::<D>(&handshake_secret, STR_SERVER_MAC, b"")
.map_err(ProtocolError::into_custom)?;
let km3 = hkdf_expand_label::<D>(&handshake_secret, STR_CLIENT_MAC, b"")
.map_err(ProtocolError::into_custom)?;
Ok((
GenericArray::clone_from_slice(&session_key),
@@ -380,33 +406,35 @@ fn hkdf_expand_label<D: Hash>(
secret: &[u8],
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
) -> Result<GenericArray<u8, D::OutputSize>, ProtocolError> {
let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalError::HkdfError)?;
hkdf_expand_label_extracted(&h, label, context, length)
hkdf_expand_label_extracted(&h, label, context)
}
fn hkdf_expand_label_extracted<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let mut okm = vec![0u8; length];
) -> Result<GenericArray<u8, D::OutputSize>, ProtocolError> {
let mut okm = GenericArray::default();
let mut hkdf_label: Vec<u8> = Vec::new();
let length_u16: u16 =
u16::try_from(D::OutputSize::USIZE).map_err(|_| ProtocolError::SerializationError)?;
let label = Serialize::<U1>::from_label(STR_OPAQUE, label)?;
let label = label.to_array_3();
let context = Serialize::<U1>::from(context)?;
let context = context.to_array_2();
let length_u16: u16 = u16::try_from(length).map_err(|_| ProtocolError::SerializationError)?;
hkdf_label.extend_from_slice(&length_u16.to_be_bytes());
let hkdf_label = [
&length_u16.to_be_bytes(),
label[0],
label[1],
label[2],
context[0],
context[1],
];
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)?);
hkdf_label.extend_from_slice(&serialize(context, 1)?);
hkdf.expand(&hkdf_label, &mut okm)
hkdf.expand_multi_info(&hkdf_label, &mut okm)
.map_err(|_| InternalError::HkdfError)?;
Ok(okm)
}
@@ -415,27 +443,22 @@ fn derive_secrets<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
hashed_derivation_transcript: &[u8],
) -> Result<Vec<u8>, ProtocolError> {
hkdf_expand_label_extracted::<D>(
hkdf,
label,
hashed_derivation_transcript,
<D as Digest>::OutputSize::USIZE,
)
) -> Result<GenericArray<u8, D::OutputSize>, ProtocolError> {
hkdf_expand_label_extracted::<D>(hkdf, label, hashed_derivation_transcript)
}
// Generate a random nonce up to NonceLen::USIZE bytes.
fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
let mut nonce_bytes = vec![0u8; NonceLen::USIZE];
let mut nonce_bytes = GenericArray::default();
rng.fill_bytes(&mut nonce_bytes);
GenericArray::clone_from_slice(&nonce_bytes)
nonce_bytes
}
// Serialization and deserialization implementations
impl<KG: KeGroup> FromBytes for Ke1State<KG> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <KG as KeGroup>::PkLen::USIZE;
fn from_bytes(bytes: &[u8]) -> Result<Self, ProtocolError> {
let key_len = KG::SkLen::USIZE;
let nonce_len = NonceLen::USIZE;
let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?;
@@ -449,15 +472,21 @@ impl<KG: KeGroup> FromBytes for Ke1State<KG> {
}
}
impl<KG: KeGroup> ToBytes for Ke1State<KG> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output
impl<KG: KeGroup> ToBytes for Ke1State<KG>
where
// Ke1State: KeSk + Nonce
KG::SkLen: Add<NonceLen>,
Sum<KG::SkLen, NonceLen>: ArrayLength<u8>,
{
type Len = Sum<KG::SkLen, NonceLen>;
fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
self.client_e_sk.to_arr().concat(self.client_nonce)
}
}
impl<KG: KeGroup> FromBytes for Ke1Message<KG> {
fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, ProtocolError> {
fn from_bytes(ke1_message_bytes: &[u8]) -> Result<Self, ProtocolError> {
let nonce_len = NonceLen::USIZE;
let checked_nonce = check_slice_size(
ke1_message_bytes,
@@ -472,15 +501,22 @@ impl<KG: KeGroup> FromBytes for Ke1Message<KG> {
}
}
impl<KG: KeGroup> ToBytes for Ke1Message<KG> {
fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
impl<KG: KeGroup> ToBytes for Ke1Message<KG>
where
// Ke1Message: Nonce + KePk
NonceLen: Add<KG::PkLen>,
Sum<NonceLen, KG::PkLen>: ArrayLength<u8>,
{
type Len = Sum<NonceLen, KG::PkLen>;
fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
self.client_nonce.concat(self.client_e_pk.to_arr())
}
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError> {
let hash_len = HashLen::USIZE;
impl<D: Hash> FromBytes for Ke2State<D> {
fn from_bytes(input: &[u8]) -> Result<Self, ProtocolError> {
let hash_len = D::OutputSize::USIZE;
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
@@ -493,19 +529,25 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat()
impl<D: Hash> ToBytes for Ke2State<D>
where
// Ke2State: (Hash + Hash) + Hash
D::OutputSize: Add<D::OutputSize>,
Sum<D::OutputSize, D::OutputSize>: ArrayLength<u8> + Add<D::OutputSize>,
Sum<Sum<D::OutputSize, D::OutputSize>, D::OutputSize>: ArrayLength<u8>,
{
type Len = Sum<Sum<D::OutputSize, D::OutputSize>, D::OutputSize>;
fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
self.km3
.clone()
.concat(self.hashed_transcript.clone())
.concat(self.session_key.clone())
}
}
impl<KG: KeGroup, HashLen: ArrayLength<u8>> FromBytes for Ke2Message<KG, HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError> {
impl<KG: KeGroup, D: Hash> FromBytes for Ke2Message<D, KG> {
fn from_bytes(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <KG as KeGroup>::PkLen::USIZE;
let nonce_len = NonceLen::USIZE;
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
@@ -517,12 +559,12 @@ impl<KG: KeGroup, HashLen: ArrayLength<u8>> FromBytes for Ke2Message<KG, HashLen
)?;
let checked_mac = check_slice_size(
&unchecked_server_e_pk[key_len..],
HashLen::USIZE,
D::OutputSize::USIZE,
"ke1_message mac",
)?;
// Check the public key bytes
let server_e_pk = KeyPair::<CS::KeGroup>::check_public_key(PublicKey::from_bytes(
let server_e_pk = KeyPair::<KG>::check_public_key(PublicKey::from_bytes(
&unchecked_server_e_pk[..key_len],
)?)?;
@@ -534,21 +576,33 @@ impl<KG: KeGroup, HashLen: ArrayLength<u8>> FromBytes for Ke2Message<KG, HashLen
}
}
impl<KG: KeGroup, HashLen: ArrayLength<u8>> ToBytes for Ke2Message<KG, HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
impl<D: Hash, KG: KeGroup> ToBytes for Ke2Message<D, KG>
where
// Ke2Message: (Nonce + KePk) + Hash
NonceLen: Add<KG::PkLen>,
Sum<NonceLen, KG::PkLen>: ArrayLength<u8> + Add<D::OutputSize>,
Sum<Sum<NonceLen, KG::PkLen>, D::OutputSize>: ArrayLength<u8>,
{
type Len = Sum<Sum<NonceLen, KG::PkLen>, D::OutputSize>;
fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
self.server_nonce
.concat(self.server_e_pk.to_arr())
.concat(self.mac.clone())
}
}
impl<KG: KeGroup, HashLen: ArrayLength<u8>> Ke2Message<KG, HashLen> {
fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
[&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
impl<D: Hash, KG: KeGroup> Ke2Message<D, KG> {
fn to_bytes_without_info_or_mac(&self) -> impl Iterator<Item = &[u8]> {
// MSRV: array `into_iter` isn't available in 1.51
#[allow(deprecated)]
IntoIter::new([self.server_nonce.as_slice(), self.server_e_pk.as_slice()])
}
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, ProtocolError> {
let checked_bytes = check_slice_size(bytes, HashLen::USIZE, "ke3_message")?;
impl<D: Hash> FromBytes for Ke3Message<D> {
fn from_bytes(bytes: &[u8]) -> Result<Self, ProtocolError> {
let checked_bytes = check_slice_size(bytes, D::OutputSize::USIZE, "ke3_message")?;
Ok(Self {
mac: GenericArray::clone_from_slice(checked_bytes),
@@ -556,39 +610,10 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
}
}
// Zeroize on drop implementations
// This can't be derived because of the use of a generic parameter
impl<KG: KeGroup> Zeroize for Ke1State<KG> {
fn zeroize(&mut self) {
self.client_e_sk.zeroize();
self.client_nonce.zeroize();
}
}
impl<KG: KeGroup> Drop for Ke1State<KG> {
fn drop(&mut self) {
self.zeroize();
}
}
// This can't be derived because of the use of a phantom parameter
impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
fn zeroize(&mut self) {
self.km3.zeroize();
self.hashed_transcript.zeroize();
self.session_key.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
fn drop(&mut self) {
self.zeroize();
impl<D: Hash> ToBytes for Ke3Message<D> {
type Len = D::OutputSize;
fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
self.mac.clone()
}
}
+148 -220
View File
@@ -11,9 +11,8 @@
use crate::errors::{InternalError, ProtocolError};
use crate::key_exchange::group::KeGroup;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::ops::Deref;
use derive_where::DeriveWhere;
use generic_array::typenum::Unsigned;
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
@@ -21,65 +20,24 @@ use zeroize::Zeroize;
/// A Keypair trait with public-private verification
#[cfg_attr(
feature = "serialize",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "S: serde::Deserialize<'de>",
serialize = "S: serde::Serialize"
))
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(
bound(
deserialize = "S: serde_::Deserialize<'de>",
serialize = "S: serde_::Serialize"
),
crate = "serde_"
)
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Zeroize(drop))]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; S)]
pub struct KeyPair<KG: KeGroup, S: SecretKey<KG> = PrivateKey<KG>> {
pk: PublicKey<KG>,
sk: S,
}
impl<KG: KeGroup, S: SecretKey<KG>> Clone for KeyPair<KG, S> {
fn clone(&self) -> Self {
Self {
pk: self.pk.clone(),
sk: self.sk.clone(),
}
}
}
impl<KG: KeGroup, S: SecretKey<KG> + Debug> Debug for KeyPair<KG, S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("KeyPair")
.field("pk", &self.pk)
.field("sk", &self.sk)
.finish()
}
}
impl<KG: KeGroup, S: SecretKey<KG> + PartialEq> PartialEq for KeyPair<KG, S> {
fn eq(&self, other: &Self) -> bool {
self.pk.eq(&other.pk) && self.sk.eq(&other.sk)
}
}
impl<KG: KeGroup, S: SecretKey<KG> + Eq> Eq for KeyPair<KG, S> {}
impl<KG: KeGroup, S: SecretKey<KG> + core::hash::Hash> core::hash::Hash for KeyPair<KG, S> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.pk.hash(state);
self.sk.hash(state);
}
}
// This can't be derived because of the use of a generic parameter
impl<KG: KeGroup, S: SecretKey<KG>> Zeroize for KeyPair<KG, S> {
fn zeroize(&mut self) {
self.pk.zeroize();
self.sk.zeroize();
}
}
impl<KG: KeGroup, S: SecretKey<KG>> Drop for KeyPair<KG, S> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<KG: KeGroup, S: SecretKey<KG>> KeyPair<KG, S> {
/// The public key component
pub fn public(&self) -> &PublicKey<KG> {
@@ -113,20 +71,18 @@ impl<KG: KeGroup, S: SecretKey<KG>> KeyPair<KG, S> {
impl<KG: KeGroup> KeyPair<KG> {
/// Generating a random key pair given a cryptographic rng
pub(crate) fn generate_random<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<Self, InternalError> {
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = KG::random_sk(rng);
let pk = KG::public_key(&sk);
Ok(Self {
Self {
pk: PublicKey(Key(pk.to_arr())),
sk: PrivateKey(Key(sk)),
})
}
}
}
#[cfg(test)]
impl<KG: KeGroup + Debug> KeyPair<KG> {
impl<KG: KeGroup> KeyPair<KG> {
/// Test-only strategy returning a proptest Strategy based on
/// generate_random
fn uniform_keypair_strategy() -> proptest::prelude::BoxedStrategy<Self> {
@@ -138,7 +94,7 @@ impl<KG: KeGroup + Debug> KeyPair<KG> {
any::<[u8; 32]>()
.prop_filter_map("valid random keypair", |seed| {
let mut rng = StdRng::from_seed(seed);
Some(Self::generate_random(&mut rng).unwrap())
Some(Self::generate_random(&mut rng))
})
.no_shrink()
.boxed()
@@ -147,52 +103,14 @@ impl<KG: KeGroup + Debug> KeyPair<KG> {
/// A minimalist key type built around a \[u8; 32\]
#[cfg_attr(
feature = "serialize",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[repr(transparent)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub struct Key<L: ArrayLength<u8>>(GenericArray<u8, L>);
impl<L: ArrayLength<u8>> Clone for Key<L> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<L: ArrayLength<u8>> Debug for Key<L> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_tuple("Key").field(&self.0).finish()
}
}
impl<L: ArrayLength<u8>> Eq for Key<L> {}
impl<L: ArrayLength<u8>> PartialEq for Key<L> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}
impl<L: ArrayLength<u8>> core::hash::Hash for Key<L> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
// This can't be derived because of the use of a generic parameter
impl<L: ArrayLength<u8>> Zeroize for Key<L> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<L: ArrayLength<u8>> Drop for Key<L> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<L: ArrayLength<u8>> Deref for Key<L> {
type Target = GenericArray<u8, L>;
@@ -210,32 +128,16 @@ impl<L: ArrayLength<u8>> Key<L> {
}
/// Wrapper around a Key to enforce that it's a private one.
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[repr(transparent)]
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub struct PrivateKey<KG: KeGroup>(Key<KG::SkLen>);
impl_clone_for!(
tuple PrivateKey<KG: KeGroup>,
[0],
);
impl_debug_eq_hash_for!(
tuple PrivateKey<KG: KeGroup>,
[0],
);
// This can't be derived because of the use of a generic parameter
impl<KG: KeGroup> Zeroize for PrivateKey<KG> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<KG: KeGroup> Drop for PrivateKey<KG> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<KG: KeGroup> Deref for PrivateKey<KG> {
type Target = Key<KG::SkLen>;
@@ -264,15 +166,20 @@ impl<KG: KeGroup> PrivateKey<KG> {
pub trait SecretKey<KG: KeGroup>: Clone + Sized + Zeroize {
/// Custom error type that can be passed down to `InternalError::Custom`
type Error;
/// Serialization size in bytes.
type Len: ArrayLength<u8>;
/// Diffie-Hellman key exchange implementation
fn diffie_hellman(&self, pk: PublicKey<KG>) -> Result<Vec<u8>, InternalError<Self::Error>>;
fn diffie_hellman(
&self,
pk: PublicKey<KG>,
) -> Result<GenericArray<u8, KG::PkLen>, InternalError<Self::Error>>;
/// Returns public key from private key
fn public_key(&self) -> Result<PublicKey<KG>, InternalError<Self::Error>>;
/// Serialization into bytes
fn serialize(&self) -> Vec<u8>;
fn serialize(&self) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
fn deserialize(input: &[u8]) -> Result<Self, InternalError<Self::Error>>;
@@ -280,18 +187,22 @@ pub trait SecretKey<KG: KeGroup>: Clone + Sized + Zeroize {
impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
type Error = core::convert::Infallible;
type Len = KG::SkLen;
fn diffie_hellman(&self, pk: PublicKey<KG>) -> Result<Vec<u8>, InternalError> {
fn diffie_hellman(
&self,
pk: PublicKey<KG>,
) -> Result<GenericArray<u8, KG::PkLen>, InternalError> {
let pk = KG::from_pk_slice(&pk)?;
Ok(pk.diffie_hellman(self).to_vec())
Ok(pk.diffie_hellman(self))
}
fn public_key(&self) -> Result<PublicKey<KG>, InternalError> {
Ok(PublicKey(Key(KG::public_key(&self.0).to_arr())))
}
fn serialize(&self) -> Vec<u8> {
self.to_vec()
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.to_arr()
}
fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
@@ -300,32 +211,15 @@ impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
}
/// Wrapper around a Key to enforce that it's a public one.
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[repr(transparent)]
#[cfg_attr(
feature = "serde",
derive(serde_::Deserialize, serde_::Serialize),
serde(bound = "", crate = "serde_")
)]
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub struct PublicKey<KG: KeGroup>(Key<KG::PkLen>);
impl_clone_for!(
tuple PublicKey<KG: KeGroup>,
[0],
);
impl_debug_eq_hash_for!(
tuple PublicKey<KG: KeGroup>,
[0],
);
// This can't be derived because of the use of a generic parameter
impl<KG: KeGroup> Zeroize for PublicKey<KG> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<KG: KeGroup> Drop for PublicKey<KG> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<KG: KeGroup> Deref for PublicKey<KG> {
type Target = Key<KG::PkLen>;
@@ -355,85 +249,109 @@ mod tests {
use super::*;
use crate::errors::*;
use core::slice::from_raw_parts;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use proptest::prelude::*;
use rand::rngs::OsRng;
#[test]
fn test_zeroize_key() -> Result<(), ProtocolError> {
let key_len = <RistrettoPoint as KeGroup>::PkLen::USIZE;
let mut key = Key::<<RistrettoPoint as KeGroup>::PkLen>(GenericArray::clone_from_slice(
&alloc::vec![
fn inner<G: KeGroup>() -> Result<(), ProtocolError> {
let key_len = G::PkLen::USIZE;
let mut key = Key::<G::PkLen>(GenericArray::clone_from_slice(&alloc::vec![
1u8;
key_len
],
));
let ptr = key.as_ptr();
]));
let ptr = key.as_ptr();
Zeroize::zeroize(&mut key);
Zeroize::zeroize(&mut key);
let bytes = unsafe { from_raw_parts(ptr, key_len) };
assert!(bytes.iter().all(|&x| x == 0));
let bytes = unsafe { from_raw_parts(ptr, key_len) };
assert!(bytes.iter().all(|&x| x == 0));
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<curve25519_dalek::ristretto::RistrettoPoint>()?;
#[cfg(feature = "p256")]
inner::<p256_::PublicKey>()?;
Ok(())
}
#[test]
fn test_zeroize_keypair() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut keypair = KeyPair::<RistrettoPoint>::generate_random(&mut rng)?;
let pk_ptr = keypair.pk.as_ptr();
let sk_ptr = keypair.sk.as_ptr();
let pk_len = <RistrettoPoint as KeGroup>::PkLen::USIZE;
let sk_len = <RistrettoPoint as KeGroup>::SkLen::USIZE;
fn test_zeroize_keypair() {
fn inner<G: KeGroup>() {
let mut rng = OsRng;
let mut keypair = KeyPair::<G>::generate_random(&mut rng);
let pk_ptr = keypair.pk.as_ptr();
let sk_ptr = keypair.sk.as_ptr();
let pk_len = G::PkLen::USIZE;
let sk_len = G::SkLen::USIZE;
Zeroize::zeroize(&mut keypair);
Zeroize::zeroize(&mut keypair);
let pk_bytes = unsafe { from_raw_parts(pk_ptr, pk_len) };
let sk_bytes = unsafe { from_raw_parts(sk_ptr, sk_len) };
let pk_bytes = unsafe { from_raw_parts(pk_ptr, pk_len) };
let sk_bytes = unsafe { from_raw_parts(sk_ptr, sk_len) };
assert!(pk_bytes.iter().all(|&x| x == 0));
assert!(sk_bytes.iter().all(|&x| x == 0));
assert!(pk_bytes.iter().all(|&x| x == 0));
assert!(sk_bytes.iter().all(|&x| x == 0));
}
Ok(())
#[cfg(feature = "ristretto255")]
inner::<curve25519_dalek::ristretto::RistrettoPoint>();
#[cfg(feature = "p256")]
inner::<p256_::PublicKey>();
}
proptest! {
#[test]
fn test_ristretto_check(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
}
macro_rules! test {
($mod:ident, $point:ty) => {
mod $mod {
use super::*;
use proptest::prelude::*;
#[test]
fn test_ristretto_pub_from_priv(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&sk.public_key()?, pk);
}
proptest! {
#[test]
fn check(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(KeyPair::<$point>::check_public_key(pk.clone()).is_ok());
}
#[test]
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
#[test]
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&sk.public_key()?, pk);
}
let dh1 = kp2.private().diffie_hellman(kp1.public().clone())?;
let dh2 = kp1.private().diffie_hellman(kp2.public().clone())?;
#[test]
fn dh(kp1 in KeyPair::<$point>::uniform_keypair_strategy(),
kp2 in KeyPair::<$point>::uniform_keypair_strategy()) {
prop_assert_eq!(dh1, dh2);
}
let dh1 = kp2.private().diffie_hellman(kp1.public().clone())?;
let dh2 = kp1.private().diffie_hellman(kp2.public().clone())?;
#[test]
fn test_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
prop_assert_eq!(dh1, dh2);
}
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
let kp2_private_bytes = kp2.private().to_vec();
#[test]
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
prop_assert_eq!(sk_bytes, kp2_private_bytes);
}
let kp2 = KeyPair::<$point>::from_private_key_slice(&sk_bytes)?;
let kp2_private_bytes = kp2.private().to_vec();
prop_assert_eq!(sk_bytes, kp2_private_bytes);
}
}
}
};
}
#[cfg(feature = "ristretto255")]
test!(ristretto, curve25519_dalek::ristretto::RistrettoPoint);
#[cfg(feature = "p256")]
test!(p256, p256_::PublicKey);
#[test]
fn remote_key() {
use crate::{
@@ -443,37 +361,48 @@ mod tests {
ServerLoginStartParameters, ServerLoginStartResult, ServerRegistration,
ServerRegistrationStartResult, ServerSetup,
};
use curve25519_dalek::ristretto::RistrettoPoint;
#[cfg(feature = "ristretto255")]
use curve25519_dalek::ristretto::RistrettoPoint as KeCurve;
#[cfg(not(feature = "ristretto255"))]
use p256_::PublicKey as KeCurve;
use rand::rngs::OsRng;
struct Default;
impl CipherSuite for Default {
type OprfGroup = RistrettoPoint;
type KeGroup = RistrettoPoint;
#[cfg(feature = "ristretto255")]
type OprfGroup = KeCurve;
#[cfg(not(feature = "ristretto255"))]
type OprfGroup = p256_::ProjectivePoint;
type KeGroup = KeCurve;
type KeyExchange = crate::key_exchange::tripledh::TripleDH;
#[cfg(feature = "ristretto255")]
type Hash = sha2::Sha512;
#[cfg(not(feature = "ristretto255"))]
type Hash = sha2::Sha256;
type SlowHash = crate::slow_hash::NoOpHash;
}
#[derive(Clone, Zeroize)]
struct RemoteKey(PrivateKey<RistrettoPoint>);
struct RemoteKey(PrivateKey<KeCurve>);
impl SecretKey<RistrettoPoint> for RemoteKey {
impl SecretKey<KeCurve> for RemoteKey {
type Error = core::convert::Infallible;
type Len = <KeCurve as KeGroup>::SkLen;
fn diffie_hellman(
&self,
pk: PublicKey<RistrettoPoint>,
) -> Result<Vec<u8>, InternalError<Self::Error>> {
pk: PublicKey<KeCurve>,
) -> Result<GenericArray<u8, <KeCurve as KeGroup>::PkLen>, InternalError<Self::Error>>
{
self.0.diffie_hellman(pk)
}
fn public_key(&self) -> Result<PublicKey<RistrettoPoint>, InternalError<Self::Error>> {
fn public_key(&self) -> Result<PublicKey<KeCurve>, InternalError<Self::Error>> {
self.0.public_key()
}
fn serialize(&self) -> Vec<u8> {
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.0.serialize()
}
@@ -484,12 +413,11 @@ mod tests {
const PASSWORD: &str = "password";
let sk = RistrettoPoint::random_sk(&mut OsRng);
let sk = KeCurve::random_sk(&mut OsRng);
let sk = RemoteKey(PrivateKey(Key(sk)));
let keypair = KeyPair::from_private_key(sk).unwrap();
let server_setup =
ServerSetup::<Default, RemoteKey>::new_with_key(&mut OsRng, keypair).unwrap();
let server_setup = ServerSetup::<Default, RemoteKey>::new_with_key(&mut OsRng, keypair);
let ClientRegistrationStartResult {
message,
+220 -73
View File
@@ -26,7 +26,7 @@
//! * a slow hashing function.
//!
//! We will use the following choices in this example:
//! ```
//! ```ignore
//! use opaque_ke::CipherSuite;
//! struct Default;
//! impl CipherSuite for Default {
@@ -51,6 +51,7 @@
//! # use opaque_ke::CipherSuite;
//! # use opaque_ke::ServerSetup;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -58,6 +59,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! use rand::{rngs::OsRng, RngCore};
//! let mut rng = OsRng;
//! let server_setup = ServerSetup::<Default>::new(&mut rng);
@@ -84,6 +93,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -91,6 +101,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! use opaque_ke::ClientRegistration;
//! use rand::{rngs::OsRng, RngCore};
//! let mut client_rng = OsRng;
@@ -115,6 +133,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -122,6 +141,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -130,7 +157,7 @@
//! # )?;
//! use opaque_ke::ServerRegistration;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! let server_registration_start_result = ServerRegistration::<Default>::start(
//! &server_setup,
//! client_registration_start_result.message,
@@ -153,6 +180,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -160,6 +188,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -167,7 +203,7 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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,
@@ -192,6 +228,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -199,6 +236,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -206,7 +251,7 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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 = ServerRegistration::<Default>::finish(
@@ -235,6 +280,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -242,6 +288,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! use opaque_ke::ClientLogin;
//! let mut client_rng = OsRng;
@@ -269,6 +323,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -276,6 +331,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -283,16 +346,16 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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 = ServerRegistration::<Default>::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",
//! # )?;
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//! let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
//! &mut server_rng,
@@ -323,6 +386,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -330,6 +394,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -337,17 +409,17 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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 = ServerRegistration::<Default>::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",
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
@@ -369,6 +441,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -376,6 +449,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -383,17 +464,17 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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 = ServerRegistration::<Default>::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",
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
@@ -445,6 +526,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -452,6 +534,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -459,7 +549,7 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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
@@ -468,14 +558,14 @@
//! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::default(),
//! )?;
//! # let password_file_bytes = ServerRegistration::<Default>::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",
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
@@ -523,6 +613,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -530,6 +621,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -537,7 +636,7 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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(
@@ -545,14 +644,14 @@
//! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::default()
//! )?;
//! # let password_file_bytes = ServerRegistration::<Default>::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",
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
@@ -589,6 +688,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -596,6 +696,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -603,23 +711,23 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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::new(
//! Some(Identifiers::ClientAndServerIdentifiers(
//! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! )),
//! Identifiers {
//! client: Some(b"Alice_the_Cryptographer"),
//! server: Some(b"Facebook"),
//! },
//! None,
//! ),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! The same identifiers must also be supplied using [ServerLoginStartParameters::WithIdentifiers] in [Server Login Start](#server-login-start):
//! The same identifiers must also be supplied using [ServerLoginStartParameters] in [Server Login Start](#server-login-start):
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
@@ -628,6 +736,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -635,6 +744,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -642,16 +759,16 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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::new(Some(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())), None))?;
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize()?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::new(Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") }, None))?;
//! # 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",
//! # )?;
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//! # let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
//! &mut server_rng,
@@ -659,12 +776,13 @@
//! 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(),
//! ),
//! ),
//! ServerLoginStartParameters {
//! context: None,
//! identifiers: Identifiers {
//! client: Some(b"Alice_the_Cryptographer"),
//! server: Some(b"Facebook"),
//! },
//! },
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
@@ -678,6 +796,7 @@
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
@@ -685,6 +804,14 @@
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
@@ -692,28 +819,28 @@
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng)?;
//! # 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::new(Some(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())), None))?;
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize()?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::new(Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") }, None))?;
//! # 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",
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # 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())))?;
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters { context: None, identifiers: Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") } })?;
//! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::new(
//! None,
//! Some(Identifiers::ClientAndServerIdentifiers(
//! b"Alice_the_Cryptographer".to_vec(),
//! b"Facebook".to_vec(),
//! )),
//! Identifiers {
//! client: Some(b"Alice_the_Cryptographer"),
//! server: Some(b"Facebook"),
//! },
//! None,
//! ),
//! )?;
@@ -723,7 +850,7 @@
//! 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!
//!
//! Note that if only one of the client and server identifiers are present, then [Identifiers::ClientIdentifier] and [Identifiers::ServerIdentifier] can be
//! Note that if only one of the client and server identifiers are present, then [Identifiers] can be
//! used to specify them individually.
//!
//! ## Key Exchange Context
@@ -731,7 +858,7 @@
//! 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 second login message, where the server can populate [ServerLoginStartParameters], and
//! - The third login message, where the client can populate [ClientLoginFinishParameters].
//!
//! For both of these messages, the `WithContextAndIdentifiers` variant can be used to specify these fields in addition to
@@ -751,51 +878,60 @@
//! [`SecretKey`](keypair::SecretKey`) trait. This allows [`ServerSetup`] to be constructed using an existing keypair
//! without exposing the bytes of the private key to this library.
//! ```
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use generic_array::{GenericArray, typenum::U32};
//! # use opaque_ke::{CipherSuite, errors::{InternalError}, keypair::{KeyPair, PrivateKey, PublicKey, SecretKey}, ServerSetup};
//! # use generic_array::{GenericArray, typenum::U0};
//! # use opaque_ke::{CipherSuite, errors::{InternalError}, key_exchange::group::KeGroup, keypair::{KeyPair, PrivateKey, PublicKey, SecretKey}, ServerSetup};
//! # use rand::rngs::OsRng;
//! # use zeroize::Zeroize;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfGroup = RistrettoPoint;
//! # type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfGroup = p256_::ProjectivePoint;
//! # type KeGroup = p256_::PublicKey;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # #[derive(Debug)]
//! # struct YourRemoteKeyError;
//! # #[derive(Clone, Zeroize)]
//! # struct YourRemoteKey(PrivateKey<RistrettoPoint>);
//! # struct YourRemoteKey(PrivateKey<<Default as CipherSuite>::KeGroup>);
//! # impl YourRemoteKey {
//! # fn diffie_hellman(&self, pk: &[u8]) -> Result<Vec<u8>, YourRemoteKeyError> { todo!() }
//! # fn public_key(&self) -> Result<GenericArray<u8, U32>, YourRemoteKeyError> { Ok(GenericArray::default()) }
//! # fn diffie_hellman(&self, pk: &[u8]) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { todo!() }
//! # fn public_key(&self) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { Ok(GenericArray::default()) }
//! # }
//! impl SecretKey<RistrettoPoint> for YourRemoteKey {
//! impl SecretKey<<Default as CipherSuite>::KeGroup> for YourRemoteKey {
//! type Error = YourRemoteKeyError;
//! type Len = U0;
//!
//! fn diffie_hellman(
//! &self,
//! pk: PublicKey<RistrettoPoint>,
//! ) -> Result<Vec<u8>, InternalError<Self::Error>> {
//! pk: PublicKey<<Default as CipherSuite>::KeGroup>,
//! ) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, InternalError<Self::Error>> {
//! YourRemoteKey::diffie_hellman(self, &pk.to_arr()).map_err(InternalError::Custom)
//! }
//!
//! fn public_key(
//! &self
//! ) -> Result<PublicKey<RistrettoPoint>, InternalError<Self::Error>> {
//! ) -> Result<PublicKey<<Default as CipherSuite>::KeGroup>, InternalError<Self::Error>> {
//! YourRemoteKey::public_key(self).map(PublicKey::from_arr)
//! .map_err(InternalError::Custom)
//! }
//!
//! fn serialize(&self) -> Vec<u8> {
//! // if you use serde and the "serialize" crate feature, you won't need this
//! fn serialize(&self) -> GenericArray<u8, Self::Len> {
//! // if you use Serde and the "serde" crate feature, you won't need this
//! todo!()
//! }
//!
//! fn deserialize(input: &[u8]) -> Result<Self, InternalError<Self::Error>> {
//! // if you use serde and the "serialize" crate feature, you won't need this
//! // if you use Serde and the "serde" crate feature, you won't need this
//! todo!()
//! }
//! }
@@ -814,28 +950,34 @@
//! 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
//! - The `serde` 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 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 `ristretto255_u64` feature is included as the default.
//! Other features are mapped as `ristretto255_u32`, `ristretto255_fiat_u64` and `ristretto255_fiat_u32`. Any `ristretto255_*`
//! backend feature will enable the `ristretto255` feature, which can be used too, but keep in mind that `curve25519-dalek`
//! will fail to compile without a selected backend. This enabled to use `curve25519_dalek::ristretto::RistrettoPoint` as a
//! `KeGroup` and `OprfGroup`.
//!
//! - The `p256` feature enables the use of `p256::ProjectivePoint` as a `Group` for `CipherSuite`. Note that this
//! is currently an experimental feature ⚠️, and is not yet ready for production use.
//! - The `x25519` feature is similar to the `ristretto255` feature and requires to select a backend like `x25519_u64`, other
//! backends are the same as in `ristretto255_*`. This enables `x25519_dalek::PublicKey` as a `KeGroup`.
//!
//! - The `ristretto255_simd` feature is re-exported from
//! [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features) and enables parallel formulas,
//! using either AVX2 or AVX512-IFMA. This will automatically enable the `ristretto255_u64` feature and requires Rust nightly.
//!
//! - The `p256` feature enables the use of `p256::PublicKey` as a `KeGroup` and `p256::ProjectivePoint` as a `OprfGroup` for
//! `CipherSuite`. Note that this is currently an experimental feature ⚠️, and is not yet ready for production use.
//!
//! - The `bench` feature is used only for running performance benchmarks for this implementation.
//!
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
#![deny(unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(any(feature = "u64_backend", feature = "u32_backend",)))]
compile_error!(
"no dalek arithmetic backend cargo feature enabled! \
please enable one of: u64_backend, u32_backend"
);
#![warn(clippy::cargo, missing_docs)]
#![allow(clippy::multiple_crate_versions)]
extern crate alloc;
@@ -868,6 +1010,11 @@ pub use crate::messages::{
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload,
};
pub use crate::messages::{
CredentialFinalizationLen, CredentialRequestLen, CredentialResponseLen, RegistrationRequestLen,
RegistrationResponseLen, RegistrationUploadLen,
};
pub use crate::opaque::ServerRegistrationLen;
pub use crate::opaque::{
ClientLogin, ClientRegistration, ServerLogin, ServerRegistration, ServerSetup,
};
Regular → Executable
+219 -128
View File
@@ -9,21 +9,28 @@
use crate::{
ciphersuite::CipherSuite,
envelope::Envelope,
envelope::{Envelope, EnvelopeLen},
errors::{
utils::{check_slice_size, check_slice_size_atleast},
ProtocolError,
},
key_exchange::{
group::KeGroup,
traits::{FromBytes, KeyExchange, ToBytes},
traits::{FromBytes, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, ToBytes},
tripledh::NonceLen,
},
keypair::{KeyPair, PublicKey, SecretKey},
opaque::ServerSetup,
opaque::{MaskedResponse, MaskedResponseLen, ServerSetup},
};
use core::array::IntoIter;
use core::ops::Add;
use derive_where::DeriveWhere;
use digest::{Digest, FixedOutput};
use generic_array::sequence::Concat;
use generic_array::{
typenum::{Sum, Unsigned},
ArrayLength, GenericArray,
};
use alloc::vec::Vec;
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use rand::{CryptoRng, RngCore};
use voprf::group::Group;
@@ -33,13 +40,21 @@ use voprf::group::Group;
////////////////////////////
/// The message sent by the client to the server, to initiate registration
#[derive(DeriveWhere)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; CS::OprfGroup)]
pub struct RegistrationRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup, CS::Hash>,
}
impl_serialize_and_deserialize_for!(RegistrationRequest);
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
#[derive(DeriveWhere)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; CS::OprfGroup)]
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfGroup, CS::Hash>,
@@ -47,8 +62,18 @@ pub struct RegistrationResponse<CS: CipherSuite> {
pub(crate) server_s_pk: PublicKey<CS::KeGroup>,
}
impl_serialize_and_deserialize_for!(
RegistrationResponse
where
// RegistrationResponse: KgPk + KePk
<CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
);
/// The final message from the client, containing sealed cryptographic
/// identifiers
#[derive(DeriveWhere)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
@@ -59,33 +84,98 @@ pub struct RegistrationUpload<CS: CipherSuite> {
pub(crate) client_s_pk: PublicKey<CS::KeGroup>,
}
impl_serialize_and_deserialize_for!(
RegistrationUpload
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> | Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
);
/// The message sent by the user to the server, to initiate registration
#[derive(DeriveWhere)]
#[derive_where(Clone, Zeroize)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message,
)]
pub struct CredentialRequest<CS: CipherSuite> {
pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup, CS::Hash>,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message,
}
impl_serialize_and_deserialize_for!(
CredentialRequest
where
// CredentialRequest: KgPk + Ke1Message
<CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
);
/// The answer sent by the server to the user, upon reception of the
/// login attempt
#[derive(DeriveWhere)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
)]
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfGroup, CS::Hash>,
pub(crate) masking_nonce: Vec<u8>,
pub(crate) masked_response: Vec<u8>,
pub(crate) masking_nonce: GenericArray<u8, NonceLen>,
pub(crate) masked_response: MaskedResponse<CS>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
}
impl_serialize_and_deserialize_for!(
CredentialResponse
where
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
ArrayLength<u8> | Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> | Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
);
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
#[derive(DeriveWhere)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message,
)]
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message,
}
impl_serialize_and_deserialize_for!(CredentialFinalization);
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
/// Length of [`RegistrationRequest`] in bytes for serialization.
#[allow(type_alias_bounds)]
pub type RegistrationRequestLen<CS: CipherSuite> = <CS::OprfGroup as Group>::ElemLen;
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes
#[cfg(test)]
@@ -96,8 +186,8 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
}
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok(self.blinded_element.serialize())
pub fn serialize(&self) -> GenericArray<u8, RegistrationRequestLen<CS>> {
self.blinded_element.value().to_arr()
}
/// Deserialization from bytes
@@ -108,14 +198,23 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
}
}
/// Length of [`RegistrationResponse`] in bytes for serialization.
#[allow(type_alias_bounds)]
pub type RegistrationResponseLen<CS: CipherSuite> =
Sum<<CS::OprfGroup as Group>::ElemLen, <CS::KeGroup as KeGroup>::PkLen>;
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
self.evaluation_element.serialize(),
self.server_s_pk.to_vec(),
]
.concat())
pub fn serialize(&self) -> GenericArray<u8, RegistrationResponseLen<CS>>
where
// RegistrationResponse: KgPk + KePk
<CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
{
self.evaluation_element
.value()
.to_arr()
.concat(self.server_s_pk.to_arr())
}
/// Deserialization from bytes
@@ -147,15 +246,30 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
}
}
/// Length of [`RegistrationUpload`] in bytes for serialization.
#[allow(type_alias_bounds)]
pub type RegistrationUploadLen<CS: CipherSuite> = Sum<
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>,
>;
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
self.client_s_pk.to_arr().to_vec(),
self.masking_key.to_vec(),
self.envelope.serialize(),
]
.concat())
pub fn serialize(&self) -> GenericArray<u8, RegistrationUploadLen<CS>>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
{
self.client_s_pk
.to_arr()
.concat(self.masking_key.clone())
.concat(self.envelope.serialize())
}
/// Deserialization from bytes
@@ -181,25 +295,43 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
rng: &mut R,
server_setup: &ServerSetup<CS, S>,
) -> Self {
let mut masking_key = alloc::vec![0u8; <CS::Hash as Digest>::OutputSize::USIZE];
let mut masking_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut masking_key);
Self {
envelope: Envelope::<CS>::dummy(),
masking_key: GenericArray::clone_from_slice(&masking_key),
masking_key,
client_s_pk: server_setup.fake_keypair.public().clone(),
}
}
}
/// Length of [`CredentialRequest`] in bytes for serialization.
#[allow(type_alias_bounds)]
pub type CredentialRequestLen<CS: CipherSuite> =
Sum<<CS::OprfGroup as Group>::ElemLen, Ke1MessageLen<CS>>;
impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
self.blinded_element.serialize(),
self.ke1_message.to_bytes(),
]
.concat())
pub fn serialize(&self) -> GenericArray<u8, CredentialRequestLen<CS>>
where
// CredentialRequest: KgPk + Ke1Message
<CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
self.blinded_element
.value()
.to_arr()
.concat(self.ke1_message.to_bytes())
}
pub(crate) fn serialize_iter<'a>(
blinded_element: &'a GenericArray<u8, <CS::OprfGroup as Group>::ElemLen>,
ke1_message: &'a GenericArray<u8, Ke1MessageLen<CS>>,
) -> impl Iterator<Item = &'a [u8]> {
// MSRV: array `into_iter` isn't available in 1.51
#[allow(deprecated)]
IntoIter::new([blinded_element.as_slice(), ke1_message])
}
/// Deserialization from bytes
@@ -220,7 +352,7 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
}
let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes::<CS>(
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes(
&checked_slice[elem_len..],
)?;
@@ -239,26 +371,51 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
}
}
/// Length of [`CredentialResponse`] in bytes for serialization.
#[allow(type_alias_bounds)]
pub type CredentialResponseLen<CS: CipherSuite> =
Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
#[allow(type_alias_bounds)]
pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
Sum<Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
Self::serialize_without_ke(
&self.evaluation_element.value(),
&self.masking_nonce,
&self.masked_response,
),
self.ke2_message.to_bytes(),
]
.concat())
pub fn serialize(&self) -> GenericArray<u8, CredentialResponseLen<CS>>
where
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
self.evaluation_element
.value()
.to_arr()
.concat(self.masking_nonce)
.concat(self.masked_response.serialize())
.concat(self.ke2_message.to_bytes())
}
pub(crate) fn serialize_without_ke(
beta: &CS::OprfGroup,
masking_nonce: &[u8],
masked_response: &[u8],
) -> Vec<u8> {
[&beta.to_arr(), masking_nonce, masked_response].concat()
pub(crate) fn serialize_without_ke<'a>(
beta: &'a GenericArray<u8, <CS::OprfGroup as Group>::ElemLen>,
masking_nonce: &'a GenericArray<u8, NonceLen>,
masked_response: &'a MaskedResponse<CS>,
) -> impl Iterator<Item = &'a [u8]> {
// MSRV: array `into_iter` isn't available in 1.51
#[allow(deprecated)]
IntoIter::new([beta.as_slice(), masking_nonce.as_slice()])
.into_iter()
.chain(masked_response.iter())
}
/// Deserialization from bytes
@@ -287,12 +444,13 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
return Err(ProtocolError::IdentityGroupElementError);
}
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 masking_nonce =
GenericArray::clone_from_slice(&checked_slice[elem_len..elem_len + nonce_len]);
let masked_response = MaskedResponse::deserialize(
&checked_slice[elem_len + nonce_len..elem_len + nonce_len + masked_response_len],
);
let ke2_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message::from_bytes::<CS>(
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message::from_bytes(
&checked_slice[elem_len + nonce_len + masked_response_len..],
)?;
@@ -310,94 +468,27 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
pub fn set_evaluation_element_for_testing(&self, beta: CS::OprfGroup) -> Self {
Self {
evaluation_element: voprf::EvaluationElement::from_value_unchecked(beta),
masking_nonce: self.masking_nonce.clone(),
masking_nonce: self.masking_nonce,
masked_response: self.masked_response.clone(),
ke2_message: self.ke2_message.clone(),
}
}
}
/// Length of [`CredentialFinalization`] in bytes for serialization.
#[allow(type_alias_bounds)]
pub type CredentialFinalizationLen<CS: CipherSuite> = Ke3MessageLen<CS>;
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok(self.ke3_message.to_bytes())
pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>> {
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::KeGroup>>::KE3Message::from_bytes::<CS>(
input,
)?;
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message::from_bytes(input)?;
Ok(Self { ke3_message })
}
}
///////////////////////////
// Trait Implementations //
// ===================== //
///////////////////////////
impl_clone_for!(
struct RegistrationRequest<CS: CipherSuite>,
[blinded_element],
);
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [blinded_element], [CS::OprfGroup, CS::Hash]);
impl_serialize_and_deserialize_for!(RegistrationRequest);
impl_clone_for!(
struct RegistrationResponse<CS: CipherSuite>,
[evaluation_element, server_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationResponse<CS: CipherSuite>,
[evaluation_element, server_s_pk],
[CS::OprfGroup, CS::Hash],
);
impl_serialize_and_deserialize_for!(RegistrationResponse);
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_serialize_and_deserialize_for!(RegistrationUpload);
impl_clone_for!(
struct CredentialRequest<CS: CipherSuite>,
[blinded_element, ke1_message],
);
impl_debug_eq_hash_for!(
struct CredentialRequest<CS: CipherSuite>,
[blinded_element, ke1_message],
[
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message
],
);
impl_serialize_and_deserialize_for!(CredentialRequest);
impl_clone_for!(
struct CredentialResponse<CS: CipherSuite>,
[evaluation_element, masking_nonce, masked_response, ke2_message],
);
impl_debug_eq_hash_for!(
struct CredentialResponse<CS: CipherSuite>,
[evaluation_element, masking_nonce, masked_response, ke2_message],
[
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
],
);
impl_serialize_and_deserialize_for!(CredentialResponse);
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::KeGroup>>::KE3Message],
);
impl_serialize_and_deserialize_for!(CredentialFinalization);
Regular → Executable
+392 -414
View File
File diff suppressed because it is too large Load Diff
+151 -27
View File
@@ -6,26 +6,33 @@
// of this source tree.
use crate::errors::ProtocolError;
use alloc::vec::Vec;
use core::marker::PhantomData;
use digest::Update;
use generic_array::{
typenum::{U0, U2},
ArrayLength, GenericArray,
};
use hmac::Mac;
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp(input: usize, length: usize) -> Result<alloc::vec::Vec<u8>, ProtocolError> {
let sizeof_usize = core::mem::size_of::<usize>();
pub(crate) fn i2osp<L: ArrayLength<u8>>(
input: usize,
) -> Result<GenericArray<u8, L>, ProtocolError> {
const SIZEOF_USIZE: usize = core::mem::size_of::<usize>();
// Check if input >= 256^length
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 {
return Err(ProtocolError::SerializationError);
}
if length <= sizeof_usize {
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
if L::USIZE <= SIZEOF_USIZE {
return Ok(GenericArray::clone_from_slice(
&input.to_be_bytes()[SIZEOF_USIZE - L::USIZE..],
));
}
let mut output = alloc::vec![0u8; length];
output.splice(
length - sizeof_usize..length,
input.to_be_bytes().iter().cloned(),
);
let mut output = GenericArray::default();
output[L::USIZE - SIZEOF_USIZE..L::USIZE].copy_from_slice(&input.to_be_bytes());
Ok(output)
}
@@ -40,17 +47,94 @@ pub(crate) fn os2ip(input: &[u8]) -> Result<usize, ProtocolError> {
Ok(usize::from_be_bytes(output_array))
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result<Vec<u8>, ProtocolError> {
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
/// Computes `I2OSP(len(input), max_bytes) || input` and helps hold output without allocation.
pub(crate) struct Serialize<
'a,
L1: ArrayLength<u8>,
L2: ArrayLength<u8> = U0,
L3: ArrayLength<u8> = U0,
> {
octet: GenericArray<u8, L1>,
input: Input<'a, L2, L3>,
}
enum Input<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> {
Owned(GenericArray<u8, L1>),
Borrowed(&'a [u8]),
Label(([&'a [u8]; 2], PhantomData<L2>)),
}
impl<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>, L3: ArrayLength<u8>> Serialize<'a, L1, L2, L3> {
// Variation of `serialize` that takes a borrowed `input
pub(crate) fn from(input: &'a [u8]) -> Result<Serialize<'a, L1, L2>, ProtocolError> {
Ok(Serialize {
octet: i2osp::<L1>(input.len())?,
input: Input::Borrowed(input),
})
}
// Variation of `serialize` that takes an owned `input`
pub(crate) fn from_owned(
input: GenericArray<u8, L2>,
) -> Result<Serialize<'a, L1, L2>, ProtocolError> {
Ok(Serialize {
octet: i2osp::<L1>(input.len())?,
input: Input::Owned(input),
})
}
// Variation of `serialize` that takes a label
pub(crate) fn from_label(
opaque: &'a [u8],
label: &'a [u8],
) -> Result<Serialize<'a, L1, U0, U2>, ProtocolError> {
Ok(Serialize {
octet: i2osp::<L1>(opaque.len() + label.len())?,
input: Input::Label(([opaque, label], PhantomData)),
})
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &[u8]> {
// Some magic to make it output the same type in all branches.
Some(self.octet.as_slice())
.into_iter()
.chain(match &self.input {
Input::Owned(bytes) => Some(bytes.as_slice()),
Input::Borrowed(bytes) => Some(*bytes),
Input::Label(_) => None,
})
.chain(if let Input::Label((iter, _)) = &self.input {
Some(iter[0]).into_iter().chain(Some(iter[1]).into_iter())
} else {
None.into_iter().chain(None)
})
}
}
impl<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> Serialize<'a, L1, L2, U0> {
pub(crate) fn to_array_2(&self) -> [&[u8]; 2] {
let input = match &self.input {
Input::Borrowed(value) => value,
Input::Owned(value) => value.as_slice(),
_ => unreachable!("unexpected `Serialize` constructed with wrong generics"),
};
[self.octet.as_slice(), input]
}
}
impl<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> Serialize<'a, L1, L2, U2> {
pub(crate) fn to_array_3(&self) -> [&[u8]; 3] {
match self.input {
Input::Label((label, _)) => [self.octet.as_slice(), label[0], label[1]],
_ => unreachable!("unexpected `Serialize` constructed with wrong generics"),
}
}
}
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
// (input, remainder)
pub(crate) fn tokenize(
input: &[u8],
size_bytes: usize,
) -> Result<(Vec<u8>, Vec<u8>), ProtocolError> {
pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(&[u8], &[u8]), ProtocolError> {
if size_bytes > core::mem::size_of::<usize>() || input.len() < size_bytes {
return Err(ProtocolError::SerializationError);
}
@@ -61,29 +145,69 @@ pub(crate) fn tokenize(
}
Ok((
input[size_bytes..size_bytes + size].to_vec(),
input[size_bytes + size..].to_vec(),
&input[size_bytes..size_bytes + size],
&input[size_bytes + size..],
))
}
pub(crate) trait UpdateExt {
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self;
}
impl<T: Update> UpdateExt for T {
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self {
let mut self_ = self;
for bytes in iter {
self_ = self_.chain(bytes);
}
self_
}
}
pub(crate) trait MacExt {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>);
}
impl<T: Mac> MacExt for T {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>) {
for bytes in iter {
self.update(bytes);
}
}
}
/// The purpose of this macro is to simplify [`concat`](alloc::slice::Concat::concat)ing
/// slices into an [`Iterator`] to avoid allocation
macro_rules! chain {
(
$item1:expr,
$($item2:expr),+$(,)?
) => {
$item1$(.chain($item2))+
};
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod unit_tests {
use super::*;
use generic_array::typenum::{U1, U2};
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp(0, 1).is_ok());
assert!(i2osp::<U1>(0).is_ok());
assert!(i2osp(255, 1).is_ok());
assert!(i2osp(256, 1).is_err());
assert!(i2osp(257, 1).is_err());
assert!(i2osp::<U1>(255).is_ok());
assert!(i2osp::<U1>(256).is_err());
assert!(i2osp::<U1>(257).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());
assert!(i2osp::<U2>(256 * 256 - 1).is_ok());
assert!(i2osp::<U2>(256 * 256).is_err());
assert!(i2osp::<U2>(256 * 256 + 1).is_err());
}
}
Regular → Executable
+494 -285
View File
@@ -7,441 +7,650 @@
use crate::{
ciphersuite::CipherSuite,
envelope::{Envelope, InnerEnvelopeMode},
envelope::{Envelope, EnvelopeLen, InnerEnvelopeMode},
errors::*,
key_exchange::{
group::KeGroup,
traits::{Ke1MessageLen, Ke2MessageLen},
},
key_exchange::{
traits::{FromBytes, KeyExchange, ToBytes},
tripledh::{NonceLen, TripleDH},
},
keypair::KeyPair,
serialization::{i2osp, os2ip, serialize},
messages::CredentialResponseWithoutKeLen,
opaque::MaskedResponseLen,
serialization::{i2osp, os2ip, Serialize},
*,
};
#[cfg(test)]
use alloc::vec;
#[cfg(test)]
use alloc::vec::Vec;
use core::ops::Add;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use digest::FixedOutput;
use generic_array::{
typenum::{Sum, Unsigned, U2},
ArrayLength, GenericArray,
};
use proptest::{collection::vec, prelude::*};
use rand::{rngs::OsRng, RngCore};
use voprf::group::Group;
use sha2::Digest;
struct Default;
impl CipherSuite for Default {
type OprfGroup = RistrettoPoint;
type KeGroup = RistrettoPoint;
#[cfg(feature = "ristretto255")]
struct Ristretto255;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Ristretto255 {
type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = crate::slow_hash::NoOpHash;
}
const HASH_SIZE: usize = 64; // Because of SHA512
const MAC_SIZE: usize = 64; // Because of SHA512
#[cfg(feature = "p256")]
struct P256;
#[cfg(feature = "p256")]
impl CipherSuite for P256 {
type OprfGroup = p256_::ProjectivePoint;
type KeGroup = p256_::PublicKey;
type KeyExchange = TripleDH;
type Hash = sha2::Sha256;
type SlowHash = crate::slow_hash::NoOpHash;
}
fn random_ristretto_point() -> RistrettoPoint {
fn random_point<CS: CipherSuite>() -> CS::KeGroup {
let mut rng = OsRng;
let mut random_bits = [0u8; 64];
rng.fill_bytes(&mut random_bits);
// This is because RistrettoPoint is on an obsolete sha2 version
let mut bits = [0u8; 64];
let mut hasher = sha2::Sha512::new();
hasher.update(&random_bits[..]);
bits.copy_from_slice(&hasher.finalize());
RistrettoPoint::from_uniform_bytes(&bits)
let sk = CS::KeGroup::random_sk(&mut rng);
CS::KeGroup::public_key(&sk)
}
#[test]
fn client_registration_roundtrip() -> Result<(), ProtocolError> {
let pw = b"hunter2";
let mut rng = OsRng;
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let pw = b"hunter2";
let mut rng = OsRng;
let blind_result =
&voprf::NonVerifiableClient::<RistrettoPoint, sha2::Sha512>::blind(pw.to_vec(), &mut rng)?;
let blind_result =
&voprf::NonVerifiableClient::<CS::OprfGroup, CS::Hash>::blind(pw.to_vec(), &mut rng)?;
let bytes: Vec<u8> = [
serialize(&blind_result.state.serialize(), 2)?,
serialize(&blind_result.message.serialize(), 2)?,
]
.concat();
let bytes: Vec<u8> = chain!(
Serialize::<U2>::from(&blind_result.state.serialize())?.iter(),
Serialize::<U2>::from(&blind_result.message.serialize())?.iter(),
)
.flatten()
.cloned()
.collect();
let reg = ClientRegistration::<CS>::deserialize(&bytes)?;
let reg_bytes = reg.serialize()?;
assert_eq!(reg_bytes, bytes);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
let reg = ClientRegistration::<Default>::deserialize(&bytes[..])?;
let reg_bytes = reg.serialize()?;
assert_eq!(reg_bytes, bytes);
Ok(())
}
#[test]
fn server_registration_roundtrip() -> Result<(), ProtocolError> {
// If we don't have envelope and client_pk, the server registration just
// contains the prf key
let mut rng = OsRng;
let mut masking_key = [0u8; HASH_SIZE];
rng.fill_bytes(&mut masking_key);
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
{
// If we don't have envelope and client_pk, the server registration just
// contains the prf key
let mut rng = OsRng;
let mut masking_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut masking_key);
// Construct a mock envelope
let mut mock_envelope_bytes = Vec::new();
mock_envelope_bytes.extend_from_slice(&vec![0; NonceLen::USIZE]); // empty nonce
// Construct a mock envelope
let mut mock_envelope_bytes = Vec::new();
mock_envelope_bytes.extend_from_slice(&[0; NonceLen::USIZE]); // empty nonce
// mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
mock_envelope_bytes
.extend_from_slice(&GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default()); // length-MAC_SIZE hmac
let mock_client_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
// serialization order: oprf_key, public key, envelope
let mut bytes = Vec::<u8>::new();
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::<CS>::deserialize(&bytes)?;
let reg_bytes = reg.serialize();
assert_eq!(*reg_bytes, bytes);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
let mock_client_kp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
// serialization order: oprf_key, public key, envelope
let mut bytes = Vec::<u8>::new();
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[..])?;
let reg_bytes = reg.serialize()?;
assert_eq!(reg_bytes, bytes);
Ok(())
}
#[test]
fn registration_request_roundtrip() -> Result<(), ProtocolError> {
let pt = random_ristretto_point();
let pt_bytes = pt.to_arr().to_vec();
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let pt = random_point::<CS>();
let pt_bytes = pt.to_arr().to_vec();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
let mut input = Vec::new();
input.extend_from_slice(&pt_bytes);
let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice())?;
let r1_bytes = r1.serialize()?;
assert_eq!(input, r1_bytes);
let r1 = RegistrationRequest::<CS>::deserialize(&input)?;
let r1_bytes = r1.serialize();
assert_eq!(input, *r1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
// Assert that identity group element is rejected
let identity = CS::OprfGroup::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(
match RegistrationRequest::<Default>::deserialize(identity_bytes.as_slice()) {
assert!(matches!(
RegistrationRequest::<CS>::deserialize(&identity_bytes),
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
))) => true,
_ => false,
}
);
)))
));
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn registration_response_roundtrip() -> Result<(), ProtocolError> {
let pt = random_ristretto_point();
let beta_bytes = pt.to_arr();
let mut rng = OsRng;
let skp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let pubkey_bytes = skp.public().to_arr();
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// RegistrationResponse: KgPk + KePk
<CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
{
let pt = random_point::<CS>();
let beta_bytes = pt.to_arr();
let mut rng = OsRng;
let skp = KeyPair::<CS::KeGroup>::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());
let mut input = Vec::new();
input.extend_from_slice(&beta_bytes);
input.extend_from_slice(&pubkey_bytes);
let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice())?;
let r2_bytes = r2.serialize()?;
assert_eq!(input, r2_bytes);
let r2 = RegistrationResponse::<CS>::deserialize(&input)?;
let r2_bytes = r2.serialize();
assert_eq!(input, *r2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
// Assert that identity group element is rejected
let identity = CS::OprfGroup::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match RegistrationResponse::<Default>::deserialize(
&[identity_bytes, pubkey_bytes.to_vec()].concat()
) {
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
))) => true,
_ => false,
});
assert!(matches!(
RegistrationResponse::<CS>::deserialize(
&[identity_bytes, pubkey_bytes.to_vec()].concat()
),
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
)))
));
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn registration_upload_roundtrip() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let skp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let pubkey_bytes = skp.public().to_arr();
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
{
let mut rng = OsRng;
let skp = KeyPair::<CS::KeGroup>::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 key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut nonce = [0u8; NonceLen::USIZE];
rng.fill_bytes(&mut nonce);
let mut masking_key = vec![0u8; <sha2::Sha512 as Digest>::OutputSize::USIZE];
rng.fill_bytes(&mut masking_key);
let mut masking_key = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut masking_key);
let randomized_pwd_hasher = hkdf::Hkdf::new(None, &key);
let randomized_pwd_hasher = hkdf::Hkdf::new(None, &key);
let (envelope, _, _) = Envelope::<Default>::seal_raw(
randomized_pwd_hasher,
&nonce,
&pubkey_bytes,
InnerEnvelopeMode::Internal,
)
.unwrap();
let envelope_bytes = envelope.serialize();
let (envelope, _, _) = Envelope::<CS>::seal_raw(
randomized_pwd_hasher,
nonce.into(),
Some(pubkey_bytes.as_slice()).into_iter(),
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 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[..])?;
let r3_bytes = r3.serialize()?;
assert_eq!(input, r3_bytes);
let r3 = RegistrationUpload::<CS>::deserialize(&input)?;
let r3_bytes = r3.serialize();
assert_eq!(input, *r3_bytes);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn credential_request_roundtrip() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let alpha = random_ristretto_point();
let alpha_bytes = alpha.to_arr().to_vec();
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// CredentialRequest: KgPk + Ke1Message
<CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
let mut rng = OsRng;
let alpha = random_point::<CS>();
let alpha_bytes = alpha.to_arr();
let client_e_kp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let mut client_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
let client_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
let mut client_nonce = [0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
let ke1m: Vec<u8> = [client_nonce.as_ref(), client_e_kp.public()].concat();
let mut input = Vec::new();
input.extend_from_slice(&alpha_bytes);
input.extend_from_slice(&ke1m[..]);
let mut input = Vec::new();
input.extend_from_slice(&alpha_bytes);
input.extend_from_slice(&ke1m);
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice())?;
let l1_bytes = l1.serialize()?;
assert_eq!(input, l1_bytes);
let l1 = CredentialRequest::<CS>::deserialize(&input)?;
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 that identity group element is rejected
let identity = CS::OprfGroup::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialRequest::<Default>::deserialize(
&[identity_bytes, ke1m.to_vec()].concat()
) {
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
))) => true,
_ => false,
});
assert!(matches!(
CredentialRequest::<CS>::deserialize(&[identity_bytes, ke1m.to_vec()].concat()),
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
)))
));
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn credential_response_roundtrip() -> Result<(), ProtocolError> {
let pt = random_ristretto_point();
let pt_bytes = pt.to_arr().to_vec();
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
let pt = random_point::<CS>();
let pt_bytes = pt.to_arr();
let mut rng = OsRng;
let mut rng = OsRng;
let mut masking_nonce = vec![0u8; 32];
rng.fill_bytes(&mut masking_nonce);
let mut masking_nonce = [0u8; 32];
rng.fill_bytes(&mut masking_nonce);
let mut masked_response =
vec![0u8; <RistrettoPoint as Group>::ElemLen::USIZE + Envelope::<Default>::len()];
rng.fill_bytes(&mut masked_response);
let mut masked_response =
vec![0u8; <CS::OprfGroup as Group>::ElemLen::USIZE + Envelope::<CS>::len()];
rng.fill_bytes(&mut masked_response);
let server_e_kp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut server_nonce);
let server_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
let mut mac = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut mac);
let mut server_nonce = [0u8; NonceLen::USIZE];
rng.fill_bytes(&mut server_nonce);
let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
let ke2m: Vec<u8> = [server_nonce.as_ref(), server_e_kp.public(), &mac].concat();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
input.extend_from_slice(&masking_nonce);
input.extend_from_slice(&masked_response);
input.extend_from_slice(&ke2m[..]);
let mut input = Vec::new();
input.extend_from_slice(&pt_bytes);
input.extend_from_slice(&masking_nonce);
input.extend_from_slice(&masked_response);
input.extend_from_slice(&ke2m);
let l2 = CredentialResponse::<Default>::deserialize(&input)?;
let l2_bytes = l2.serialize()?;
assert_eq!(input, l2_bytes);
let l2 = CredentialResponse::<CS>::deserialize(&input)?;
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 that identity group element is rejected
let identity = CS::OprfGroup::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialResponse::<Default>::deserialize(
&[
identity_bytes,
masking_nonce.to_vec(),
masked_response,
ke2m.to_vec()
]
.concat()
) {
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
))) => true,
_ => false,
});
assert!(matches!(
CredentialResponse::<CS>::deserialize(
&[
identity_bytes,
masking_nonce.to_vec(),
masked_response,
ke2m.to_vec()
]
.concat()
),
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::errors::InternalError::PointError,
)))
));
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn credential_finalization_roundtrip() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut mac = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut mac);
let input: Vec<u8> = [&mac[..]].concat();
let input = mac;
let l3 = CredentialFinalization::<Default>::deserialize(&input)?;
let l3_bytes = l3.serialize()?;
assert_eq!(input, l3_bytes);
let l3 = CredentialFinalization::<CS>::deserialize(&input)?;
let l3_bytes = l3.serialize();
assert_eq!(input.as_slice(), l3_bytes.as_slice());
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn client_login_roundtrip() -> Result<(), ProtocolError> {
let pw = b"hunter2";
let mut rng = OsRng;
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// CredentialRequest: KgPk + Ke1Message
<CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
let pw = b"hunter2";
let mut rng = OsRng;
let client_e_kp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let mut client_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
let client_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
let mut client_nonce = [0; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
let serialized_credential_request = b"serialized credential_request".to_vec();
let l1_data = [client_e_kp.private().to_arr().to_vec(), client_nonce].concat();
let l1_data = [
client_e_kp.private().to_arr().to_vec(),
client_nonce.to_vec(),
]
.concat();
let blind_result =
&voprf::NonVerifiableClient::<RistrettoPoint, sha2::Sha512>::blind(pw.to_vec(), &mut rng)?;
let blind_result =
voprf::NonVerifiableClient::<CS::OprfGroup, CS::Hash>::blind(pw.to_vec(), &mut rng)?;
let credential_request = CredentialRequest::<CS> {
blinded_element: blind_result.message,
ke1_message:
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes(
&[client_nonce.as_ref(), client_e_kp.public()].concat(),
)?,
};
let bytes: Vec<u8> = chain!(
Serialize::<U2>::from(&blind_result.state.serialize())?.iter(),
Serialize::<U2>::from(&credential_request.serialize())?.iter(),
Serialize::<U2>::from(&l1_data)?.iter(),
)
.flatten()
.cloned()
.collect();
let reg = ClientLogin::<CS>::deserialize(&bytes)?;
let reg_bytes = reg.serialize()?;
assert_eq!(reg_bytes, bytes);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
let bytes: Vec<u8> = [
serialize(&blind_result.state.serialize(), 2)?,
serialize(&serialized_credential_request, 2)?,
serialize(&l1_data, 2)?,
]
.concat();
let reg = ClientLogin::<Default>::deserialize(&bytes[..])?;
let reg_bytes = reg.serialize()?;
assert_eq!(reg_bytes, bytes);
Ok(())
}
#[test]
fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
let mut rng = OsRng;
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let client_e_kp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let mut client_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
let client_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::from_bytes::<
Default,
>(&ke1m[..])?;
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke1m);
let ke1m = [client_nonce.as_slice(), client_e_kp.public()].concat();
let reg =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes(&ke1m)?;
let reg_bytes = reg.to_bytes();
assert_eq!(*reg_bytes, ke1m);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
let mut rng = OsRng;
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let server_e_kp = KeyPair::<<Default as CipherSuite>::OprfGroup>::generate_random(&mut rng)?;
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut server_nonce);
let server_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
let mut mac = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut server_nonce);
let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
let ke2m: Vec<u8> = [server_nonce.as_slice(), server_e_kp.public(), &mac].concat();
let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::from_bytes::<
Default,
>(&ke2m[..])?;
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke2m);
let reg =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message::from_bytes(&ke2m)?;
let reg_bytes = reg.to_bytes();
assert_eq!(*reg_bytes, ke2m);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
#[test]
fn ke3_message_roundtrip() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut mac = GenericArray::<_, <CS::Hash as Digest>::OutputSize>::default();
rng.fill_bytes(&mut mac);
let ke3m: Vec<u8> = [&mac[..]].concat();
let ke3m: Vec<u8> = [mac].concat();
let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::from_bytes::<
Default,
>(&ke3m[..])?;
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke3m);
let reg =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message::from_bytes(&ke3m)?;
let reg_bytes = reg.to_bytes();
assert_eq!(*reg_bytes, ke3m);
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
#[cfg(feature = "p256")]
inner::<P256>()?;
Ok(())
}
proptest! {
#[test]
fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..core::mem::size_of::<usize>())) {
use generic_array::typenum::{U0, U1, U2, U3, U4, U5, U6, U7};
#[test]
fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..core::mem::size_of::<usize>())) {
assert_eq!(i2osp(os2ip(&bytes).unwrap(), bytes.len()).unwrap(), bytes);
let input = os2ip(&bytes).unwrap();
let output = match bytes.len() {
0 => i2osp::<U0>(input).unwrap().to_vec(),
1 => i2osp::<U1>(input).unwrap().to_vec(),
2 => i2osp::<U2>(input).unwrap().to_vec(),
3 => i2osp::<U3>(input).unwrap().to_vec(),
4 => i2osp::<U4>(input).unwrap().to_vec(),
5 => i2osp::<U5>(input).unwrap().to_vec(),
6 => i2osp::<U6>(input).unwrap().to_vec(),
7 => i2osp::<U7>(input).unwrap().to_vec(),
_ => unreachable!("unexpected size")
};
assert_eq!(output, bytes);
}
}
#[test]
fn test_nocrash_registration_request(bytes in vec(any::<u8>(), 0..200)) {
RegistrationRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
macro_rules! test {
($mod:ident, $CS:ty) => {
mod $mod {
use super::*;
proptest! {
#[test]
fn test_nocrash_registration_request(bytes in vec(any::<u8>(), 0..200)) {
RegistrationRequest::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_response(bytes in vec(any::<u8>(), 0..200)) {
RegistrationResponse::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_upload(bytes in vec(any::<u8>(), 0..200)) {
RegistrationUpload::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_request(bytes in vec(any::<u8>(), 0..500)) {
CredentialRequest::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_response(bytes in vec(any::<u8>(), 0..500)) {
CredentialResponse::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_finalization(bytes in vec(any::<u8>(), 0..500)) {
CredentialFinalization::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
ClientRegistration::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
ServerRegistration::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
ClientLogin::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
ServerLogin::<$CS>::deserialize(&bytes).map_or(true, |_| true);
}
}
}
};
}
#[test]
fn test_nocrash_registration_response(bytes in vec(any::<u8>(), 0..200)) {
RegistrationResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_upload(bytes in vec(any::<u8>(), 0..200)) {
RegistrationUpload::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_request(bytes in vec(any::<u8>(), 0..500)) {
CredentialRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_response(bytes in vec(any::<u8>(), 0..500)) {
CredentialResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_finalization(bytes in vec(any::<u8>(), 0..500)) {
CredentialFinalization::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
ClientRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
ServerRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
ClientLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
ServerLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
}
#[cfg(feature = "ristretto255")]
test!(ristretto255, Ristretto255);
#[cfg(feature = "p256")]
test!(p256, P256);
+5 -8
View File
@@ -8,10 +8,7 @@
//! Trait specifying a slow hashing function
use crate::{errors::InternalError, hash::Hash};
use alloc::vec::Vec;
use digest::Digest;
#[cfg(feature = "slow-hash")]
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// Used for the slow hashing function in OPAQUE
@@ -20,7 +17,7 @@ pub trait SlowHash<D: Hash>: Default {
fn hash(
&self,
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalError>;
) -> Result<GenericArray<u8, <D as Digest>::OutputSize>, InternalError>;
}
/// A no-op hash which simply returns its input
@@ -31,8 +28,8 @@ impl<D: Hash> SlowHash<D> for NoOpHash {
fn hash(
&self,
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalError> {
Ok(input.to_vec())
) -> Result<GenericArray<u8, <D as Digest>::OutputSize>, InternalError> {
Ok(input)
}
}
@@ -41,8 +38,8 @@ impl<D: Hash> SlowHash<D> for argon2::Argon2<'_> {
fn hash(
&self,
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalError> {
let mut output = alloc::vec![0u8; <D as Digest>::OutputSize::USIZE];
) -> Result<GenericArray<u8, <D as Digest>::OutputSize>, InternalError> {
let mut output = GenericArray::default();
self.hash_password_into(&input, &[0; argon2::MIN_SALT_LEN], &mut output)
.map_err(|_| InternalError::SlowHashError)?;
Ok(output)
Regular → Executable
+1105 -565
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ use rand::{CryptoRng, Error, RngCore};
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
///
///
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct CycleRng {
v: Vec<u8>,
}
+3 -3
View File
@@ -21,7 +21,7 @@ fn parse_vector_types(input: &str) -> String {
for caps in re.captures_iter(input) {
let vector_type = format!(
"\"{}\": [\n {} \n]",
caps["type"].to_string(),
&caps["type"],
parse_ciphersuites(chunks[count])
);
vector_types.push(vector_type);
@@ -44,8 +44,8 @@ fn parse_ciphersuites(input: &str) -> String {
for caps in re.captures_iter(input) {
let ciphersuite = format!(
"{{ \"{}, {}\": {{ {} }} }}",
caps["group"].to_string(),
caps["hash"].to_string(),
&caps["group"],
&caps["hash"],
parse_params(chunks[count])
);
ciphersuites.push(ciphersuite);
Regular → Executable
+238 -137
View File
@@ -6,11 +6,29 @@
// of this source tree.
use crate::{
ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, opaque::*,
slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
ciphersuite::CipherSuite,
envelope::EnvelopeLen,
errors::*,
key_exchange::{
group::KeGroup,
traits::{Ke1MessageLen, Ke2MessageLen},
tripledh::{NonceLen, TripleDH},
},
messages::{
CredentialRequestLen, CredentialResponseLen, CredentialResponseWithoutKeLen,
RegistrationResponseLen, RegistrationUploadLen,
},
opaque::*,
slow_hash::NoOpHash,
tests::mock_rng::CycleRng,
*,
};
use alloc::{string::ToString, vec, vec::Vec};
use core::ops::Add;
use digest::FixedOutput;
use generic_array::{typenum::Sum, ArrayLength};
use json::JsonValue;
use voprf::group::Group;
#[allow(non_snake_case)]
#[derive(Debug)]
@@ -130,29 +148,23 @@ fn populate_test_vectors(values: &JsonValue) -> OpaqueTestVectorParameters {
}
}
fn get_password_file_bytes<CS: CipherSuite>(
parameters: &OpaqueTestVectorParameters,
) -> Result<Vec<u8>, ProtocolError> {
fn get_password_file_bytes<CS: CipherSuite>(parameters: &OpaqueTestVectorParameters) -> Vec<u8>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
{
let password_file = ServerRegistration::<CS>::finish(
RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap(),
RegistrationUpload::deserialize(&parameters.registration_upload).unwrap(),
);
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.clone())),
(None, Some(y)) => Some(Identifiers::ServerIdentifier(y.clone())),
(Some(x), Some(y)) => Some(Identifiers::ClientAndServerIdentifiers(
x.clone(),
y.clone(),
)),
}
password_file.serialize().to_vec()
}
macro_rules! json_to_test_vectors {
@@ -175,32 +187,36 @@ fn tests() -> Result<(), ProtocolError> {
let rfc = json::parse(super::parser::rfc_to_json(super::opaque_vectors::VECTORS).as_str())
.expect("Could not parse json");
let ristretto_real_tvs = json_to_test_vectors!(rfc, "Real", "ristretto255, SHA512",);
#[cfg(feature = "ristretto255")]
{
let ristretto_real_tvs = json_to_test_vectors!(rfc, "Real", "ristretto255, SHA512",);
let ristretto_fake_tvs = json_to_test_vectors!(rfc, "Fake", "ristretto255, SHA512",);
let ristretto_fake_tvs = json_to_test_vectors!(rfc, "Fake", "ristretto255, SHA512",);
if ristretto_real_tvs.len() == 0 || ristretto_fake_tvs.len() == 0 {
panic!("Parsing error");
assert!(
!(ristretto_real_tvs.is_empty() || ristretto_fake_tvs.is_empty()),
"Parsing error"
);
struct Ristretto255Sha512NoSlowHash;
impl CipherSuite for Ristretto255Sha512NoSlowHash {
type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
test_registration_request::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_registration_response::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_registration_upload::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_ke1::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_ke2::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_ke3::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_server_login_finish::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_fake_vectors::<Ristretto255Sha512NoSlowHash>(&ristretto_fake_tvs)?;
}
struct Ristretto255Sha512NoSlowHash;
impl CipherSuite for Ristretto255Sha512NoSlowHash {
type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
test_registration_request::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_registration_response::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_registration_upload::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_ke1::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_ke2::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_ke3::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_server_login_finish::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
test_fake_vectors::<Ristretto255Sha512NoSlowHash>(&ristretto_fake_tvs)?;
#[cfg(feature = "p256")]
{
let p256_real_tvs =
@@ -208,14 +224,15 @@ fn tests() -> Result<(), ProtocolError> {
let p256_fake_tvs =
json_to_test_vectors!(rfc, "Fake", "P256_XMD:SHA-256_SSWU_RO_, SHA256",);
if p256_real_tvs.len() == 0 || p256_fake_tvs.len() == 0 {
panic!("Parsing error");
}
assert!(
!(p256_real_tvs.is_empty() || p256_fake_tvs.is_empty()),
"Parsing error"
);
struct P256Sha256NoSlowHash;
impl CipherSuite for P256Sha256NoSlowHash {
type OprfGroup = p256_::ProjectivePoint;
type KeGroup = p256_::ProjectivePoint;
type KeGroup = p256_::PublicKey;
type KeyExchange = TripleDH;
type Hash = sha2::Sha256;
type SlowHash = NoOpHash;
@@ -243,7 +260,7 @@ fn test_registration_request<CS: CipherSuite>(
ClientRegistration::<CS>::start(&mut rng, &parameters.password)?;
assert_eq!(
hex::encode(&parameters.registration_request),
hex::encode(client_registration_start_result.message.serialize()?)
hex::encode(client_registration_start_result.message.serialize())
);
}
Ok(())
@@ -251,19 +268,24 @@ fn test_registration_request<CS: CipherSuite>(
fn test_registration_response<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError> {
) -> Result<(), ProtocolError>
where
// RegistrationResponse: KgPk + KePk
<CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let server_setup = ServerSetup::<CS>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
parameters.oprf_seed.as_slice(),
&parameters.server_private_key,
&parameters.dummy_private_key,
]
.concat(),
)?;
let server_registration_start_result = ServerRegistration::<CS>::start(
&server_setup,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
RegistrationRequest::deserialize(&parameters.registration_request).unwrap(),
&parameters.credential_identifier,
)?;
assert_eq!(
@@ -272,7 +294,7 @@ fn test_registration_response<CS: CipherSuite>(
);
assert_eq!(
hex::encode(&parameters.registration_response),
hex::encode(server_registration_start_result.message.serialize()?)
hex::encode(server_registration_start_result.message.serialize())
);
}
Ok(())
@@ -280,7 +302,17 @@ fn test_registration_response<CS: CipherSuite>(
fn test_registration_upload<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError> {
) -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
@@ -289,11 +321,14 @@ fn test_registration_upload<CS: CipherSuite>(
let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce.to_vec());
let result = client_registration_start_result.state.finish(
&mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
match parse_identifiers(&parameters.client_identity, &parameters.server_identity) {
None => ClientRegistrationFinishParameters::default(),
Some(ids) => ClientRegistrationFinishParameters::new(Some(ids), None),
},
RegistrationResponse::deserialize(&parameters.registration_response).unwrap(),
ClientRegistrationFinishParameters::new(
Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
None,
),
)?;
assert_eq!(
hex::encode(&parameters.auth_key),
@@ -305,7 +340,7 @@ fn test_registration_upload<CS: CipherSuite>(
);
assert_eq!(
hex::encode(&parameters.registration_upload),
hex::encode(result.message.serialize()?)
hex::encode(result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.export_key),
@@ -316,18 +351,23 @@ fn test_registration_upload<CS: CipherSuite>(
Ok(())
}
fn test_ke1<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError> {
fn test_ke1<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
where
// CredentialRequest: KgPk + Ke1Message
<CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let client_login_start = [
&parameters.blind_login[..],
&parameters.client_private_keyshare[..],
&parameters.client_nonce[..],
parameters.blind_login.as_slice(),
&parameters.client_private_keyshare,
&parameters.client_nonce,
]
.concat();
println!(
"&parameters.blind_login[..]: {:?}",
hex::encode(&parameters.blind_login[..])
"parameters.blind_login: {:?}",
hex::encode(&parameters.blind_login)
);
let mut client_login_start_rng = CycleRng::new(client_login_start);
@@ -335,32 +375,59 @@ fn test_ke1<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), P
ClientLogin::<CS>::start(&mut client_login_start_rng, &parameters.password)?;
assert_eq!(
hex::encode(&parameters.KE1),
hex::encode(client_login_start_result.message.serialize()?)
hex::encode(client_login_start_result.message.serialize())
);
}
Ok(())
}
fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError> {
fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let server_setup = ServerSetup::<CS>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
parameters.oprf_seed.as_slice(),
&parameters.server_private_key,
&parameters.dummy_private_key,
]
.concat(),
)?;
let record = ServerRegistration::<CS>::deserialize(
&get_password_file_bytes::<CS>(&parameters)?[..],
)?;
let record =
ServerRegistration::<CS>::deserialize(&get_password_file_bytes::<CS>(parameters))?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
&parameters.masking_nonce[..],
&parameters.server_private_keyshare[..],
&parameters.server_nonce[..],
parameters.masking_nonce.as_slice(),
&parameters.server_private_keyshare,
&parameters.server_nonce,
]
.concat(),
);
@@ -368,14 +435,14 @@ fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), P
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
Some(record),
CredentialRequest::<CS>::deserialize(&parameters.KE1[..]).unwrap(),
CredentialRequest::<CS>::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,
),
ServerLoginStartParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
assert_eq!(
@@ -392,18 +459,25 @@ fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), P
);
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize()?)
hex::encode(server_login_start_result.message.serialize())
);
}
Ok(())
}
fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError> {
fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let client_login_start = [
&parameters.blind_login[..],
&parameters.client_private_keyshare[..],
&parameters.client_nonce[..],
parameters.blind_login.as_slice(),
&parameters.client_private_keyshare,
&parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
@@ -411,17 +485,15 @@ fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), P
ClientLogin::<CS>::start(&mut client_login_start_rng, &parameters.password)?;
let client_login_finish_result = client_login_start_result.state.finish(
CredentialResponse::<CS>::deserialize(&parameters.KE2[..])?,
match parse_identifiers(&parameters.client_identity, &parameters.server_identity) {
None => {
ClientLoginFinishParameters::new(Some(parameters.context.clone()), None, None)
}
Some(ids) => ClientLoginFinishParameters::new(
Some(parameters.context.clone()),
Some(ids),
None,
),
},
CredentialResponse::<CS>::deserialize(&parameters.KE2)?,
ClientLoginFinishParameters::new(
Some(&parameters.context.clone()),
Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
None,
),
)?;
assert_eq!(
@@ -438,7 +510,7 @@ fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), P
);
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),
@@ -450,26 +522,41 @@ fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), P
fn test_server_login_finish<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError> {
) -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<<CS::KeGroup as KeGroup>::PkLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let server_setup = ServerSetup::<CS>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
parameters.oprf_seed.as_slice(),
&parameters.server_private_key,
&parameters.dummy_private_key,
]
.concat(),
)?;
let record = ServerRegistration::<CS>::deserialize(
&get_password_file_bytes::<CS>(&parameters)?[..],
)?;
let record =
ServerRegistration::<CS>::deserialize(&get_password_file_bytes::<CS>(parameters))?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
&parameters.masking_nonce[..],
&parameters.server_private_keyshare[..],
&parameters.server_nonce[..],
parameters.masking_nonce.as_slice(),
&parameters.server_private_keyshare,
&parameters.server_nonce,
]
.concat(),
);
@@ -477,20 +564,20 @@ fn test_server_login_finish<CS: CipherSuite>(
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
Some(record),
CredentialRequest::<CS>::deserialize(&parameters.KE1[..]).unwrap(),
CredentialRequest::<CS>::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,
),
ServerLoginStartParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
let server_login_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&parameters.KE3[..])?)?;
.finish(CredentialFinalization::deserialize(&parameters.KE3)?)?;
assert_eq!(
hex::encode(&parameters.session_key),
@@ -502,23 +589,37 @@ fn test_server_login_finish<CS: CipherSuite>(
fn test_fake_vectors<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError> {
) -> Result<(), ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<<CS::Hash as FixedOutput>::OutputSize>,
Sum<NonceLen, <CS::Hash as FixedOutput>::OutputSize>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let server_setup = ServerSetup::<CS>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
parameters.oprf_seed.as_slice(),
&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[..],
parameters.dummy_masking_key.as_slice(),
&parameters.masking_nonce,
&parameters.server_private_keyshare,
&parameters.server_nonce,
]
.concat(),
);
@@ -526,19 +627,19 @@ fn test_fake_vectors<CS: CipherSuite>(
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
None,
CredentialRequest::<CS>::deserialize(&parameters.KE1[..]).unwrap(),
CredentialRequest::<CS>::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,
),
ServerLoginStartParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize()?)
hex::encode(server_login_start_result.message.serialize())
);
}
Ok(())