Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba8e940e08 | ||
|
|
ac08801f18 | ||
|
|
a49e089e54 | ||
|
|
37f98b3742 | ||
|
|
fa0fb48654 | ||
|
|
4da6bbce0c | ||
|
|
a457d82793 | ||
|
|
782eeb7090 | ||
|
|
bb7d17a102 | ||
|
|
7f84984f61 | ||
|
|
e4636e0a97 | ||
|
|
e694a88ef9 | ||
|
|
f596e4bf0e | ||
|
|
1dbfed8763 | ||
|
|
d3204203ca | ||
|
|
160ac47ffa | ||
|
|
6307ea9eed | ||
|
|
e35410ab95 | ||
|
|
1f608e464c | ||
|
|
e32b240ce2 | ||
|
|
099f887530 | ||
|
|
9d3963f98e | ||
|
|
be6d042d80 | ||
|
|
d6ace87c21 | ||
|
|
6eeeb0db28 | ||
|
|
6dc1c8b212 | ||
|
|
e421aaad07 | ||
|
|
511d371c9a | ||
|
|
f73fc55254 | ||
|
|
2a2d0888a5 | ||
|
|
ba53089199 | ||
|
|
fa6752cdc5 | ||
|
|
9f6b32a5ea | ||
|
|
f5b5391ee0 | ||
|
|
605d2b639b | ||
|
|
9e3743a604 | ||
|
|
6b22064863 | ||
|
|
94630dff7c | ||
|
|
2794dfaaa9 | ||
|
|
9f414d4a82 | ||
|
|
6f5a1fc6aa | ||
|
|
4c82dfaf13 | ||
|
|
7fcde1c50d | ||
|
|
f8c976d659 | ||
|
|
95152c3b35 | ||
|
|
51b3781e5c | ||
|
|
f18eaea8cf | ||
|
|
2d7b4292ff | ||
|
|
f165310c96 | ||
|
|
09f7cfdf78 | ||
|
|
05514e49aa | ||
|
|
eb1a93c379 | ||
|
|
697cd7a788 | ||
|
|
d8906c092a | ||
|
|
3252ca96de | ||
|
|
552a546b6c | ||
|
|
230b1bcef6 | ||
|
|
3954cd23b8 | ||
|
|
aabd5266c3 | ||
|
|
bd37deb7a7 | ||
|
|
6deddbe33c | ||
|
|
c8cbf56336 | ||
|
|
28645a7cea | ||
|
|
07f8048a3d | ||
|
|
0fc3448777 | ||
|
|
7cc1c0992a | ||
|
|
9c06c98ad6 | ||
|
|
344e8ad8d1 | ||
|
|
c7f6abb591 | ||
|
|
85b0ef031d | ||
|
|
8ccb5ad510 | ||
|
|
18bcd23102 | ||
|
|
9b5f7f455d | ||
|
|
25ae7ff9bf | ||
|
|
aabdb53581 | ||
|
|
587ef935a7 | ||
|
|
288105861d | ||
|
|
6b9583e928 | ||
|
|
958d0b7a27 | ||
|
|
40ea015d6d | ||
|
|
39ec88674a | ||
|
|
c97ddaf5d2 | ||
|
|
ae890dbac4 | ||
|
|
56d38dba2c | ||
|
|
786bc51fdd | ||
|
|
887b4577fa | ||
|
|
3c2a208606 | ||
|
|
3c555e82ae |
@@ -1,6 +1,8 @@
|
||||
name: Rust CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, repoened, synchronize]
|
||||
|
||||
@@ -13,16 +15,19 @@ jobs:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
toolchain:
|
||||
- nightly
|
||||
- 1.41.0
|
||||
name: test
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
- name: Install ${{ matrix.toolchain }} toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -32,6 +37,90 @@ jobs:
|
||||
command: test
|
||||
args: --no-default-features --features ${{ matrix.backend_feature }}
|
||||
|
||||
cross-test:
|
||||
name: Test on ${{ matrix.target }} (using cross)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
# 32-bit x86
|
||||
- i686-unknown-linux-gnu
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- run: cargo install cross
|
||||
# Note: just use `cross` as you would `cargo`, but always
|
||||
# pass the `--target=${{ matrix.target }}` arg. (Yes, really).
|
||||
- run: cross test --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.backend_feature }}
|
||||
|
||||
|
||||
slow-hash-test:
|
||||
name: Test on ${{ matrix.target }} with slow hash
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
- run: cargo test --verbose --features slow-hash --no-default-features --features ${{ matrix.backend_feature }}
|
||||
|
||||
|
||||
simple-login-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain:
|
||||
- nightly
|
||||
- 1.41.0
|
||||
name: test simple_login command-line example
|
||||
steps:
|
||||
- name: install expect
|
||||
run: sudo apt-get install expect
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
- name: install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
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
|
||||
|
||||
digital-locker-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain:
|
||||
- nightly
|
||||
- 1.41.0
|
||||
name: test digital_locker command-line example
|
||||
steps:
|
||||
- name: install expect
|
||||
run: sudo apt-get install expect
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
- name: install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
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
|
||||
|
||||
benches:
|
||||
name: cargo bench compilation
|
||||
runs-on: ubuntu-latest
|
||||
@@ -39,7 +128,7 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published] # Only publish to crates.io when we formally publish a release
|
||||
# For more on how to formally release on Github, read https://help.github.com/en/articles/creating-releases
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: hecrj/setup-rust-action@v1
|
||||
with:
|
||||
rust-version: ${{ matrix.rust }}
|
||||
- uses: actions/checkout@master
|
||||
- name: Login to crates.io
|
||||
run: cargo login $CRATES_IO_TOKEN
|
||||
env:
|
||||
CRATES_IO_TOKEN: ${{ secrets.crates_io_token }} # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets
|
||||
- name: Dry run publish opaque-ke
|
||||
run: cargo publish --dry-run --manifest-path Cargo.toml
|
||||
- name: Publish opaque-ke
|
||||
run: cargo publish --manifest-path Cargo.toml
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
|
||||
@@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.0 (March 1, 2020)
|
||||
|
||||
* Removed dependency on generic-bytes-derive package
|
||||
|
||||
## 0.4.0 (February 26, 2020)
|
||||
|
||||
* Adherence to protocol format described in
|
||||
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
|
||||
* Renamed to_bytes() and try_from() to serialize() and deserialize() for
|
||||
top-level structs
|
||||
* Conformed all message type parameters to be parameterized in the
|
||||
Ciphersuite object
|
||||
|
||||
## 0.3.1 (February 11, 2020)
|
||||
|
||||
* Re-exporting the rand library (and including it as a dependency instead of
|
||||
just rand_core)
|
||||
* Exposing a convenience function for converting from byte array to Key type
|
||||
|
||||
## 0.3.0 (February 8, 2020)
|
||||
|
||||
* General API and documentation improvements, including the support of custom
|
||||
identifiers, optional result parameters, and the use of the export key
|
||||
* Compliance with RFC 8017 on data serialization functions (I2OSP / OS2IP)
|
||||
* Adherence to protocol format described in
|
||||
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-02
|
||||
* Added parameters for key exchange additional data
|
||||
* Added simple_login and digital_locker examples
|
||||
|
||||
## 0.2.1 (October 22, 2020)
|
||||
|
||||
* Changed visibility of hash module to be public
|
||||
|
||||
## 0.2.0 (September 3, 2020)
|
||||
|
||||
* Added CipherSuite API for specifying underlying primitives
|
||||
|
||||
Generated
+435
-242
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "opaque-ke"
|
||||
version = "0.2.0"
|
||||
version = "0.5.0"
|
||||
repository = "https://github.com/novifinancial/opaque-ke"
|
||||
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
|
||||
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
|
||||
@@ -13,34 +13,34 @@ readme = "README.md"
|
||||
default = ["u64_backend"]
|
||||
slow-hash = ["scrypt"]
|
||||
bench = []
|
||||
u64_backend = ["curve25519-dalek/u64_backend", "x25519-dalek/u64_backend"]
|
||||
u32_backend = ["curve25519-dalek/u32_backend", "x25519-dalek/u32_backend"]
|
||||
u64_backend = ["curve25519-dalek/u64_backend"]
|
||||
u32_backend = ["curve25519-dalek/u32_backend"]
|
||||
|
||||
[dependencies]
|
||||
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] }
|
||||
digest = "0.9.0"
|
||||
displaydoc = "0.1.7"
|
||||
fiat-crypto = { version = "0.1.5"}
|
||||
generic-array = "0.14.4"
|
||||
hkdf = "0.9.0"
|
||||
hmac = "0.9.0"
|
||||
rand_core = "0.5.1"
|
||||
scrypt = { version = "0.4.1", optional = true }
|
||||
sha2 = "0.9.1"
|
||||
subtle = { version = "^2.2.1", default-features = false }
|
||||
thiserror = "1.0.20"
|
||||
x25519-dalek = { version = "1.0.1", default-features = false, features = ["std"] }
|
||||
zeroize = "1.1"
|
||||
generic-bytes = { version = "0.1.0" }
|
||||
hkdf = "0.10.0"
|
||||
hmac = "0.10.1"
|
||||
rand = "0.8"
|
||||
scrypt = { version = "0.5.0", optional = true }
|
||||
subtle = { version = "2.3.0", default-features = false }
|
||||
thiserror = "1.0.22"
|
||||
zeroize = "1.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.32"
|
||||
base64 = "0.12.3"
|
||||
anyhow = "1.0.35"
|
||||
base64 = "0.13.0"
|
||||
chacha20poly1305 = "0.7.1"
|
||||
criterion = "0.3.3"
|
||||
hex = "0.4.2"
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.0.57"
|
||||
serde_json = "1.0.60"
|
||||
sha2 = "0.9.2"
|
||||
proptest = "0.10.1"
|
||||
rand = "0.7"
|
||||
rustyline = "6.3.0"
|
||||
|
||||
[[bench]]
|
||||
name = "oprf"
|
||||
|
||||
@@ -22,14 +22,14 @@ Installation
|
||||
Add the following line to the dependencies of your `Cargo.toml`:
|
||||
|
||||
```
|
||||
opaque-ke = "0.2.0"
|
||||
opaque-ke = "0.5.0"
|
||||
```
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
|
||||
- [draft-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06), containing a specification for the OPAQUE protocol
|
||||
- [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html), containing a detailed (byte-level) specification for OPAQUE
|
||||
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
|
||||
|
||||
Contributors
|
||||
@@ -41,7 +41,7 @@ To learn more about contributing to this project, [see this document](./CONTRIBU
|
||||
|
||||
#### Acknowledgments
|
||||
|
||||
Special thanks go to Hugo Krawczyk for helping to clarify discrepancies and making suggestions for improving
|
||||
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving
|
||||
this implementation.
|
||||
|
||||
|
||||
|
||||
+12
-89
@@ -7,40 +7,22 @@
|
||||
extern crate criterion;
|
||||
|
||||
use criterion::Criterion;
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::arr;
|
||||
use opaque_ke::{
|
||||
group::Group,
|
||||
oprf::{generate_oprf1_shim, generate_oprf2_shim, generate_oprf3_shim, OprfClientBytes},
|
||||
oprf::{blind_shim, evaluate_shim, finalize_shim},
|
||||
};
|
||||
use rand::{prelude::ThreadRng, thread_rng};
|
||||
use sha2::Sha256;
|
||||
use sha2::Sha512;
|
||||
|
||||
fn oprf1(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
c.bench_function("generate_oprf1 with Ristretto", move |b| {
|
||||
c.bench_function("blind with Ristretto", move |b| {
|
||||
b.iter(|| {
|
||||
let OprfClientBytes {
|
||||
alpha: _alpha,
|
||||
blinding_factor: _blinding_factor,
|
||||
} = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn oprf1_edwards(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
c.bench_function("generate_oprf1 with Edwards", move |b| {
|
||||
b.iter(|| {
|
||||
let OprfClientBytes {
|
||||
alpha: _alpha,
|
||||
blinding_factor: _blinding_factor,
|
||||
} = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
|
||||
blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -49,40 +31,16 @@ fn oprf2(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor: _blinding_factor,
|
||||
} = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
|
||||
let (_, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
|
||||
|
||||
c.bench_function("generate_oprf2 with Ristretto", move |b| {
|
||||
c.bench_function("evaluate with Ristretto", move |b| {
|
||||
b.iter(|| {
|
||||
let _beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn oprf2_edwards(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor: _blinding_factor,
|
||||
} = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
|
||||
|
||||
c.bench_function("generate_oprf2 with Edwards", move |b| {
|
||||
b.iter(|| {
|
||||
let _beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
|
||||
let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -91,55 +49,20 @@ fn oprf3(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
} = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
|
||||
let (token, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
|
||||
let beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
|
||||
let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
|
||||
|
||||
c.bench_function("generate_oprf3 with Ristretto", move |b| {
|
||||
c.bench_function("finalize with Ristretto", move |b| {
|
||||
b.iter(|| {
|
||||
let _res = generate_oprf3_shim::<RistrettoPoint, Sha256>(input, beta, &blinding_factor)
|
||||
.unwrap();
|
||||
let _res = finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn oprf3_edwards(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
} = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
|
||||
let beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
|
||||
|
||||
c.bench_function("generate_oprf3 with Edwards", move |b| {
|
||||
b.iter(|| {
|
||||
let _res =
|
||||
generate_oprf3_shim::<EdwardsPoint, Sha256>(input, beta, &blinding_factor).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
oprf_benches,
|
||||
oprf1,
|
||||
oprf2,
|
||||
oprf3,
|
||||
oprf1_edwards,
|
||||
oprf2_edwards,
|
||||
oprf3_edwards
|
||||
);
|
||||
criterion_group!(oprf_benches, oprf1, oprf2, oprf3);
|
||||
criterion_main!(oprf_benches);
|
||||
|
||||
@@ -34,7 +34,7 @@ targets = [
|
||||
# The path where the advisory database is cloned/fetched into
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
# The url of the advisory database to use
|
||||
db-url = "https://github.com/rustsec/advisory-db"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
# The lint level for security vulnerabilities
|
||||
vulnerability = "deny"
|
||||
# The lint level for unmaintained crates
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
//! Demonstrates an implementation of a server-side secured digital locker using
|
||||
//! the client's OPAQUE export key, over a command-line interface
|
||||
//!
|
||||
//! A client can password-protect a secret message to be stored in a digital locker,
|
||||
//! controlled by the server. The locker's contents are only revealed to the holder
|
||||
//! of the password when attempting to open the locker.
|
||||
//!
|
||||
//! The client-server interactions are executed in a three-step protocol
|
||||
//! within the account_registration (for password registration) and
|
||||
//! account_login (for password login) functions. These steps
|
||||
//! must be performed in the specific sequence outlined in each of these
|
||||
//! functions.
|
||||
//!
|
||||
//! The CipherSuite trait allows the application to configure the
|
||||
//! primitives used by OPAQUE, but must be kept consistent across the steps
|
||||
//! of the protocol.
|
||||
//!
|
||||
//! In a more realistic client-server interaction, the client must send
|
||||
//! messages over "the wire" to the server. These bytes are serialized
|
||||
//! and explicitly annotated in the below functions.
|
||||
|
||||
use chacha20poly1305::aead::{Aead, NewAead};
|
||||
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
use std::process::exit;
|
||||
|
||||
use opaque_ke::{
|
||||
ciphersuite::CipherSuite,
|
||||
rand::{rngs::OsRng, RngCore},
|
||||
ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistration,
|
||||
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
|
||||
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
|
||||
ServerLoginStartParameters, ServerRegistration,
|
||||
};
|
||||
|
||||
// The ciphersuite trait allows to specify the underlying primitives
|
||||
// that will be used in the OPAQUE protocol
|
||||
#[allow(dead_code)]
|
||||
struct Default;
|
||||
impl CipherSuite for Default {
|
||||
type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
type Hash = sha2::Sha512;
|
||||
type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
}
|
||||
|
||||
struct Locker {
|
||||
contents: Vec<u8>,
|
||||
password_file: Vec<u8>,
|
||||
}
|
||||
|
||||
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
|
||||
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
|
||||
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
|
||||
|
||||
let mut rng = OsRng;
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
|
||||
[nonce_bytes.to_vec(), ciphertext].concat()
|
||||
}
|
||||
|
||||
// Decrypt using a key and a ciphertext (nonce included) to recover the original plaintext
|
||||
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
|
||||
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
|
||||
cipher
|
||||
.decrypt(
|
||||
Nonce::from_slice(&ciphertext[..12]),
|
||||
ciphertext[12..].as_ref(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Password-based registration and encryption of client secret message between a client and server
|
||||
fn register_locker(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
password: String,
|
||||
secret_message: String,
|
||||
) -> 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();
|
||||
|
||||
// Client sends registration_request_bytes to server
|
||||
|
||||
let mut server_rng = OsRng;
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&mut server_rng,
|
||||
RegistrationRequest::deserialize(®istration_request_bytes[..]).unwrap(),
|
||||
server_kp.public(),
|
||||
)
|
||||
.unwrap();
|
||||
let registration_response_bytes = server_registration_start_result.message.serialize();
|
||||
|
||||
// Server sends registration_response_bytes to client
|
||||
|
||||
let client_finish_registration_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut client_rng,
|
||||
RegistrationResponse::deserialize(®istration_response_bytes[..]).unwrap(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let message_bytes = client_finish_registration_result.message.serialize();
|
||||
|
||||
// Client encrypts secret message using export key
|
||||
let ciphertext = encrypt(
|
||||
&client_finish_registration_result.export_key,
|
||||
secret_message.as_bytes(),
|
||||
);
|
||||
|
||||
// Client sends message_bytes to server
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
|
||||
.unwrap();
|
||||
|
||||
Locker {
|
||||
contents: ciphertext,
|
||||
password_file: password_file.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
// Open the contents of a locker with a password between a client and server
|
||||
fn open_locker(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
password: String,
|
||||
locker: &Locker,
|
||||
) -> Result<String, String> {
|
||||
let mut client_rng = OsRng;
|
||||
let client_login_start_result = ClientLogin::<Default>::start(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||
|
||||
// Client sends credential_request_bytes to server
|
||||
|
||||
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,
|
||||
password_file,
|
||||
&server_kp.private(),
|
||||
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
|
||||
ServerLoginStartParameters::default(),
|
||||
)
|
||||
.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(),
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
// Client-detected login failure
|
||||
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();
|
||||
|
||||
// Client sends credential_finalization_bytes to server
|
||||
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
|
||||
.unwrap();
|
||||
|
||||
// Server sends locker contents, encrypted under the session key, to the client
|
||||
let encrypted_locker_contents =
|
||||
encrypt(&server_login_finish_result.session_key, &locker.contents);
|
||||
|
||||
// Client decrypts contents of locker, first under the session key, and then under the export key
|
||||
let plaintext = decrypt(
|
||||
&client_login_finish_result.export_key,
|
||||
&decrypt(
|
||||
&client_login_finish_result.session_key,
|
||||
&encrypted_locker_contents,
|
||||
),
|
||||
);
|
||||
String::from_utf8(plaintext).map_err(|_| String::from("UTF8 error"))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = OsRng;
|
||||
let server_kp = Default::generate_random_keypair(&mut rng);
|
||||
|
||||
let mut rl = Editor::<()>::new();
|
||||
let mut registered_lockers: Vec<Locker> = vec![];
|
||||
loop {
|
||||
display_lockers(®istered_lockers);
|
||||
|
||||
println!("Enter an option (1 or 2):");
|
||||
println!("1) Register a locker");
|
||||
println!("2) Open a locker\n");
|
||||
let readline = rl.readline("> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
if line != "1" && line != "2" {
|
||||
println!("Error: Invalid option (either specify 1 or 2)");
|
||||
continue;
|
||||
}
|
||||
match line.as_ref() {
|
||||
"1" => {
|
||||
let (password, secret_message) = get_two_strings(
|
||||
"Choose a password",
|
||||
"Set a secret message",
|
||||
&mut rl,
|
||||
None,
|
||||
);
|
||||
registered_lockers.push(register_locker(
|
||||
&server_kp,
|
||||
password,
|
||||
secret_message,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
"2" => {
|
||||
let (locker, password) = get_two_strings(
|
||||
"Choose a locker number",
|
||||
"Enter the password",
|
||||
&mut rl,
|
||||
None,
|
||||
);
|
||||
let locker_index: usize = match locker.parse() {
|
||||
Ok(index) => index,
|
||||
Err(_) => {
|
||||
println!("Error: Could not find locker number");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if locker_index >= registered_lockers.len() {
|
||||
println!("Error: Could not find locker number");
|
||||
continue;
|
||||
}
|
||||
|
||||
match open_locker(&server_kp, password, ®istered_lockers[locker_index]) {
|
||||
Ok(contents) => {
|
||||
println!("\n\nSuccess! Contents: {}\n\n", contents);
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"\n\nError encountered, could not open locker: {}\n\n",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => exit(0),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn display_lockers(lockers: &Vec<Locker>) {
|
||||
let mut locker_numbers = vec![];
|
||||
for (i, _) in lockers.iter().enumerate() {
|
||||
locker_numbers.push(i);
|
||||
}
|
||||
|
||||
println!(
|
||||
"\nCurrently registered locker numbers: {:?}\n",
|
||||
locker_numbers
|
||||
);
|
||||
}
|
||||
|
||||
// Handle readline errors
|
||||
fn handle_error(err: ReadlineError) {
|
||||
match err {
|
||||
ReadlineError::Interrupted => {
|
||||
println!("CTRL-C");
|
||||
}
|
||||
ReadlineError::Eof => {
|
||||
println!("CTRL-D");
|
||||
}
|
||||
err => {
|
||||
println!("Error: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A function run on the client which extracts two strings from the CLI
|
||||
fn get_two_strings(
|
||||
s1: &str,
|
||||
s2: &str,
|
||||
rl: &mut Editor<()>,
|
||||
string1: Option<String>,
|
||||
) -> (String, String) {
|
||||
let query = if string1.is_none() { s1 } else { s2 };
|
||||
let readline = rl.readline(&format!("{}: ", query));
|
||||
match readline {
|
||||
Ok(line) => match string1 {
|
||||
Some(x) => (x, line),
|
||||
None => get_two_strings(s1, s2, rl, Some(line)),
|
||||
},
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
//! Demonstrates a simple client-server password-based login protocol
|
||||
//! using OPAQUE, over a command-line interface
|
||||
//!
|
||||
//! The client-server interactions are executed in a three-step protocol
|
||||
//! within the account_registration (for password registration) and
|
||||
//! account_login (for password login) functions. These steps
|
||||
//! must be performed in the specific sequence outlined in each of these
|
||||
//! functions.
|
||||
//!
|
||||
//! The CipherSuite trait allows the application to configure the
|
||||
//! primitives used by OPAQUE, but must be kept consistent across the steps
|
||||
//! of the protocol.
|
||||
//!
|
||||
//! In a more realistic client-server interaction, the client must send
|
||||
//! messages over "the wire" to the server. These bytes are serialized
|
||||
//! and explicitly annotated in the below functions.
|
||||
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
use std::collections::HashMap;
|
||||
use std::process::exit;
|
||||
|
||||
use opaque_ke::{
|
||||
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
|
||||
ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters,
|
||||
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
|
||||
RegistrationResponse, RegistrationUpload, ServerLogin, ServerLoginStartParameters,
|
||||
ServerRegistration,
|
||||
};
|
||||
|
||||
// The ciphersuite trait allows to specify the underlying primitives
|
||||
// that will be used in the OPAQUE protocol
|
||||
#[allow(dead_code)]
|
||||
struct Default;
|
||||
impl CipherSuite for Default {
|
||||
type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
type Hash = sha2::Sha512;
|
||||
type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
}
|
||||
|
||||
// Password-based registration between a client and server
|
||||
fn account_registration(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
password: String,
|
||||
) -> Vec<u8> {
|
||||
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();
|
||||
|
||||
// Client sends registration_request_bytes to server
|
||||
|
||||
let mut server_rng = OsRng;
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&mut server_rng,
|
||||
RegistrationRequest::deserialize(®istration_request_bytes[..]).unwrap(),
|
||||
server_kp.public(),
|
||||
)
|
||||
.unwrap();
|
||||
let registration_response_bytes = server_registration_start_result.message.serialize();
|
||||
|
||||
// Server sends registration_response_bytes to client
|
||||
|
||||
let client_finish_registration_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut client_rng,
|
||||
RegistrationResponse::deserialize(®istration_response_bytes[..]).unwrap(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let message_bytes = client_finish_registration_result.message.serialize();
|
||||
|
||||
// Client sends message_bytes to server
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
|
||||
.unwrap();
|
||||
password_file.serialize()
|
||||
}
|
||||
|
||||
// Password-based login between a client and server
|
||||
fn account_login(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
password: String,
|
||||
password_file_bytes: &[u8],
|
||||
) -> bool {
|
||||
let mut client_rng = OsRng;
|
||||
let client_login_start_result = ClientLogin::<Default>::start(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||
|
||||
// Client sends credential_request_bytes to server
|
||||
|
||||
let password_file = ServerRegistration::<Default>::deserialize(password_file_bytes).unwrap();
|
||||
let mut server_rng = OsRng;
|
||||
let server_login_start_result = ServerLogin::start(
|
||||
&mut server_rng,
|
||||
password_file,
|
||||
&server_kp.private(),
|
||||
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
|
||||
ServerLoginStartParameters::default(),
|
||||
)
|
||||
.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(),
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
// Client-detected login failure
|
||||
return false;
|
||||
}
|
||||
let client_login_finish_result = result.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())
|
||||
.unwrap();
|
||||
|
||||
client_login_finish_result.session_key == server_login_finish_result.session_key
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = OsRng;
|
||||
let server_kp = Default::generate_random_keypair(&mut rng);
|
||||
|
||||
let mut rl = Editor::<()>::new();
|
||||
let mut registered_users = HashMap::<String, Vec<u8>>::new();
|
||||
loop {
|
||||
println!(
|
||||
"\nCurrently registered usernames: {:?}\n",
|
||||
registered_users.keys()
|
||||
);
|
||||
|
||||
println!("Enter an option (1 or 2):");
|
||||
println!("1) Register a user");
|
||||
println!("2) Login as a user\n");
|
||||
let readline = rl.readline("> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
if line != "1" && line != "2" {
|
||||
println!("Error: Invalid option (either specify 1 or 2)");
|
||||
continue;
|
||||
}
|
||||
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
|
||||
match line.as_ref() {
|
||||
"1" => {
|
||||
registered_users
|
||||
.insert(username, account_registration(&server_kp, password));
|
||||
continue;
|
||||
}
|
||||
"2" => match registered_users.get(&username) {
|
||||
Some(password_file_bytes) => {
|
||||
if account_login(&server_kp, password, password_file_bytes) {
|
||||
println!("\nLogin success!");
|
||||
} else {
|
||||
// Note that at this point, the client knows whether or not the login
|
||||
// succeeded. In this example, we simply rely on client-reported result
|
||||
// of login, but in a real client-server implementation, the server may not
|
||||
// know the outcome of login yet, and extra care must be taken to ensure
|
||||
// that the server can learn the outcome as well.
|
||||
println!("\nIncorrect password, please try again.");
|
||||
}
|
||||
}
|
||||
None => println!("Error: Could not find username registered"),
|
||||
},
|
||||
_ => exit(0),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Handle readline errors
|
||||
fn handle_error(err: ReadlineError) {
|
||||
match err {
|
||||
ReadlineError::Interrupted => {
|
||||
println!("CTRL-C");
|
||||
}
|
||||
ReadlineError::Eof => {
|
||||
println!("CTRL-D");
|
||||
}
|
||||
err => {
|
||||
println!("Error: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A function run on the client which extracts two strings from the CLI
|
||||
fn get_two_strings(
|
||||
s1: &str,
|
||||
s2: &str,
|
||||
rl: &mut Editor<()>,
|
||||
string1: Option<String>,
|
||||
) -> (String, String) {
|
||||
let query = if string1.is_none() { s1 } else { s2 };
|
||||
let readline = rl.readline(&format!("{}: ", query));
|
||||
match readline {
|
||||
Ok(line) => match string1 {
|
||||
Some(x) => (x, line),
|
||||
None => get_two_strings(s1, s2, rl, Some(line)),
|
||||
},
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/bin/expect -f
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
set timeout 1
|
||||
spawn cargo run --example digital_locker
|
||||
match_max 100000
|
||||
sleep 1
|
||||
expect "*
|
||||
\r
|
||||
Currently registered locker numbers: \\\[\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "1\r"
|
||||
expect "Choose a password: \r"
|
||||
send -- "foo\r"
|
||||
expect "Set a secret message: \r"
|
||||
send -- "bar\r"
|
||||
expect "*
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Choose a locker number: \r"
|
||||
send -- "1\r"
|
||||
expect "Password: \r"
|
||||
send -- "foo\r"
|
||||
expect "*Error: Could not find locker number\r
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Choose a locker number: \r"
|
||||
send -- "0\r"
|
||||
expect "Password: \r"
|
||||
send -- "baz\r"
|
||||
expect "*Error encountered, could not open locker: Incorrect password, please try again.\r
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Choose a locker number: \r"
|
||||
send -- "0\r"
|
||||
expect "Password: \r"
|
||||
send -- "foo\r"
|
||||
expect "*
|
||||
\r
|
||||
*Success! Contents: bar\r
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- ""
|
||||
expect eof
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
#!/bin/expect -f
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
set timeout 1
|
||||
spawn cargo run --example simple_login
|
||||
match_max 100000
|
||||
sleep 1
|
||||
expect "*
|
||||
\r
|
||||
Currently registered usernames: \\\[\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "1\r"
|
||||
expect "Username: \r"
|
||||
send -- "foo\r"
|
||||
expect "Password: \r"
|
||||
send -- "bar\r"
|
||||
expect "*
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Username: \r"
|
||||
send -- "baz\r"
|
||||
expect "Password: \r"
|
||||
send -- "quux\r"
|
||||
expect "*Error: Could not find username registered\r
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Username: \r"
|
||||
send -- "foo\r"
|
||||
expect "Password: \r"
|
||||
send -- "baz\r"
|
||||
expect "*Incorrect password, please try again.\r
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Username: \r"
|
||||
send -- "foo\r"
|
||||
expect "Password: \r"
|
||||
send -- "bar\r"
|
||||
expect "*
|
||||
\r
|
||||
*Login success!\r
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- ""
|
||||
expect eof
|
||||
+9
-16
@@ -6,22 +6,18 @@
|
||||
//! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE
|
||||
|
||||
use crate::{
|
||||
errors::InternalPakeError,
|
||||
hash::Hash,
|
||||
key_exchange::traits::KeyExchange,
|
||||
keypair::{Key, KeyPair},
|
||||
map_to_curve::GroupWithMapToCurve,
|
||||
slow_hash::SlowHash,
|
||||
hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
|
||||
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
|
||||
};
|
||||
use digest::Digest;
|
||||
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
/// Configures the underlying primitives used in OPAQUE
|
||||
/// * `Group`: a finite cyclic group along with a point representation, along
|
||||
/// with an extension trait PasswordToCurve that allows some customization on
|
||||
/// how to hash a password to a curve point. See `group::Group` and
|
||||
/// `map_to_curve::GroupWithMapToCurve`.
|
||||
/// * `KeyFormat`: a keypair type composed of public and private components
|
||||
/// * `KeyExchange`: The key exchange protocol to use in the login step
|
||||
/// * `Hash`: The main hashing function to use
|
||||
/// * `SlowHash`: A slow hashing function, typically used for password hashing
|
||||
@@ -30,19 +26,16 @@ pub trait CipherSuite {
|
||||
/// an extension trait PasswordToCurve that allows some customization on
|
||||
/// how to hash a password to a curve point. See `group::Group` and
|
||||
/// `map_to_curve::GroupWithMapToCurve`.
|
||||
type Group: GroupWithMapToCurve;
|
||||
/// A keypair type composed of public and private components
|
||||
type KeyFormat: KeyPair<Repr = Key> + PartialEq;
|
||||
type Group: GroupWithMapToCurve<UniformBytesLen = <Self::Hash as Digest>::OutputSize>;
|
||||
/// A key exchange protocol
|
||||
type KeyExchange: KeyExchange<Self::Hash>;
|
||||
type KeyExchange: KeyExchange<Self::Hash, Self::Group>;
|
||||
/// The main hash function use (for HKDF computations and hashing transcripts)
|
||||
type Hash: Hash;
|
||||
/// A slow hashing function, typically used for password hashing
|
||||
type SlowHash: SlowHash<Self::Hash>;
|
||||
|
||||
/// Generating a random key pair given a cryptographic rng
|
||||
fn generate_random_keypair<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<Self::KeyFormat, InternalPakeError> {
|
||||
Self::KeyFormat::generate_random(rng)
|
||||
fn generate_random_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> KeyPair<Self::Group> {
|
||||
KeyPair::<Self::Group>::generate_random(rng)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,353 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
#![allow(clippy::let_and_return)]
|
||||
|
||||
//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit
|
||||
//! limbs with \\(128\\)-bit products.
|
||||
|
||||
use core::fmt::Debug;
|
||||
use core::ops::Neg;
|
||||
use core::ops::{Add, AddAssign};
|
||||
use core::ops::{Mul, MulAssign};
|
||||
|
||||
use subtle::Choice;
|
||||
use subtle::ConditionallyNegatable;
|
||||
use subtle::ConditionallySelectable;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use fiat_crypto::curve25519_64::*;
|
||||
|
||||
/// A `FieldElement51` represents an element of the field
|
||||
/// \\( \mathbb Z / (2\^{255} - 19)\\).
|
||||
///
|
||||
/// In the 64-bit implementation, a `FieldElement` is represented in
|
||||
/// radix \\(2\^{51}\\) as five `u64`s; the coefficients are allowed to
|
||||
/// grow up to \\(2\^{54}\\) between reductions modulo \\(p\\).
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The `curve25519_dalek::field` module provides a type alias
|
||||
/// `curve25519_dalek::field::FieldElement` to either `FieldElement51`
|
||||
/// or `FieldElement2625`.
|
||||
///
|
||||
/// The backend-specific type `FieldElement51` should not be used
|
||||
/// outside of the `curve25519_dalek::field` module.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct FieldElement51(pub(crate) [u64; 5]);
|
||||
|
||||
impl Debug for FieldElement51 {
|
||||
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
|
||||
write!(f, "FieldElement51({:?})", &self.0[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for FieldElement51 {
|
||||
fn zeroize(&mut self) {
|
||||
self.0.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for FieldElement51 {
|
||||
/// Test equality between two `FieldElement`s. Since the
|
||||
/// internal representation is not canonical, the field elements
|
||||
/// are normalized to wire format before comparison.
|
||||
fn ct_eq(&self, other: &FieldElement51) -> Choice {
|
||||
self.to_bytes().ct_eq(&other.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 {
|
||||
fn add_assign(&mut self, _rhs: &'b FieldElement51) {
|
||||
let input = self.0;
|
||||
fiat_25519_add(&mut self.0, &input, &_rhs.0);
|
||||
let input = self.0;
|
||||
fiat_25519_carry(&mut self.0, &input);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 {
|
||||
type Output = FieldElement51;
|
||||
fn add(self, _rhs: &'b FieldElement51) -> FieldElement51 {
|
||||
let mut output = *self;
|
||||
fiat_25519_add(&mut output.0, &self.0, &_rhs.0);
|
||||
let input = output.0;
|
||||
fiat_25519_carry(&mut output.0, &input);
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 {
|
||||
fn mul_assign(&mut self, _rhs: &'b FieldElement51) {
|
||||
let input = self.0;
|
||||
fiat_25519_carry_mul(&mut self.0, &input, &_rhs.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 {
|
||||
type Output = FieldElement51;
|
||||
fn mul(self, _rhs: &'b FieldElement51) -> FieldElement51 {
|
||||
let mut output = *self;
|
||||
fiat_25519_carry_mul(&mut output.0, &self.0, &_rhs.0);
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Neg for &'a FieldElement51 {
|
||||
type Output = FieldElement51;
|
||||
fn neg(self) -> FieldElement51 {
|
||||
let mut output = *self;
|
||||
fiat_25519_opp(&mut output.0, &self.0);
|
||||
let input = output.0;
|
||||
fiat_25519_carry(&mut output.0, &input);
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl ConditionallySelectable for FieldElement51 {
|
||||
fn conditional_select(
|
||||
a: &FieldElement51,
|
||||
b: &FieldElement51,
|
||||
choice: Choice,
|
||||
) -> FieldElement51 {
|
||||
let mut output = [0u64; 5];
|
||||
fiat_25519_selectznz(&mut output, choice.unwrap_u8() as fiat_25519_u1, &a.0, &b.0);
|
||||
FieldElement51(output)
|
||||
}
|
||||
|
||||
fn conditional_swap(a: &mut FieldElement51, b: &mut FieldElement51, choice: Choice) {
|
||||
u64::conditional_swap(&mut a.0[0], &mut b.0[0], choice);
|
||||
u64::conditional_swap(&mut a.0[1], &mut b.0[1], choice);
|
||||
u64::conditional_swap(&mut a.0[2], &mut b.0[2], choice);
|
||||
u64::conditional_swap(&mut a.0[3], &mut b.0[3], choice);
|
||||
u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice);
|
||||
}
|
||||
|
||||
fn conditional_assign(&mut self, _rhs: &FieldElement51, choice: Choice) {
|
||||
let mut output = [0u64; 5];
|
||||
let choicebit = choice.unwrap_u8() as fiat_25519_u1;
|
||||
fiat_25519_cmovznz_u64(&mut output[0], choicebit, self.0[0], _rhs.0[0]);
|
||||
fiat_25519_cmovznz_u64(&mut output[1], choicebit, self.0[1], _rhs.0[1]);
|
||||
fiat_25519_cmovznz_u64(&mut output[2], choicebit, self.0[2], _rhs.0[2]);
|
||||
fiat_25519_cmovznz_u64(&mut output[3], choicebit, self.0[3], _rhs.0[3]);
|
||||
fiat_25519_cmovznz_u64(&mut output[4], choicebit, self.0[4], _rhs.0[4]);
|
||||
*self = FieldElement51(output);
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldElement51 {
|
||||
/// Construct zero.
|
||||
pub fn zero() -> FieldElement51 {
|
||||
FieldElement51([0, 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
/// Construct one.
|
||||
pub fn one() -> FieldElement51 {
|
||||
FieldElement51([1, 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
pub fn is_negative(&self) -> Choice {
|
||||
let bytes = self.to_bytes();
|
||||
(bytes[0] & 1).into()
|
||||
}
|
||||
|
||||
/// Raise this field element to the power (p-5)/8 = 2^252 -3.
|
||||
fn pow_p58(&self) -> FieldElement51 {
|
||||
// The bits of (p-5)/8 are 101111.....11.
|
||||
//
|
||||
// nonzero bits of exponent
|
||||
let (t19, _) = self.pow22501(); // 249..0
|
||||
let t20 = t19.pow2k(2); // 251..2
|
||||
let t21 = self * &t20; // 251..2,0
|
||||
|
||||
t21
|
||||
}
|
||||
|
||||
/// Given a nonzero field element, compute its inverse.
|
||||
///
|
||||
/// The inverse is computed as self^(p-2), since
|
||||
/// x^(p-2)x = x^(p-1) = 1 (mod p).
|
||||
///
|
||||
/// This function returns zero on input zero.
|
||||
pub fn invert(&self) -> FieldElement51 {
|
||||
// The bits of p-2 = 2^255 -19 -2 are 11010111111...11.
|
||||
//
|
||||
// nonzero bits of exponent
|
||||
let (t19, t3) = self.pow22501(); // t19: 249..0 ; t3: 3,1,0
|
||||
let t20 = t19.pow2k(5); // 254..5
|
||||
let t21 = &t20 * &t3; // 254..5,3,1,0
|
||||
|
||||
t21
|
||||
}
|
||||
|
||||
/// Compute (self^(2^250-1), self^11), used as a helper function
|
||||
/// within invert() and pow22523().
|
||||
fn pow22501(&self) -> (FieldElement51, FieldElement51) {
|
||||
// Instead of managing which temporary variables are used
|
||||
// for what, we define as many as we need and leave stack
|
||||
// allocation to the compiler
|
||||
//
|
||||
// Each temporary variable t_i is of the form (self)^e_i.
|
||||
// Squaring t_i corresponds to multiplying e_i by 2,
|
||||
// so the pow2k function shifts e_i left by k places.
|
||||
// Multiplying t_i and t_j corresponds to adding e_i + e_j.
|
||||
//
|
||||
// Temporary t_i Nonzero bits of e_i
|
||||
//
|
||||
let t0 = self.square(); // 1 e_0 = 2^1
|
||||
let t1 = t0.square().square(); // 3 e_1 = 2^3
|
||||
let t2 = self * &t1; // 3,0 e_2 = 2^3 + 2^0
|
||||
let t3 = &t0 * &t2; // 3,1,0
|
||||
let t4 = t3.square(); // 4,2,1
|
||||
let t5 = &t2 * &t4; // 4,3,2,1,0
|
||||
let t6 = t5.pow2k(5); // 9,8,7,6,5
|
||||
let t7 = &t6 * &t5; // 9,8,7,6,5,4,3,2,1,0
|
||||
let t8 = t7.pow2k(10); // 19..10
|
||||
let t9 = &t8 * &t7; // 19..0
|
||||
let t10 = t9.pow2k(20); // 39..20
|
||||
let t11 = &t10 * &t9; // 39..0
|
||||
let t12 = t11.pow2k(10); // 49..10
|
||||
let t13 = &t12 * &t7; // 49..0
|
||||
let t14 = t13.pow2k(50); // 99..50
|
||||
let t15 = &t14 * &t13; // 99..0
|
||||
let t16 = t15.pow2k(100); // 199..100
|
||||
let t17 = &t16 * &t15; // 199..0
|
||||
let t18 = t17.pow2k(50); // 249..50
|
||||
let t19 = &t18 * &t13; // 249..0
|
||||
|
||||
(t19, t3)
|
||||
}
|
||||
|
||||
/// Load a `FieldElement51` from the low 255 bits of a 256-bit
|
||||
/// input.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This function does not check that the input used the canonical
|
||||
/// representative. It masks the high bit, but it will happily
|
||||
/// decode 2^255 - 18 to 1. Applications that require a canonical
|
||||
/// encoding of every field element should decode, re-encode to
|
||||
/// the canonical encoding, and check that the input was
|
||||
/// canonical.
|
||||
///
|
||||
pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
|
||||
let mut temp = [0u8; 32];
|
||||
temp.copy_from_slice(bytes);
|
||||
temp[31] &= 127u8;
|
||||
let mut output = [0u64; 5];
|
||||
fiat_25519_from_bytes(&mut output, &temp);
|
||||
FieldElement51(output)
|
||||
}
|
||||
|
||||
/// Serialize this `FieldElement51` to a 32-byte array. The
|
||||
/// encoding is canonical.
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
let mut bytes = [0u8; 32];
|
||||
fiat_25519_to_bytes(&mut bytes, &self.0);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Given `k > 0`, return `self^(2^k)`.
|
||||
pub fn pow2k(&self, mut k: u32) -> FieldElement51 {
|
||||
let mut output = *self;
|
||||
loop {
|
||||
let input = output.0;
|
||||
fiat_25519_carry_square(&mut output.0, &input);
|
||||
k -= 1;
|
||||
if k == 0 {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given `FieldElements` `u` and `v`, compute either `sqrt(u/v)`
|
||||
/// or `sqrt(i*u/v)` in constant time.
|
||||
///
|
||||
/// This function always returns the nonnegative square root.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// - `(Choice(1), +sqrt(u/v)) ` if `v` is nonzero and `u/v` is square;
|
||||
/// - `(Choice(1), zero) ` if `u` is zero;
|
||||
/// - `(Choice(0), zero) ` if `v` is zero and `u` is nonzero;
|
||||
/// - `(Choice(0), +sqrt(i*u/v))` if `u/v` is nonsquare (so `i*u/v` is square).
|
||||
///
|
||||
pub fn sqrt_ratio_i(u: &FieldElement51, v: &FieldElement51) -> (Choice, FieldElement51) {
|
||||
// Using the same trick as in ed25519 decoding, we merge the
|
||||
// inversion, the square root, and the square test as follows.
|
||||
//
|
||||
// To compute sqrt(α), we can compute β = α^((p+3)/8).
|
||||
// Then β^2 = ±α, so multiplying β by sqrt(-1) if necessary
|
||||
// gives sqrt(α).
|
||||
//
|
||||
// To compute 1/sqrt(α), we observe that
|
||||
// 1/β = α^(p-1 - (p+3)/8) = α^((7p-11)/8)
|
||||
// = α^3 * (α^7)^((p-5)/8).
|
||||
//
|
||||
// We can therefore compute sqrt(u/v) = sqrt(u)/sqrt(v)
|
||||
// by first computing
|
||||
// r = u^((p+3)/8) v^(p-1-(p+3)/8)
|
||||
// = u u^((p-5)/8) v^3 (v^7)^((p-5)/8)
|
||||
// = (uv^3) (uv^7)^((p-5)/8).
|
||||
//
|
||||
// If v is nonzero and u/v is square, then r^2 = ±u/v,
|
||||
// so vr^2 = ±u.
|
||||
// If vr^2 = u, then sqrt(u/v) = r.
|
||||
// If vr^2 = -u, then sqrt(u/v) = r*sqrt(-1).
|
||||
//
|
||||
// If v is zero, r is also zero.
|
||||
|
||||
let v3 = &v.square() * v;
|
||||
let v7 = &v3.square() * v;
|
||||
let mut r = &(u * &v3) * &(u * &v7).pow_p58();
|
||||
let check = v * &r.square();
|
||||
|
||||
let i = &SQRT_M1;
|
||||
|
||||
let correct_sign_sqrt = check.ct_eq(u);
|
||||
let flipped_sign_sqrt = check.ct_eq(&(-u));
|
||||
let flipped_sign_sqrt_i = check.ct_eq(&(&(-u) * i));
|
||||
|
||||
let r_prime = &SQRT_M1 * &r;
|
||||
r.conditional_assign(&r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i);
|
||||
|
||||
// Choose the nonnegative square root.
|
||||
let r_is_negative = r.is_negative();
|
||||
r.conditional_negate(r_is_negative);
|
||||
|
||||
let was_nonzero_square = correct_sign_sqrt | flipped_sign_sqrt;
|
||||
|
||||
(was_nonzero_square, r)
|
||||
}
|
||||
|
||||
/// Returns the square of this field element.
|
||||
pub fn square(&self) -> FieldElement51 {
|
||||
let mut output = *self;
|
||||
fiat_25519_carry_square(&mut output.0, &self.0);
|
||||
output
|
||||
}
|
||||
|
||||
/// Returns 2 times the square of this field element.
|
||||
pub fn square2(&self) -> FieldElement51 {
|
||||
let mut output = *self;
|
||||
let mut temp = *self;
|
||||
// Void vs return type, measure cost of copying self
|
||||
fiat_25519_carry_square(&mut temp.0, &self.0);
|
||||
fiat_25519_add(&mut output.0, &temp.0, &temp.0);
|
||||
let input = output.0;
|
||||
fiat_25519_carry(&mut output.0, &input);
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// Precomputed value of one of the square roots of -1 (mod p)
|
||||
pub(crate) const SQRT_M1: FieldElement51 = FieldElement51([
|
||||
1718705420411056,
|
||||
234908883556509,
|
||||
2233514472574048,
|
||||
2117202627021982,
|
||||
765476049583133,
|
||||
]);
|
||||
@@ -1,176 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
mod field;
|
||||
|
||||
use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint};
|
||||
use field::FieldElement51;
|
||||
use sha2::Digest;
|
||||
use subtle::{ConditionallyNegatable, ConditionallySelectable};
|
||||
|
||||
const MONT_A: FieldElement51 = FieldElement51([486662, 0, 0, 0, 0]);
|
||||
|
||||
fn elligator_signal(r_0: &FieldElement51) -> MontgomeryPoint {
|
||||
let minus_a = -&MONT_A; /* A = 486662 */
|
||||
let one = FieldElement51::one();
|
||||
let d_1 = &one + &r_0.square2(); /* 2r^2 */
|
||||
|
||||
let d = &minus_a * &(d_1.invert()); /* A/(1+2r^2) */
|
||||
|
||||
let d_sq = &d.square();
|
||||
let au = &MONT_A * &d;
|
||||
|
||||
let inner = &(d_sq + &au) + &one;
|
||||
let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */
|
||||
|
||||
let (eps_is_sq, _eps) = FieldElement51::sqrt_ratio_i(&eps, &one);
|
||||
|
||||
let zero = FieldElement51::zero();
|
||||
let Atemp = FieldElement51::conditional_select(&MONT_A, &zero, eps_is_sq); /* 0, or A if nonsquare*/
|
||||
let mut u = &d + &Atemp; /* d, or d+A if nonsquare */
|
||||
u.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */
|
||||
|
||||
MontgomeryPoint(u.to_bytes())
|
||||
}
|
||||
|
||||
pub fn hash_to_point(bytes: &[u8]) -> EdwardsPoint {
|
||||
let mut hash = sha2::Sha512::new();
|
||||
hash.update(bytes);
|
||||
let h = hash.finalize();
|
||||
let mut res = [0u8; 32];
|
||||
res.copy_from_slice(&h[..32]);
|
||||
|
||||
let sign_bit = (res[31] & 0x80) >> 7;
|
||||
|
||||
let fe = FieldElement51::from_bytes(&res);
|
||||
|
||||
let M1 = elligator_signal(&fe);
|
||||
let E1_opt = M1.to_edwards(sign_bit);
|
||||
|
||||
E1_opt
|
||||
.expect("Montgomery conversion to Edwards point in Elligator failed")
|
||||
.mul_by_cofactor()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Signal tests from //
|
||||
// https://github.com/signalapp/libsignal-protocol-c/blob/master/src/curve25519/ed25519/tests/internal_fast_tests.c#L222-L282 //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const ELLIGATOR_CORRECT_OUTPUT: [u8; 32] = [
|
||||
0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac,
|
||||
0x22, 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95,
|
||||
0xf4, 0x6e,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn elligator_correct() {
|
||||
let bytes: Vec<u8> = (0u8..32u8).collect();
|
||||
let mut bits_in = [0u8; 32];
|
||||
bits_in.copy_from_slice(&bytes);
|
||||
let fe = FieldElement51::from_bytes(&bits_in);
|
||||
let eg = elligator_signal(&fe);
|
||||
assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elligator_zero_zero() {
|
||||
let zero = [0u8; 32];
|
||||
let fe = FieldElement51::from_bytes(&zero);
|
||||
let eg = elligator_signal(&fe);
|
||||
assert_eq!(eg.to_bytes(), zero);
|
||||
}
|
||||
|
||||
const HASHTOPOINT_CORRECT_OUTPUT1: [u8; 32] = [
|
||||
0xce, 0x89, 0x9f, 0xb2, 0x8f, 0xf7, 0x20, 0x91, 0x5e, 0x14, 0xf5, 0xb7, 0x99, 0x08, 0xab,
|
||||
0x17, 0xaa, 0x2e, 0xe2, 0x45, 0xb4, 0xfc, 0x2b, 0xf6, 0x06, 0x36, 0x29, 0x40, 0xed, 0x7d,
|
||||
0xe7, 0xed,
|
||||
];
|
||||
|
||||
const HASHTOPOINT_CORRECT_OUTPUT2: [u8; 32] = [
|
||||
0xa0, 0x35, 0xbb, 0xa9, 0x4d, 0x30, 0x55, 0x33, 0x0d, 0xce, 0xc2, 0x7f, 0x83, 0xde, 0x79,
|
||||
0xd0, 0x89, 0x67, 0x72, 0x4c, 0x07, 0x8d, 0x68, 0x9d, 0x61, 0x52, 0x1d, 0xf9, 0x2c, 0x5c,
|
||||
0xba, 0x77,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_point_1() {
|
||||
let bits: Vec<u8> = (0u8..32u8).collect();
|
||||
let hashed = hash_to_point(&bits);
|
||||
assert_eq!(hashed.compress().to_bytes(), HASHTOPOINT_CORRECT_OUTPUT1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_point_2() {
|
||||
let bits: Vec<u8> = (0u8..32u8).map(|u| u + 1).collect();
|
||||
let hashed = hash_to_point(&bits);
|
||||
assert_eq!(hashed.compress().to_bytes(), HASHTOPOINT_CORRECT_OUTPUT2);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Additional test vectors from Signal //
|
||||
/////////////////////////////////////////
|
||||
|
||||
fn test_vectors() -> Vec<Vec<&'static str>> {
|
||||
vec![
|
||||
vec![
|
||||
"214f306e1576f5a7577636fe303ca2c625b533319f52442b22a9fa3b7ede809f",
|
||||
"c95becf0f93595174633b9d4d6bbbeb88e16fa257176f877ce426e1424626052",
|
||||
],
|
||||
vec![
|
||||
"2eb10d432702ea7f79207da95d206f82d5a3b374f5f89f17a199531f78d3bea6",
|
||||
"d8f8b508edffbb8b6dab0f602f86a9dd759f800fe18f782fdcac47c234883e7f",
|
||||
],
|
||||
vec![
|
||||
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
|
||||
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
|
||||
],
|
||||
vec![
|
||||
"c85165952490dc1839cb69012a3d9f2cc4b02343613263ab93a26dc89fd58267",
|
||||
"43cbe8685fd3c90665b91835debb89ff1477f906f5170f38a192f6a199556537",
|
||||
],
|
||||
vec![
|
||||
"26e7fc4a78d863b1a4ccb2ce0951fbcd021e106350730ee4157bacb4502e1b76",
|
||||
"b6fc3d738c2c40719479b2f23818180cdafa72a14254d4016bbed8f0b788a835",
|
||||
],
|
||||
vec![
|
||||
"1618c08ef0233f94f0f163f9435ec7457cd7a8cd4bb6b160315d15818c30f7a2",
|
||||
"da0b703593b29dbcd28ebd6e7baea17b6f61971f3641cae774f6a5137a12294c",
|
||||
],
|
||||
vec![
|
||||
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
|
||||
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
|
||||
],
|
||||
vec![
|
||||
"a744d582b3a34d14d311b7629da06d003045ae77cebceeb4e0e72734d63bd07d",
|
||||
"fad25a5ea15d4541258af8785acaf697a886c1b872c793790e60a6837b1adbc0",
|
||||
],
|
||||
vec![
|
||||
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
|
||||
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
|
||||
],
|
||||
vec![
|
||||
"f06fc939bc10551a0fd415aebf107ef0b9c4ee1ef9a164157bdd089127782617",
|
||||
"785b2a6a00a5579cc9da1ff997ce8339b6f9fb46c6f10cf7a12ff2986341a6e0",
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn additional_signal_test_vectors() {
|
||||
for vector in test_vectors().iter() {
|
||||
let input = hex::decode(vector[0]).unwrap();
|
||||
let output = hex::decode(vector[1]).unwrap();
|
||||
|
||||
let point = hash_to_point(&input);
|
||||
assert_eq!(point.compress().to_bytes(), output[..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+223
-76
@@ -3,26 +3,83 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::hash::Hash;
|
||||
use digest::Digest;
|
||||
use generic_array::{
|
||||
typenum::{Unsigned, U32},
|
||||
GenericArray,
|
||||
use crate::{
|
||||
errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
|
||||
hash::Hash,
|
||||
keypair::Key,
|
||||
serialization::serialize,
|
||||
};
|
||||
use digest::Digest;
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use generic_bytes::SizedBytes;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// Constant string used as salt for HKDF computation
|
||||
const STR_ENVU: &[u8] = b"EnvU";
|
||||
|
||||
/// The length of the "export key" output by the client registration
|
||||
/// and login finish steps
|
||||
pub(crate) type ExportKeySize = U32;
|
||||
const STR_PAD: &[u8] = b"Pad";
|
||||
const STR_AUTH_KEY: &[u8] = b"AuthKey";
|
||||
const STR_EXPORT_KEY: &[u8] = b"ExportKey";
|
||||
|
||||
const NONCE_LEN: usize = 32;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum InnerEnvelopeMode {
|
||||
Base = 1,
|
||||
CustomIdentifier = 2,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for InnerEnvelopeMode {
|
||||
type Error = PakeError;
|
||||
fn try_from(x: u8) -> Result<Self, Self::Error> {
|
||||
match x {
|
||||
1 => Ok(InnerEnvelopeMode::Base),
|
||||
2 => Ok(InnerEnvelopeMode::CustomIdentifier),
|
||||
_ => Err(PakeError::SerializationError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InnerEnvelope {
|
||||
mode: InnerEnvelopeMode,
|
||||
nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
}
|
||||
|
||||
impl InnerEnvelope {
|
||||
pub(crate) fn serialize(&self) -> Vec<u8> {
|
||||
[&[self.mode as u8], &self.nonce[..], &self.ciphertext[..]].concat()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
|
||||
if input.is_empty() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::SerializationError,
|
||||
));
|
||||
}
|
||||
let mode = InnerEnvelopeMode::try_from(input[0])?;
|
||||
|
||||
let key_len = <Key as SizedBytes>::Len::to_usize();
|
||||
|
||||
let bytes = &input[1..];
|
||||
if bytes.len() < NONCE_LEN + key_len {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::SerializationError,
|
||||
));
|
||||
}
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
mode,
|
||||
nonce: bytes[..NONCE_LEN].to_vec(),
|
||||
ciphertext: bytes[NONCE_LEN..NONCE_LEN + key_len].to_vec(),
|
||||
},
|
||||
bytes[NONCE_LEN + key_len..].to_vec(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is an instantiation of the envelope as described in
|
||||
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
|
||||
///
|
||||
@@ -34,82 +91,106 @@ const NONCE_LEN: usize = 32;
|
||||
/// an XOR-based approach without compromising on security, and to avoid
|
||||
/// the confusion around the implementation of an RKR-secure encryption.
|
||||
pub(crate) struct Envelope<D: Hash> {
|
||||
nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
inner_envelope: InnerEnvelope,
|
||||
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
}
|
||||
|
||||
pub(crate) struct OpenedEnvelope {
|
||||
// Note that this struct represents an envelope that has been "opened" with the asssociated
|
||||
// key. This key is also used to derive the export_key parameter, which is technically
|
||||
// unrelated to the envelope's encrypted and authenticated contents.
|
||||
pub(crate) struct OpenedEnvelope<D: Hash> {
|
||||
pub(crate) client_s_sk: Vec<u8>,
|
||||
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
}
|
||||
|
||||
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
|
||||
pub(crate) plaintext: Vec<u8>,
|
||||
pub(crate) export_key: GenericArray<u8, ExportKeySize>,
|
||||
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
}
|
||||
|
||||
impl<D: Hash> Envelope<D> {
|
||||
/// The additional number of bytes added to the plaintext
|
||||
pub(crate) fn additional_size() -> usize {
|
||||
NONCE_LEN + <D as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
fn hmac_key_size() -> usize {
|
||||
<D as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
fn hmac_size() -> usize {
|
||||
fn export_key_size() -> usize {
|
||||
<D as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
fn export_key_size() -> usize {
|
||||
ExportKeySize::to_usize()
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
nonce,
|
||||
ciphertext,
|
||||
hmac,
|
||||
}
|
||||
pub(crate) fn get_mode(&self) -> InnerEnvelopeMode {
|
||||
self.inner_envelope.mode
|
||||
}
|
||||
|
||||
/// The format of the output is:
|
||||
/// nonce | ciphertext | hmac
|
||||
/// nonce_size bytes | variable length | hmac_size bytes
|
||||
/// mode | nonce | ciphertext | hmac
|
||||
/// u8 | nonce_size bytes | variable length | hmac_size bytes
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let ciphertext_start = NONCE_LEN;
|
||||
let ciphertext_end = bytes.len() - Self::hmac_size();
|
||||
|
||||
Ok(Self::new(
|
||||
bytes[..ciphertext_start].to_vec(),
|
||||
bytes[ciphertext_start..ciphertext_end].to_vec(),
|
||||
GenericArray::clone_from_slice(&bytes[ciphertext_end..]),
|
||||
))
|
||||
let (result, remainder) = Self::deserialize(bytes)
|
||||
.map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?;
|
||||
if !remainder.is_empty() {
|
||||
return Err(InternalPakeError::InvalidEnvelopeStructureError);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.nonce[..], &self.ciphertext[..], &self.hmac[..]].concat()
|
||||
self.serialize()
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self) -> Vec<u8> {
|
||||
[&self.inner_envelope.serialize(), &self.hmac[..]].concat()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
|
||||
let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?;
|
||||
|
||||
let hmac_key_size = Self::hmac_key_size();
|
||||
let hmac_and_remainder =
|
||||
check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
inner_envelope,
|
||||
hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]),
|
||||
},
|
||||
hmac_and_remainder[hmac_key_size..].to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn seal<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
key: &[u8],
|
||||
client_s_sk: &[u8],
|
||||
server_s_pk: &[u8],
|
||||
optional_ids: Option<(Vec<u8>, Vec<u8>)>,
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
|
||||
let aad = construct_aad(server_s_pk, &optional_ids);
|
||||
Self::seal_raw(rng, key, &client_s_sk, &aad, mode_from_ids(&optional_ids))
|
||||
}
|
||||
|
||||
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
|
||||
/// Note that a new nonce is sampled for each call to seal.
|
||||
pub(crate) fn seal<R: RngCore + CryptoRng>(
|
||||
pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
key: &[u8],
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
|
||||
mode: InnerEnvelopeMode,
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
|
||||
let mut nonce = vec![0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
let h = Hkdf::<D>::new(Some(&nonce), &key);
|
||||
let mut okm = vec![0u8; plaintext.len() + Self::hmac_key_size() + Self::export_key_size()];
|
||||
h.expand(STR_ENVU, &mut okm)
|
||||
let mut xor_key = vec![0u8; plaintext.len()];
|
||||
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
|
||||
let mut export_key = vec![0u8; Self::export_key_size()];
|
||||
|
||||
h.expand(STR_PAD, &mut xor_key)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
h.expand(STR_AUTH_KEY, &mut hmac_key)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
h.expand(STR_EXPORT_KEY, &mut export_key)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let xor_key = &okm[..plaintext.len()];
|
||||
let hmac_key = &okm[plaintext.len()..plaintext.len() + Self::hmac_key_size()];
|
||||
let export_key = &okm[plaintext.len() + Self::hmac_key_size()..];
|
||||
|
||||
let ciphertext: Vec<u8> = xor_key
|
||||
.iter()
|
||||
@@ -117,34 +198,75 @@ impl<D: Hash> Envelope<D> {
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
|
||||
let inner_envelope = InnerEnvelope {
|
||||
mode,
|
||||
nonce,
|
||||
ciphertext,
|
||||
};
|
||||
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
hmac.update(&nonce);
|
||||
hmac.update(&ciphertext);
|
||||
hmac.update(&inner_envelope.serialize());
|
||||
hmac.update(&aad);
|
||||
|
||||
let hmac_bytes = hmac.finalize().into_bytes();
|
||||
|
||||
Ok((
|
||||
Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()),
|
||||
*GenericArray::from_slice(&export_key),
|
||||
Self {
|
||||
inner_envelope,
|
||||
hmac: hmac_bytes,
|
||||
},
|
||||
GenericArray::clone_from_slice(&export_key),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn open(
|
||||
&self,
|
||||
key: &[u8],
|
||||
server_s_pk: &[u8],
|
||||
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
|
||||
) -> Result<OpenedEnvelope<D>, InternalPakeError> {
|
||||
// First, check that mode matches
|
||||
if self.inner_envelope.mode != mode_from_ids(optional_ids) {
|
||||
return Err(InternalPakeError::IncompatibleEnvelopeModeError);
|
||||
}
|
||||
|
||||
let aad = construct_aad(server_s_pk, optional_ids);
|
||||
let opened = self.open_raw(key, &aad)?;
|
||||
|
||||
if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
|
||||
// Plaintext should consist of a single key
|
||||
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
|
||||
}
|
||||
|
||||
Ok(OpenedEnvelope {
|
||||
client_s_sk: opened.plaintext,
|
||||
export_key: opened.export_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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(&self, key: &[u8], aad: &[u8]) -> Result<OpenedEnvelope, InternalPakeError> {
|
||||
let h = Hkdf::<D>::new(Some(&self.nonce), &key);
|
||||
let mut okm =
|
||||
vec![0u8; self.ciphertext.len() + Self::hmac_key_size() + Self::export_key_size()];
|
||||
h.expand(STR_ENVU, &mut okm)
|
||||
pub(crate) fn open_raw(
|
||||
&self,
|
||||
key: &[u8],
|
||||
aad: &[u8],
|
||||
) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> {
|
||||
let h = Hkdf::<D>::new(Some(&self.inner_envelope.nonce), &key);
|
||||
let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
|
||||
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
|
||||
let mut export_key = vec![0u8; Self::export_key_size()];
|
||||
|
||||
h.expand(STR_PAD, &mut xor_key)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
h.expand(STR_AUTH_KEY, &mut hmac_key)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
h.expand(STR_EXPORT_KEY, &mut export_key)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let xor_key = &okm[..self.ciphertext.len()];
|
||||
let hmac_key = &okm[self.ciphertext.len()..self.ciphertext.len() + Self::hmac_key_size()];
|
||||
let export_key = &okm[self.ciphertext.len() + Self::hmac_key_size()..];
|
||||
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
hmac.update(&self.nonce);
|
||||
hmac.update(&self.ciphertext);
|
||||
hmac.update(&self.inner_envelope.serialize());
|
||||
hmac.update(aad);
|
||||
if hmac.verify(&self.hmac).is_err() {
|
||||
return Err(InternalPakeError::SealOpenHmacError);
|
||||
@@ -152,20 +274,39 @@ impl<D: Hash> Envelope<D> {
|
||||
|
||||
let plaintext: Vec<u8> = xor_key
|
||||
.iter()
|
||||
.zip(self.ciphertext.iter())
|
||||
.zip(self.inner_envelope.ciphertext.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
Ok(OpenedEnvelope {
|
||||
Ok(OpenedInnerEnvelope {
|
||||
plaintext,
|
||||
export_key: *GenericArray::from_slice(&export_key),
|
||||
export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
|
||||
&export_key,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn construct_aad(server_s_pk: &[u8], optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> {
|
||||
let ids = optional_ids
|
||||
.iter()
|
||||
.flat_map(|(l, r)| [serialize(l, 2), serialize(r, 2)].concat())
|
||||
.collect();
|
||||
[server_s_pk.to_vec(), ids].concat()
|
||||
}
|
||||
|
||||
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
|
||||
match optional_ids {
|
||||
Some(_) => InnerEnvelopeMode::CustomIdentifier,
|
||||
None => InnerEnvelopeMode::Base,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand_core::OsRng;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[test]
|
||||
fn seal_and_open() {
|
||||
@@ -176,9 +317,15 @@ mod tests {
|
||||
let mut msg = [0u8; 100];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let (envelope, export_key_1) =
|
||||
Envelope::<sha2::Sha256>::seal(&key, &msg, b"aad", &mut rng).unwrap();
|
||||
let opened_envelope = envelope.open(&key, b"aad").unwrap();
|
||||
let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
|
||||
&mut rng,
|
||||
&key,
|
||||
&msg,
|
||||
b"aad",
|
||||
InnerEnvelopeMode::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
|
||||
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
|
||||
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use thiserror::Error;
|
||||
/// Represents an error in the manipulation of internal cryptographic data
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum InternalPakeError {
|
||||
/// Deserializing from a byte sequence failed
|
||||
InvalidByteSequence,
|
||||
/// Invalid length for {name}: expected {len}, but is actually {actual_len}.
|
||||
SizeError {
|
||||
/// name
|
||||
@@ -25,6 +27,8 @@ pub enum InternalPakeError {
|
||||
SubGroupError,
|
||||
/// hashing to a key failed
|
||||
HashingFailure,
|
||||
/// Computing the hash-to-curve function failed
|
||||
HashToCurveError,
|
||||
/// Computing HKDF failed while deriving subkeys
|
||||
HkdfError,
|
||||
/// Computing HMAC failed while supplying a secret key
|
||||
@@ -40,6 +44,15 @@ pub enum InternalPakeError {
|
||||
/// This error occurs when the envelope seal open hmac check fails
|
||||
/// HMAC check in seal open failed.
|
||||
SealOpenHmacError,
|
||||
/// This error occurs when the envelope cannot be constructed properly
|
||||
/// based on the credentials that were specified to be required.
|
||||
InvalidEnvelopeStructureError,
|
||||
/// This error occurs when attempting to open an envelope of the wrong
|
||||
/// type (base mode, custom identifier)
|
||||
IncompatibleEnvelopeModeError,
|
||||
/// This error occurs when the envelope is opened and deserialization
|
||||
/// fails
|
||||
UnexpectedEnvelopeContentsError,
|
||||
}
|
||||
|
||||
/// Represents an error in password checking
|
||||
@@ -58,6 +71,8 @@ pub enum PakeError {
|
||||
KeyExchangeMacValidationError,
|
||||
/// Error in validating credentials
|
||||
InvalidLoginError,
|
||||
/// Error with serializing / deserializing protocol messages
|
||||
SerializationError,
|
||||
}
|
||||
|
||||
// This is meant to express future(ly) non-trivial ways of converting the
|
||||
@@ -78,6 +93,9 @@ pub enum ProtocolError {
|
||||
/// This error occurs when the server answer cannot be handled
|
||||
/// Server response cannot be handled.
|
||||
ServerError,
|
||||
/// This error occurs when the server specifies an envelope credentials
|
||||
/// format that is invalid
|
||||
ServerInvalidEnvelopeCredentialsFormatError,
|
||||
/// This error occurs when the client request cannot be handled
|
||||
/// Client request cannot be handled.
|
||||
ClientError,
|
||||
@@ -108,6 +126,24 @@ impl From<::std::convert::Infallible> for ProtocolError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<generic_bytes::TryFromSizedBytesError> for InternalPakeError {
|
||||
fn from(_: generic_bytes::TryFromSizedBytesError) -> Self {
|
||||
InternalPakeError::InvalidByteSequence
|
||||
}
|
||||
}
|
||||
|
||||
impl From<generic_bytes::TryFromSizedBytesError> for PakeError {
|
||||
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
|
||||
PakeError::CryptoError(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<generic_bytes::TryFromSizedBytesError> for ProtocolError {
|
||||
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
|
||||
PakeError::CryptoError(e.into()).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod utils {
|
||||
use super::*;
|
||||
|
||||
@@ -125,4 +161,19 @@ pub(crate) mod utils {
|
||||
}
|
||||
Ok(slice)
|
||||
}
|
||||
|
||||
pub fn check_slice_size_atleast<'a>(
|
||||
slice: &'a [u8],
|
||||
expected_len: usize,
|
||||
arg_name: &'static str,
|
||||
) -> Result<&'a [u8], InternalPakeError> {
|
||||
if slice.len() < expected_len {
|
||||
return Err(InternalPakeError::SizeError {
|
||||
name: arg_name,
|
||||
len: expected_len,
|
||||
actual_len: slice.len(),
|
||||
});
|
||||
}
|
||||
Ok(slice)
|
||||
}
|
||||
}
|
||||
|
||||
+39
-112
@@ -6,11 +6,10 @@
|
||||
//! Defines the Group trait to specify the underlying prime order group used in
|
||||
//! OPAQUE's OPRF
|
||||
|
||||
use crate::elligator;
|
||||
use crate::errors::InternalPakeError;
|
||||
|
||||
use curve25519_dalek::{
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
constants::RISTRETTO_BASEPOINT_POINT,
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
@@ -18,18 +17,20 @@ use generic_array::{
|
||||
typenum::{U32, U64},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::ops::Mul;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
|
||||
/// subgroup is noted additively — as in the draft RFC — in this trait.
|
||||
pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
|
||||
pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
|
||||
/// The type of base field scalars
|
||||
type Scalar: Zeroize;
|
||||
type Scalar: Zeroize + Clone;
|
||||
/// The byte length necessary to represent scalars
|
||||
type ScalarLen: ArrayLength<u8>;
|
||||
/// Return a scalat from its fixed-length bytes representation
|
||||
/// Return a scalar from its fixed-length bytes representation
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError>;
|
||||
@@ -57,6 +58,12 @@ pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
|
||||
|
||||
/// Hashes a slice of pseudo-random bytes of the correct length to a curve point
|
||||
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self;
|
||||
|
||||
/// Get the base point for the group
|
||||
fn base_point() -> Self;
|
||||
|
||||
/// Multiply the point by a scalar, represented as a slice
|
||||
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
|
||||
}
|
||||
|
||||
/// The implementation of such a subgroup for Ristretto
|
||||
@@ -71,7 +78,20 @@ impl Group for RistrettoPoint {
|
||||
Ok(Scalar::from_bytes_mod_order(bits))
|
||||
}
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
Scalar::random(rng)
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 64];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
|
||||
}
|
||||
|
||||
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
|
||||
#[cfg(test)]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
||||
}
|
||||
}
|
||||
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
|
||||
GenericArray::from_slice(scalar.as_bytes())
|
||||
@@ -87,7 +107,7 @@ impl Group for RistrettoPoint {
|
||||
) -> Result<Self, InternalPakeError> {
|
||||
CompressedRistretto::from_slice(element_bits)
|
||||
.decompress()
|
||||
.ok_or_else(|| InternalPakeError::PointError)
|
||||
.ok_or(InternalPakeError::PointError)
|
||||
}
|
||||
// serialization of a group element
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
|
||||
@@ -97,114 +117,21 @@ impl Group for RistrettoPoint {
|
||||
|
||||
type UniformBytesLen = U64;
|
||||
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
|
||||
let mut bits = [0u8; 64];
|
||||
bits.copy_from_slice(&uniform_bytes);
|
||||
|
||||
// https://caniuse.rs/features/array_gt_32_impls
|
||||
let bits: [u8; 64] = {
|
||||
let mut bytes = [0u8; 64];
|
||||
bytes.copy_from_slice(uniform_bytes);
|
||||
bytes
|
||||
};
|
||||
RistrettoPoint::from_uniform_bytes(&bits)
|
||||
}
|
||||
}
|
||||
|
||||
/// The implementation of such a subgroup for points on the large Curve25519-subgroup
|
||||
impl Group for EdwardsPoint {
|
||||
type Scalar = Scalar;
|
||||
type ScalarLen = U32;
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError> {
|
||||
let mut bits = [0u8; 32];
|
||||
bits.copy_from_slice(scalar_bits);
|
||||
Ok(Scalar::from_bytes_mod_order(bits))
|
||||
}
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
Scalar::random(rng)
|
||||
}
|
||||
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
|
||||
GenericArray::from_slice(scalar.as_bytes())
|
||||
}
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
||||
scalar.invert()
|
||||
fn base_point() -> Self {
|
||||
RISTRETTO_BASEPOINT_POINT
|
||||
}
|
||||
|
||||
// The byte length necessary to represent group elements
|
||||
type ElemLen = U32;
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError> {
|
||||
let point = CompressedEdwardsY::from_slice(element_bits)
|
||||
.decompress()
|
||||
.ok_or_else(|| InternalPakeError::PointError)?;
|
||||
|
||||
if point.is_small_order() {
|
||||
return Err(InternalPakeError::SubGroupError);
|
||||
}
|
||||
Ok(point)
|
||||
}
|
||||
// serialization of a group element
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
|
||||
let c = self.compress();
|
||||
*GenericArray::from_slice(c.as_bytes())
|
||||
}
|
||||
|
||||
type UniformBytesLen = U32;
|
||||
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
|
||||
elligator::hash_to_point(uniform_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
const EIGHT_TORSION: [[u8; 32]; 8] = [
|
||||
[
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
],
|
||||
[
|
||||
199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250,
|
||||
44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122,
|
||||
],
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 128,
|
||||
],
|
||||
[
|
||||
38, 232, 149, 143, 194, 178, 39, 176, 69, 195, 244, 137, 242, 239, 152, 240, 213, 223,
|
||||
172, 5, 211, 198, 51, 57, 177, 56, 2, 136, 109, 83, 252, 5,
|
||||
],
|
||||
[
|
||||
236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127,
|
||||
],
|
||||
[
|
||||
38, 232, 149, 143, 194, 178, 39, 176, 69, 195, 244, 137, 242, 239, 152, 240, 213, 223,
|
||||
172, 5, 211, 198, 51, 57, 177, 56, 2, 136, 109, 83, 252, 133,
|
||||
],
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0,
|
||||
],
|
||||
[
|
||||
199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250,
|
||||
44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 250,
|
||||
],
|
||||
];
|
||||
|
||||
fn deserialize_point(pt: &[u8]) -> Result<EdwardsPoint> {
|
||||
let mut bytes = [0u8; 32];
|
||||
bytes.copy_from_slice(&pt[..32]);
|
||||
|
||||
curve25519_dalek::edwards::CompressedEdwardsY(bytes)
|
||||
.decompress()
|
||||
.ok_or_else(|| anyhow!("Point decompression failed!"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_small_subgroup_edwards() {
|
||||
for pt in &EIGHT_TORSION[..] {
|
||||
assert!(deserialize_point(&pt[..]).is_ok());
|
||||
assert!(EdwardsPoint::from_element_slice(GenericArray::from_slice(&pt[..])).is_err());
|
||||
}
|
||||
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self {
|
||||
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
|
||||
self * Scalar::from_bits(arr)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-11
@@ -3,20 +3,13 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
//! A convenience trait for digest bounds used throughout the library
|
||||
|
||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::ArrayLength;
|
||||
|
||||
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
|
||||
// Associated types could be simplified when they are made as defaults:
|
||||
// https://github.com/rust-lang/rust/issues/29661
|
||||
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {
|
||||
/// The block size for the hash function
|
||||
type BlockSize: ArrayLength<u8>;
|
||||
/// The output size of the hash function
|
||||
type OutputSize: ArrayLength<u8>;
|
||||
}
|
||||
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
|
||||
|
||||
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {
|
||||
type BlockSize = T::BlockSize;
|
||||
type OutputSize = T::OutputSize;
|
||||
}
|
||||
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
|
||||
|
||||
+29
-21
@@ -4,50 +4,58 @@
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
errors::{InternalPakeError, ProtocolError},
|
||||
errors::{PakeError, ProtocolError},
|
||||
group::Group,
|
||||
hash::Hash,
|
||||
keypair::{Key, KeyPair},
|
||||
keypair::Key,
|
||||
};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub trait KeyExchange<D: Hash> {
|
||||
type KE1State: TryFrom<Vec<u8>, Error = InternalPakeError> + ToBytes;
|
||||
type KE2State: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
|
||||
type KE1Message: TryFrom<Vec<u8>, Error = InternalPakeError> + ToBytes;
|
||||
type KE2Message: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
|
||||
type KE3Message: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
|
||||
pub trait KeyExchange<D: Hash, G: Group> {
|
||||
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
l1_component: Vec<u8>,
|
||||
fn generate_ke1<R: RngCore + CryptoRng>(
|
||||
info: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
|
||||
|
||||
fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
fn generate_ke2<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
l1_bytes: Vec<u8>,
|
||||
l2_bytes: Vec<u8>,
|
||||
ke1_message: Self::KE1Message,
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_sk: KeyFormat::Repr,
|
||||
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
|
||||
client_s_pk: Key,
|
||||
server_s_sk: Key,
|
||||
id_u: Vec<u8>,
|
||||
id_s: Vec<u8>,
|
||||
e_info: Vec<u8>,
|
||||
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
|
||||
|
||||
fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
fn generate_ke3(
|
||||
l2_component: Vec<u8>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
client_s_sk: KeyFormat::Repr,
|
||||
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError>;
|
||||
serialized_credential_request: &[u8],
|
||||
server_s_pk: Key,
|
||||
client_s_sk: Key,
|
||||
id_u: Vec<u8>,
|
||||
id_s: Vec<u8>,
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn finish_ke(
|
||||
ke3_message: Self::KE3Message,
|
||||
ke2_state: &Self::KE2State,
|
||||
) -> Result<Vec<u8>, ProtocolError>;
|
||||
|
||||
fn ke1_state_size() -> usize;
|
||||
|
||||
fn ke2_message_size() -> usize;
|
||||
}
|
||||
|
||||
|
||||
+338
-218
@@ -5,184 +5,215 @@
|
||||
|
||||
//! An implementation of the Triple Diffie-Hellman key exchange protocol
|
||||
use crate::{
|
||||
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
|
||||
errors::{
|
||||
utils::{check_slice_size, check_slice_size_atleast},
|
||||
InternalPakeError, PakeError, ProtocolError,
|
||||
},
|
||||
group::Group,
|
||||
hash::Hash,
|
||||
key_exchange::traits::{KeyExchange, ToBytes},
|
||||
keypair::{Key, KeyPair, SizedBytes},
|
||||
sized_bytes_using_constant_and_try_from,
|
||||
keypair::{Key, KeyPair, SizedBytesExt},
|
||||
serialization::{serialize, tokenize},
|
||||
};
|
||||
use digest::Digest;
|
||||
use digest::{Digest, FixedOutput};
|
||||
use generic_array::{
|
||||
typenum::{U64, U96},
|
||||
GenericArray,
|
||||
typenum::{Unsigned, U32},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use generic_bytes::SizedBytes;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const KEY_LEN: usize = 32;
|
||||
pub(crate) const NONCE_LEN: usize = 32;
|
||||
const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
|
||||
const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
|
||||
pub(crate) type NonceLen = U32;
|
||||
|
||||
static STR_3DH: &[u8] = b"3DH keys";
|
||||
static STR_3DH: &[u8] = b"3DH";
|
||||
static STR_CLIENT_MAC: &[u8] = b"client mac";
|
||||
static STR_HANDSHAKE_SECRET: &[u8] = b"handshake secret";
|
||||
static STR_SERVER_MAC: &[u8] = b"server mac";
|
||||
static STR_HANDSHAKE_ENC: &[u8] = b"handshake enc";
|
||||
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
|
||||
static STR_SESSION_SECRET: &[u8] = b"session secret";
|
||||
static STR_OPAQUE: &[u8] = b"OPAQUE ";
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
/// The Triple Diffie-Hellman key exchange implementation
|
||||
pub struct TripleDH;
|
||||
|
||||
impl<D: Hash> KeyExchange<D> for TripleDH {
|
||||
type KE1State = KE1State;
|
||||
type KE2State = KE2State;
|
||||
type KE1Message = KE1Message;
|
||||
type KE2Message = KE2Message;
|
||||
type KE3Message = KE3Message;
|
||||
impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
type KE1State = Ke1State;
|
||||
type KE2State = Ke2State<<D as FixedOutput>::OutputSize>;
|
||||
type KE1Message = Ke1Message;
|
||||
type KE2Message = Ke2Message<<D as FixedOutput>::OutputSize>;
|
||||
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
l1_component: Vec<u8>,
|
||||
fn generate_ke1<R: RngCore + CryptoRng>(
|
||||
info: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
|
||||
let client_e_kp = KeyFormat::generate_random(rng)?;
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
let client_e_kp = KeyPair::<G>::generate_random(rng);
|
||||
let client_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let ke1_message = KE1Message {
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
let ke1_message = Ke1Message {
|
||||
client_nonce,
|
||||
info,
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
|
||||
let mut hasher = D::new();
|
||||
hasher.update(&l1_data);
|
||||
let hashed_l1 = hasher.finalize();
|
||||
|
||||
Ok((
|
||||
KE1State {
|
||||
Ke1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
hashed_l1: hashed_l1.to_vec(),
|
||||
client_nonce,
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn generate_ke2<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
l1_bytes: Vec<u8>,
|
||||
serialized_credential_request: Vec<u8>,
|
||||
l2_bytes: Vec<u8>,
|
||||
ke1_message: Self::KE1Message,
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_sk: KeyFormat::Repr,
|
||||
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
|
||||
let server_e_kp = KeyFormat::generate_random(rng)?;
|
||||
let mut server_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
client_s_pk: Key,
|
||||
server_s_sk: Key,
|
||||
id_u: Vec<u8>,
|
||||
id_s: Vec<u8>,
|
||||
e_info: Vec<u8>,
|
||||
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> {
|
||||
let server_e_kp = KeyPair::<G>::generate_random(rng);
|
||||
let server_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
|
||||
let mut transcript_hasher = D::new()
|
||||
.chain(STR_3DH)
|
||||
.chain(&serialize(&id_u, 2))
|
||||
.chain(&serialized_credential_request[..])
|
||||
.chain(&serialize(&id_s, 2))
|
||||
.chain(&l2_bytes[..])
|
||||
.chain(&server_nonce[..])
|
||||
.chain(&server_e_kp.public().to_arr());
|
||||
|
||||
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
|
||||
TripleDHComponents {
|
||||
pk1: ke1_message.client_e_pk.clone(),
|
||||
sk1: server_e_kp.private().clone(),
|
||||
pk2: ke1_message.client_e_pk,
|
||||
sk2: server_s_sk.clone(),
|
||||
pk3: client_s_pk.clone(),
|
||||
sk2: server_s_sk,
|
||||
pk3: client_s_pk,
|
||||
sk3: server_e_kp.private().clone(),
|
||||
},
|
||||
&ke1_message.client_nonce,
|
||||
&server_nonce,
|
||||
client_s_pk,
|
||||
KeyFormat::public_from_private(&server_s_sk),
|
||||
&transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
let mut hasher = D::new();
|
||||
hasher.update(&l1_bytes);
|
||||
let hashed_l1 = hasher.finalize();
|
||||
// Compute encryption of e_info
|
||||
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let mut encryption_pad = vec![0u8; e_info.len()];
|
||||
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let ciphertext: Vec<u8> = encryption_pad
|
||||
.iter()
|
||||
.zip(e_info.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
|
||||
let transcript2: Vec<u8> = [
|
||||
&hashed_l1[..],
|
||||
&l2_bytes[..],
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public().to_arr(),
|
||||
]
|
||||
.concat();
|
||||
transcript_hasher.update(&serialize(&ciphertext, 2));
|
||||
|
||||
let mut hasher2 = D::new();
|
||||
hasher2.update(&transcript2);
|
||||
let hashed_transcript = hasher2.finalize();
|
||||
let mut mac_hasher =
|
||||
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac_hasher.update(&transcript_hasher.clone().finalize());
|
||||
let mac = mac_hasher.finalize().into_bytes();
|
||||
|
||||
let mut mac = Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.update(&hashed_transcript);
|
||||
transcript_hasher.update(&mac);
|
||||
|
||||
Ok((
|
||||
KE2State {
|
||||
km3: km3.to_vec(),
|
||||
hashed_transcript: hashed_transcript.to_vec(),
|
||||
shared_secret: shared_secret.to_vec(),
|
||||
ke1_message.info,
|
||||
Ke2State {
|
||||
km3,
|
||||
hashed_transcript: transcript_hasher.finalize(),
|
||||
session_key,
|
||||
},
|
||||
KE2Message {
|
||||
server_nonce: server_nonce.to_vec(),
|
||||
Ke2Message {
|
||||
server_nonce,
|
||||
server_e_pk: server_e_kp.public().clone(),
|
||||
mac: mac.finalize().into_bytes().to_vec(),
|
||||
e_info: ciphertext,
|
||||
mac,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn generate_ke3(
|
||||
l2_component: Vec<u8>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
client_s_sk: KeyFormat::Repr,
|
||||
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
|
||||
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
|
||||
serialized_credential_request: &[u8],
|
||||
server_s_pk: Key,
|
||||
client_s_sk: Key,
|
||||
id_u: Vec<u8>,
|
||||
id_s: Vec<u8>,
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
|
||||
let mut transcript_hasher = D::new()
|
||||
.chain(STR_3DH)
|
||||
.chain(&serialize(&id_u, 2))
|
||||
.chain(&serialized_credential_request)
|
||||
.chain(&serialize(&id_s, 2))
|
||||
.chain(&l2_component[..])
|
||||
.chain(&ke2_message.to_bytes_without_info_or_mac());
|
||||
|
||||
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
|
||||
TripleDHComponents {
|
||||
pk1: ke2_message.server_e_pk.clone(),
|
||||
sk1: ke1_state.client_e_sk.clone(),
|
||||
pk2: server_s_pk.clone(),
|
||||
pk2: server_s_pk,
|
||||
sk2: ke1_state.client_e_sk.clone(),
|
||||
pk3: ke2_message.server_e_pk.clone(),
|
||||
sk3: client_s_sk.clone(),
|
||||
sk3: client_s_sk,
|
||||
},
|
||||
&ke1_state.client_nonce,
|
||||
&ke2_message.server_nonce,
|
||||
KeyFormat::public_from_private(&client_s_sk),
|
||||
server_s_pk,
|
||||
&transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
let transcript: Vec<u8> = [
|
||||
&ke1_state.hashed_l1[..],
|
||||
&l2_component[..],
|
||||
&ke2_message.server_nonce[..],
|
||||
&ke2_message.server_e_pk[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher = D::new();
|
||||
hasher.update(&transcript);
|
||||
let hashed_transcript = hasher.finalize();
|
||||
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2));
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
server_mac.update(&hashed_transcript);
|
||||
server_mac.update(&transcript_hasher.clone().finalize());
|
||||
|
||||
if ke2_message.mac != server_mac.finalize().into_bytes().to_vec() {
|
||||
if ke2_message.mac != server_mac.finalize().into_bytes() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
transcript_hasher.update(ke2_message.mac.to_vec());
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.update(&hashed_transcript);
|
||||
client_mac.update(&transcript_hasher.finalize());
|
||||
|
||||
// Compute decryption of e_info
|
||||
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
|
||||
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let plaintext: Vec<u8> = encryption_pad
|
||||
.iter()
|
||||
.zip(ke2_message.e_info.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
|
||||
Ok((
|
||||
shared_secret.to_vec(),
|
||||
KE3Message {
|
||||
mac: client_mac.finalize().into_bytes().to_vec(),
|
||||
plaintext,
|
||||
session_key.to_vec(),
|
||||
Ke3Message {
|
||||
mac: client_mac.finalize().into_bytes(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn finish_ke(
|
||||
ke3_message: Self::KE3Message,
|
||||
ke2_state: &Self::KE2State,
|
||||
@@ -191,155 +222,172 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
|
||||
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.update(&ke2_state.hashed_transcript);
|
||||
|
||||
if ke3_message.mac != client_mac.finalize().into_bytes().to_vec() {
|
||||
if ke3_message.mac != client_mac.finalize().into_bytes() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ke2_state.shared_secret.to_vec())
|
||||
}
|
||||
|
||||
fn ke1_state_size() -> usize {
|
||||
KE1_STATE_LEN
|
||||
Ok(ke2_state.session_key.to_vec())
|
||||
}
|
||||
|
||||
fn ke2_message_size() -> usize {
|
||||
KE2_MESSAGE_LEN
|
||||
NonceLen::to_usize() + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
|
||||
}
|
||||
}
|
||||
|
||||
/// The client state produced after the first key exchange message
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct KE1State {
|
||||
pub struct Ke1State {
|
||||
client_e_sk: Key,
|
||||
client_nonce: Vec<u8>,
|
||||
hashed_l1: Vec<u8>,
|
||||
client_nonce: GenericArray<u8, NonceLen>,
|
||||
}
|
||||
|
||||
/// The first key exchange message
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct KE1Message {
|
||||
pub(crate) client_nonce: Vec<u8>,
|
||||
pub struct Ke1Message {
|
||||
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(crate) info: Vec<u8>,
|
||||
pub(crate) client_e_pk: Key,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE1State {
|
||||
type Error = InternalPakeError;
|
||||
impl TryFrom<&[u8]> for Ke1State {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&bytes, KE1_STATE_LEN, "ke1_state")?;
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let nonce_len = NonceLen::to_usize();
|
||||
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
|
||||
|
||||
Ok(Self {
|
||||
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
|
||||
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
|
||||
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
|
||||
client_nonce: GenericArray::clone_from_slice(
|
||||
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes for KE1State {
|
||||
impl ToBytes for Ke1State {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.client_e_sk.to_arr(),
|
||||
&self.client_nonce[..],
|
||||
&self.hashed_l1[..],
|
||||
]
|
||||
.concat();
|
||||
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
sized_bytes_using_constant_and_try_from!(KE1State, U96);
|
||||
|
||||
impl ToBytes for KE1Message {
|
||||
impl ToBytes for Ke1Message {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
|
||||
[
|
||||
&self.client_nonce[..],
|
||||
&serialize(&self.info, 2),
|
||||
&self.client_e_pk.to_arr(),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE1Message {
|
||||
type Error = InternalPakeError;
|
||||
impl TryFrom<&[u8]> for Ke1Message {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(ke1_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes =
|
||||
check_slice_size(&ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let nonce_len = NonceLen::to_usize();
|
||||
let checked_nonce =
|
||||
check_slice_size_atleast(ke1_message_bytes, nonce_len, "ke1_message nonce")?;
|
||||
|
||||
let (info, remainder) = tokenize(&checked_nonce[nonce_len..], 2)?;
|
||||
|
||||
let checked_client_e_pk = check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
|
||||
|
||||
Ok(Self {
|
||||
client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
|
||||
client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
|
||||
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
|
||||
info,
|
||||
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sized_bytes_using_constant_and_try_from!(KE1Message, U64);
|
||||
|
||||
/// The server state produced after the second key exchange message
|
||||
pub struct KE2State {
|
||||
km3: Vec<u8>,
|
||||
hashed_transcript: Vec<u8>,
|
||||
shared_secret: Vec<u8>,
|
||||
pub struct Ke2State<HashLen: ArrayLength<u8>> {
|
||||
km3: GenericArray<u8, HashLen>,
|
||||
hashed_transcript: GenericArray<u8, HashLen>,
|
||||
session_key: GenericArray<u8, HashLen>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
pub struct KE2Message {
|
||||
server_nonce: Vec<u8>,
|
||||
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: Key,
|
||||
mac: Vec<u8>,
|
||||
e_info: Vec<u8>,
|
||||
mac: GenericArray<u8, HashLen>,
|
||||
}
|
||||
|
||||
impl ToBytes for KE2State {
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
[
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.shared_secret[..],
|
||||
&self.session_key[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE2State {
|
||||
type Error = ProtocolError;
|
||||
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(ke1_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
|
||||
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
|
||||
let hash_len = HashLen::to_usize();
|
||||
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
|
||||
|
||||
Ok(Self {
|
||||
km3: checked_bytes[..KEY_LEN].to_vec(),
|
||||
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
|
||||
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
|
||||
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
|
||||
hashed_transcript: GenericArray::clone_from_slice(
|
||||
&checked_bytes[hash_len..2 * hash_len],
|
||||
),
|
||||
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes for KE2Message {
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.server_nonce[..],
|
||||
&self.server_e_pk.to_arr(),
|
||||
[
|
||||
&self.to_bytes_without_info_or_mac(),
|
||||
&serialize(&self.e_info, 2),
|
||||
&self.mac[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE2Message {
|
||||
type Error = ProtocolError;
|
||||
impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
|
||||
fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
|
||||
[&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from(ke2_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&ke2_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
|
||||
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2Message<HashLen> {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
|
||||
let nonce_len = NonceLen::to_usize();
|
||||
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
|
||||
let checked_server_e_pk = check_slice_size_atleast(
|
||||
&checked_nonce[nonce_len..],
|
||||
KEY_LEN,
|
||||
"ke2_message server_e_pk",
|
||||
)?;
|
||||
let (e_info, remainder) = tokenize(&checked_server_e_pk[KEY_LEN..], 2)?;
|
||||
let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?;
|
||||
|
||||
Ok(Self {
|
||||
server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
|
||||
server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
|
||||
mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
|
||||
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
|
||||
server_e_pk: Key::from_bytes(&checked_server_e_pk[..KEY_LEN])?,
|
||||
e_info,
|
||||
mac: GenericArray::clone_from_slice(&checked_mac),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
// The triple of public and private components used in the 3DH computation
|
||||
struct TripleDHComponents {
|
||||
pk1: Key,
|
||||
@@ -350,69 +398,141 @@ struct TripleDHComponents {
|
||||
sk3: Key,
|
||||
}
|
||||
|
||||
// Consists of a shared secret, followed by two mac keys
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
// Consists of a session key, followed by two mac keys and an encryption key: (session_key, km2, ke2, km3)
|
||||
type TripleDHDerivationResult<D> = (
|
||||
GenericArray<u8, <D as Hash>::OutputSize>,
|
||||
GenericArray<u8, <D as Hash>::OutputSize>,
|
||||
GenericArray<u8, <D as Hash>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
);
|
||||
|
||||
// Internal function which takes the public and private components of the client and server keypairs, along
|
||||
// with some auxiliary metadata, to produce the shared secret and two MAC keys
|
||||
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>, D: Hash>(
|
||||
dh: TripleDHComponents,
|
||||
client_nonce: &[u8],
|
||||
server_nonce: &[u8],
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
|
||||
let ikm: Vec<u8> = [
|
||||
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
|
||||
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
|
||||
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let info: Vec<u8> = [
|
||||
STR_3DH,
|
||||
&client_nonce,
|
||||
&server_nonce,
|
||||
&client_s_pk.to_arr(),
|
||||
&server_s_pk.to_arr(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
const OUTPUT_SIZE: usize = 32;
|
||||
let mut okm = [0u8; 3 * OUTPUT_SIZE];
|
||||
let h = Hkdf::<D>::new(None, &ikm);
|
||||
h.expand(&info, &mut okm)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
Ok((
|
||||
GenericArray::clone_from_slice(&okm[..OUTPUT_SIZE]),
|
||||
GenericArray::clone_from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
|
||||
GenericArray::clone_from_slice(&okm[2 * OUTPUT_SIZE..]),
|
||||
))
|
||||
}
|
||||
|
||||
/// The third key exchange message
|
||||
pub struct KE3Message {
|
||||
mac: Vec<u8>,
|
||||
pub struct Ke3Message<HashLen: ArrayLength<u8>> {
|
||||
mac: GenericArray<u8, HashLen>,
|
||||
}
|
||||
|
||||
impl ToBytes for KE3Message {
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.mac.clone()
|
||||
self.mac.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE3Message {
|
||||
type Error = ProtocolError;
|
||||
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke3Message<HashLen> {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&bytes, KEY_LEN, "ke3_message")?;
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&bytes, HashLen::to_usize(), "ke3_message")?;
|
||||
|
||||
Ok(Self {
|
||||
mac: checked_bytes.to_vec(),
|
||||
mac: GenericArray::clone_from_slice(&checked_bytes),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Internal function which takes the public and private components of the client and server keypairs, along
|
||||
// with some auxiliary metadata, to produce the session key and two MAC keys
|
||||
fn derive_3dh_keys<D: Hash, G: Group>(
|
||||
dh: TripleDHComponents,
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
|
||||
let ikm: Vec<u8> = [
|
||||
&KeyPair::<G>::diffie_hellman(dh.pk1, dh.sk1)?[..],
|
||||
&KeyPair::<G>::diffie_hellman(dh.pk2, dh.sk2)?[..],
|
||||
&KeyPair::<G>::diffie_hellman(dh.pk3, dh.sk3)?[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
|
||||
let handshake_secret = derive_secrets::<D>(
|
||||
&extracted_ikm,
|
||||
&STR_HANDSHAKE_SECRET,
|
||||
&hashed_derivation_transcript,
|
||||
)?;
|
||||
let session_key = derive_secrets::<D>(
|
||||
&extracted_ikm,
|
||||
&STR_SESSION_SECRET,
|
||||
&hashed_derivation_transcript,
|
||||
)?;
|
||||
|
||||
let km2 = hkdf_expand_label::<D>(
|
||||
&handshake_secret,
|
||||
&STR_SERVER_MAC,
|
||||
b"",
|
||||
<D as Digest>::OutputSize::to_usize(),
|
||||
)?;
|
||||
let ke2 = hkdf_expand_label::<D>(
|
||||
&handshake_secret,
|
||||
&STR_HANDSHAKE_ENC,
|
||||
b"",
|
||||
<D as Digest>::OutputSize::to_usize(),
|
||||
)?;
|
||||
let km3 = hkdf_expand_label::<D>(
|
||||
&handshake_secret,
|
||||
&STR_CLIENT_MAC,
|
||||
b"",
|
||||
<D as Digest>::OutputSize::to_usize(),
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
GenericArray::clone_from_slice(&session_key),
|
||||
GenericArray::clone_from_slice(&km2),
|
||||
GenericArray::clone_from_slice(&ke2),
|
||||
GenericArray::clone_from_slice(&km3),
|
||||
))
|
||||
}
|
||||
|
||||
fn hkdf_expand_label<D: Hash>(
|
||||
secret: &[u8],
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
length: usize,
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalPakeError::HkdfError)?;
|
||||
hkdf_expand_label_extracted(&h, label, context, length)
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
let mut hkdf_label: Vec<u8> = Vec::new();
|
||||
hkdf_label.extend_from_slice(&length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
|
||||
|
||||
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)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
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::to_usize(),
|
||||
)
|
||||
}
|
||||
|
||||
// Generate a random nonce up to NonceLen::to_usize() bytes.
|
||||
fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
|
||||
let mut nonce_bytes = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
GenericArray::clone_from_slice(&nonce_bytes)
|
||||
}
|
||||
|
||||
+91
-211
@@ -5,73 +5,104 @@
|
||||
|
||||
//! Contains the keypair types that must be supplied for the OPAQUE API
|
||||
|
||||
use crate::errors::{utils::check_slice_size, InternalPakeError};
|
||||
use generic_array::{
|
||||
sequence::Concat,
|
||||
typenum::{Sum, Unsigned, U32},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::group::Group;
|
||||
use generic_array::{typenum::U32, GenericArray};
|
||||
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
|
||||
#[cfg(test)]
|
||||
use proptest::prelude::*;
|
||||
#[cfg(test)]
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::fmt::Debug;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use std::ops::{Add, Deref};
|
||||
|
||||
/// A trait for sized key material that can be represented within a fixed byte
|
||||
/// array size, used to represent our DH key types
|
||||
pub trait SizedBytes: Sized + PartialEq {
|
||||
/// The typed representation of the byte length
|
||||
type Len: ArrayLength<u8>;
|
||||
|
||||
/// Converts this sized key material to a `GenericArray` of the same
|
||||
/// size. One can convert this to a `&[u8]` with `GenericArray::as_slice()`
|
||||
/// but the size information is then lost from the type.
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::Len>;
|
||||
|
||||
/// How to parse such sized material from a byte slice.
|
||||
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError>;
|
||||
/// Convenience extension trait of SizedBytes
|
||||
pub trait SizedBytesExt: SizedBytes {
|
||||
/// Convert from bytes
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, TryFromSizedBytesError> {
|
||||
<Self as SizedBytes>::from_arr(GenericArray::from_slice(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
/// A Keypair trait with public-private verification
|
||||
pub trait KeyPair: Sized {
|
||||
/// The single key representation must have a specific byte size itself
|
||||
type Repr: SizedBytes + Clone;
|
||||
// blanket implementation
|
||||
impl<T> SizedBytesExt for T where T: SizedBytes {}
|
||||
|
||||
/// A Keypair trait with public-private verification
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct KeyPair<G> {
|
||||
pk: Key,
|
||||
sk: Key,
|
||||
_g: PhantomData<G>,
|
||||
}
|
||||
|
||||
impl<G: Group> KeyPair<G> {
|
||||
/// The public key component
|
||||
fn public(&self) -> &Self::Repr;
|
||||
pub fn public(&self) -> &Key {
|
||||
&self.pk
|
||||
}
|
||||
|
||||
/// The private key component
|
||||
fn private(&self) -> &Self::Repr;
|
||||
pub fn private(&self) -> &Key {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
/// A constructor that receives public and private key independently as
|
||||
/// bytes
|
||||
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError>;
|
||||
pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
|
||||
Ok(Self {
|
||||
pk: public,
|
||||
sk: private,
|
||||
_g: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generating a random key pair given a cryptographic rng
|
||||
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError>;
|
||||
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
let sk = G::random_scalar(rng);
|
||||
let sk_bytes = G::scalar_as_bytes(&sk);
|
||||
let pk = G::base_point().mult_by_slice(&sk_bytes);
|
||||
Self {
|
||||
pk: Key(pk.to_arr().to_vec()),
|
||||
sk: Key(sk_bytes.to_vec()),
|
||||
_g: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtaining a public key from secret bytes. At all times, we should have
|
||||
/// &public_from_private(self.private()) == self.public()
|
||||
fn public_from_private(secret: &Self::Repr) -> Self::Repr;
|
||||
pub(crate) fn public_from_private(bytes: &Key) -> Key {
|
||||
let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]);
|
||||
Key(G::base_point().mult_by_slice(&bytes_data).to_arr().to_vec())
|
||||
}
|
||||
|
||||
/// Check whether a public key is valid. This is meant to be applied on
|
||||
/// material provided through the network which fits the key
|
||||
/// representation (i.e. can be mapped to a curve point), but presents
|
||||
/// some risk - e.g. small subgroup check
|
||||
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError>;
|
||||
pub(crate) fn check_public_key(key: Key) -> Result<Key, InternalPakeError> {
|
||||
G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key)
|
||||
}
|
||||
|
||||
/// Computes the diffie hellman function on a public key and private key
|
||||
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8>;
|
||||
pub(crate) fn diffie_hellman(pk: Key, sk: Key) -> Result<Vec<u8>, InternalPakeError> {
|
||||
let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]);
|
||||
let point = G::from_element_slice(&pk_data)?;
|
||||
let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]);
|
||||
Ok(G::mult_by_slice(&point, &secret_data).to_arr().to_vec())
|
||||
}
|
||||
|
||||
/// Obtains a KeyPair from a slice representing the private key
|
||||
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let sk = Key::from_arr(GenericArray::from_slice(&input))?;
|
||||
let pk = Self::public_from_private(&sk);
|
||||
Self::new(pk, sk)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
trait KeyPairExt: KeyPair + Debug {
|
||||
impl<G: Group + Debug> KeyPair<G> {
|
||||
/// Test-only strategy returning a proptest Strategy based on
|
||||
/// generate_random
|
||||
fn uniform_keypair_strategy() -> BoxedStrategy<Self> {
|
||||
@@ -80,101 +111,14 @@ trait KeyPairExt: KeyPair + Debug {
|
||||
any::<[u8; 32]>()
|
||||
.prop_filter_map("valid random keypair", |seed| {
|
||||
let mut rng = StdRng::from_seed(seed);
|
||||
Self::generate_random(&mut rng).ok()
|
||||
Some(Self::generate_random(&mut rng))
|
||||
})
|
||||
.no_shrink()
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
// blanket implementation
|
||||
#[cfg(test)]
|
||||
impl<KP> KeyPairExt for KP where KP: KeyPair + Debug {}
|
||||
|
||||
/// This assumes you have defined:
|
||||
/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
|
||||
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
|
||||
/// and it both of the above to produce a sensible SizedBytes implementation
|
||||
///
|
||||
/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
|
||||
/// not, it's better to use the macro below rather than this one, where possible.
|
||||
#[macro_export]
|
||||
macro_rules! sized_bytes_using_constant_and_try_from {
|
||||
($sized_type: ident, $len: ident) => {
|
||||
impl SizedBytes for $sized_type {
|
||||
type Len = $len;
|
||||
|
||||
fn to_arr(&self) -> generic_array::GenericArray<u8, Self::Len> {
|
||||
generic_array::GenericArray::clone_from_slice(&self.to_bytes())
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes = check_slice_size(
|
||||
bytes,
|
||||
<Self::Len as generic_array::typenum::Unsigned>::to_usize(),
|
||||
"bytes",
|
||||
)?;
|
||||
std::convert::TryFrom::try_from(checked_bytes.to_vec())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// This assumes you have defined a SizedBytes instance for a `T`, and defines:
|
||||
/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
|
||||
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
|
||||
///
|
||||
/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
|
||||
/// not, it's better to use this macro than the one above, where possible.
|
||||
macro_rules! try_from_and_to_bytes_using_sized_bytes {
|
||||
($sized_type: ident) => {
|
||||
impl TryFrom<&[u8]> for $sized_type {
|
||||
type Error = InternalPakeError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
<$sized_type as SizedBytes>::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl $sized_type {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_arr().to_vec()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
|
||||
/// with any length of keys. This encodes that we serialize the public key
|
||||
/// first, followed by the private key in binary formats (and expect it in this
|
||||
/// order upon decoding).
|
||||
impl<T, KP> SizedBytes for KP
|
||||
where
|
||||
T: SizedBytes + Clone,
|
||||
KP: KeyPair<Repr = T> + PartialEq,
|
||||
T::Len: Add<T::Len>,
|
||||
Sum<T::Len, T::Len>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<T::Len, T::Len>;
|
||||
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
|
||||
let private = self.private().to_arr();
|
||||
let public = self.public().to_arr();
|
||||
public.concat(private)
|
||||
}
|
||||
|
||||
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes =
|
||||
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
|
||||
let single_key_len = <<KP::Repr as SizedBytes>::Len as Unsigned>::to_usize();
|
||||
let public = <T as SizedBytes>::from_bytes(&checked_bytes[..single_key_len])?;
|
||||
let private = <T as SizedBytes>::from_bytes(&checked_bytes[single_key_len..])?;
|
||||
KP::new(public, private)
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimalist key type built around [u8;32]
|
||||
/// A minimalist key type built around a \[u8; 32\]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct Key(Vec<u8>);
|
||||
@@ -194,112 +138,48 @@ impl SizedBytes for Key {
|
||||
GenericArray::clone_from_slice(&self.0[..])
|
||||
}
|
||||
|
||||
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes =
|
||||
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
|
||||
Ok(Key(checked_bytes.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
try_from_and_to_bytes_using_sized_bytes!(Key);
|
||||
|
||||
/// A representation of an X25519 keypair according to RFC7748
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct X25519KeyPair {
|
||||
pk: Key,
|
||||
sk: Key,
|
||||
}
|
||||
|
||||
impl X25519KeyPair {
|
||||
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
|
||||
let sk = StaticSecret::new(rng);
|
||||
let pk = PublicKey::from(&sk);
|
||||
(pk.as_bytes().to_vec(), sk.to_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair for X25519KeyPair {
|
||||
type Repr = Key;
|
||||
|
||||
fn public(&self) -> &Self::Repr {
|
||||
&self.pk
|
||||
}
|
||||
|
||||
fn private(&self) -> &Self::Repr {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
|
||||
Ok(X25519KeyPair {
|
||||
pk: public,
|
||||
sk: private,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
|
||||
let (public, private) = X25519KeyPair::gen(rng);
|
||||
Ok(X25519KeyPair {
|
||||
pk: Key(public),
|
||||
sk: Key(private),
|
||||
})
|
||||
}
|
||||
|
||||
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
|
||||
let mut secret_data = [0u8; 32];
|
||||
secret_data.copy_from_slice(&secret.0[..]);
|
||||
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
|
||||
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
|
||||
}
|
||||
|
||||
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
|
||||
let mut key_bytes = [0u8; 32];
|
||||
key_bytes.copy_from_slice(&key);
|
||||
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
|
||||
.to_edwards(1)
|
||||
.ok_or(InternalPakeError::PointError)?;
|
||||
if !point.is_torsion_free() {
|
||||
Err(InternalPakeError::SubGroupError)
|
||||
} else {
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
|
||||
let mut pk_data = [0; 32];
|
||||
pk_data.copy_from_slice(&pk.0[..]);
|
||||
let mut sk_data = [0; 32];
|
||||
sk_data.copy_from_slice(&sk.0[..]);
|
||||
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
|
||||
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
|
||||
Ok(Key(key_bytes.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_x25519_check(kp in X25519KeyPair::uniform_keypair_strategy()) {
|
||||
fn test_ristretto_check(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
prop_assert!(X25519KeyPair::check_public_key(pk.clone()).is_ok());
|
||||
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_x25519_pub_from_priv(kp in X25519KeyPair::uniform_keypair_strategy()) {
|
||||
fn test_ristretto_pub_from_priv(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
let sk = kp.private();
|
||||
prop_assert_eq!(&X25519KeyPair::public_from_private(sk), pk);
|
||||
prop_assert_eq!(&KeyPair::<RistrettoPoint>::public_from_private(sk), pk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
|
||||
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
|
||||
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
|
||||
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
|
||||
|
||||
prop_assert_eq!(dh1, dh2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_x25519_dh(kp1 in X25519KeyPair::uniform_keypair_strategy(),
|
||||
kp2 in X25519KeyPair::uniform_keypair_strategy()) {
|
||||
fn test_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
let sk_bytes = kp.private().to_vec();
|
||||
|
||||
let dh1 = X25519KeyPair::diffie_hellman(kp1.public().clone(), kp2.private().clone());
|
||||
let dh2 = X25519KeyPair::diffie_hellman(kp2.public().clone(), kp1.private().clone());
|
||||
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
|
||||
let kp2_private_bytes = kp2.private().to_vec();
|
||||
|
||||
prop_assert_eq!(dh1,dh2);
|
||||
prop_assert_eq!(sk_bytes, kp2_private_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+535
-207
@@ -5,7 +5,7 @@
|
||||
|
||||
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
|
||||
//!
|
||||
//! Note: This implementation is in sync with [draft-krawczyk-cfrg-opaque-06](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06),
|
||||
//! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html),
|
||||
//! but this specification is subject to change, until the final version published by the IETF.
|
||||
//!
|
||||
//! # Overview
|
||||
@@ -13,7 +13,6 @@
|
||||
//! OPAQUE is a protocol between a client and a server. They must first agree on a collection of primitives
|
||||
//! to be kept consistent throughout protocol execution. These include:
|
||||
//! * a finite cyclic group along with a point representation,
|
||||
//! * a keypair type,
|
||||
//! * a key exchange protocol,
|
||||
//! * a hashing function, and
|
||||
//! * a slow hashing function.
|
||||
@@ -24,374 +23,681 @@
|
||||
//! struct Default;
|
||||
//! impl CipherSuite for Default {
|
||||
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! type Hash = sha2::Sha256;
|
||||
//! type Hash = sha2::Sha512;
|
||||
//! type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! }
|
||||
//! ```
|
||||
//! See [examples/simple_login.rs](https://github.com/novifinancial/opaque-ke/blob/master/examples/simple_login.rs)
|
||||
//! for a working example of a simple password-based login using OPAQUE.
|
||||
//!
|
||||
//! Note that our choice of slow hashing function in this example, `NoOpHash`, is selected only to ensure
|
||||
//! that the tests execute quickly. A real application should use an actual slow hashing function, such as `Scrypt`.
|
||||
//!
|
||||
//! We have included a concrete instantiation of the authenticated key exchange protocol using 3DH. In the future, we plan to
|
||||
//! add support for other KE protocols as well.
|
||||
//! that the tests execute quickly. A real application should use an actual slow hashing function, such as `scrypt`,
|
||||
//! which can be enabled through the `slow-hash` feature.
|
||||
//!
|
||||
//! ## Setup
|
||||
//! To setup the protocol, the server begins by generating a static keypair:
|
||||
//! To set up the protocol, the server begins by generating a static keypair:
|
||||
//! ```
|
||||
//! # use opaque_ke::keypair::{KeyPair, X25519KeyPair, SizedBytes};
|
||||
//! # use opaque_ke::errors::ProtocolError;
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! use rand_core::{OsRng, RngCore};
|
||||
//! use rand::{rngs::OsRng, RngCore};
|
||||
//! let mut rng = OsRng;
|
||||
//! let server_kp = Default::generate_random_keypair(&mut rng)?;
|
||||
//! let server_kp = Default::generate_random_keypair(&mut rng);
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! The server must persist this keypair for the registration and login steps, where the public component will be
|
||||
//! used by the client during both registration and login, and the private component will be used by the server during login.
|
||||
//!
|
||||
//! ## Registration
|
||||
//! The registration protocol between the client and server consists of four steps along with three messages, denoted
|
||||
//! as `r1`, `r2`, and `r3`. Before registration begins, it is expected that the server's static public key, `server_kp.public()`,
|
||||
//! has been transmitted to the client in an offline step. A successful execution of the registration protocol results in the
|
||||
//! server producing a password file corresponding to the tuple combination of (password, pepper, server public key) provided by
|
||||
//! The registration protocol between the client and server consists of four steps along with three messages:
|
||||
//! [RegistrationRequest], [RegistrationResponse], and [RegistrationUpload]. A successful execution of the registration protocol results in the
|
||||
//! server producing a password file corresponding to the password provided by
|
||||
//! the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.
|
||||
//!
|
||||
//! In the first step (client registration start), the client chooses a registration password and an optional "pepper", and
|
||||
//! runs `ClientRegistration::start` to produce a message `r1`:
|
||||
//! ### Client Registration Start
|
||||
//! In the first step of registration, the client chooses as input a registration password. The client runs [ClientRegistration::start]
|
||||
//! to produce a [ClientRegistrationStartResult], which consists of a [RegistrationRequest] to be sent to the server and
|
||||
//! a [ClientRegistration] which must be persisted on the client for the final step of client registration.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ServerRegistration,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! use rand_core::{OsRng, RngCore};
|
||||
//! use opaque_ke::ClientRegistration;
|
||||
//! use rand::{rngs::OsRng, RngCore};
|
||||
//! let mut client_rng = OsRng;
|
||||
//! let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! b"password",
|
||||
//! Some(b"pepper"),
|
||||
//! let client_registration_start_result = ClientRegistration::<Default>::start(
|
||||
//! &mut client_rng,
|
||||
//! b"password",
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! `r1` is sent to the server, and `client_state` must be persisted on the client for the final step of client
|
||||
//! registration.
|
||||
//!
|
||||
//! In the second step (server registration start), the server takes as input the `r1` message from the client and runs
|
||||
//! `ServerRegistration::start` to produce `r2`:
|
||||
//! ### Server Registration Start
|
||||
//! In the second step of registration, the server takes as input the instance of [RegistrationRequest] from the client, and
|
||||
//! the server's public key `server_kp.public()`.
|
||||
//! The server runs [ServerRegistration::start] to produce an a [ServerRegistrationStartResult], which consists of
|
||||
//! a [RegistrationResponse] to be returned to the client and
|
||||
//! a [ServerRegistration] which must be persisted on the server for the final step of server registration.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ClientRegistration,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # use rand::{rngs::OsRng, RngCore};
|
||||
//! # let mut client_rng = OsRng;
|
||||
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! use opaque_ke::ServerRegistration;
|
||||
//! let mut server_rng = OsRng;
|
||||
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
|
||||
//! let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
//! &mut server_rng,
|
||||
//! client_registration_start_result.message,
|
||||
//! server_kp.public(),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! `r2` is returned to the client, and `server_state` must be persisted on the server for the final step of server
|
||||
//! registration.
|
||||
//!
|
||||
//! In the third step (client registration finish), the client takes as input the `r2` message from the server, along
|
||||
//! with the server's static public key `server_kp.public()`, and uses `client_state` from the first step to run
|
||||
//! `finish` and produce a message `r3` along with the export key `export_key_registration`:
|
||||
//! ### Client Registration Finish
|
||||
//! In the third step of registration, the client takes as input
|
||||
//! a [RegistrationResponse] from the server, and
|
||||
//! a [ClientRegistration] from the first step of registration.
|
||||
//! The client runs [ClientRegistration::finish] to produce a [ClientRegistrationFinishResult], which consists of a [RegistrationUpload]
|
||||
//! to be sent to the server and an `export_key` field which can be used optionally as described in the [Export Key](#export-key) section.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # use rand::{rngs::OsRng, RngCore};
|
||||
//! # let mut client_rng = OsRng;
|
||||
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
|
||||
//! let (r3, export_key_registration) =
|
||||
//! client_state.finish(r2, server_kp.public(), &mut client_rng)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
//! &mut client_rng,
|
||||
//! server_registration_start_result.message,
|
||||
//! ClientRegistrationFinishParameters::default(),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! `r3` is sent to the server, and the client can optionally use `export_key_registration` for applications that choose to
|
||||
//! process user information beyond the OPAQUE functionality (e.g., additional secrets or credentials).
|
||||
//!
|
||||
//! In the fourth step of registration, the server takes as input the `r3` message from the client and uses
|
||||
//! `server_state` from the second step to run `finish` and produce `password_file`:
|
||||
//! ### Server Registration Finish
|
||||
//! In the fourth step of registration, the server takes as input
|
||||
//! a [RegistrationUpload] from the client, and
|
||||
//! a [ServerRegistration] from the second step.
|
||||
//! The server runs [ServerRegistration::finish] to produce a finalized [ServerRegistration].
|
||||
//! At this point, the client can be considered as successfully registered, and the server can invoke
|
||||
//! [ServerRegistration::serialize] to store the password file for use during the login protocol.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # use rand::{rngs::OsRng, RngCore};
|
||||
//! # let mut client_rng = OsRng;
|
||||
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
|
||||
//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
|
||||
//! let password_file = server_state.finish(r3)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
|
||||
//! let password_file = server_registration_start_result.state.finish(
|
||||
//! client_registration_finish_result.message,
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! At this point, the client can be considered as successfully registered, and the server can store
|
||||
//! `password_file.to_bytes()` for use during the login protocol.
|
||||
//!
|
||||
//!
|
||||
//! ## Login
|
||||
//! The login protocol between a client and server also consists of four steps along with three messages, denoted as
|
||||
//! `l1`, `l2`, and `l3`. The server is expected to have access to the a password file corresponding to an output
|
||||
//! of the registration phase. The login protocol will execute successfully only if the same tuple combination of
|
||||
//! (password, pepper, server public key) is presented as was used in the registration phase that produced the
|
||||
//! password file that the server is testing against.
|
||||
//! The login protocol between a client and server also consists of four steps along with three messages:
|
||||
//! [CredentialRequest], [CredentialResponse], [CredentialFinalization]. The server is expected to have access to the password file
|
||||
//! corresponding to an output of the registration phase. The login protocol will execute successfully only if the same password
|
||||
//! was used in the registration phase that produced the password file that the server is testing against.
|
||||
//!
|
||||
//! In the first step (client login start), the client chooses a registration password and an optional "pepper", and runs
|
||||
//! `ClientLogin::start` to produce a message `l1`:
|
||||
//! ### Client Login Start
|
||||
//! In the first step of login, the client chooses as input a login password.
|
||||
//! The client runs [ClientLogin::start] to produce an output consisting of
|
||||
//! a [CredentialRequest] to be sent to the server, and
|
||||
//! a [ClientLogin] which must be persisted on the client for the final step of client login.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ClientRegistration, ServerRegistration, ServerLogin, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # use rand::{rngs::OsRng, RngCore};
|
||||
//! use opaque_ke::{ClientLogin, ClientLoginStartParameters};
|
||||
//! let mut client_rng = OsRng;
|
||||
//! let (l1, client_state) = ClientLogin::<Default>::start(
|
||||
//! let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! &mut client_rng,
|
||||
//! b"password",
|
||||
//! Some(b"pepper"),
|
||||
//! &mut client_rng,
|
||||
//! ClientLoginStartParameters::default(),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! `l1` is sent to the server, and `client_state` must be persisted on the client for the final step of client login.
|
||||
//!
|
||||
//! In the second step (server login start), the server takes as input the `l1` message from the client, the server's
|
||||
//! private key `server_kp.private()`, along with a serialized version of the password file, `password_file_bytes`, and
|
||||
//! runs `ServerLogin::start` to produce `l2`:
|
||||
//! ### Server Login Start
|
||||
//! In the second step of login, the server takes as input
|
||||
//! a [CredentialRequest] from the client,
|
||||
//! the server's private key `server_kp.private()`, and
|
||||
//! the password file output from registration.
|
||||
//! The server runs [ServerLogin::start] to produce an output consisting of
|
||||
//! a [CredentialResponse] which is returned to the client, and
|
||||
//! a [ServerLogin] which must be persisted on the server for the final step of login.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # use rand::{rngs::OsRng, RngCore};
|
||||
//! # let mut client_rng = OsRng;
|
||||
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
|
||||
//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
|
||||
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
|
||||
//! # let (l1, client_state) = ClientLogin::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! use std::convert::TryFrom;
|
||||
//! let password_file = ServerRegistration::<Default>::try_from(&password_file_bytes[..])?;
|
||||
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
|
||||
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
|
||||
//! let mut server_rng = OsRng;
|
||||
//! let (l2, server_state) =
|
||||
//! ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! `l2` is returned to the client, and `server_state` must be persisted on the server for the final step of server login.
|
||||
//!
|
||||
//! In the third step (client login finish), the client takes as input the `l2` message from the server, along with the
|
||||
//! server's static public key `server_kp.public()`, and uses `client_state` from the first step to run `finish` and produce
|
||||
//! a message `l3`, the shared secret `client_shared_secret`, and the export key `export_key_login`:
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # let mut client_rng = OsRng;
|
||||
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # &mut client_rng,
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
|
||||
//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
|
||||
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
|
||||
//! # let (l1, client_state) = ClientLogin::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # &mut client_rng,
|
||||
//! # )?;
|
||||
//! # use std::convert::TryFrom;
|
||||
//! # let password_file =
|
||||
//! # ServerRegistration::<Default>::try_from(
|
||||
//! # &password_file_bytes[..],
|
||||
//! # )?;
|
||||
//! # let (l2, server_state) =
|
||||
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
|
||||
//! let (l3, client_shared_secret, export_key_login) = client_state.finish(
|
||||
//! l2,
|
||||
//! &server_kp.public(),
|
||||
//! &mut client_rng,
|
||||
//! let server_login_start_result = ServerLogin::start(
|
||||
//! &mut server_rng,
|
||||
//! password_file,
|
||||
//! &server_kp.private(),
|
||||
//! client_login_start_result.message,
|
||||
//! ServerLoginStartParameters::default(),
|
||||
//! )?;
|
||||
//! assert_eq!(export_key_registration, export_key_login);
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! Note that if the client supplies a tuple (password, pepper, server public key) that does not match the tuple
|
||||
//! used to create the password file, then at this point the `finish` algorithm outputs the error `InvalidLoginError`.
|
||||
//!
|
||||
//! If `finish` completes successfully, then `l3` is sent to the server, and (similarly to registration) the client
|
||||
//! can use `export_key_login` for applications that can take advantage of the fact that this key is identical to
|
||||
//! `export_key_registration`.
|
||||
//!
|
||||
//! In the fourth step of login, the server takes as input the `l3` message from the client and uses `server_state` from
|
||||
//! the second step to run `finish`:
|
||||
//! ### Client Login Finish
|
||||
//! In the third step of login, the client takes as input a [CredentialResponse] from the server.
|
||||
//! The client runs [ClientLogin::finish] and produces an output consisting of
|
||||
//! a [CredentialFinalization] to be sent to the server to complete the protocol,
|
||||
//! the `session_key` sequence of bytes which will match the server's session key upon a successful login.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
|
||||
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha256;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
|
||||
//! # }
|
||||
//! # use rand_core::{OsRng, RngCore};
|
||||
//! # use rand::{rngs::OsRng, RngCore};
|
||||
//! # let mut client_rng = OsRng;
|
||||
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
|
||||
//! # let (r3, export_key) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
|
||||
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
|
||||
//! # let (l1, client_state) = ClientLogin::<Default>::start(
|
||||
//! # b"password",
|
||||
//! # Some(b"pepper"),
|
||||
//! # &mut client_rng,
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! # use std::convert::TryFrom;
|
||||
//! # let password_file =
|
||||
//! # ServerRegistration::<Default>::try_from(
|
||||
//! # ServerRegistration::<Default>::deserialize(
|
||||
//! # &password_file_bytes[..],
|
||||
//! # )?;
|
||||
//! # let (l2, server_state) =
|
||||
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
|
||||
//! # let (l3, client_shared_secret, export_key) = client_state.finish(
|
||||
//! # l2,
|
||||
//! # &server_kp.public(),
|
||||
//! # &mut client_rng,
|
||||
//! # )?;
|
||||
//! let server_shared_secret = server_state.finish(l3)?;
|
||||
//! assert_eq!(client_shared_secret, server_shared_secret);
|
||||
//! # let server_login_start_result =
|
||||
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
|
||||
//! let client_login_finish_result = client_login_start_result.state.finish(
|
||||
//! server_login_start_result.message,
|
||||
//! ClientLoginFinishParameters::default(),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! If the protocol completes successfully, then the server obtains a `server_shared_secret` which is guaranteed to
|
||||
//! match `client_shared_secret`. Otherwise, on failure, the `finish` algorithm outputs the error `InvalidLoginError`.
|
||||
//!
|
||||
//! ### Server Login Finish
|
||||
//! In the fourth step of login, the server takes as input a [CredentialFinalization] from the client and runs [ServerLogin::finish] to
|
||||
//! produce an output consisting of the `session_key` sequence of bytes which will match the client's session key upon a successful login.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # 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(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! # let password_file =
|
||||
//! # ServerRegistration::<Default>::deserialize(
|
||||
//! # &password_file_bytes[..],
|
||||
//! # )?;
|
||||
//! # let server_login_start_result =
|
||||
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
|
||||
//! # let client_login_finish_result = client_login_start_result.state.finish(
|
||||
//! # server_login_start_result.message,
|
||||
//! # ClientLoginFinishParameters::default(),
|
||||
//! # )?;
|
||||
//! let server_login_finish_result = server_login_start_result.state.finish(
|
||||
//! client_login_finish_result.message,
|
||||
//! )?;
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! client_login_finish_result.session_key,
|
||||
//! server_login_finish_result.session_key,
|
||||
//! );
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! If the protocol completes successfully, then the server obtains a `server_login_finish_result.session_key` which is guaranteed to
|
||||
//! match `client_login_finish_result.session_key` (see the [Session Key](#session-key) section).
|
||||
//! Otherwise, on failure, the [ServerLogin::finish] algorithm outputs the error [InvalidLoginError](errors::PakeError::InvalidLoginError).
|
||||
//!
|
||||
//! # Advanced Usage
|
||||
//!
|
||||
//! This implementation offers support for several optional features of OPAQUE, described below. They are not critical to the
|
||||
//! execution of the main protocol, but can provide additional security benefits which can be suitable for various applications that rely on
|
||||
//! OPAQUE for authentication.
|
||||
//!
|
||||
//! ## Session Key
|
||||
//!
|
||||
//! Upon a successful completion of the OPAQUE protocol (the client runs login with the same password used during registration),
|
||||
//! the client and server have access to a session key, which is a pseudorandomly distributed 32-byte string which only the client
|
||||
//! and server know. Multiple login runs using the same password for the same client will produce different session keys, distributed
|
||||
//! as uniformly random strings. Thus, the session key can be used to establish a secure channel between the client and server.
|
||||
//!
|
||||
//! The session key can be accessed from the `session_key` field of [ClientLoginFinishResult] and [ServerLoginFinishResult]. See
|
||||
//! the combination of [Client Login Finish](#client-login-finish) and [Server Login Finish](#server-login-finish) for example usage.
|
||||
//!
|
||||
//! ## Checking Server Consistency
|
||||
//!
|
||||
//! A [ClientLoginFinishResult] contains the `server_s_pk` field, which is represents the static public key of the server that is established
|
||||
//! during the setup phase. This can be used by the client to verify the authenticity of the server it engages with during the login phase. In particular,
|
||||
//! the client can check that the static public key of the server supplied during registration matches this field during login.
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # 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(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! // During setup, server generates its static keypair
|
||||
//! let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//!
|
||||
//! // During setup or registration, the server transmits its static public key to the client
|
||||
//! let server_s_pk = server_kp.public(); // obtained from the server
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! # let password_file =
|
||||
//! # ServerRegistration::<Default>::deserialize(
|
||||
//! # &password_file_bytes[..],
|
||||
//! # )?;
|
||||
//! # let server_login_start_result =
|
||||
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
|
||||
//!
|
||||
//! // And then later, during login...
|
||||
//! let client_login_finish_result = client_login_start_result.state.finish(
|
||||
//! server_login_start_result.message,
|
||||
//! ClientLoginFinishParameters::default(),
|
||||
//! )?;
|
||||
//!
|
||||
//! // Check that the server's static public key matches what was obtained during
|
||||
//! // setup or registration
|
||||
//! assert_eq!(
|
||||
//! &client_login_finish_result.server_s_pk,
|
||||
//! server_s_pk,
|
||||
//! );
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! Note that without this check over the consistency of the server's static public key, a malicious actor could impersonate the registration server if it were able to copy the password
|
||||
//! file output during registration! Therefore, it is recommended to perform the following check in the application layer if the client can obtain a copy of the server's static
|
||||
//! public key beforehand.
|
||||
//!
|
||||
//!
|
||||
//! ## Export Key
|
||||
//!
|
||||
//! The export key is a pseudorandomly distributed 32-byte string output by both the
|
||||
//! [Client Registration Finish](#client-registration-finish) and [Client Login Finish](#client-login-finish) steps.
|
||||
//! The same export key string will be output by both functions only if the exact same password is passed to [ClientRegistration::start] and [ClientLogin::start].
|
||||
//!
|
||||
//! The export key retains as much secrecy as the password itself, and is similarly derived through an evaluation of the slow hashing function. Hence, only the parties which
|
||||
//! know the password the client uses during registration and login can recover this secret, as it is never exposed to the server. As a result, the export key
|
||||
//! can be used (separately from the OPAQUE protocol) to provide confidentiality and integrity to other data which only the client should be able to process.
|
||||
//! For instance, if the server is expected to maintain any client-side secrets which require a password to access, then this export key can be used to encrypt
|
||||
//! these secrets so that they remain hidden from the server (see [examples/digital_locker.rs](https://github.com/novifinancial/opaque-ke/blob/master/examples/digital_locker.rs)
|
||||
//! for a working example).
|
||||
//!
|
||||
//! You can access the export key from the `export_key` field of [ClientRegistrationFinishResult] and [ClientLoginFinishResult].
|
||||
//!
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # 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(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! // During registration...
|
||||
//! let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
//! &mut client_rng,
|
||||
//! server_registration_start_result.message,
|
||||
//! ClientRegistrationFinishParameters::default()
|
||||
//! )?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! # let password_file =
|
||||
//! # ServerRegistration::<Default>::deserialize(
|
||||
//! # &password_file_bytes[..],
|
||||
//! # )?;
|
||||
//! # let server_login_start_result =
|
||||
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
|
||||
//!
|
||||
//! // And then later, during login...
|
||||
//! let client_login_finish_result = client_login_start_result.state.finish(
|
||||
//! server_login_start_result.message,
|
||||
//! ClientLoginFinishParameters::default(),
|
||||
//! )?;
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! client_registration_finish_result.export_key,
|
||||
//! client_login_finish_result.export_key,
|
||||
//! );
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ## Custom Identifiers
|
||||
//!
|
||||
//! Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered "username" which is sent to the server to
|
||||
//! identify the corresponding password file established during registration. The server may also have an identifier corresponding to an entity (e.g. facebook.com).
|
||||
//! By default, neither of these public identifiers need to be supplied to the OPAQUE protocol.
|
||||
//!
|
||||
//! But, for applications that wish to cryptographically bind these identities to
|
||||
//! the registered password file as well as the session key output by the login phase, these custom identifiers can be specified through
|
||||
//! [ClientRegistrationFinishParameters::WithIdentifiers] in [Client Registration Finish](#client-registration-finish):
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # 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(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
//! &mut client_rng,
|
||||
//! server_registration_start_result.message,
|
||||
//! ClientRegistrationFinishParameters::WithIdentifiers(
|
||||
//! b"username".to_vec(),
|
||||
//! b"facebook.com".to_vec(),
|
||||
//! ),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! The same identifiers must also be supplied using [ServerLoginStartParameters::WithIdentifiers] in [Server Login Start](#server-login-start):
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # 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(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
|
||||
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! let server_login_start_result = ServerLogin::start(
|
||||
//! &mut server_rng,
|
||||
//! password_file,
|
||||
//! &server_kp.private(),
|
||||
//! client_login_start_result.message,
|
||||
//! ServerLoginStartParameters::WithIdentifiers(
|
||||
//! b"username".to_vec(),
|
||||
//! b"facebook.com".to_vec(),
|
||||
//! ),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! as well as [ClientLoginFinishParameters::WithIdentifiers] in [Client Login Finish](#client-login-finish):
|
||||
//! ```
|
||||
//! # use opaque_ke::{
|
||||
//! # errors::ProtocolError,
|
||||
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
|
||||
//! # slow_hash::NoOpHash,
|
||||
//! # };
|
||||
//! # use opaque_ke::ciphersuite::CipherSuite;
|
||||
//! # struct Default;
|
||||
//! # impl CipherSuite for Default {
|
||||
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
|
||||
//! # type Hash = sha2::Sha512;
|
||||
//! # 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(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # )?;
|
||||
//! # let mut server_rng = OsRng;
|
||||
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
|
||||
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
|
||||
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
|
||||
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
|
||||
//! # let client_login_start_result = ClientLogin::<Default>::start(
|
||||
//! # &mut client_rng,
|
||||
//! # b"password",
|
||||
//! # ClientLoginStartParameters::default(),
|
||||
//! # )?;
|
||||
//! # let password_file =
|
||||
//! # ServerRegistration::<Default>::deserialize(
|
||||
//! # &password_file_bytes[..],
|
||||
//! # )?;
|
||||
//! # let server_login_start_result =
|
||||
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
|
||||
//! let client_login_finish_result = client_login_start_result.state.finish(
|
||||
//! server_login_start_result.message,
|
||||
//! ClientLoginFinishParameters::WithIdentifiers(
|
||||
//! b"username".to_vec(),
|
||||
//! b"facebook.com".to_vec(),
|
||||
//! ),
|
||||
//! )?;
|
||||
//! # Ok::<(), ProtocolError>(())
|
||||
//! ```
|
||||
//! Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete
|
||||
//! the protocol!
|
||||
//!
|
||||
//! ## Key Exchange Additional Data
|
||||
//!
|
||||
//! A key exchange protocol typically supports the passing of data between the two parties before the exchange is complete, so as to bind the integrity
|
||||
//! and/or confidentiality of application-specific data to the security of the key exchange. During the login phase, the client and server can pass
|
||||
//! additional data alongside the first two messages of the protocol, with confidential data being supported for the second message.
|
||||
//!
|
||||
//! The following three messages support passing of additional data:
|
||||
//! - The first login message, where the client can populate [ClientLoginStartParameters::WithInfo] with plaintext additional data, and
|
||||
//! the server can retrieve using the `plain_info` field of [ServerLoginStartResult].
|
||||
//! - The second login message, where the server can populate [ServerLoginStartParameters::WithInfo] with confidential additional data,
|
||||
//! and the client can retrieve using the `confidential_info` field of [ClientLoginFinishResult].
|
||||
//!
|
||||
//! For the second login message, the `WithInfoAndIdentifiers` variant can be used to specify these fields in addition to
|
||||
//! [custom identifiers](#custom-identifiers), with the ordering of the fields as `WithInfoAndIdentifiers(confidential_info, username, server_name)`.
|
||||
//!
|
||||
//!
|
||||
|
||||
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
|
||||
@@ -407,13 +713,14 @@ compile_error!(
|
||||
pub mod errors;
|
||||
|
||||
// High-level API
|
||||
pub mod opaque;
|
||||
mod opaque;
|
||||
|
||||
mod messages;
|
||||
|
||||
pub mod ciphersuite;
|
||||
mod envelope;
|
||||
mod hash;
|
||||
pub mod hash;
|
||||
|
||||
mod elligator;
|
||||
pub mod group;
|
||||
|
||||
pub mod map_to_curve;
|
||||
@@ -428,5 +735,26 @@ mod oprf;
|
||||
|
||||
pub mod slow_hash;
|
||||
|
||||
mod serialization;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
// Exports
|
||||
|
||||
pub use rand;
|
||||
|
||||
pub use crate::messages::{
|
||||
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
|
||||
RegistrationResponse, RegistrationUpload,
|
||||
};
|
||||
pub use crate::opaque::{ClientLogin, ClientRegistration, ServerLogin, ServerRegistration};
|
||||
pub use crate::opaque::{
|
||||
ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistrationFinishParameters,
|
||||
ServerLoginStartParameters,
|
||||
};
|
||||
pub use crate::opaque::{
|
||||
ClientLoginFinishResult, ClientLoginStartResult, ClientRegistrationFinishResult,
|
||||
ClientRegistrationStartResult, ServerLoginFinishResult, ServerLoginStartResult,
|
||||
ServerRegistrationStartResult,
|
||||
};
|
||||
|
||||
+204
-13
@@ -6,29 +6,220 @@
|
||||
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
|
||||
//! curve point
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::group::Group;
|
||||
use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint};
|
||||
|
||||
use crate::hash::Hash;
|
||||
use crate::serialization::i2osp;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use digest::{BlockInput, Digest};
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_array::GenericArray;
|
||||
use hkdf::Hkdf;
|
||||
use sha2::{Sha256, Sha512};
|
||||
|
||||
/// A subtrait of Group specifying how to hash a password into a point
|
||||
pub trait GroupWithMapToCurve: Group {
|
||||
/// transforms a password and optional pepper into a curve point
|
||||
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self;
|
||||
/// The ciphersuite identifier as dictated by
|
||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
|
||||
const SUITE_ID: usize;
|
||||
|
||||
/// transforms a password and domain separation tag (DST) into a curve point
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
|
||||
|
||||
/// Generates the contextString parameter as defined in
|
||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
|
||||
fn get_context_string(mode: u8) -> Vec<u8> {
|
||||
[i2osp(mode as usize, 1), i2osp(Self::SUITE_ID, 2)].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupWithMapToCurve for RistrettoPoint {
|
||||
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
|
||||
let (hashed_input, _) = Hkdf::<Sha512>::extract(pepper, password);
|
||||
<Self as Group>::hash_to_curve(GenericArray::from_slice(&hashed_input))
|
||||
const SUITE_ID: usize = 0x0001;
|
||||
|
||||
// Implements the hash_to_ristretto255() function from
|
||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let uniform_bytes =
|
||||
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
|
||||
Ok(<Self as Group>::hash_to_curve(
|
||||
&GenericArray::clone_from_slice(&uniform_bytes[..]),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl GroupWithMapToCurve for EdwardsPoint {
|
||||
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, password);
|
||||
<Self as Group>::hash_to_curve(GenericArray::from_slice(&hashed_input))
|
||||
// Computes ceil(x / y)
|
||||
fn div_ceil(x: usize, y: usize) -> usize {
|
||||
let additive = (x % y != 0) as usize;
|
||||
x / y + additive
|
||||
}
|
||||
|
||||
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalPakeError> {
|
||||
if x.len() != y.len() {
|
||||
return Err(InternalPakeError::HashToCurveError);
|
||||
}
|
||||
|
||||
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
|
||||
}
|
||||
|
||||
// Corresponds to the expand_message_xmd() function defined in
|
||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
||||
pub(crate) fn expand_message_xmd<H: Hash>(
|
||||
msg: &[u8],
|
||||
dst: &[u8],
|
||||
len_in_bytes: usize,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
|
||||
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
|
||||
|
||||
let ell = div_ceil(len_in_bytes, b_in_bytes);
|
||||
if ell > 255 {
|
||||
return Err(InternalPakeError::HashToCurveError);
|
||||
}
|
||||
let dst_prime = [dst, &i2osp(dst.len(), 1)].concat();
|
||||
let z_pad = i2osp(0, r_in_bytes);
|
||||
let l_i_b_str = i2osp(len_in_bytes, 2);
|
||||
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1), &dst_prime].concat();
|
||||
|
||||
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
|
||||
|
||||
let mut h = H::new();
|
||||
h.update(&b[0]);
|
||||
h.update(&i2osp(1, 1));
|
||||
h.update(&dst_prime);
|
||||
b.push(h.finalize_reset().to_vec()); // b[1]
|
||||
|
||||
let mut uniform_bytes: Vec<u8> = Vec::new();
|
||||
uniform_bytes.extend_from_slice(&b[1]);
|
||||
|
||||
for i in 2..(ell + 1) {
|
||||
h.update(xor(&b[0], &b[i - 1])?);
|
||||
h.update(&i2osp(i, 1));
|
||||
h.update(&dst_prime);
|
||||
b.push(h.finalize_reset().to_vec()); // b[i]
|
||||
uniform_bytes.extend_from_slice(&b[i]);
|
||||
}
|
||||
|
||||
Ok(uniform_bytes[..len_in_bytes].to_vec())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
struct Params {
|
||||
msg: &'static str,
|
||||
len_in_bytes: usize,
|
||||
uniform_bytes: &'static str,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_message_xmd() {
|
||||
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
||||
let test_vectors: Vec<Params> = vec![
|
||||
Params {
|
||||
msg: "",
|
||||
len_in_bytes: 0x20,
|
||||
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
|
||||
92181df928fca88",
|
||||
},
|
||||
Params {
|
||||
msg: "abc",
|
||||
len_in_bytes: 0x20,
|
||||
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
|
||||
5a1151775fe4da7",
|
||||
},
|
||||
Params {
|
||||
msg: "abcdef0123456789",
|
||||
len_in_bytes: 0x20,
|
||||
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
|
||||
},
|
||||
Params {
|
||||
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
||||
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
||||
qqqqqqqqqqqqqqqqqqqqqqqqq",
|
||||
len_in_bytes: 0x20,
|
||||
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
|
||||
1716b1b964e1c642",
|
||||
},
|
||||
Params {
|
||||
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
len_in_bytes: 0x20,
|
||||
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
|
||||
350db46f429b771b",
|
||||
},
|
||||
Params {
|
||||
msg: "",
|
||||
len_in_bytes: 0x80,
|
||||
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
|
||||
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
|
||||
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
|
||||
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
|
||||
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
|
||||
},
|
||||
Params {
|
||||
msg: "abc",
|
||||
len_in_bytes: 0x80,
|
||||
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
|
||||
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
|
||||
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
|
||||
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
|
||||
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
|
||||
},
|
||||
Params {
|
||||
msg: "abcdef0123456789",
|
||||
len_in_bytes: 0x80,
|
||||
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
|
||||
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
|
||||
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
|
||||
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
|
||||
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
|
||||
},
|
||||
Params {
|
||||
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
||||
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
|
||||
qqqqqqqqqqqqqqqqqqqqqqqqq",
|
||||
len_in_bytes: 0x80,
|
||||
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
|
||||
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
|
||||
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
|
||||
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
|
||||
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
|
||||
},
|
||||
Params {
|
||||
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
len_in_bytes: 0x80,
|
||||
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
|
||||
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
|
||||
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
|
||||
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
|
||||
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
|
||||
},
|
||||
];
|
||||
let dst = "QUUX-V01-CS02-with-expander";
|
||||
|
||||
for tv in test_vectors {
|
||||
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
|
||||
tv.msg.as_bytes(),
|
||||
dst.as_bytes(),
|
||||
tv.len_in_bytes,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
//! Contains the messages used for OPAQUE
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
envelope::Envelope,
|
||||
errors::{
|
||||
utils::{check_slice_size, check_slice_size_atleast},
|
||||
PakeError, ProtocolError,
|
||||
},
|
||||
group::Group,
|
||||
key_exchange::traits::{KeyExchange, ToBytes},
|
||||
keypair::{Key, KeyPair, SizedBytesExt},
|
||||
};
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use generic_bytes::SizedBytes;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// Messages
|
||||
// =========
|
||||
|
||||
/// The message sent by the client to the server, to initiate registration
|
||||
pub struct RegistrationRequest<CS: CipherSuite> {
|
||||
/// blinded password information
|
||||
pub(crate) alpha: CS::Group,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> RegistrationRequest<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
self.alpha.to_arr().to_vec()
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
|
||||
let checked_slice = check_slice_size(&input, elem_len, "first_message_bytes")?;
|
||||
// Check that the message is actually containing an element of the
|
||||
// correct subgroup
|
||||
let arr = GenericArray::from_slice(checked_slice);
|
||||
let alpha = CS::Group::from_element_slice(arr)?;
|
||||
Ok(Self { alpha })
|
||||
}
|
||||
}
|
||||
|
||||
/// The answer sent by the server to the user, upon reception of the
|
||||
/// registration attempt
|
||||
pub struct RegistrationResponse<CS: CipherSuite> {
|
||||
/// The server's oprf output
|
||||
pub(crate) beta: CS::Group,
|
||||
/// Server's static public key
|
||||
pub(crate) server_s_pk: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> RegistrationResponse<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[self.beta.to_arr().to_vec(), self.server_s_pk.clone()].concat()
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
|
||||
let key_len = <Key as SizedBytes>::Len::to_usize();
|
||||
let checked_slice =
|
||||
check_slice_size(&input, elem_len + key_len, "registration_response_bytes")?;
|
||||
|
||||
// Check that the message is actually containing an element of the
|
||||
// correct subgroup
|
||||
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
|
||||
let beta = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
Ok(Self {
|
||||
server_s_pk: checked_slice[elem_len..].to_vec(),
|
||||
beta,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The final message from the client, containing sealed cryptographic
|
||||
/// identifiers
|
||||
pub struct RegistrationUpload<CS: CipherSuite> {
|
||||
/// The "envelope" generated by the user, containing sealed
|
||||
/// cryptographic identifiers
|
||||
pub(crate) envelope: Envelope<CS::Hash>,
|
||||
/// The user's public key
|
||||
pub(crate) client_s_pk: Key,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> RegistrationUpload<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[
|
||||
self.client_s_pk.to_arr().to_vec(),
|
||||
self.envelope.serialize(),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let key_len = <Key as SizedBytes>::Len::to_usize();
|
||||
|
||||
let checked_slice = check_slice_size_atleast(&input, key_len, "registration_upload_bytes")?;
|
||||
|
||||
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[key_len..])?;
|
||||
|
||||
if !remainder.is_empty() {
|
||||
return Err(PakeError::SerializationError.into());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
envelope,
|
||||
client_s_pk: KeyPair::<CS::Group>::check_public_key(Key::from_bytes(
|
||||
&checked_slice[..key_len],
|
||||
)?)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The message sent by the user to the server, to initiate registration
|
||||
pub struct CredentialRequest<CS: CipherSuite> {
|
||||
/// blinded password information
|
||||
pub(crate) alpha: CS::Group,
|
||||
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> CredentialRequest<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut credential_request: Vec<u8> = Vec::new();
|
||||
credential_request.extend_from_slice(&self.alpha.to_arr());
|
||||
credential_request.extend_from_slice(&self.ke1_message.to_bytes());
|
||||
credential_request
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
|
||||
|
||||
let checked_slice =
|
||||
check_slice_size_atleast(&input, elem_len, "login_first_message_bytes")?;
|
||||
|
||||
// Check that the message is actually containing an element of the
|
||||
// correct subgroup
|
||||
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
|
||||
let alpha = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
let ke1_message =
|
||||
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(
|
||||
&checked_slice[elem_len..],
|
||||
)?;
|
||||
|
||||
Ok(Self { alpha, ke1_message })
|
||||
}
|
||||
}
|
||||
|
||||
/// The answer sent by the server to the user, upon reception of the
|
||||
/// login attempt
|
||||
pub struct CredentialResponse<CS: CipherSuite> {
|
||||
/// the server's oprf output
|
||||
pub(crate) beta: CS::Group,
|
||||
pub(crate) server_s_pk: Key,
|
||||
/// the user's sealed information,
|
||||
pub(crate) envelope: Envelope<CS::Hash>,
|
||||
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> CredentialResponse<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[
|
||||
Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope),
|
||||
self.ke2_message.to_bytes(),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_without_ke(
|
||||
beta: &CS::Group,
|
||||
server_s_pk: &Key,
|
||||
envelope: &Envelope<CS::Hash>,
|
||||
) -> Vec<u8> {
|
||||
[
|
||||
&beta.to_arr(),
|
||||
&server_s_pk.to_arr()[..],
|
||||
&envelope.to_bytes(),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
|
||||
let key_len = <Key as SizedBytes>::Len::to_usize();
|
||||
let checked_slice =
|
||||
check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?;
|
||||
|
||||
// Check that the message is actually containing an element of the
|
||||
// correct subgroup
|
||||
let beta_bytes = &checked_slice[..elem_len];
|
||||
let arr = GenericArray::from_slice(beta_bytes);
|
||||
let beta = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
|
||||
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
|
||||
|
||||
let (envelope, remainder) =
|
||||
Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len + key_len..])?;
|
||||
|
||||
let ke2_message_size = CS::KeyExchange::ke2_message_size();
|
||||
let checked_remainder =
|
||||
check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
|
||||
let ke2_message =
|
||||
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::try_from(
|
||||
&checked_remainder,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
beta,
|
||||
server_s_pk,
|
||||
envelope,
|
||||
ke2_message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The answer sent by the client to the server, upon reception of the
|
||||
/// sealed envelope
|
||||
pub struct CredentialFinalization<CS: CipherSuite> {
|
||||
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> CredentialFinalization<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
self.ke3_message.to_bytes()
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let ke3_message =
|
||||
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(input)?;
|
||||
Ok(Self { ke3_message })
|
||||
}
|
||||
}
|
||||
+460
-602
File diff suppressed because it is too large
Load Diff
+84
-83
@@ -5,89 +5,109 @@
|
||||
|
||||
use crate::{
|
||||
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
|
||||
serialization::serialize,
|
||||
};
|
||||
use digest::Digest;
|
||||
use generic_array::GenericArray;
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
pub struct OprfClientBytes<Grp: Group> {
|
||||
pub alpha: Grp,
|
||||
pub blinding_factor: Grp::Scalar,
|
||||
/// Used to store the OPRF input and blinding factor
|
||||
pub struct Token<Grp: Group> {
|
||||
pub(crate) data: Vec<u8>,
|
||||
pub(crate) blind: Grp::Scalar,
|
||||
}
|
||||
|
||||
static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-";
|
||||
static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-";
|
||||
static MODE_BASE: u8 = 0x00;
|
||||
|
||||
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
|
||||
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
|
||||
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
|
||||
/// an HKDF computation.
|
||||
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
|
||||
pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
pepper: Option<&[u8]>,
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<OprfClientBytes<G>, InternalPakeError> {
|
||||
let mapped_point = G::map_to_curve(input, pepper);
|
||||
let blinding_factor = G::random_scalar(blinding_factor_rng);
|
||||
let alpha = mapped_point * &blinding_factor;
|
||||
Ok(OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
})
|
||||
) -> Result<(Token<G>, G), InternalPakeError> {
|
||||
let blind = G::random_scalar(blinding_factor_rng);
|
||||
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
|
||||
let mapped_point = G::map_to_curve::<H>(input, &dst)?;
|
||||
let blind_token = mapped_point * &blind;
|
||||
Ok((
|
||||
Token {
|
||||
data: input.to_vec(),
|
||||
blind,
|
||||
},
|
||||
blind_token,
|
||||
))
|
||||
}
|
||||
|
||||
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
|
||||
/// message is sent from the server (who holds the OPRF key) to the client.
|
||||
pub(crate) fn generate_oprf2<G: Group>(
|
||||
point: G,
|
||||
oprf_key: &G::Scalar,
|
||||
) -> Result<G, InternalPakeError> {
|
||||
Ok(point * oprf_key)
|
||||
pub(crate) fn evaluate<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
|
||||
point * oprf_key
|
||||
}
|
||||
|
||||
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
|
||||
/// the client unblinds the server's message.
|
||||
pub(crate) fn generate_oprf3<G: Group, H: Hash>(
|
||||
pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
point: G,
|
||||
blinding_factor: &G::Scalar,
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
|
||||
let unblinded = point * &G::scalar_invert(&blinding_factor);
|
||||
let ikm: Vec<u8> = [&unblinded.to_arr()[..], input].concat();
|
||||
let (prk, _) = Hkdf::<H>::extract(None, &ikm);
|
||||
Ok(prk)
|
||||
blind: &G::Scalar,
|
||||
evaluated_element: G,
|
||||
) -> GenericArray<u8, <H as Digest>::OutputSize> {
|
||||
let unblinded_element = evaluated_element * &G::scalar_invert(blind);
|
||||
finalize_after_unblind::<G, H>(input, unblinded_element)
|
||||
}
|
||||
|
||||
// Benchmarking shims
|
||||
#[cfg(feature = "bench")]
|
||||
#[inline]
|
||||
pub fn generate_oprf1_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
|
||||
fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
unblinded_element: G,
|
||||
) -> GenericArray<u8, <H as Digest>::OutputSize> {
|
||||
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
|
||||
let hash_input = [
|
||||
serialize(input, 2),
|
||||
serialize(&unblinded_element.to_arr().to_vec(), 2),
|
||||
serialize(&finalize_dst, 2),
|
||||
]
|
||||
.concat();
|
||||
<H as Digest>::digest(&hash_input)
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Benchmarking shims //
|
||||
////////////////////////
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
pepper: Option<&[u8]>,
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<OprfClientBytes<G>, InternalPakeError> {
|
||||
generate_oprf1(input, pepper, blinding_factor_rng)
|
||||
) -> Result<(Token<G>, G), InternalPakeError> {
|
||||
blind::<R, G, H>(input, blinding_factor_rng)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn generate_oprf2_shim<G: Group>(
|
||||
point: G,
|
||||
oprf_key: &G::Scalar,
|
||||
) -> Result<G, InternalPakeError> {
|
||||
generate_oprf2(point, oprf_key)
|
||||
pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
|
||||
evaluate(point, oprf_key)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn generate_oprf3_shim<G: Group, H: Hash>(
|
||||
input: &[u8],
|
||||
pub fn finalize_shim<G: GroupWithMapToCurve, H: Hash>(
|
||||
token: &Token<G>,
|
||||
point: G,
|
||||
blinding_factor: &G::Scalar,
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
|
||||
generate_oprf3::<G, H>(input, point, blinding_factor)
|
||||
Ok(finalize::<G, H>(&token.data, &token.blind, point))
|
||||
}
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
///////////
|
||||
// Tests //
|
||||
// ===== //
|
||||
///////////
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -95,41 +115,32 @@ mod tests {
|
||||
use crate::group::Group;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::{arr, GenericArray};
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::OsRng;
|
||||
use sha2::{Sha256, Sha512};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha512;
|
||||
|
||||
fn prf(
|
||||
input: &[u8],
|
||||
oprf_key: &[u8; 32],
|
||||
) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
|
||||
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
|
||||
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input));
|
||||
fn prf(input: &[u8], oprf_key: &[u8; 32]) -> GenericArray<u8, <Sha512 as Digest>::OutputSize> {
|
||||
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
|
||||
let point = RistrettoPoint::map_to_curve::<Sha512>(input, &dst).unwrap();
|
||||
let scalar =
|
||||
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
|
||||
let res = point * scalar;
|
||||
let ikm: Vec<u8> = [&res.to_arr()[..], &input].concat();
|
||||
|
||||
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
|
||||
prk
|
||||
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oprf_retrieval() -> Result<(), InternalPakeError> {
|
||||
let input = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
} = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
|
||||
let salt_bytes = arr![
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng)?;
|
||||
let oprf_key_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes)?;
|
||||
let beta = generate_oprf2::<RistrettoPoint>(alpha, &salt)?;
|
||||
let res = generate_oprf3::<RistrettoPoint, sha2::Sha256>(input, beta, &blinding_factor)?;
|
||||
let res2 = prf(&input[..], &salt.as_bytes());
|
||||
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
|
||||
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
|
||||
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta);
|
||||
let res2 = prf(&input[..], &oprf_key.as_bytes());
|
||||
assert_eq!(res, res2);
|
||||
Ok(())
|
||||
}
|
||||
@@ -139,23 +150,13 @@ mod tests {
|
||||
let mut rng = OsRng;
|
||||
let mut input = vec![0u8; 64];
|
||||
rng.fill_bytes(&mut input);
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
|
||||
let res = generate_oprf3::<RistrettoPoint, sha2::Sha256>(&input, alpha, &blinding_factor)
|
||||
.unwrap();
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
|
||||
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha);
|
||||
|
||||
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
|
||||
let mut bits = [0u8; 64];
|
||||
bits.copy_from_slice(&hashed_input);
|
||||
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
|
||||
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
|
||||
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point);
|
||||
|
||||
let point = RistrettoPoint::from_uniform_bytes(&bits);
|
||||
let mut ikm: Vec<u8> = Vec::new();
|
||||
ikm.extend_from_slice(&point.to_arr());
|
||||
ikm.extend_from_slice(&input);
|
||||
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
|
||||
|
||||
assert_eq!(res, prk);
|
||||
assert_eq!(res, res2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::errors::PakeError;
|
||||
|
||||
// Corresponds to the I2OSP() function from RFC8017
|
||||
pub(crate) fn i2osp(input: usize, length: usize) -> Vec<u8> {
|
||||
if length <= std::mem::size_of::<usize>() {
|
||||
return (&input.to_be_bytes()[std::mem::size_of::<usize>() - length..]).to_vec();
|
||||
}
|
||||
|
||||
let mut output = vec![0u8; length];
|
||||
output.splice(
|
||||
length - std::mem::size_of::<usize>()..length,
|
||||
input.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
output
|
||||
}
|
||||
|
||||
// Corresponds to the OS2IP() function from RFC8017
|
||||
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, PakeError> {
|
||||
if input.len() > std::mem::size_of::<usize>() {
|
||||
return Err(PakeError::SerializationError);
|
||||
}
|
||||
|
||||
let mut output_array = [0u8; std::mem::size_of::<usize>()];
|
||||
output_array[std::mem::size_of::<usize>() - input.len()..].copy_from_slice(input);
|
||||
Ok(usize::from_be_bytes(output_array))
|
||||
}
|
||||
|
||||
// Computes I2OSP(len(input), max_bytes) || input
|
||||
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Vec<u8> {
|
||||
[&i2osp(input.len(), max_bytes), input].concat()
|
||||
}
|
||||
|
||||
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
|
||||
// (input, remainder)
|
||||
pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec<u8>, Vec<u8>), PakeError> {
|
||||
if size_bytes > std::mem::size_of::<usize>() || input.len() < size_bytes {
|
||||
return Err(PakeError::SerializationError);
|
||||
}
|
||||
|
||||
let size = os2ip(&input[..size_bytes])?;
|
||||
if size_bytes + size > input.len() {
|
||||
return Err(PakeError::SerializationError);
|
||||
}
|
||||
|
||||
Ok((
|
||||
input[size_bytes..size_bytes + size].to_vec(),
|
||||
input[size_bytes + size..].to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,403 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
envelope::{Envelope, InnerEnvelopeMode},
|
||||
group::Group,
|
||||
key_exchange::{
|
||||
traits::{KeyExchange, ToBytes},
|
||||
tripledh::{NonceLen, TripleDH},
|
||||
},
|
||||
opaque::*,
|
||||
serialization::{i2osp, os2ip, serialize},
|
||||
*,
|
||||
};
|
||||
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_bytes::SizedBytes;
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
use sha2::Digest;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
struct Default;
|
||||
impl CipherSuite for Default {
|
||||
type Group = RistrettoPoint;
|
||||
type KeyExchange = TripleDH;
|
||||
type Hash = sha2::Sha512;
|
||||
type SlowHash = crate::slow_hash::NoOpHash;
|
||||
}
|
||||
|
||||
const MAX_INFO_LENGTH: usize = 10;
|
||||
const MAC_SIZE: usize = 64; // Because of SHA512
|
||||
|
||||
fn random_ristretto_point() -> RistrettoPoint {
|
||||
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)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_registration_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
|
||||
// serialization order: scalar, password
|
||||
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
|
||||
let reg = ClientRegistration::<Default>::deserialize(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.serialize();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_registration_roundtrip() {
|
||||
// If we don't have envelope and client_pk, the server registration just
|
||||
// contains the prf key
|
||||
let mut rng = OsRng;
|
||||
let oprf_key = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
let mut oprf_bytes: Vec<u8> = vec![];
|
||||
oprf_bytes.extend_from_slice(oprf_key.as_bytes());
|
||||
let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
|
||||
let reg_bytes = reg.serialize();
|
||||
assert_eq!(reg_bytes, oprf_bytes);
|
||||
|
||||
let mut ciphertext = [0u8; 32];
|
||||
rng.fill_bytes(&mut ciphertext);
|
||||
|
||||
// Construct a mock envelope
|
||||
let mut mock_envelope_bytes = Vec::new();
|
||||
mock_envelope_bytes.extend_from_slice(&[1; 1]); // mode = 1
|
||||
mock_envelope_bytes.extend_from_slice(&vec![0; NonceLen::to_usize()]); // empty nonce
|
||||
mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
|
||||
mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
|
||||
|
||||
let mock_client_kp = Default::generate_random_keypair(&mut rng);
|
||||
// serialization order: oprf_key, public key, envelope
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.extend_from_slice(oprf_key.as_bytes());
|
||||
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
|
||||
bytes.extend_from_slice(&mock_envelope_bytes);
|
||||
let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.serialize();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registration_request_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let pt_bytes = pt.to_arr().to_vec();
|
||||
|
||||
let mut input = Vec::new();
|
||||
input.extend_from_slice(pt_bytes.as_slice());
|
||||
|
||||
let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap();
|
||||
let r1_bytes = r1.serialize();
|
||||
assert_eq!(input, r1_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registration_response_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let beta_bytes = pt.to_arr();
|
||||
let mut rng = OsRng;
|
||||
let skp = Default::generate_random_keypair(&mut rng);
|
||||
let 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 r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap();
|
||||
let r2_bytes = r2.serialize();
|
||||
assert_eq!(input, r2_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registration_upload_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
let skp = Default::generate_random_keypair(&mut rng);
|
||||
let pubkey_bytes = skp.public().to_arr();
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
rng.fill_bytes(&mut key);
|
||||
|
||||
let mut msg = [0u8; 32];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
|
||||
&mut rng,
|
||||
&key,
|
||||
&msg,
|
||||
&pubkey_bytes,
|
||||
InnerEnvelopeMode::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let envelope_bytes = envelope.serialize();
|
||||
|
||||
let mut input = Vec::new();
|
||||
input.extend_from_slice(&pubkey_bytes[..]);
|
||||
input.extend_from_slice(&envelope_bytes);
|
||||
|
||||
let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap();
|
||||
let r3_bytes = r3.serialize();
|
||||
assert_eq!(input, r3_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credential_request_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
let alpha = random_ristretto_point();
|
||||
let alpha_bytes = alpha.to_arr().to_vec();
|
||||
|
||||
let client_e_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
|
||||
let mut info = [0u8; MAX_INFO_LENGTH];
|
||||
rng.fill_bytes(&mut info);
|
||||
|
||||
let ke1m: Vec<u8> = [
|
||||
&client_nonce[..],
|
||||
&serialize(&info.to_vec(), 2),
|
||||
&client_e_kp.public(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut input = Vec::new();
|
||||
input.extend_from_slice(&alpha_bytes);
|
||||
input.extend_from_slice(&ke1m[..]);
|
||||
|
||||
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap();
|
||||
let l1_bytes = l1.serialize();
|
||||
assert_eq!(input, l1_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credential_response_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let pt_bytes = pt.to_arr().to_vec();
|
||||
|
||||
let mut rng = OsRng;
|
||||
let skp = Default::generate_random_keypair(&mut rng);
|
||||
let pubkey_bytes = skp.public().to_arr();
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
rng.fill_bytes(&mut key);
|
||||
|
||||
let mut msg = [0u8; 32];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
|
||||
&mut rng,
|
||||
&key,
|
||||
&msg,
|
||||
&pubkey_bytes,
|
||||
InnerEnvelopeMode::Base,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let server_e_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut mac = [0u8; MAC_SIZE];
|
||||
rng.fill_bytes(&mut mac);
|
||||
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let mut e_info = [0u8; MAX_INFO_LENGTH];
|
||||
rng.fill_bytes(&mut e_info);
|
||||
|
||||
let ke2m: Vec<u8> = [
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public(),
|
||||
&serialize(&e_info.to_vec(), 2),
|
||||
&mac[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut input = Vec::new();
|
||||
input.extend_from_slice(pt_bytes.as_slice());
|
||||
input.extend_from_slice(&pubkey_bytes.as_slice());
|
||||
input.extend_from_slice(&envelope.serialize());
|
||||
input.extend_from_slice(&ke2m[..]);
|
||||
|
||||
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
|
||||
let l2_bytes = l2.serialize();
|
||||
assert_eq!(input, l2_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_third_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
let mut mac = [0u8; MAC_SIZE];
|
||||
rng.fill_bytes(&mut mac);
|
||||
|
||||
let input: Vec<u8> = [&mac[..]].concat();
|
||||
|
||||
let l3 = CredentialFinalization::<Default>::deserialize(&input).unwrap();
|
||||
let l3_bytes = l3.serialize();
|
||||
assert_eq!(input, l3_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_login_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
|
||||
let client_e_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut client_nonce = vec![0u8; NonceLen::to_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();
|
||||
|
||||
// serialization order: scalar, credential_request, ke1_state, password
|
||||
let bytes: Vec<u8> = [
|
||||
&sc.as_bytes()[..],
|
||||
&serialize(&serialized_credential_request, 2),
|
||||
&serialize(&l1_data, 2),
|
||||
&pw[..],
|
||||
]
|
||||
.concat();
|
||||
let reg = ClientLogin::<Default>::deserialize(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.serialize();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ke1_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let client_e_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
|
||||
let mut info = [0u8; MAX_INFO_LENGTH];
|
||||
rng.fill_bytes(&mut info);
|
||||
|
||||
let ke1m: Vec<u8> = [
|
||||
&client_nonce[..],
|
||||
&serialize(&info.to_vec(), 2),
|
||||
&client_e_kp.public(),
|
||||
]
|
||||
.concat();
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::try_from(&ke1m[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, ke1m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ke2_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let server_e_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut mac = [0u8; MAC_SIZE];
|
||||
rng.fill_bytes(&mut mac);
|
||||
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
let mut e_info = [0u8; MAX_INFO_LENGTH];
|
||||
rng.fill_bytes(&mut e_info);
|
||||
|
||||
let ke2m: Vec<u8> = [
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public(),
|
||||
&serialize(&e_info.to_vec(), 2),
|
||||
&mac[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::try_from(&ke2m[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, ke2m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ke3_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
let mut mac = [0u8; MAC_SIZE];
|
||||
rng.fill_bytes(&mut mac);
|
||||
|
||||
let ke3m: Vec<u8> = [&mac[..]].concat();
|
||||
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::try_from(&ke3m[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, ke3m);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
|
||||
#[test]
|
||||
fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..std::mem::size_of::<usize>())) {
|
||||
assert_eq!(i2osp(os2ip(&bytes)?, bytes.len()), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_registration_request(bytes in vec(any::<u8>(), 0..200)) {
|
||||
RegistrationRequest::<Default>::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);
|
||||
}
|
||||
|
||||
}
|
||||
+15
-5
@@ -5,9 +5,10 @@
|
||||
|
||||
//! Trait specifying a slow hashing function
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::hash::Hash;
|
||||
use crate::{errors::InternalPakeError, hash::Hash};
|
||||
use digest::Digest;
|
||||
#[cfg(feature = "slow-hash")]
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_array::GenericArray;
|
||||
|
||||
/// Used for the slow hashing function in OPAQUE
|
||||
@@ -29,15 +30,24 @@ impl<D: Hash> SlowHash<D> for NoOpHash {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "slow-hash")]
|
||||
const DEFAULT_SCRYPT_LOG_N: u8 = 15u8;
|
||||
#[cfg(feature = "slow-hash")]
|
||||
const DEFAULT_SCRYPT_R: u32 = 8u32;
|
||||
#[cfg(feature = "slow-hash")]
|
||||
const DEFAULT_SCRYPT_P: u32 = 1u32;
|
||||
|
||||
#[cfg(feature = "slow-hash")]
|
||||
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
|
||||
fn hash(
|
||||
input: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
let params = scrypt::ScryptParams::new(15, 8, 1).unwrap();
|
||||
let mut output = [0u8; <D as Digest>::OutputSize::to_usize()];
|
||||
let params =
|
||||
scrypt::ScryptParams::new(DEFAULT_SCRYPT_LOG_N, DEFAULT_SCRYPT_R, DEFAULT_SCRYPT_P)
|
||||
.map_err(|_| InternalPakeError::SlowHashError)?;
|
||||
let mut output = vec![0u8; <D as Digest>::OutputSize::to_usize()];
|
||||
scrypt::scrypt(&input, &[], ¶ms, &mut output)
|
||||
.map_err(|_| InternalPakeError::SlowHashError)?;
|
||||
Ok(output.to_vec())
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,708 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
errors::*,
|
||||
group::Group,
|
||||
key_exchange::tripledh::{NonceLen, TripleDH},
|
||||
keypair::{Key, SizedBytesExt},
|
||||
opaque::*,
|
||||
slow_hash::NoOpHash,
|
||||
tests::mock_rng::CycleRng,
|
||||
*,
|
||||
};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_bytes::SizedBytes;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use serde_json::Value;
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
|
||||
struct RistrettoSha5123dhNoSlowHash;
|
||||
impl CipherSuite for RistrettoSha5123dhNoSlowHash {
|
||||
type Group = RistrettoPoint;
|
||||
type KeyExchange = TripleDH;
|
||||
type Hash = sha2::Sha512;
|
||||
type SlowHash = NoOpHash;
|
||||
}
|
||||
|
||||
pub struct TestVectorParameters {
|
||||
pub client_s_pk: Vec<u8>,
|
||||
pub client_s_sk: Vec<u8>,
|
||||
pub client_e_pk: Vec<u8>,
|
||||
pub client_e_sk: Vec<u8>,
|
||||
pub server_s_pk: Vec<u8>,
|
||||
pub server_s_sk: Vec<u8>,
|
||||
pub server_e_pk: Vec<u8>,
|
||||
pub server_e_sk: Vec<u8>,
|
||||
pub id_u: Vec<u8>,
|
||||
pub id_s: Vec<u8>,
|
||||
pub password: Vec<u8>,
|
||||
pub blinding_factor: Vec<u8>,
|
||||
pub oprf_key: Vec<u8>,
|
||||
pub envelope_nonce: Vec<u8>,
|
||||
pub client_nonce: Vec<u8>,
|
||||
pub server_nonce: Vec<u8>,
|
||||
pub info1: Vec<u8>,
|
||||
pub einfo2: Vec<u8>,
|
||||
pub registration_request: Vec<u8>,
|
||||
pub registration_response: Vec<u8>,
|
||||
pub registration_upload: Vec<u8>,
|
||||
pub credential_request: Vec<u8>,
|
||||
pub credential_response: Vec<u8>,
|
||||
pub credential_finalization: Vec<u8>,
|
||||
client_registration_state: Vec<u8>,
|
||||
server_registration_state: Vec<u8>,
|
||||
client_login_state: Vec<u8>,
|
||||
server_login_state: Vec<u8>,
|
||||
pub password_file: Vec<u8>,
|
||||
pub export_key: Vec<u8>,
|
||||
pub session_key: Vec<u8>,
|
||||
}
|
||||
|
||||
static TEST_VECTOR: &str = r#"
|
||||
{
|
||||
"client_s_pk": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd215",
|
||||
"client_s_sk": "3000848b34d9073885d427e766b7093cc13bdce992ef31cd00ba2c77ff074504",
|
||||
"client_e_pk": "5260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718",
|
||||
"client_e_sk": "1526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e4009",
|
||||
"server_s_pk": "c21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273",
|
||||
"server_s_sk": "2a38b385e9fb7e0a89aa0f005c729b0c4e22eeedea8c105cf136d9c5c334880f",
|
||||
"server_e_pk": "125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f325",
|
||||
"server_e_sk": "7ef6ada36f3983c0f24705a29d3a453e3e17c28d347f226b24c0aa5ab12a700d",
|
||||
"id_u": "696455",
|
||||
"id_s": "696453",
|
||||
"password": "70617373776f7264",
|
||||
"blinding_factor": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105",
|
||||
"oprf_key": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02",
|
||||
"envelope_nonce": "78b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a468",
|
||||
"client_nonce": "43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a64",
|
||||
"server_nonce": "680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e",
|
||||
"info1": "696e666f31",
|
||||
"einfo2": "65696e666f32",
|
||||
"registration_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d",
|
||||
"registration_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273",
|
||||
"registration_upload": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010",
|
||||
"credential_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718",
|
||||
"credential_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b2730278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f32500068a4c321f3c375613862ba83f7e5abb8f1d26dbd8035d39f192eb1324c2214457098054a2cbdf5d4ab2894eaf152af8c4be61d701c5a1ab1ec3e1cee5810898140b81771db0be",
|
||||
"credential_finalization": "330aa8ac01bbc9d9642fe1c286187379efe12da14aceab86b22449d21f242d89adabc0295751b4e007beabc413ae9cbf9979e324749953705fd85b87c9c1b1a2",
|
||||
"client_registration_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba10570617373776f7264",
|
||||
"client_login_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105006714ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b11971800401526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e400943497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a6470617373776f7264",
|
||||
"server_registration_state": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02",
|
||||
"server_login_state": "89cd93dce8f59cf7b187736c50cbc3ca2e0bbbad0a0be1ddee180a2d95db60695b384be2dd673434ef94bcdbb1b457f63f41cd79ed2422c021c34ca1433b70576920ebb950ad1c40c8c015ec6832e12427e755ba21d005f0b6d5d66d2368ab5ec6f32151fced5a3aa25472c425912242de3638ef57f28b0dd02a956064e5bb9b8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47",
|
||||
"password_file": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b026e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010",
|
||||
"export_key": "8197f91f0d4de1ab126d8dfd06abd0d5df420ce40a135ef376e4ffe515930f413632390e7dc3dcfd19afff62b9113e10eb6c359fc327df6e9ad4d0f06c242322",
|
||||
"session_key": "8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47"
|
||||
}
|
||||
"#;
|
||||
|
||||
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
values[key]
|
||||
.as_str()
|
||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
||||
}
|
||||
|
||||
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
|
||||
TestVectorParameters {
|
||||
client_s_pk: decode(&values, "client_s_pk").unwrap(),
|
||||
client_s_sk: decode(&values, "client_s_sk").unwrap(),
|
||||
client_e_pk: decode(&values, "client_e_pk").unwrap(),
|
||||
client_e_sk: decode(&values, "client_e_sk").unwrap(),
|
||||
server_s_pk: decode(&values, "server_s_pk").unwrap(),
|
||||
server_s_sk: decode(&values, "server_s_sk").unwrap(),
|
||||
server_e_pk: decode(&values, "server_e_pk").unwrap(),
|
||||
server_e_sk: decode(&values, "server_e_sk").unwrap(),
|
||||
id_u: decode(&values, "id_u").unwrap(),
|
||||
id_s: decode(&values, "id_s").unwrap(),
|
||||
password: decode(&values, "password").unwrap(),
|
||||
blinding_factor: decode(&values, "blinding_factor").unwrap(),
|
||||
oprf_key: decode(&values, "oprf_key").unwrap(),
|
||||
envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
|
||||
client_nonce: decode(&values, "client_nonce").unwrap(),
|
||||
server_nonce: decode(&values, "server_nonce").unwrap(),
|
||||
info1: decode(&values, "info1").unwrap(),
|
||||
einfo2: decode(&values, "einfo2").unwrap(),
|
||||
registration_request: decode(&values, "registration_request").unwrap(),
|
||||
registration_response: decode(&values, "registration_response").unwrap(),
|
||||
registration_upload: decode(&values, "registration_upload").unwrap(),
|
||||
credential_request: decode(&values, "credential_request").unwrap(),
|
||||
credential_response: decode(&values, "credential_response").unwrap(),
|
||||
credential_finalization: decode(&values, "credential_finalization").unwrap(),
|
||||
client_registration_state: decode(&values, "client_registration_state").unwrap(),
|
||||
client_login_state: decode(&values, "client_login_state").unwrap(),
|
||||
server_registration_state: decode(&values, "server_registration_state").unwrap(),
|
||||
server_login_state: decode(&values, "server_login_state").unwrap(),
|
||||
password_file: decode(&values, "password_file").unwrap(),
|
||||
export_key: decode(&values, "export_key").unwrap(),
|
||||
session_key: decode(&values, "session_key").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_test_vectors(p: &TestVectorParameters) -> String {
|
||||
let mut s = String::new();
|
||||
s.push_str("{\n");
|
||||
s.push_str(format!("\"client_s_pk\": \"{}\",\n", hex::encode(&p.client_s_pk)).as_str());
|
||||
s.push_str(format!("\"client_s_sk\": \"{}\",\n", hex::encode(&p.client_s_sk)).as_str());
|
||||
s.push_str(format!("\"client_e_pk\": \"{}\",\n", hex::encode(&p.client_e_pk)).as_str());
|
||||
s.push_str(format!("\"client_e_sk\": \"{}\",\n", hex::encode(&p.client_e_sk)).as_str());
|
||||
s.push_str(format!("\"server_s_pk\": \"{}\",\n", hex::encode(&p.server_s_pk)).as_str());
|
||||
s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
|
||||
s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
|
||||
s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
|
||||
s.push_str(format!("\"id_u\": \"{}\",\n", hex::encode(&p.id_u)).as_str());
|
||||
s.push_str(format!("\"id_s\": \"{}\",\n", hex::encode(&p.id_s)).as_str());
|
||||
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"blinding_factor\": \"{}\",\n",
|
||||
hex::encode(&p.blinding_factor)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"envelope_nonce\": \"{}\",\n",
|
||||
hex::encode(&p.envelope_nonce)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
|
||||
s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
|
||||
s.push_str(format!("\"info1\": \"{}\",\n", hex::encode(&p.info1)).as_str());
|
||||
s.push_str(format!("\"einfo2\": \"{}\",\n", hex::encode(&p.einfo2)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"registration_request\": \"{}\",\n",
|
||||
hex::encode(&p.registration_request)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"registration_response\": \"{}\",\n",
|
||||
hex::encode(&p.registration_response)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"registration_upload\": \"{}\",\n",
|
||||
hex::encode(&p.registration_upload)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"credential_request\": \"{}\",\n",
|
||||
hex::encode(&p.credential_request)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"credential_response\": \"{}\",\n",
|
||||
hex::encode(&p.credential_response)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"credential_finalization\": \"{}\",\n",
|
||||
hex::encode(&p.credential_finalization)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"client_registration_state\": \"{}\",\n",
|
||||
hex::encode(&p.client_registration_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"client_login_state\": \"{}\",\n",
|
||||
hex::encode(&p.client_login_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"server_registration_state\": \"{}\",\n",
|
||||
hex::encode(&p.server_registration_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"server_login_state\": \"{}\",\n",
|
||||
hex::encode(&p.server_login_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"password_file\": \"{}\",\n",
|
||||
hex::encode(&p.password_file)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"export_key\": \"{}\",\n", hex::encode(&p.export_key)).as_str());
|
||||
s.push_str(format!("\"session_key\": \"{}\"\n", hex::encode(&p.session_key)).as_str());
|
||||
s.push_str("}\n");
|
||||
s
|
||||
}
|
||||
|
||||
fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Inputs
|
||||
let server_s_kp = CS::generate_random_keypair(&mut rng);
|
||||
let server_e_kp = CS::generate_random_keypair(&mut rng);
|
||||
let client_s_kp = CS::generate_random_keypair(&mut rng);
|
||||
let client_e_kp = CS::generate_random_keypair(&mut rng);
|
||||
let id_u = b"idU";
|
||||
let id_s = b"idS";
|
||||
let password = b"password";
|
||||
let mut oprf_key_raw = [0u8; 32];
|
||||
rng.fill_bytes(&mut oprf_key_raw);
|
||||
let mut envelope_nonce = [0u8; 32];
|
||||
rng.fill_bytes(&mut envelope_nonce);
|
||||
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let blinding_factor = CS::Group::random_scalar(&mut rng);
|
||||
let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone();
|
||||
|
||||
let info1 = b"info1";
|
||||
let einfo2 = b"einfo2";
|
||||
|
||||
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_bytes.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<CS>::start(&mut blinding_factor_registration_rng, password).unwrap();
|
||||
let blinding_factor_bytes_returned =
|
||||
CS::Group::scalar_as_bytes(&client_registration_start_result.state.token.blind).clone();
|
||||
assert_eq!(
|
||||
hex::encode(&blinding_factor_bytes),
|
||||
hex::encode(&blinding_factor_bytes_returned)
|
||||
);
|
||||
|
||||
let registration_request_bytes = client_registration_start_result
|
||||
.message
|
||||
.serialize()
|
||||
.to_vec();
|
||||
let client_registration_state = client_registration_start_result.state.serialize().to_vec();
|
||||
|
||||
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
|
||||
let server_registration_start_result = ServerRegistration::<CS>::start(
|
||||
&mut oprf_key_rng,
|
||||
client_registration_start_result.message,
|
||||
server_s_kp.public(),
|
||||
)
|
||||
.unwrap();
|
||||
let registration_response_bytes = server_registration_start_result
|
||||
.message
|
||||
.serialize()
|
||||
.to_vec();
|
||||
let oprf_key_bytes =
|
||||
CS::Group::scalar_as_bytes(&server_registration_start_result.state.oprf_key).clone();
|
||||
let server_registration_state = server_registration_start_result.state.serialize().to_vec();
|
||||
|
||||
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
|
||||
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().to_arr());
|
||||
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
|
||||
|
||||
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
|
||||
let client_registration_finish_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut finish_registration_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
let registration_upload_bytes = client_registration_finish_result
|
||||
.message
|
||||
.serialize()
|
||||
.to_vec();
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)
|
||||
.unwrap();
|
||||
let password_file_bytes = password_file.serialize();
|
||||
|
||||
let mut client_login_start: Vec<u8> = Vec::new();
|
||||
client_login_start.extend_from_slice(&blinding_factor_bytes);
|
||||
client_login_start.extend_from_slice(&client_e_kp.private().to_arr());
|
||||
client_login_start.extend_from_slice(&client_nonce);
|
||||
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result = ClientLogin::<CS>::start(
|
||||
&mut client_login_start_rng,
|
||||
password,
|
||||
ClientLoginStartParameters::WithInfo(info1.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize().to_vec();
|
||||
let client_login_state = client_login_start_result.state.serialize().to_vec();
|
||||
|
||||
let mut server_e_sk_and_nonce_rng = CycleRng::new(
|
||||
[
|
||||
server_e_kp.private().to_arr().to_vec(),
|
||||
server_nonce.to_vec(),
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_e_sk_and_nonce_rng,
|
||||
password_file,
|
||||
server_s_kp.private(),
|
||||
client_login_start_result.message,
|
||||
ServerLoginStartParameters::WithInfoAndIdentifiers(
|
||||
einfo2.to_vec(),
|
||||
id_u.to_vec(),
|
||||
id_s.to_vec(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize().to_vec();
|
||||
let server_login_state = server_login_start_result.state.serialize().to_vec();
|
||||
|
||||
let client_login_finish_result = client_login_start_result
|
||||
.state
|
||||
.finish(
|
||||
server_login_start_result.message,
|
||||
ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize();
|
||||
|
||||
TestVectorParameters {
|
||||
client_s_pk: client_s_kp.public().to_arr().to_vec(),
|
||||
client_s_sk: client_s_kp.private().to_arr().to_vec(),
|
||||
client_e_pk: client_e_kp.public().to_arr().to_vec(),
|
||||
client_e_sk: client_e_kp.private().to_arr().to_vec(),
|
||||
server_s_pk: server_s_kp.public().to_arr().to_vec(),
|
||||
server_s_sk: server_s_kp.private().to_arr().to_vec(),
|
||||
server_e_pk: server_e_kp.public().to_arr().to_vec(),
|
||||
server_e_sk: server_e_kp.private().to_arr().to_vec(),
|
||||
id_u: id_u.to_vec(),
|
||||
id_s: id_s.to_vec(),
|
||||
password: password.to_vec(),
|
||||
blinding_factor: blinding_factor_bytes.to_vec(),
|
||||
oprf_key: oprf_key_bytes.to_vec(),
|
||||
envelope_nonce: envelope_nonce.to_vec(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
server_nonce: server_nonce.to_vec(),
|
||||
info1: info1.to_vec(),
|
||||
einfo2: einfo2.to_vec(),
|
||||
registration_request: registration_request_bytes,
|
||||
registration_response: registration_response_bytes,
|
||||
registration_upload: registration_upload_bytes,
|
||||
credential_request: credential_request_bytes,
|
||||
credential_response: credential_response_bytes,
|
||||
credential_finalization: credential_finalization_bytes,
|
||||
password_file: password_file_bytes,
|
||||
client_registration_state,
|
||||
server_registration_state,
|
||||
client_login_state,
|
||||
server_login_state,
|
||||
session_key: client_login_finish_result.session_key,
|
||||
export_key: client_registration_finish_result.export_key.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_test_vectors() {
|
||||
let parameters = generate_parameters::<RistrettoSha5123dhNoSlowHash>();
|
||||
println!("{}", stringify_test_vectors(¶meters));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_request() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
let mut rng = CycleRng::new(parameters.blinding_factor.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(&mut rng, ¶meters.password)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.registration_request),
|
||||
hex::encode(client_registration_start_result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_registration_state),
|
||||
hex::encode(client_registration_start_result.state.serialize())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_response() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut oprf_key_rng,
|
||||
RegistrationRequest::deserialize(¶meters.registration_request[..])?,
|
||||
&Key::from_bytes(¶meters.server_s_pk[..])?,
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(parameters.registration_response),
|
||||
hex::encode(server_registration_start_result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_registration_state),
|
||||
hex::encode(server_registration_start_result.state.serialize())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_upload() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_s_sk_and_nonce: Vec<u8> =
|
||||
[parameters.client_s_sk, parameters.envelope_nonce].concat();
|
||||
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
|
||||
let result = ClientRegistration::<RistrettoSha5123dhNoSlowHash>::deserialize(
|
||||
¶meters.client_registration_state[..],
|
||||
)?
|
||||
.finish(
|
||||
&mut finish_registration_rng,
|
||||
RegistrationResponse::deserialize(¶meters.registration_response[..])?,
|
||||
ClientRegistrationFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.registration_upload),
|
||||
hex::encode(result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(parameters.export_key),
|
||||
hex::encode(result.export_key.to_vec())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_file() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let server_registration = ServerRegistration::<RistrettoSha5123dhNoSlowHash>::deserialize(
|
||||
¶meters.server_registration_state[..],
|
||||
)?;
|
||||
let password_file = server_registration.finish(RegistrationUpload::deserialize(
|
||||
¶meters.registration_upload[..],
|
||||
)?)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.password_file),
|
||||
hex::encode(password_file.serialize())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credential_request() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_login_start_rng = [
|
||||
parameters.blinding_factor,
|
||||
parameters.client_e_sk,
|
||||
parameters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start_rng);
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_login_start_rng,
|
||||
¶meters.password,
|
||||
ClientLoginStartParameters::WithInfo(parameters.info1),
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.credential_request),
|
||||
hex::encode(client_login_start_result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_login_state),
|
||||
hex::encode(client_login_start_result.state.serialize())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credential_response() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let mut server_e_sk_and_nonce_rng =
|
||||
CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat());
|
||||
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_e_sk_and_nonce_rng,
|
||||
ServerRegistration::deserialize(¶meters.password_file[..])?,
|
||||
&Key::from_bytes(¶meters.server_s_sk[..])?,
|
||||
CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize(
|
||||
¶meters.credential_request[..],
|
||||
)?,
|
||||
ServerLoginStartParameters::WithInfoAndIdentifiers(
|
||||
parameters.einfo2.to_vec(),
|
||||
parameters.id_u,
|
||||
parameters.id_s,
|
||||
),
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.info1),
|
||||
hex::encode(server_login_start_result.plain_info),
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.credential_response),
|
||||
hex::encode(server_login_start_result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_login_state),
|
||||
hex::encode(server_login_start_result.state.serialize())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_credential_finalization() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_login_finish_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::deserialize(
|
||||
¶meters.client_login_state[..],
|
||||
)?
|
||||
.finish(
|
||||
CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize(
|
||||
¶meters.credential_response[..],
|
||||
)?,
|
||||
ClientLoginFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.einfo2),
|
||||
hex::encode(&client_login_finish_result.confidential_info)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_s_pk),
|
||||
hex::encode(&client_login_finish_result.server_s_pk.to_arr().to_vec())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.session_key),
|
||||
hex::encode(&client_login_finish_result.session_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.credential_finalization),
|
||||
hex::encode(client_login_finish_result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.export_key),
|
||||
hex::encode(client_login_finish_result.export_key)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let server_login_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::deserialize(
|
||||
¶meters.server_login_state[..],
|
||||
)?
|
||||
.finish(CredentialFinalization::deserialize(
|
||||
¶meters.credential_finalization[..],
|
||||
)?)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.session_key),
|
||||
hex::encode(server_login_result.session_key)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_complete_flow(
|
||||
registration_password: &[u8],
|
||||
login_password: &[u8],
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
registration_password,
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
let p_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)?;
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
login_password,
|
||||
ClientLoginStartParameters::default(),
|
||||
)?;
|
||||
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
p_file,
|
||||
&server_kp.private(),
|
||||
client_login_start_result.message,
|
||||
ServerLoginStartParameters::default(),
|
||||
)?;
|
||||
|
||||
let client_login_result = client_login_start_result.state.finish(
|
||||
server_login_start_result.message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
if hex::encode(registration_password) == hex::encode(login_password) {
|
||||
let client_login_finish_result = client_login_result?;
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(client_login_finish_result.message)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(server_login_finish_result.session_key),
|
||||
hex::encode(client_login_finish_result.session_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(client_registration_finish_result.export_key),
|
||||
hex::encode(client_login_finish_result.export_key)
|
||||
);
|
||||
} else {
|
||||
assert!(match client_login_result {
|
||||
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_flow_success() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"good password")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_flow_fail() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"bad password")
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use rand_core::{CryptoRng, Error, RngCore};
|
||||
use rand::{CryptoRng, Error, RngCore};
|
||||
use std::cmp::min;
|
||||
|
||||
/// A simple implementation of `RngCore` for testing purposes.
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
mod full_test;
|
||||
pub mod mock_rng;
|
||||
mod opaque_ke_test;
|
||||
mod serialization;
|
||||
mod opaque_test_vectors;
|
||||
mod voprf_test_vectors;
|
||||
|
||||
@@ -1,579 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
errors::*,
|
||||
group::Group,
|
||||
key_exchange::tripledh::{TripleDH, NONCE_LEN},
|
||||
keypair::{Key, KeyPair, X25519KeyPair},
|
||||
opaque::*,
|
||||
slow_hash::NoOpHash,
|
||||
tests::mock_rng::CycleRng,
|
||||
};
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
|
||||
struct X255193dhNoSlowHash;
|
||||
impl CipherSuite for X255193dhNoSlowHash {
|
||||
type Group = EdwardsPoint;
|
||||
type KeyFormat = X25519KeyPair;
|
||||
type KeyExchange = TripleDH;
|
||||
type Hash = sha2::Sha256;
|
||||
type SlowHash = NoOpHash;
|
||||
}
|
||||
|
||||
pub struct TestVectorParameters {
|
||||
pub client_s_pk: Vec<u8>,
|
||||
pub client_s_sk: Vec<u8>,
|
||||
pub client_e_pk: Vec<u8>,
|
||||
pub client_e_sk: Vec<u8>,
|
||||
pub server_s_pk: Vec<u8>,
|
||||
pub server_s_sk: Vec<u8>,
|
||||
pub server_e_pk: Vec<u8>,
|
||||
pub server_e_sk: Vec<u8>,
|
||||
pub password: Vec<u8>,
|
||||
pub blinding_factor_raw: Vec<u8>,
|
||||
pub blinding_factor: Vec<u8>,
|
||||
pub pepper: Vec<u8>,
|
||||
pub oprf_key: Vec<u8>,
|
||||
pub envelope_nonce: Vec<u8>,
|
||||
pub client_nonce: Vec<u8>,
|
||||
pub server_nonce: Vec<u8>,
|
||||
pub r1: Vec<u8>,
|
||||
pub r2: Vec<u8>,
|
||||
pub r3: Vec<u8>,
|
||||
pub l1: Vec<u8>,
|
||||
pub l2: Vec<u8>,
|
||||
pub l3: Vec<u8>,
|
||||
client_registration_state: Vec<u8>,
|
||||
server_registration_state: Vec<u8>,
|
||||
client_login_state: Vec<u8>,
|
||||
server_login_state: Vec<u8>,
|
||||
pub password_file: Vec<u8>,
|
||||
pub export_key: Vec<u8>,
|
||||
pub shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
static TEST_VECTOR: &str = r#"
|
||||
{
|
||||
"client_s_pk": "b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29",
|
||||
"client_s_sk": "701e8cd1263abd2f2a22d4dc94b1d5fe3c9cb14030e7e7c154745825b059fd7f",
|
||||
"client_e_pk": "97cb1eb93a69542597517b110ccca457d5ce8d8bfcbfb2a9258bb7b4bd7f716e",
|
||||
"client_e_sk": "80616968ed8daae02c02d3ba41a70104ed0deecd2276e058994d601a1351b359",
|
||||
"server_s_pk": "e12d737e520eaf8504fbf302c2945011bff360bdf02ee102f2ebd6a883c80e02",
|
||||
"server_s_sk": "9075d3d3c5b6bc2f6218e7672c0532c619ce09dddf196006c5ffdaf628a3d760",
|
||||
"server_e_pk": "f73d27d7ca78ded52209bc3bae000f9d95b147360edac1e97c148a3a7396a279",
|
||||
"server_e_sk": "a0e59a07908fc793c590fd83343003a54330e24af908ed31c921e6e6504c3248",
|
||||
"password": "70617373776f7264",
|
||||
"blinding_factor_raw": "ca2d8ae51794579bd0f46044d7daccf222b4590053536b48575bc169f7478fd0a0b580fb0aae948c26ba403a2e7b98f563e434a0aad93f4105419c474453c34e",
|
||||
"blinding_factor": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e02",
|
||||
"pepper": "706570706572",
|
||||
"oprf_key": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907",
|
||||
"envelope_nonce": "b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8",
|
||||
"client_nonce": "b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d05572",
|
||||
"server_nonce": "a213c02274e7f20fc3b571d25e98854c5dae2cfde6c9bf228a66bf3eff3e2a97",
|
||||
"r1": "7e2c67a156ab27490f20008fcae9e9f722d8a9f4eeac373a711259981ca05dd5",
|
||||
"r2": "710fdd19883e869e784c84f2864fa0bfc227662404b77cc8a54d79ae7fb931ea",
|
||||
"r3": "b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29",
|
||||
"l1": "7e2c67a156ab27490f20008fcae9e9f722d8a9f4eeac373a711259981ca05dd5b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d0557297cb1eb93a69542597517b110ccca457d5ce8d8bfcbfb2a9258bb7b4bd7f716e",
|
||||
"l2": "710fdd19883e869e784c84f2864fa0bfc227662404b77cc8a54d79ae7fb931eab0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933a0e59a07908fc793c590fd83343003a54330e24af908ed31c921e6e6504c3248f73d27d7ca78ded52209bc3bae000f9d95b147360edac1e97c148a3a7396a27939ccf2a17a5b281068665b4865e6c6331533461a8e10a4ceffc4c6a6609c326a",
|
||||
"l3": "127144e6469e001d56237a58c8c869a8173e042bf2ff19d8331441d36ada9c3f",
|
||||
"client_registration_state": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e0270617373776f7264",
|
||||
"client_login_state": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e0280616968ed8daae02c02d3ba41a70104ed0deecd2276e058994d601a1351b359b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d05572f258311568d792d6ebecee225c0fde4512139e29a435e9f9a0b82dc3809a83ab70617373776f7264",
|
||||
"server_registration_state": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907",
|
||||
"server_login_state": "ebc0953924d55ad66aa801a7c85f47f35889b90002451a04fb7134b8a2a5a33cd69098c0a81ce06f58cbe4fd6ba23c9c1404ad6f639ba64d5f0f7bf0a041fc5872b17f13bd41cbfbdfa8d74bc94ec1abcc77b9a3da8fbad918ca0a5f84a81443",
|
||||
"password_file": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933",
|
||||
"export_key": "da3a52148a58168c9f804df5e216e3d3f16e935d4d70a5eb249433d88e02ae4c",
|
||||
"shared_secret": "72b17f13bd41cbfbdfa8d74bc94ec1abcc77b9a3da8fbad918ca0a5f84a81443"
|
||||
}
|
||||
"#;
|
||||
|
||||
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
values[key]
|
||||
.as_str()
|
||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
||||
}
|
||||
|
||||
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
|
||||
TestVectorParameters {
|
||||
client_s_pk: decode(&values, "client_s_pk").unwrap(),
|
||||
client_s_sk: decode(&values, "client_s_sk").unwrap(),
|
||||
client_e_pk: decode(&values, "client_e_pk").unwrap(),
|
||||
client_e_sk: decode(&values, "client_e_sk").unwrap(),
|
||||
server_s_pk: decode(&values, "server_s_pk").unwrap(),
|
||||
server_s_sk: decode(&values, "server_s_sk").unwrap(),
|
||||
server_e_pk: decode(&values, "server_e_pk").unwrap(),
|
||||
server_e_sk: decode(&values, "server_e_sk").unwrap(),
|
||||
password: decode(&values, "password").unwrap(),
|
||||
blinding_factor_raw: decode(&values, "blinding_factor_raw").unwrap(),
|
||||
blinding_factor: decode(&values, "blinding_factor").unwrap(),
|
||||
pepper: decode(&values, "pepper").unwrap(),
|
||||
oprf_key: decode(&values, "oprf_key").unwrap(),
|
||||
envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
|
||||
client_nonce: decode(&values, "client_nonce").unwrap(),
|
||||
server_nonce: decode(&values, "server_nonce").unwrap(),
|
||||
r1: decode(&values, "r1").unwrap(),
|
||||
r2: decode(&values, "r2").unwrap(),
|
||||
r3: decode(&values, "r3").unwrap(),
|
||||
l1: decode(&values, "l1").unwrap(),
|
||||
l2: decode(&values, "l2").unwrap(),
|
||||
l3: decode(&values, "l3").unwrap(),
|
||||
client_registration_state: decode(&values, "client_registration_state").unwrap(),
|
||||
client_login_state: decode(&values, "client_login_state").unwrap(),
|
||||
server_registration_state: decode(&values, "server_registration_state").unwrap(),
|
||||
server_login_state: decode(&values, "server_login_state").unwrap(),
|
||||
password_file: decode(&values, "password_file").unwrap(),
|
||||
export_key: decode(&values, "export_key").unwrap(),
|
||||
shared_secret: decode(&values, "shared_secret").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_test_vectors(p: &TestVectorParameters) -> String {
|
||||
let mut s = String::new();
|
||||
s.push_str("{\n");
|
||||
s.push_str(format!("\"client_s_pk\": \"{}\",\n", hex::encode(&p.client_s_pk)).as_str());
|
||||
s.push_str(format!("\"client_s_sk\": \"{}\",\n", hex::encode(&p.client_s_sk)).as_str());
|
||||
s.push_str(format!("\"client_e_pk\": \"{}\",\n", hex::encode(&p.client_e_pk)).as_str());
|
||||
s.push_str(format!("\"client_e_sk\": \"{}\",\n", hex::encode(&p.client_e_sk)).as_str());
|
||||
s.push_str(format!("\"server_s_pk\": \"{}\",\n", hex::encode(&p.server_s_pk)).as_str());
|
||||
s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
|
||||
s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
|
||||
s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
|
||||
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"blinding_factor_raw\": \"{}\",\n",
|
||||
hex::encode(&p.blinding_factor_raw)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"blinding_factor\": \"{}\",\n",
|
||||
hex::encode(&p.blinding_factor)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"pepper\": \"{}\",\n", hex::encode(&p.pepper)).as_str());
|
||||
s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"envelope_nonce\": \"{}\",\n",
|
||||
hex::encode(&p.envelope_nonce)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
|
||||
s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
|
||||
s.push_str(format!("\"r1\": \"{}\",\n", hex::encode(&p.r1)).as_str());
|
||||
s.push_str(format!("\"r2\": \"{}\",\n", hex::encode(&p.r2)).as_str());
|
||||
s.push_str(format!("\"r3\": \"{}\",\n", hex::encode(&p.r3)).as_str());
|
||||
s.push_str(format!("\"l1\": \"{}\",\n", hex::encode(&p.l1)).as_str());
|
||||
s.push_str(format!("\"l2\": \"{}\",\n", hex::encode(&p.l2)).as_str());
|
||||
s.push_str(format!("\"l3\": \"{}\",\n", hex::encode(&p.l3)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"client_registration_state\": \"{}\",\n",
|
||||
hex::encode(&p.client_registration_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"client_login_state\": \"{}\",\n",
|
||||
hex::encode(&p.client_login_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"server_registration_state\": \"{}\",\n",
|
||||
hex::encode(&p.server_registration_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"server_login_state\": \"{}\",\n",
|
||||
hex::encode(&p.server_login_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"password_file\": \"{}\",\n",
|
||||
hex::encode(&p.password_file)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"export_key\": \"{}\",\n", hex::encode(&p.export_key)).as_str());
|
||||
s.push_str(format!("\"shared_secret\": \"{}\"\n", hex::encode(&p.shared_secret)).as_str());
|
||||
s.push_str("}\n");
|
||||
s
|
||||
}
|
||||
|
||||
fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Inputs
|
||||
let server_s_kp = CS::generate_random_keypair(&mut rng).unwrap();
|
||||
let server_e_kp = CS::generate_random_keypair(&mut rng).unwrap();
|
||||
let client_s_kp = CS::generate_random_keypair(&mut rng).unwrap();
|
||||
let client_e_kp = CS::generate_random_keypair(&mut rng).unwrap();
|
||||
let password = b"password";
|
||||
let pepper = b"pepper";
|
||||
let mut blinding_factor_raw = [0u8; 64];
|
||||
rng.fill_bytes(&mut blinding_factor_raw);
|
||||
let mut oprf_key_raw = [0u8; 32];
|
||||
rng.fill_bytes(&mut oprf_key_raw);
|
||||
let mut envelope_nonce = [0u8; 32];
|
||||
rng.fill_bytes(&mut envelope_nonce);
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
let mut server_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
|
||||
let (r1, client_registration) = ClientRegistration::<CS>::start(
|
||||
password,
|
||||
Some(pepper),
|
||||
&mut blinding_factor_registration_rng,
|
||||
)
|
||||
.unwrap();
|
||||
let r1_bytes = r1.to_bytes().to_vec();
|
||||
let blinding_factor_bytes =
|
||||
CS::Group::scalar_as_bytes(&client_registration.blinding_factor).clone();
|
||||
let client_registration_state = client_registration.to_bytes().to_vec();
|
||||
|
||||
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
|
||||
let (r2, server_registration) = ServerRegistration::<CS>::start(r1, &mut oprf_key_rng).unwrap();
|
||||
let r2_bytes = r2.to_bytes().to_vec();
|
||||
let oprf_key_bytes = CS::Group::scalar_as_bytes(&server_registration.oprf_key).clone();
|
||||
let server_registration_state = server_registration.to_bytes().to_vec();
|
||||
|
||||
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
|
||||
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private());
|
||||
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
|
||||
|
||||
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
|
||||
let (r3, export_key_registration) = client_registration
|
||||
.finish(r2, server_s_kp.public(), &mut finish_registration_rng)
|
||||
.unwrap();
|
||||
let r3_bytes = r3.to_bytes().to_vec();
|
||||
|
||||
let password_file = server_registration.finish(r3).unwrap();
|
||||
let password_file_bytes = password_file.to_bytes();
|
||||
|
||||
let mut client_login_start: Vec<u8> = Vec::new();
|
||||
client_login_start.extend_from_slice(&blinding_factor_raw);
|
||||
client_login_start.extend_from_slice(&client_e_kp.private());
|
||||
client_login_start.extend_from_slice(&client_nonce);
|
||||
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let (l1, client_login) =
|
||||
ClientLogin::<CS>::start(password, Some(pepper), &mut client_login_start_rng).unwrap();
|
||||
let l1_bytes = l1.to_bytes().to_vec();
|
||||
let client_login_state = client_login.to_bytes().to_vec();
|
||||
|
||||
let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_vec());
|
||||
let (l2, server_login) = ServerLogin::<CS>::start(
|
||||
password_file,
|
||||
server_s_kp.private(),
|
||||
l1,
|
||||
&mut server_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
let l2_bytes = l2.to_bytes().to_vec();
|
||||
let server_login_state = server_login.to_bytes().to_vec();
|
||||
|
||||
let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_vec());
|
||||
let (l3, client_shared_secret, _export_key_login) = client_login
|
||||
.finish(l2, server_s_kp.public(), &mut client_e_sk_rng)
|
||||
.unwrap();
|
||||
let l3_bytes = l3.to_bytes().to_vec();
|
||||
|
||||
TestVectorParameters {
|
||||
client_s_pk: client_s_kp.public().to_vec(),
|
||||
client_s_sk: client_s_kp.private().to_vec(),
|
||||
client_e_pk: client_e_kp.public().to_vec(),
|
||||
client_e_sk: client_e_kp.private().to_vec(),
|
||||
server_s_pk: server_s_kp.public().to_vec(),
|
||||
server_s_sk: server_s_kp.private().to_vec(),
|
||||
server_e_pk: server_e_kp.public().to_vec(),
|
||||
server_e_sk: server_e_kp.private().to_vec(),
|
||||
password: password.to_vec(),
|
||||
blinding_factor_raw: blinding_factor_raw.to_vec(),
|
||||
blinding_factor: blinding_factor_bytes.to_vec(),
|
||||
pepper: pepper.to_vec(),
|
||||
oprf_key: oprf_key_bytes.to_vec(),
|
||||
envelope_nonce: envelope_nonce.to_vec(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
server_nonce: server_nonce.to_vec(),
|
||||
r1: r1_bytes,
|
||||
r2: r2_bytes,
|
||||
r3: r3_bytes,
|
||||
l1: l1_bytes,
|
||||
l2: l2_bytes,
|
||||
l3: l3_bytes,
|
||||
password_file: password_file_bytes,
|
||||
client_registration_state,
|
||||
server_registration_state,
|
||||
client_login_state,
|
||||
server_login_state,
|
||||
shared_secret: client_shared_secret,
|
||||
export_key: export_key_registration.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_test_vectors() {
|
||||
let parameters = generate_parameters::<X255193dhNoSlowHash>();
|
||||
println!("{}", stringify_test_vectors(¶meters));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r1() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
let mut blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
|
||||
let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start(
|
||||
¶meters.password,
|
||||
Some(¶meters.pepper),
|
||||
&mut blinding_factor_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(¶meters.r1), hex::encode(r1.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_registration_state),
|
||||
hex::encode(client_registration.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r2() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
|
||||
let (r2, server_registration) = ServerRegistration::<X255193dhNoSlowHash>::start(
|
||||
RegisterFirstMessage::try_from(¶meters.r1[..]).unwrap(),
|
||||
&mut oprf_key_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_registration_state),
|
||||
hex::encode(server_registration.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r3() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_s_sk_and_nonce: Vec<u8> =
|
||||
[parameters.client_s_sk, parameters.envelope_nonce].concat();
|
||||
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
|
||||
let (r3, export_key_registration) = ClientRegistration::<X255193dhNoSlowHash>::try_from(
|
||||
¶meters.client_registration_state[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish(
|
||||
RegisterSecondMessage::try_from(¶meters.r2[..]).unwrap(),
|
||||
&Key::try_from(¶meters.server_s_pk[..]).unwrap(),
|
||||
&mut finish_registration_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hex::encode(parameters.r3), hex::encode(r3.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(parameters.export_key),
|
||||
hex::encode(export_key_registration.to_vec())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_file() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let server_registration = ServerRegistration::<X255193dhNoSlowHash>::try_from(
|
||||
¶meters.server_registration_state[..],
|
||||
)
|
||||
.unwrap();
|
||||
let password_file = server_registration
|
||||
.finish(RegisterThirdMessage::try_from(¶meters.r3[..]).unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.password_file),
|
||||
hex::encode(password_file.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l1() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_login_start = [
|
||||
parameters.blinding_factor_raw,
|
||||
parameters.client_e_sk,
|
||||
parameters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let (l1, client_login) = ClientLogin::<X255193dhNoSlowHash>::start(
|
||||
¶meters.password,
|
||||
Some(¶meters.pepper),
|
||||
&mut client_login_start_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(¶meters.l1), hex::encode(l1.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_login_state),
|
||||
hex::encode(client_login.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l2() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
|
||||
let (l2, server_login) = ServerLogin::<X255193dhNoSlowHash>::start(
|
||||
ServerRegistration::try_from(¶meters.password_file[..]).unwrap(),
|
||||
&Key::try_from(¶meters.server_s_sk[..]).unwrap(),
|
||||
LoginFirstMessage::<X255193dhNoSlowHash>::try_from(¶meters.l1[..]).unwrap(),
|
||||
&mut server_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hex::encode(¶meters.l2), hex::encode(l2.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_login_state),
|
||||
hex::encode(server_login.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l3() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
|
||||
let (l3, shared_secret, export_key_login) = ClientLogin::<X255193dhNoSlowHash>::try_from(
|
||||
¶meters.client_login_state[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish(
|
||||
LoginSecondMessage::<EdwardsPoint, X25519KeyPair, TripleDH, sha2::Sha256>::try_from(
|
||||
¶meters.l2[..],
|
||||
)
|
||||
.unwrap(),
|
||||
&Key::try_from(¶meters.server_s_pk[..])?,
|
||||
&mut client_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.shared_secret),
|
||||
hex::encode(&shared_secret)
|
||||
);
|
||||
assert_eq!(hex::encode(¶meters.l3), hex::encode(l3.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.export_key),
|
||||
hex::encode(export_key_login)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let shared_secret =
|
||||
ServerLogin::<X255193dhNoSlowHash>::try_from(¶meters.server_login_state[..])
|
||||
.unwrap()
|
||||
.finish(LoginThirdMessage::try_from(¶meters.l3[..])?)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.shared_secret),
|
||||
hex::encode(shared_secret)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_complete_flow(
|
||||
registration_password: &[u8],
|
||||
login_password: &[u8],
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = X255193dhNoSlowHash::generate_random_keypair(&mut server_rng)?;
|
||||
let (register_m1, client_state) = ClientRegistration::<X255193dhNoSlowHash>::start(
|
||||
registration_password,
|
||||
None,
|
||||
&mut client_rng,
|
||||
)?;
|
||||
let (register_m2, server_state) =
|
||||
ServerRegistration::<X255193dhNoSlowHash>::start(register_m1, &mut server_rng)?;
|
||||
let (register_m3, registration_export_key) =
|
||||
client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
|
||||
let p_file = server_state.finish(register_m3)?;
|
||||
let (login_m1, client_login_state) =
|
||||
ClientLogin::<X255193dhNoSlowHash>::start(login_password, None, &mut client_rng)?;
|
||||
let (login_m2, server_login_state) = ServerLogin::<X255193dhNoSlowHash>::start(
|
||||
p_file,
|
||||
&server_kp.private(),
|
||||
login_m1,
|
||||
&mut server_rng,
|
||||
)?;
|
||||
|
||||
let client_login_result =
|
||||
client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng);
|
||||
|
||||
if hex::encode(registration_password) == hex::encode(login_password) {
|
||||
let (login_m3, client_shared_secret, login_export_key) = client_login_result?;
|
||||
let server_shared_secret = server_login_state.finish(login_m3)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(server_shared_secret),
|
||||
hex::encode(client_shared_secret)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(registration_export_key),
|
||||
hex::encode(login_export_key)
|
||||
);
|
||||
} else {
|
||||
let res = match client_login_result {
|
||||
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
|
||||
_ => false,
|
||||
};
|
||||
assert!(res);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_flow_success() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"good password")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_flow_fail() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"bad password")
|
||||
}
|
||||
@@ -0,0 +1,583 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
errors::*,
|
||||
key_exchange::tripledh::TripleDH,
|
||||
keypair::{Key, SizedBytesExt},
|
||||
opaque::*,
|
||||
slow_hash::NoOpHash,
|
||||
tests::mock_rng::CycleRng,
|
||||
*,
|
||||
};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use serde_json::Value;
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
|
||||
struct Ristretto255Sha512NoSlowHash;
|
||||
impl CipherSuite for Ristretto255Sha512NoSlowHash {
|
||||
type Group = RistrettoPoint;
|
||||
type KeyExchange = TripleDH;
|
||||
type Hash = sha2::Sha512;
|
||||
type SlowHash = NoOpHash;
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum EnvelopeMode {
|
||||
Base,
|
||||
CustomIdentifier,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub struct TestVectorParameters {
|
||||
pub envelope_mode: EnvelopeMode,
|
||||
pub client_public_key: Vec<u8>,
|
||||
pub client_private_key: Vec<u8>,
|
||||
pub client_keyshare: Vec<u8>,
|
||||
pub client_private_keyshare: Vec<u8>,
|
||||
pub server_public_key: Vec<u8>,
|
||||
pub server_private_key: Vec<u8>,
|
||||
pub server_keyshare: Vec<u8>,
|
||||
pub server_private_keyshare: Vec<u8>,
|
||||
pub client_identity: Vec<u8>,
|
||||
pub server_identity: Vec<u8>,
|
||||
pub password: Vec<u8>,
|
||||
pub blind_registration: Vec<u8>,
|
||||
pub oprf_key: Vec<u8>,
|
||||
pub envelope_nonce: Vec<u8>,
|
||||
pub client_nonce: Vec<u8>,
|
||||
pub server_nonce: Vec<u8>,
|
||||
pub client_info: Vec<u8>,
|
||||
pub server_info: Vec<u8>,
|
||||
pub registration_request: Vec<u8>,
|
||||
pub registration_response: Vec<u8>,
|
||||
pub registration_upload: Vec<u8>,
|
||||
pub KE1: Vec<u8>,
|
||||
pub blind_login: Vec<u8>,
|
||||
pub KE2: Vec<u8>,
|
||||
pub KE3: Vec<u8>,
|
||||
pub export_key: Vec<u8>,
|
||||
pub session_key: Vec<u8>,
|
||||
}
|
||||
|
||||
// Pulled from "OPAQUE-3DH Test Vector 1" and "OPAQUE-3DH Test Vector 6"
|
||||
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
|
||||
static TEST_VECTORS: &[&str] = &[
|
||||
r#"
|
||||
OPRF: 0001
|
||||
Hash: SHA512
|
||||
SlowHash: Identity
|
||||
EnvelopeMode: 01
|
||||
Group: ristretto255
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da54
|
||||
20d3eebcd2b
|
||||
client_private_key: 8bcb0b70dac18de24eef12e737d6b28724d3e37774e0b092f
|
||||
9f70b255defaf04
|
||||
client_public_key: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa1905
|
||||
df6ea129343b09
|
||||
server_private_key: f3a0829898a89239dce29ccc98ec8b449a34b255ba1e6f944
|
||||
829d18e0d589b0f
|
||||
server_public_key: 66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb74
|
||||
9c22c762389c3c
|
||||
client_info: 68656c6c6f20626f62
|
||||
server_info: 6772656574696e677320616c696365
|
||||
server_nonce: 98b8081059f60ffed9336f026fd8e124737205ac73f5348ae5bebdb
|
||||
49456c70f
|
||||
client_nonce: 58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e
|
||||
36427ca44
|
||||
server_keyshare: 5214e3ddc73db786480b79fa2da787f2080b82cbe922c2a9592b
|
||||
44597d9a702e
|
||||
client_keyshare: a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
|
||||
b98265a5e651
|
||||
server_private_keyshare: c4d002aa4cfcf281657cf36fe562bc60d9133e0e72a7
|
||||
4432f685b2b6a4b42a0c
|
||||
client_private_keyshare: de2e98f422bf7b99be19f7da7cac62f1599d35a225ec
|
||||
6340149a0aaff3102003
|
||||
blind_registration: 7e5bcbf82a46109ee0d24e9bcab41fc830a6ce8b82fc1e921
|
||||
3a043b743b95800
|
||||
blind_login: c4d5a15f0d5ffc354e340454ec779f575e4573a3886ab5e57e4da298
|
||||
4bdd5306
|
||||
oprf_key: 080d0a4d352de92672ab709b1ae1888cb48dfabc2d6ca5b914b335512fe
|
||||
70508
|
||||
auth_key: 7bb7f2b831ee30d3e5cc4012c8f721a4d8f9dd494932d53776e043df9bd
|
||||
2aa284025b8b006fd8449536446ff50698f46c73fccb53f20d80898f185307d1d39e5
|
||||
prk: b0aefddbb21d1b97bc40c07b172e0bf172ec740de4f6274f69d46350a447e9b1
|
||||
b3fb1e4cefc7d8e393ff58a5c45c74d0615ee0eecde116f3d4e744142eb2ee89
|
||||
pseudorandom_pad: 36a828b3b57bf242c4c47ccd9cb84e5b3cefaffe09629c6b94d
|
||||
eba0ccec5fa39
|
||||
envelope: 01cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3
|
||||
eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a55
|
||||
3d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e
|
||||
906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
|
||||
handshake_secret: 2b041dcf12ac9b75dded88f891c25d76746ce9e2c1a43118ac4
|
||||
aa5721cdc1bc2f0691e6c012a1ea9eb95ab4899b3e7058d37fe9546c46b0511877e40
|
||||
f55aac6c
|
||||
handshake_encrypt_key: ceef10f15d869a4cea8174fa98d0d96c7aaf8602d006fe
|
||||
0c5274a40173db76cac820138c5890bb63fb974d1e3e925850cc2464e2c10f0a9a776
|
||||
9a45e80889b1e
|
||||
server_mac_key: f8fd7fdc349b5ae1339515e05912c89a795f561a117cdc84d8d8b
|
||||
5f05b05751abfb87fa01c799c5d367244d1e32eab67ff926833c6025c556acffa4af1
|
||||
f3871a
|
||||
client_mac_key: 92a30cc82c374c06895aa07e81f0cf5f25309a24b595faefcd225
|
||||
1f9219b47e47d17da4fe8b572dedefa350ed365f87b217973e90d0b647a2ccf1d796a
|
||||
8970f6
|
||||
registration_request: ec9027daa5e9a901d641286a7ded51364142936ac7636e1
|
||||
42e3f4368b4bd8124
|
||||
registration_response: 8867d7c8c2c576a6322d49d46078ea32f479aed917c70a
|
||||
636d3ada4397ea1c0e66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749
|
||||
c22c762389c3c
|
||||
registration_upload: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa19
|
||||
05df6ea129343b0901cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da
|
||||
5420d3eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129
|
||||
932a553d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3cc
|
||||
d8699e906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
|
||||
KE1: e06a32011e1b1704eb686b263e5d132fff4e9f6429cd93b98db107485006792c
|
||||
58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e36427ca4400096
|
||||
8656c6c6f20626f62a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
|
||||
b98265a5e651
|
||||
KE2: 66f6b5fa1a4eb6bd7a0c93ed2639a31cba0d02e2df744003641d5a30a4a12364
|
||||
66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749c22c762389c3c01cc7
|
||||
abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3eebcd2bbd6323c3
|
||||
6fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a553d469ffa9999fcb
|
||||
d37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e906027842694b62
|
||||
93b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e98b8081059f60ffed9336f0
|
||||
26fd8e124737205ac73f5348ae5bebdb49456c70f5214e3ddc73db786480b79fa2da7
|
||||
87f2080b82cbe922c2a9592b44597d9a702e000f72f38a0945819089c44c86820c51d
|
||||
89cc35f77df03d330101bbed3b2f69066112f32529bdda0998657350fc9f8da4cde73
|
||||
408ad931f4c2ea6237ccae4696483388b174f50cf96d439139b0f8680c3b
|
||||
KE3: 9f0e4f73455ca9fe06bb52ad02670b09be5a03db11a73be4422f19963be082b0
|
||||
eb55871022e8d1d87adc3ab50de7c738058eb659866d091648f2fed12e23fd53
|
||||
export_key: 66c0b72aa829f13a166fb1a1168f1e26023921f0eed1126def4f81ba0
|
||||
4924ad6012e42b63656ec199ba27670d1e7f23dc0a927714edc140134dde5a5d2063d
|
||||
fc
|
||||
session_key: 951c2bb1b876725fa7d3829db791dddd406a688507b47e24101bd0cc
|
||||
5d071760b6fba59e8758a6ea6d7e5f51a715b49a47c50fee9a7c8a0451243c3ee837f
|
||||
d30
|
||||
"#,
|
||||
r#"
|
||||
OPRF: 0001
|
||||
Hash: SHA512
|
||||
SlowHash: Identity
|
||||
EnvelopeMode: 02
|
||||
Group: ristretto255
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
client_identity: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613b2
|
||||
e16a6dde6b05
|
||||
server_identity: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8e
|
||||
de8d6d272c65
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb241809
|
||||
0438877888c
|
||||
client_private_key: dc70a99bbabf1ebe98b192e93cedceb9c0164e95b891bd8bc
|
||||
81721b83d66b00b
|
||||
client_public_key: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613
|
||||
b2e16a6dde6b05
|
||||
server_private_key: 709687a36c94592ab76579f42ce1be6961f0700496e71df80
|
||||
6ebd5320554720d
|
||||
server_public_key: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c
|
||||
8ede8d6d272c65
|
||||
client_info: 68656c6c6f20626f62
|
||||
server_info: 6772656574696e677320616c696365
|
||||
server_nonce: ef49d83cef5f1411ea30abb82b08bd85423aadb86e2c19df5930b3c
|
||||
8498b9f97
|
||||
client_nonce: 4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d6
|
||||
77165cd3b
|
||||
server_keyshare: 96a9587e233e67f2397f10fec6355b68102534f1f1b115b4ddf7
|
||||
485840efcd7c
|
||||
client_keyshare: 54f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
|
||||
e5033632611a
|
||||
server_private_keyshare: 6650d64df70618a878504ce73dcca27b1af125c67e48
|
||||
1e7bd49d0b24709b200f
|
||||
client_private_keyshare: ebb01c59f99bc955df622548e247f7ef180732909ff3
|
||||
c5f87ff8c7867b8be704
|
||||
blind_registration: 308f1d3fa1fea402f3c90b04601274050a3c6f467387c2f48
|
||||
878823949b0e109
|
||||
blind_login: 141e21373228a44b09d4c00da9a6bbaf9a5e54a1687c07f327833643
|
||||
4245510b
|
||||
oprf_key: b7126967aa0cb69c311b71343843ea041bae30e2bde41b548b8fbd8bced
|
||||
97604
|
||||
auth_key: 9f761a5a56a74269b382403aeba47c1d24b9e200e1839efcb616fb280da
|
||||
1b6ba7bd71d6455dd3cd979c545608cfbfc1c4e9ba677e1d40848054a00696c4b2589
|
||||
prk: 30bc3f37a757890ac17ce46043f3c5ed30c96fb8743205e77e84dc167d98e114
|
||||
6093150ff7b4d002f793bfe717e88d174ed2669abdd9e96af473a7ac82973b0a
|
||||
pseudorandom_pad: b909f990c94b9c5949a2b8d0874d602f846b7981b331fd79978
|
||||
b530cc46c8670
|
||||
envelope: 02f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438
|
||||
877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a36
|
||||
7bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc
|
||||
439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
|
||||
handshake_secret: ff89f264f8c3974238f4c8d736af7b0a55f2e4edc487cbf3e5c
|
||||
7b4bbf21acd7c1d28354c2c8555fba57c4d4b1fbb4b772bfdf909881f67dd517cc9f4
|
||||
f6ebaeac
|
||||
handshake_encrypt_key: 3071b181f639062cf70b74d0ffe5ec8fa695da13cd2f00
|
||||
e74b8b7ef348ae7a5df9c3a32c9f7aeaad5a28379712cf849b9707e221dce124abfad
|
||||
d0225a8e8e045
|
||||
server_mac_key: 7c37b344d189cbbeff80bbb4b78e2703d1a80dc28239923094287
|
||||
62f7ce2a93b11f6e85dd45c02809afe8583d4aad6377e72788773af92eef33c690692
|
||||
20ae76
|
||||
client_mac_key: 2c4aff12ba7aa911a51f9e5b7a7c01439d854c97e4b8ec842a9db
|
||||
d78345760328fd5a72424e49e25ec8fe1b6d9d42f774516f400948bd5a105d995d000
|
||||
2fc83b
|
||||
registration_request: 3c8b89966e261a5aaf7aeb6dcdd94c87ce311bf197221b8
|
||||
7ef44632d58f18a05
|
||||
registration_response: caf9243d7ef3e267815632bf79c85a27a23f218a438815
|
||||
2a523f6a310949807beae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8
|
||||
ede8d6d272c65
|
||||
registration_upload: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc6
|
||||
13b2e16a6dde6b0502f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418
|
||||
090438877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4
|
||||
f90a367bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d
|
||||
2566cc439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
|
||||
KE1: 8261a1efd78bea73faf256a23c200d729259886530fa43b875c1ca124b09bc7e
|
||||
4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d677165cd3b00096
|
||||
8656c6c6f20626f6254f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
|
||||
e5033632611a
|
||||
KE2: fa1f33a43a03123ebe35345ef93aa23b57ea8bfbee7022b05a179d60768ba02e
|
||||
eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8ede8d6d272c6502f41
|
||||
e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438877888c6579500b
|
||||
73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a367bdb425fa1dd4c4
|
||||
9e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc439c83030464eff
|
||||
ecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282ef49d83cef5f1411ea30abb
|
||||
82b08bd85423aadb86e2c19df5930b3c8498b9f9796a9587e233e67f2397f10fec635
|
||||
5b68102534f1f1b115b4ddf7485840efcd7c000f7ebe71d4ab326006a3aeca802435d
|
||||
c995a38ac6662221f974cb920992d82b8ef8d147c77e29b628a82b5ccb01ea2f7bb60
|
||||
af94cd1860e1bd974a11a1c9bd827789f663c4758eb71058c244138de0c2
|
||||
KE3: d81f93397cdba85a43993d4d9afbdc67f147adfa2b223213b19692cb820eef48
|
||||
5073eda4c8236b2f47702404ad60d9a875d189626fc7b7cc861825385470ae54
|
||||
export_key: 03192555940b5b42e64e6200bf55cc701f1bace3d402a2f8d83977843
|
||||
51a1e3fa1f07a471b783b208acb1d92be47903b6fa3a0df9f4d4b7956ee4f431e2950
|
||||
f6
|
||||
session_key: 58a7fa98bf3b7b52da21406abfb11d98734354edd47d7b32462c0513
|
||||
f0617c89824ea6031d4147a86fc9f6c6837ce640c12fb937d764f296d1a9421ad1b2a
|
||||
5d5
|
||||
"#,
|
||||
];
|
||||
|
||||
macro_rules! parse {
|
||||
( $v:ident, $s:expr ) => {
|
||||
match decode(&$v, $s) {
|
||||
Some(x) => x,
|
||||
None => vec![],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! rfc_to_params {
|
||||
( $v:ident ) => {
|
||||
$v.iter()
|
||||
.map(|x| populate_test_vectors(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap()))
|
||||
.collect::<Vec<TestVectorParameters>>()
|
||||
};
|
||||
}
|
||||
|
||||
fn rfc_to_json(input: &str) -> String {
|
||||
let mut json = vec![];
|
||||
for line in input.lines() {
|
||||
// If line contains colon, then
|
||||
if line.contains(":") {
|
||||
if json.len() > 0 {
|
||||
// Adding closing quote for previous line, comma, and newline
|
||||
json.push("\",\n".to_string());
|
||||
}
|
||||
|
||||
let mut iter = line.split(":");
|
||||
let key = iter.next().unwrap().split_whitespace().next().unwrap();
|
||||
let val = iter.next().unwrap().split_whitespace().next().unwrap();
|
||||
|
||||
json.push(format!(" \"{}\": \"{}", key, val));
|
||||
} else {
|
||||
let s = line.trim().to_string();
|
||||
if s.len() > 0 {
|
||||
json.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format!("{{\n{}\"\n}}", json.join(""))
|
||||
}
|
||||
|
||||
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
values[key]
|
||||
.as_str()
|
||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
||||
}
|
||||
|
||||
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
|
||||
TestVectorParameters {
|
||||
envelope_mode: match values["EnvelopeMode"].as_str() {
|
||||
Some("01") => EnvelopeMode::Base,
|
||||
Some("02") => EnvelopeMode::CustomIdentifier,
|
||||
_ => panic!("Could not match envelope mode"),
|
||||
},
|
||||
client_public_key: parse!(values, "client_public_key"),
|
||||
client_private_key: parse!(values, "client_private_key"),
|
||||
client_keyshare: parse!(values, "client_keyshare"),
|
||||
client_private_keyshare: parse!(values, "client_private_keyshare"),
|
||||
server_public_key: parse!(values, "server_public_key"),
|
||||
server_private_key: parse!(values, "server_private_key"),
|
||||
server_keyshare: parse!(values, "server_keyshare"),
|
||||
server_private_keyshare: parse!(values, "server_private_keyshare"),
|
||||
client_identity: parse!(values, "client_identity"),
|
||||
server_identity: parse!(values, "server_identity"),
|
||||
password: parse!(values, "password"),
|
||||
blind_registration: parse!(values, "blind_registration"),
|
||||
oprf_key: parse!(values, "oprf_key"),
|
||||
envelope_nonce: parse!(values, "envelope_nonce"),
|
||||
client_nonce: parse!(values, "client_nonce"),
|
||||
server_nonce: parse!(values, "server_nonce"),
|
||||
client_info: parse!(values, "client_info"),
|
||||
server_info: parse!(values, "server_info"),
|
||||
registration_request: parse!(values, "registration_request"),
|
||||
registration_response: parse!(values, "registration_response"),
|
||||
registration_upload: parse!(values, "registration_upload"),
|
||||
KE1: parse!(values, "KE1"),
|
||||
KE2: parse!(values, "KE2"),
|
||||
KE3: parse!(values, "KE3"),
|
||||
blind_login: parse!(values, "blind_login"),
|
||||
export_key: parse!(values, "export_key"),
|
||||
session_key: parse!(values, "session_key"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
|
||||
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key.clone());
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut oprf_key_rng,
|
||||
RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(),
|
||||
&Key::from_bytes(¶meters.server_public_key[..]).unwrap(),
|
||||
)?;
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(RegistrationUpload::deserialize(¶meters.registration_upload[..]).unwrap())?;
|
||||
|
||||
Ok(password_file.serialize())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_request() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut rng,
|
||||
¶meters.password,
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.registration_request),
|
||||
hex::encode(client_registration_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_response() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut oprf_key_rng,
|
||||
RegistrationRequest::deserialize(¶meters.registration_request[..]).unwrap(),
|
||||
&Key::from_bytes(¶meters.server_public_key[..]).unwrap(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(parameters.registration_response),
|
||||
hex::encode(server_registration_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registration_upload() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut rng,
|
||||
¶meters.password,
|
||||
)?;
|
||||
|
||||
let sk_u_and_nonce: Vec<u8> =
|
||||
[parameters.client_private_key, parameters.envelope_nonce].concat();
|
||||
let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
|
||||
let result = client_registration_start_result.state.finish(
|
||||
&mut finish_registration_rng,
|
||||
RegistrationResponse::deserialize(¶meters.registration_response[..]).unwrap(),
|
||||
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
|
||||
ClientRegistrationFinishParameters::WithIdentifiers(
|
||||
parameters.client_identity,
|
||||
parameters.server_identity,
|
||||
)
|
||||
} else {
|
||||
ClientRegistrationFinishParameters::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.registration_upload),
|
||||
hex::encode(result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(parameters.export_key),
|
||||
hex::encode(result.export_key.to_vec())
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ke1() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let client_login_start = [
|
||||
parameters.blind_login,
|
||||
parameters.client_private_keyshare,
|
||||
parameters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut client_login_start_rng,
|
||||
¶meters.password,
|
||||
ClientLoginStartParameters::WithInfo(parameters.client_info),
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE1),
|
||||
hex::encode(client_login_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ke2() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let password_file_bytes = get_password_file_bytes(¶meters)?;
|
||||
|
||||
let mut server_private_keyshare_and_nonce_rng =
|
||||
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
|
||||
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut server_private_keyshare_and_nonce_rng,
|
||||
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
|
||||
&Key::from_bytes(¶meters.server_private_key[..]).unwrap(),
|
||||
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE1[..])
|
||||
.unwrap(),
|
||||
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
|
||||
ServerLoginStartParameters::WithInfoAndIdentifiers(
|
||||
parameters.server_info.to_vec(),
|
||||
parameters.client_identity,
|
||||
parameters.server_identity,
|
||||
)
|
||||
} else {
|
||||
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
|
||||
},
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_info),
|
||||
hex::encode(server_login_start_result.plain_info),
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE2),
|
||||
hex::encode(server_login_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ke3() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let client_login_start = [
|
||||
parameters.blind_login,
|
||||
parameters.client_private_keyshare,
|
||||
parameters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut client_login_start_rng,
|
||||
¶meters.password,
|
||||
ClientLoginStartParameters::WithInfo(parameters.client_info),
|
||||
)?;
|
||||
|
||||
let client_login_finish_result = client_login_start_result.state.finish(
|
||||
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE2[..])?,
|
||||
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
|
||||
ClientLoginFinishParameters::WithIdentifiers(
|
||||
parameters.client_identity,
|
||||
parameters.server_identity,
|
||||
)
|
||||
} else {
|
||||
ClientLoginFinishParameters::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_info),
|
||||
hex::encode(&client_login_finish_result.confidential_info)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.session_key),
|
||||
hex::encode(&client_login_finish_result.session_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE3),
|
||||
hex::encode(client_login_finish_result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.export_key),
|
||||
hex::encode(client_login_finish_result.export_key)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
for parameters in rfc_to_params!(TEST_VECTORS) {
|
||||
let password_file_bytes = get_password_file_bytes(¶meters)?;
|
||||
|
||||
let mut server_private_keyshare_and_nonce_rng =
|
||||
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
|
||||
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
|
||||
&mut server_private_keyshare_and_nonce_rng,
|
||||
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
|
||||
&Key::from_bytes(¶meters.server_private_key[..]).unwrap(),
|
||||
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(¶meters.KE1[..])
|
||||
.unwrap(),
|
||||
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
|
||||
ServerLoginStartParameters::WithInfoAndIdentifiers(
|
||||
parameters.server_info.to_vec(),
|
||||
parameters.client_identity,
|
||||
parameters.server_identity,
|
||||
)
|
||||
} else {
|
||||
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
|
||||
},
|
||||
)?;
|
||||
|
||||
let server_login_result = server_login_start_result
|
||||
.state
|
||||
.finish(CredentialFinalization::deserialize(¶meters.KE3[..])?)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.session_key),
|
||||
hex::encode(server_login_result.session_key)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
envelope::Envelope,
|
||||
group::Group,
|
||||
key_exchange::{
|
||||
traits::{KeyExchange, ToBytes},
|
||||
tripledh::{TripleDH, NONCE_LEN},
|
||||
},
|
||||
keypair::{KeyPair, SizedBytes, X25519KeyPair},
|
||||
opaque::*,
|
||||
};
|
||||
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::typenum::Unsigned;
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
use rand_core::{OsRng, RngCore};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
struct Default;
|
||||
impl CipherSuite for Default {
|
||||
type Group = RistrettoPoint;
|
||||
type KeyFormat = crate::keypair::X25519KeyPair;
|
||||
type KeyExchange = TripleDH;
|
||||
type Hash = sha2::Sha256;
|
||||
type SlowHash = crate::slow_hash::NoOpHash;
|
||||
}
|
||||
|
||||
fn random_ristretto_point() -> RistrettoPoint {
|
||||
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)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_registration_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
// serialization order: scalar, password
|
||||
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
|
||||
let reg = ClientRegistration::<Default>::try_from(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_registration_roundtrip() {
|
||||
// If we don't have envelope and client_pk, the server registration just
|
||||
// contains the prf key
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
let mut oprf_bytes: Vec<u8> = vec![];
|
||||
oprf_bytes.extend_from_slice(sc.as_bytes());
|
||||
let reg = ServerRegistration::<Default>::try_from(&oprf_bytes[..]).unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, oprf_bytes);
|
||||
// If we do have envelope and client pk, the server registration contains
|
||||
// the whole kit
|
||||
let key_len =
|
||||
<<<Default as CipherSuite>::KeyFormat as KeyPair>::Repr as SizedBytes>::Len::to_usize();
|
||||
let envelope_size = key_len + Envelope::<sha2::Sha256>::additional_size();
|
||||
let mut mock_envelope_bytes = vec![0u8; envelope_size];
|
||||
rng.fill_bytes(&mut mock_envelope_bytes);
|
||||
println!("{}", mock_envelope_bytes.len());
|
||||
let mock_client_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||
// serialization order: scalar, public key, envelope
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.extend_from_slice(sc.as_bytes());
|
||||
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
|
||||
bytes.extend_from_slice(&mock_envelope_bytes);
|
||||
let reg = ServerRegistration::<Default>::try_from(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_first_message_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let pt_bytes = pt.to_arr();
|
||||
let r1 = RegisterFirstMessage::<RistrettoPoint>::try_from(pt_bytes.as_slice()).unwrap();
|
||||
let r1_bytes = r1.to_bytes();
|
||||
assert_eq!(pt_bytes, r1_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_second_message_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let pt_bytes = pt.to_arr();
|
||||
|
||||
let message = pt_bytes.to_vec();
|
||||
let r2 = RegisterSecondMessage::<RistrettoPoint>::try_from(&message[..]).unwrap();
|
||||
let r2_bytes = r2.to_bytes();
|
||||
assert_eq!(message, r2_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_third_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
let skp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||
let pubkey_bytes = skp.public().to_arr();
|
||||
|
||||
let mut key = [0u8; 32];
|
||||
rng.fill_bytes(&mut key);
|
||||
|
||||
let mut msg = [0u8; 32];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let (ciphertext, _) =
|
||||
Envelope::<sha2::Sha256>::seal(&key, &msg, &pubkey_bytes, &mut rng).unwrap();
|
||||
|
||||
let message: Vec<u8> = [&ciphertext.to_bytes(), &pubkey_bytes[..]].concat();
|
||||
let r3 = RegisterThirdMessage::<X25519KeyPair, sha2::Sha256>::try_from(&message[..]).unwrap();
|
||||
let r3_bytes = r3.to_bytes();
|
||||
assert_eq!(message, r3_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_login_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
|
||||
let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
|
||||
let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(l1_data);
|
||||
let hashed_l1 = hasher.finalize();
|
||||
|
||||
// serialization order: scalar, password, ke1_state
|
||||
let bytes: Vec<u8> = [
|
||||
&sc.as_bytes()[..],
|
||||
&pw[..],
|
||||
client_e_kp.public(),
|
||||
&client_nonce,
|
||||
hashed_l1.as_slice(),
|
||||
]
|
||||
.concat();
|
||||
let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn login_first_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
|
||||
let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
|
||||
let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha256>>::KE1Message::try_from(ke1m[..].to_vec()).unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, ke1m);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_register_first_message(bytes in vec(any::<u8>(), 0..200)) {
|
||||
RegisterFirstMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_register_second_message(bytes in vec(any::<u8>(), 0..200)) {
|
||||
RegisterSecondMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_register_third_message(bytes in vec(any::<u8>(), 0..200)) {
|
||||
RegisterThirdMessage::<crate::keypair::X25519KeyPair, sha2::Sha512>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_login_first_message(bytes in vec(any::<u8>(), 0..500)) {
|
||||
LoginFirstMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) {
|
||||
LoginSecondMessage::<RistrettoPoint, crate::keypair::X25519KeyPair, TripleDH, sha2::Sha512>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_login_third_message(bytes in vec(any::<u8>(), 0..500)) {
|
||||
LoginThirdMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
|
||||
ClientRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
|
||||
ServerRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
|
||||
ClientLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
|
||||
ServerLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
use crate::{errors::*, group::Group, oprf};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::GenericArray;
|
||||
use serde_json::Value;
|
||||
use sha2::Sha512;
|
||||
|
||||
struct VOPRFTestVectorParameters {
|
||||
sksm: Vec<u8>,
|
||||
input: Vec<u8>,
|
||||
blind: Vec<u8>,
|
||||
blinded_element: Vec<u8>,
|
||||
evaluation_element: Vec<u8>,
|
||||
output: Vec<u8>,
|
||||
}
|
||||
|
||||
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
|
||||
// in base mode
|
||||
static OPRF_RISTRETTO255_SHA512: &'static [&str] = &[
|
||||
r#"
|
||||
{
|
||||
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
|
||||
"input": "00",
|
||||
"blind": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
|
||||
"blinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
|
||||
"evaluation_element": "fc6c2b854553bf1ed6674072ed0bde1a9911e02b4bd64aa02cfb428f30251e77",
|
||||
"output": "d8ed12382086c74564ae19b7a2b5ed9bdc52656d1fc151faaae51aaba86291e8df0b2143a92f24d44d5efd0892e2e26721d27d88745343493634a66d3a925e3a"
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
{
|
||||
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
|
||||
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
|
||||
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
|
||||
"blinded_element": "28a5e797b710f76d20a52507145fbf320a574ec2c8ab0e33e65dd2c277d0ee56",
|
||||
"evaluation_element": "345e140b707257ae83d4911f7ead3177891e7a62c54097732802c4c7a98ab25a",
|
||||
"output": "4d5f4221b5ebfd4d1a9dd54830e1ed0bce5a8f30a792723a6fddfe6cfe9f86bb1d95a3725818aeb725eb0b1b52e01ee9a72f47042372ef66c307770054d674fc"
|
||||
}
|
||||
"#,
|
||||
];
|
||||
|
||||
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
values[key]
|
||||
.as_str()
|
||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
||||
}
|
||||
|
||||
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
|
||||
VOPRFTestVectorParameters {
|
||||
sksm: decode(&values, "sksm").unwrap(),
|
||||
input: decode(&values, "input").unwrap(),
|
||||
blind: decode(&values, "blind").unwrap(),
|
||||
blinded_element: decode(&values, "blinded_element").unwrap(),
|
||||
evaluation_element: decode(&values, "evaluation_element").unwrap(),
|
||||
output: decode(&values, "output").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// Tests input -> blind, blinded_element
|
||||
#[test]
|
||||
fn test_blind() -> Result<(), PakeError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
let mut rng = CycleRng::new(parameters.blind.to_vec());
|
||||
|
||||
let (token, blinded_element) =
|
||||
oprf::blind::<_, RistrettoPoint, Sha512>(¶meters.input, &mut rng)?;
|
||||
|
||||
assert_eq!(
|
||||
¶meters.blind,
|
||||
&RistrettoPoint::scalar_as_bytes(&token.blind).to_vec()
|
||||
);
|
||||
assert_eq!(
|
||||
¶meters.blinded_element,
|
||||
&blinded_element.to_arr().to_vec()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Tests sksm, blinded_element -> evaluation_element
|
||||
#[test]
|
||||
fn test_evaluate() -> Result<(), PakeError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
let evaluation_element = oprf::evaluate::<RistrettoPoint>(
|
||||
RistrettoPoint::from_element_slice(GenericArray::from_slice(
|
||||
¶meters.blinded_element,
|
||||
))
|
||||
.unwrap(),
|
||||
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(¶meters.sksm)).unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
¶meters.evaluation_element,
|
||||
&evaluation_element.to_arr().to_vec()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Tests input, blind, evaluation_element -> output
|
||||
#[test]
|
||||
fn test_finalize() -> Result<(), PakeError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
|
||||
let output = oprf::finalize::<RistrettoPoint, Sha512>(
|
||||
¶meters.input,
|
||||
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(¶meters.blind))?,
|
||||
RistrettoPoint::from_element_slice(GenericArray::from_slice(
|
||||
¶meters.evaluation_element,
|
||||
))?,
|
||||
);
|
||||
|
||||
assert_eq!(¶meters.output, &output.to_vec());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user