Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d257aaf78
|
||
|
|
0e9eeb10b2
|
||
|
|
09286d34fc
|
||
|
|
b9b8699ee7
|
||
|
|
2e1bcfe4c2
|
||
|
|
efa98962b7
|
||
|
|
71df1ee49a
|
||
|
|
5e2ba86643
|
||
|
|
bc588b149e | ||
|
|
b20d78c074 | ||
|
|
2dee6fbc01 | ||
|
|
a85a7bea8f | ||
|
|
22bcbfc821 | ||
|
|
9b5eef4f8f | ||
|
|
8afec46637 | ||
|
|
67297dc73a | ||
|
|
6d2af37a28 | ||
|
|
96498709f4 | ||
|
|
75fe4cdddb | ||
|
|
ed5e7cf116 | ||
|
|
4dae85b3a9 | ||
|
|
af220c6d00 | ||
|
|
79e4eacc40 | ||
|
|
c94f942ed7 | ||
|
|
3f8026d30e | ||
|
|
33464543c1 | ||
|
|
a1624a92dd | ||
|
|
6593833622 | ||
|
|
226fe0ea7a | ||
|
|
8f9d7ec125 | ||
|
|
c04fb97b5c | ||
|
|
1b6633cdcc | ||
|
|
5b492aedbf | ||
|
|
8ee35498ea | ||
|
|
3777ee680a | ||
|
|
fdf6a1103b | ||
|
|
bebd2c605b | ||
|
|
58b4d746c0 | ||
|
|
b16cf8ce05 | ||
|
|
560de5b718 | ||
|
|
27c6d4658b | ||
|
|
d324584d79 | ||
|
|
6b69e93dc9 | ||
|
|
ff3a8b960d | ||
|
|
df8e61d24b | ||
|
|
94d9c563a4 | ||
|
|
4e525560c7 | ||
|
|
a21678e969 | ||
|
|
066f88f434 | ||
|
|
7eb363f571 | ||
|
|
1cc30c255e | ||
|
|
31c65f5d79 | ||
|
|
56cdf9a024 | ||
|
|
c728c37f15 | ||
|
|
47b37779d4 | ||
|
|
ead80ad892 | ||
|
|
83795f3f1c | ||
|
|
07c3f74582 | ||
|
|
d13d261cf1 | ||
|
|
d65cc4d38d | ||
|
|
4487f11f87 | ||
|
|
bca1abfe42 | ||
|
|
7f0b6a8e66 | ||
|
|
c3498ead45 | ||
|
|
4f69de68fd | ||
|
|
deb7ca3dc0 | ||
|
|
c73eb963db | ||
|
|
a88373722e | ||
|
|
98b42d609f | ||
|
|
e6c9870899 | ||
|
|
7b205b73eb | ||
|
|
8b755bf076 | ||
|
|
f8f5d22721 | ||
|
|
c1c1c40191 | ||
|
|
3096c4d9df | ||
|
|
c255364ca0 | ||
|
|
5400128833 | ||
|
|
b40092db28 | ||
|
|
09489ddf8b | ||
|
|
1fbe25507e | ||
|
|
8901b74030 | ||
|
|
2dc2b0e617 | ||
|
|
7da97be1db | ||
|
|
a0cab10663 | ||
|
|
765a15d58f | ||
|
|
86915604ee | ||
|
|
cdb5222235 | ||
|
|
d2bdcb391e | ||
|
|
556f6c2bd4 | ||
|
|
d2a2139348 | ||
|
|
94fd3598d0 | ||
|
|
9ce5d8d191 | ||
|
|
22eded4ed8 | ||
|
|
e08ee1925c | ||
|
|
5a0aef607c | ||
|
|
6e90913132 | ||
|
|
f83773dec9 | ||
|
|
3fe6bbb80f | ||
|
|
384207acbb | ||
|
|
f952a26e29 | ||
|
|
b2f10858e0 | ||
|
|
47a26a19c5 | ||
|
|
36f0a55518 | ||
|
|
82e4436d39 | ||
|
|
d59d0b775b | ||
|
|
adbd50f523 | ||
|
|
29b2ebef1b | ||
|
|
f1f4184400 | ||
|
|
77fbdb29f8 | ||
|
|
328b07f217 | ||
|
|
65a0c2f98d | ||
|
|
11a93fe63e | ||
|
|
8df79cb2b4 | ||
|
|
d1dfee9a65 | ||
|
|
390db51910 | ||
|
|
aee4b5d50e | ||
|
|
a99a934ead | ||
|
|
6c7840514d | ||
|
|
8a7bcf9097 | ||
|
|
88673d8e05 | ||
|
|
10a38bc58b | ||
|
|
a169d8a1c5 | ||
|
|
e491c1b533 | ||
|
|
2373ca8680 | ||
|
|
42e06e0aff | ||
|
|
db4e07811e | ||
|
|
a5b9330b63 | ||
|
|
3f6b6d4afe | ||
|
|
ec528eb4d9 | ||
|
|
4298deadff | ||
|
|
49dc5b9050 | ||
|
|
dcaedc278c | ||
|
|
460e2aef1c | ||
|
|
b90163a007 | ||
|
|
35886152ca | ||
|
|
1ec96e942b | ||
|
|
68d0dd2988 | ||
|
|
b27b29e652 | ||
|
|
7b1328b9b7 | ||
|
|
dedc814416 | ||
|
|
77461b640d | ||
|
|
4ce61acc6e | ||
|
|
17b9c49473 | ||
|
|
3d01a605df | ||
|
|
124d64c9ca | ||
|
|
d61e2d0b5c | ||
|
|
990acf67e3 | ||
|
|
b4882f8810 | ||
|
|
8de2f37235 | ||
|
|
e6b5a5dcf6 | ||
|
|
6062bdb5cd | ||
|
|
e5db303dd1 | ||
|
|
2955bcf5ac | ||
|
|
93d92133f9 | ||
|
|
b4e59cc60e | ||
|
|
c0e1181cac | ||
|
|
2d9b1d550e | ||
|
|
ff54d32fdd | ||
|
|
52d68d6f77 | ||
|
|
dd97a81641 | ||
|
|
349329cdeb | ||
|
|
c8c57785af | ||
|
|
809337f458 | ||
|
|
e86fbac0b7 | ||
|
|
81b2719587 | ||
|
|
f05fcf0278 | ||
|
|
eb59676a94 | ||
|
|
48590056ca | ||
|
|
ed086c9528 | ||
|
|
ca50d92f96 | ||
|
|
535b9b8ee4 | ||
|
|
1572ff0104 | ||
|
|
f0c13945d1 | ||
|
|
98f1821897 | ||
|
|
210e0e99df | ||
|
|
cd85efc603 | ||
|
|
2c7fe4e382 | ||
|
|
30e27a11e2 | ||
|
|
0935bea8ff | ||
|
|
51b14f34e0 | ||
|
|
055e76a115 | ||
|
|
940d1dcdb2 | ||
|
|
8bc5e7dc02 | ||
|
|
468e0690d7 | ||
|
|
b15c89f997 | ||
|
|
acaf778ee7 | ||
|
|
ba8e940e08 | ||
|
|
ac08801f18 | ||
|
|
a49e089e54 | ||
|
|
37f98b3742 | ||
|
|
fa0fb48654 | ||
|
|
4da6bbce0c | ||
|
|
a457d82793 | ||
|
|
782eeb7090 | ||
|
|
bb7d17a102 | ||
|
|
7f84984f61 | ||
|
|
e4636e0a97 | ||
|
|
e694a88ef9 | ||
|
|
f596e4bf0e |
@@ -0,0 +1,3 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
@@ -0,0 +1,156 @@
|
||||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize ]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ gitea.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: cargo fmt
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Run cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: cargo clippy
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: cargo-registry-
|
||||
- name: Run cargo clippy
|
||||
run: cargo clippy --all-targets --features argon2,std,curve25519,ecdsa,ed25519,kem -- -D warnings
|
||||
- name: Run cargo doc
|
||||
run: cargo doc --no-deps --document-private-items --features argon2,std,curve25519,ecdsa,ed25519,kem
|
||||
env:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
|
||||
test:
|
||||
name: test (${{ matrix.toolchain }} / ${{ matrix.backend_feature || 'no backend' }} / ${{ matrix.frontend_feature || 'no frontend' }})
|
||||
runs-on: linux_amd64
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
backend_feature:
|
||||
- ""
|
||||
- --features ristretto255
|
||||
- --features ristretto255,kem
|
||||
- --features curve25519
|
||||
- --features ecdsa
|
||||
- --features ed25519
|
||||
- --features ristretto255,curve25519,ecdsa,ed25519
|
||||
frontend_feature:
|
||||
- ""
|
||||
- --features argon2
|
||||
- --features serde
|
||||
toolchain:
|
||||
- stable
|
||||
- "1.90.0"
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@${{ matrix.toolchain }}
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: cargo-registry-
|
||||
- name: Run cargo test
|
||||
run: cargo test --no-default-features ${{ matrix.backend_feature }} ${{ matrix.frontend_feature }}
|
||||
- name: Run cargo test with std
|
||||
run: cargo test --no-default-features --features std ${{ matrix.backend_feature }} ${{ matrix.frontend_feature }}
|
||||
|
||||
simple-login-test:
|
||||
name: test simple_login example
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run expect
|
||||
run: expect -f scripts/simple_login.exp
|
||||
|
||||
digital-locker-test:
|
||||
name: test digital_locker example
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run expect
|
||||
run: expect -f scripts/digital_locker.exp
|
||||
|
||||
build-no-std:
|
||||
name: no-std (${{ matrix.target }} / ${{ matrix.backend_feature || 'no backend' }})
|
||||
runs-on: linux_amd64
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- wasm32-unknown-unknown
|
||||
- thumbv6m-none-eabi
|
||||
backend_feature:
|
||||
- ""
|
||||
- ristretto255
|
||||
- curve25519
|
||||
- ecdsa
|
||||
- ed25519
|
||||
- ristretto255,curve25519,ecdsa,ed25519
|
||||
frontend_feature:
|
||||
- argon2
|
||||
- serde
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- name: Build no-std
|
||||
run: cargo build --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.frontend_feature }},${{ matrix.backend_feature }}
|
||||
|
||||
benches:
|
||||
name: cargo bench compilation
|
||||
runs-on: linux_amd64
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
backend_feature:
|
||||
- --features ristretto255
|
||||
- --features ristretto255,kem
|
||||
- ""
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run cargo bench --no-run
|
||||
run: cargo bench --no-default-features ${{ matrix.backend_feature }} --no-run
|
||||
|
||||
audit:
|
||||
name: cargo audit
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install cargo-audit
|
||||
run: cargo install cargo-audit
|
||||
- name: Run cargo audit
|
||||
run: cargo audit -D warnings
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Login to crates.io
|
||||
run: cargo login $CRATES_IO_TOKEN
|
||||
env:
|
||||
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
|
||||
- name: Dry run publish
|
||||
run: cargo publish --dry-run --manifest-path Cargo.toml
|
||||
|
||||
- name: Publish
|
||||
run: cargo publish --manifest-path Cargo.toml
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
|
||||
@@ -1,182 +0,0 @@
|
||||
name: Rust CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- tls
|
||||
- master
|
||||
pull_request:
|
||||
types: [opened, repoened, synchronize]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
backend_feature:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
name: test
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Run cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
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
|
||||
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: nightly
|
||||
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
|
||||
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: nightly
|
||||
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
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Run cargo bench --no-run
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: bench
|
||||
args: --features "bench" --no-run
|
||||
|
||||
clippy:
|
||||
name: cargo clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Run cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
|
||||
format:
|
||||
name: cargo fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Run cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
deny-check:
|
||||
name: cargo-deny check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
@@ -1,29 +0,0 @@
|
||||
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 }}
|
||||
+11
-4
@@ -1,5 +1,12 @@
|
||||
.vs
|
||||
.vscode/
|
||||
src/.DS_Store
|
||||
/target
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
pkg/
|
||||
demo/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
+205
-1
@@ -1,6 +1,210 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.0 (February 8, 2020)
|
||||
## 1.0.0-rc.0 (July 3, 2026)
|
||||
|
||||
* Fixed doc showing incorrect MSRV
|
||||
* Fixed readme incorrect doc link
|
||||
* Bump `ecdsa` to `0.17`
|
||||
* Bump `voprf-vx` to `1.0.0-rc.1`
|
||||
* Added `ZeroizeOnDrop` bound to `Hash` trait
|
||||
* Replaced manual `Drop` impls on `Ke2Builder` and `KemKe2Builder` with `#[derive_where(ZeroizeOnDrop)]`
|
||||
* Replaced deprecated `ExpandedKeyEncoding` with seed-based serialization (`KeyInit` / `KeyExport`) for KEM decapsulation keys
|
||||
* Fixed deserialization of `voprf` types to pass exact-length slices (compatibility with `voprf` trailing bytes rejection)
|
||||
* Regenerated test vectors
|
||||
|
||||
## 1.0.0-pre.1 (July 2, 2026)
|
||||
|
||||
* Fixed README package name
|
||||
* Implement `zeroize` feature on `argon2`, `ed25519-dalek`, `hmac`, `chacha20poly1305` and `sha2`
|
||||
* Removed `rand_core` dependency to use it through `rand` re-export
|
||||
|
||||
## 1.0.0-pre.0 (July 1, 2026)
|
||||
|
||||
Forked from [facebook/opaque-ke](https://github.com/facebook/opaque-ke/) at `4.1.0-pre.2`.
|
||||
|
||||
* Upgraded dependencies:
|
||||
* `ml-kem`: `0.3.0-rc.0` to `0.3`
|
||||
* `digest`: `0.10` to `0.11`
|
||||
* `elliptic-curve`: `0.13` to `0.14`
|
||||
* `curve25519-dalek`: `4` to `5.0.0-rc`
|
||||
* `ed25519-dalek`: `2` to `3.0.0-rc`
|
||||
* `ecdsa`: `0.16` to `0.17.0-rc.23`
|
||||
* `hkdf`: `0.12` to `0.13`
|
||||
* `hmac`: `0.12` to `0.13`
|
||||
* `rand`: `0.8` to `0.10`
|
||||
* `rand_chacha`: `0.3` to `0.10`
|
||||
* `rfc6979`: `0.4` to `0.6` (now internal to `ecdsa`)
|
||||
* `sha2`: `0.10` to `0.11`
|
||||
* `getrandom`: `0.2` to `0.4` (WASM target)
|
||||
* `p256`/`p384`/`p521`: `0.13` to `0.14.0-rc.15` (dev-dependency)
|
||||
* `cryptoki`: `0.9` to `0.12` (dev-dependency)
|
||||
* `rustyline`: `17` to `18` (dev-dependency)
|
||||
* `scrypt`: `0.11` to `0.12` (dev-dependency)
|
||||
* `voprf` replaced by `voprf-vx 1.0.0-pre.0`
|
||||
* Bump `generic-array 0.14` to `generic-array 1.4` with `hybrid-array 0.4` interop
|
||||
* Added `hybrid-array 0.4` for interop
|
||||
* Added `ConcatExt` trait to disambiguate from `[T]::concat`
|
||||
* Added **`cryptography`** to `categories` in `Cargo.toml`
|
||||
* Replaced `Hmac` with `SimpleHmac` throughout for `digest 0.11` compatibility
|
||||
* Replaced `bincode` with `postcard` for `no_std` serialization
|
||||
* Replaced license appendix in files while keeping original copyright
|
||||
* Re-exported `hybrid_array` from crate root
|
||||
* Updated `Hash` trait to remove `BlockSizeUser` bounds incompatible with `digest 0.11`
|
||||
* Updated `GroupEncoding Repr` bound to `hybrid_array::Array`
|
||||
* Fixed `MaskedResponse::serialize` field ordering to match deserialization
|
||||
* Increased **MSRV** to **1.90**
|
||||
* Renamed crate to `opaque-vx`
|
||||
* Removed direct `rfc6979` dependency (handled by `ecdsa` internally)
|
||||
* Removed unstable `rustfmt` configurations for **Rust stable** compatibility
|
||||
* Removed Facebook-specific contributions (CLA, bounty program) from `CONTRIBUTING.md`
|
||||
* Removed `v3` to `v4` migration test (no longer relevant for fork)
|
||||
|
||||
## 4.1.0-pre.2 (March 26, 2026)
|
||||
|
||||
* Upgraded ml-kem from 0.2 to 0.3.0-rc.0
|
||||
* Increased MSRV to 1.87
|
||||
|
||||
## 4.1.0-pre.1 (November 17, 2025)
|
||||
|
||||
* Added ml-kem re-export behind the kem feature
|
||||
|
||||
## 4.1.0-pre.0 (November 11, 2025)
|
||||
|
||||
* Fixed dependency exporting for the rand crate
|
||||
* Added TripleDhKem key exchange protocol
|
||||
|
||||
## 4.0.1 (October 30, 2025)
|
||||
|
||||
* Fixing docs building issue
|
||||
|
||||
## 4.0.0 (October 23, 2025)
|
||||
|
||||
* Increased MSRV to 1.83
|
||||
* Synced implementation with RFC 9807 (no core protocol changes)
|
||||
* Added a SIGMA-I key exchange implementation
|
||||
* Removed KeGroup type from the Ciphersuite trait (now part of KeyExchange type)
|
||||
* **Breaking: existing Ciphersuite trait definitions need to be updated**
|
||||
* Ensured that dummy record is always created to avoid timing attack issues
|
||||
* Modified the dummy registration file to only contain the public key
|
||||
instead of the keypair
|
||||
* **Breaking: existing `ServerSetup`s need to be updated**
|
||||
```rust
|
||||
// Given `old` is a `ServerSetup` from `opaque-ke` v3.
|
||||
let old_serialized = old.serialize();
|
||||
|
||||
type OldSeedLen = <<<OldCipherSuite as opaque_ke_3::CipherSuite>::OprfCs as voprf::CipherSuite>::Hash as OutputSizeUser>::OutputSize;
|
||||
type OldSkLen = <<OldCipherSuite as opaque_ke_3::CipherSuite>::KeGroup as opaque_ke_3::key_exchange::group::KeGroup>::SkLen;
|
||||
|
||||
let (old_serialied_rest, old_fake_keypair_serialized): (
|
||||
GenericArray<u8, Sum<OldSeedLen, OldSkLen>>,
|
||||
_,
|
||||
) = old_serialized.split();
|
||||
|
||||
let old_fake_keypair =
|
||||
KeyPair::<<OldCipherSuite as opaque_ke_3::CipherSuite>::KeGroup>::from_private_key_slice(
|
||||
&old_fake_keypair_serialized,
|
||||
)
|
||||
.unwrap();
|
||||
let old_fake_pk_serialized = old_fake_keypair.public().serialize();
|
||||
|
||||
let new_serialized = old_serialied_rest.concat(old_fake_pk_serialized);
|
||||
// Given `NewCipherSuite` is a `CipherSuite` implementation equivalent to `OldCipherSuite`.
|
||||
ServerSetup::<NewCipherSuite>::deserialize(&new_serialized).unwrap()
|
||||
```
|
||||
* Added remote OPRF seed support
|
||||
* Replace remote private key trait with a state machine, facilitating async support.
|
||||
* Serde de/serialization formats have been simplified
|
||||
* **Breaking: existing `ServerRegistration`s may need to be updated**
|
||||
```rust
|
||||
// Given `old` is a `ServerRegistration` from `opaque-ke` v3.
|
||||
let old_serialized = old.serialize();
|
||||
// Given `NewCipherSuite` is a `CipherSuite` implementation equivalent to the old cipher suite.
|
||||
ServerRegistration::<NewCipherSuite>::deserialize(&old_serialized).unwrap()
|
||||
```
|
||||
|
||||
## 3.0.0 (October 10, 2024)
|
||||
|
||||
* Synced implementation with draft-irtf-cfrg-opaque-16
|
||||
* **Breaking: protocol context string changed from `RFCXXXX` to `OPAQUEv1-`**
|
||||
* Dropped unmaintained json crate in favor of serde_json
|
||||
* Updated dependencies
|
||||
* Increased MSRV to 1.74
|
||||
* Adjusted curve25519 support logic
|
||||
* Adjusted key generation logic to be in line with commit 727b9ac of
|
||||
https://github.com/cfrg/draft-irtf-cfrg-opaque
|
||||
* Updated VOPRF to draft 19
|
||||
* **Breaking: backwards-incompatible changes introduced in OPRF protocol**
|
||||
* Added P384 testing support
|
||||
* Renaming of X25519 to Curve25519
|
||||
|
||||
## 2.0.0 (September 21, 2022)
|
||||
|
||||
* Synced implementation with draft-irtf-cfrg-opaque-10
|
||||
* Changed argon2 salt length to recommended value (16 bytes)
|
||||
* Fixed issue from 2.0.0-pre.2 not pinning voprf dependency correctly
|
||||
* Split out VOPRF implementation into its own crate
|
||||
* Added support for running the API without performing
|
||||
allocations
|
||||
* Revamped the way the Group trait was used, so as to be more
|
||||
easily extendable to other groups
|
||||
* Added support for p256 as the group and x25519 as the key exchange group
|
||||
* Added common traits for each public-facing struct, including serde support
|
||||
|
||||
## 1.2.0 (October 7, 2021)
|
||||
|
||||
* Added explicit support for the thumbv6m-none-eabi target (no-std)
|
||||
|
||||
## 1.1.0 (August 18, 2021)
|
||||
|
||||
* Updated dependencies and bumped MSRV to 1.51
|
||||
* Added no_std support
|
||||
|
||||
## 1.0.0 (July 19, 2021)
|
||||
|
||||
* Branched from v0.5.0
|
||||
* Various security improvements: non-zero scalars, zeroizing on drop,
|
||||
constant-time operations, reflected value check, and adding an
|
||||
i2osp error condition
|
||||
|
||||
## 0.6.0 (June 30, 2021)
|
||||
|
||||
* Synced implementation with draft-irtf-cfrg-opaque-05, which changes
|
||||
the envelope structure and introduces a ServerSetup object to be
|
||||
maintained by the server
|
||||
* Various security improvements: non-zero scalars, zeroizing on drop,
|
||||
constant-time operations
|
||||
* Adding serde support behind a feature
|
||||
* Supporting common traits (eb59676)
|
||||
* Swapping out scrypt for argon2 (535b9b8) for the slow-hash feature
|
||||
* Adding support for common traits on public structs
|
||||
* Updated dependencies
|
||||
|
||||
## 0.5.1 (July 16, 2021)
|
||||
|
||||
* Various security improvements: non-zero scalars, zeroizing on drop,
|
||||
constant-time operations, reflected value check, and adding an
|
||||
i2osp error condition
|
||||
|
||||
## 0.5.0 (March 1, 2021)
|
||||
|
||||
* Removed dependency on generic-bytes-derive package
|
||||
|
||||
## 0.4.0 (February 26, 2021)
|
||||
|
||||
* Adherence to protocol format described in
|
||||
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
|
||||
* Renamed to_bytes() and try_from() to serialize() and deserialize() for
|
||||
top-level structs
|
||||
* Conformed all message type parameters to be parameterized in the
|
||||
Ciphersuite object
|
||||
|
||||
## 0.3.1 (February 11, 2021)
|
||||
|
||||
* Re-exporting the rand library (and including it as a dependency instead of
|
||||
just rand_core)
|
||||
* Exposing a convenience function for converting from byte array to Key type
|
||||
|
||||
## 0.3.0 (February 8, 2021)
|
||||
|
||||
* General API and documentation improvements, including the support of custom
|
||||
identifiers, optional result parameters, and the use of the export key
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <opensource-conduct@fb.com>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
+2
-20
@@ -2,29 +2,11 @@
|
||||
We want to make contributing to this project as easy and transparent as
|
||||
possible.
|
||||
|
||||
## Pull Requests
|
||||
We actively welcome your pull requests.
|
||||
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes.
|
||||
5. If you haven't already, complete the Contributor License Agreement ("CLA").
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need
|
||||
to do this once to work on any of Facebook's open source projects.
|
||||
|
||||
Complete your CLA here: <https://code.facebook.com/cla>
|
||||
|
||||
## Issues
|
||||
We use GitHub issues to track public bugs. Please ensure your description is
|
||||
clear and has sufficient instructions to be able to reproduce the issue.
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
disclosure of security bugs. In those cases, please go through the process
|
||||
outlined on that page and do not file a public issue.
|
||||
|
||||
## License
|
||||
By contributing to opaque-ke, you agree that your contributions will be
|
||||
licensed under the LICENSE file in the root directory of this source tree.
|
||||
licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root
|
||||
directory of this source tree.
|
||||
|
||||
Generated
+1202
-598
File diff suppressed because it is too large
Load Diff
+118
-38
@@ -1,50 +1,130 @@
|
||||
[package]
|
||||
name = "opaque-ke"
|
||||
version = "0.3.0"
|
||||
repository = "https://github.com/novifinancial/opaque-ke"
|
||||
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
|
||||
authors = [
|
||||
"VexaHub Developers",
|
||||
"Kevin Lewi <[email protected]>",
|
||||
"François Garillot <[email protected]>",
|
||||
]
|
||||
categories = ["no-std", "cryptography"]
|
||||
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
|
||||
authors = ["Kevin Lewi <[email protected]>", "François Garillot <[email protected]>"]
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
edition = "2024"
|
||||
exclude = ["/src/tests/"]
|
||||
keywords = ["cryptography", "opaque", "passwords", "authentication", "pake"]
|
||||
license = "Apache-2.0 OR MIT"
|
||||
name = "opaque-vx"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/vexahub/opaque-vx"
|
||||
rust-version = "1.90"
|
||||
version = "1.0.0-rc.0"
|
||||
|
||||
[features]
|
||||
default = ["u64_backend"]
|
||||
slow-hash = ["scrypt"]
|
||||
bench = []
|
||||
u64_backend = ["curve25519-dalek/u64_backend"]
|
||||
u32_backend = ["curve25519-dalek/u32_backend"]
|
||||
argon2 = ["dep:argon2"]
|
||||
curve25519 = ["dep:curve25519-dalek"]
|
||||
default = ["ristretto255", "serde"]
|
||||
ecdsa = ["dep:ecdsa"]
|
||||
ed25519 = ["dep:curve25519-dalek", "dep:ed25519-dalek"]
|
||||
kem = ["dep:ml-kem"]
|
||||
ristretto255 = ["dep:curve25519-dalek", "voprf/ristretto255-ciphersuite"]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"curve25519-dalek?/serde",
|
||||
"ecdsa?/serde",
|
||||
"ed25519-dalek?/serde",
|
||||
"elliptic-curve/serde",
|
||||
"generic-array/serde",
|
||||
"hybrid-array/serde",
|
||||
"voprf/serde",
|
||||
"zeroize/serde",
|
||||
]
|
||||
std = ["dep:getrandom", "rand/std"]
|
||||
|
||||
[dependencies]
|
||||
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] }
|
||||
digest = "0.9.0"
|
||||
displaydoc = "0.1.7"
|
||||
generic-array = "0.14.4"
|
||||
generic-bytes = { version = "0.1.0" }
|
||||
generic-bytes-derive = { version = "0.1.0" }
|
||||
hkdf = "0.10.0"
|
||||
hmac = "0.10.1"
|
||||
rand_core = { version = "0.6.0", features = ["getrandom"] }
|
||||
scrypt = { version = "0.5.0", optional = true }
|
||||
subtle = { version = "2.3.0", default-features = false }
|
||||
thiserror = "1.0.22"
|
||||
zeroize = "1.1.1"
|
||||
argon2 = { version = "0.6.0-rc.8", default-features = false, features = [
|
||||
"zeroize",
|
||||
"alloc",
|
||||
], optional = true }
|
||||
curve25519-dalek = { version = "5", default-features = false, features = [
|
||||
"zeroize",
|
||||
], optional = true }
|
||||
derive-where = { version = "1.6", features = ["zeroize-on-drop"] }
|
||||
digest = { version = "0.11", features = ["zeroize"] }
|
||||
displaydoc = { version = "0.2", default-features = false }
|
||||
ecdsa = { version = "0.17", default-features = false, features = [
|
||||
"algorithm",
|
||||
], optional = true }
|
||||
ed25519-dalek = { version = "3", default-features = false, features = [
|
||||
"zeroize",
|
||||
"digest",
|
||||
"hazmat",
|
||||
], optional = true }
|
||||
elliptic-curve = { version = "0.14", features = ["sec1"] }
|
||||
generic-array = { version = "1.4", features = ["hybrid-array-0_4", "zeroize"] }
|
||||
hybrid-array = { version = "0.4", features = ["extra-sizes", "zeroize"] }
|
||||
hkdf = "0.13"
|
||||
hmac = { version = "0.13", features = ["zeroize"] }
|
||||
ml-kem = { version = "0.3", default-features = false, features = [
|
||||
"zeroize",
|
||||
], optional = true }
|
||||
rand = { version = "0.10", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = [
|
||||
"derive",
|
||||
], optional = true }
|
||||
subtle = { version = "2.6", default-features = false }
|
||||
voprf = { package = "voprf-vx", version = "1.0.0-rc.1", default-features = false, features = [
|
||||
"danger",
|
||||
] }
|
||||
zeroize = { version = "1.9", features = ["zeroize_derive"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.4", features = ["wasm_js"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
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.60"
|
||||
sha2 = "0.9.2"
|
||||
proptest = "0.10.1"
|
||||
rand = "0.8"
|
||||
rustyline = "7.0.0"
|
||||
anyhow = "1"
|
||||
bincode-next = { version = "3", features = ["serde", "alloc"] }
|
||||
chacha20poly1305 = { version = "0.11", features = ["zeroize"] }
|
||||
criterion = "0.8"
|
||||
cryptoki = "0.12"
|
||||
elliptic-curve = { version = "0.14", features = ["alloc", "pkcs8"] }
|
||||
hex = "0.4"
|
||||
p256 = { version = "0.14", default-features = false, features = [
|
||||
"ecdsa",
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"oprf",
|
||||
] }
|
||||
p384 = { version = "0.14", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"oprf",
|
||||
] }
|
||||
p521 = { version = "0.14.0-rc.15", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"oprf",
|
||||
] }
|
||||
pastey = "0.2"
|
||||
proptest = "1"
|
||||
rand = "0.10"
|
||||
rand_chacha = "0.10"
|
||||
regex = "1"
|
||||
sha2 = { version = "0.11", default-features = false, features = ["zeroize"] }
|
||||
thiserror = "2"
|
||||
# MSRV
|
||||
rustyline = "18"
|
||||
scrypt = "0.12"
|
||||
serde_json = "1"
|
||||
|
||||
[[bench]]
|
||||
name = "oprf"
|
||||
harness = false
|
||||
required-features = ["bench"]
|
||||
name = "opaque"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
targets = []
|
||||
|
||||
[[example]]
|
||||
name = "simple_login"
|
||||
required-features = ["argon2"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test_hsm)'] }
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,20 +1,28 @@
|
||||
## The OPAQUE key exchange protocol 
|
||||
## The OPAQUE key exchange protocol
|
||||
|
||||
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an asymmetric password-authenticated key exchange protocol. It allows a client to authenticate to a server using a password, without ever having to expose the plaintext password to the server.
|
||||
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an augmented password-authenticated key exchange protocol. It allows a
|
||||
client to authenticate to a server using a password, without ever having to expose the plaintext password to the server.
|
||||
|
||||
This implementation is based on the [Internet Draft for OPAQUE](https://github.com/cfrg/draft-irtf-cfrg-opaque).
|
||||
This implementation is based on [RFC 9807](https://datatracker.ietf.org/doc/rfc9807/).
|
||||
|
||||
This is a fork of [facebook/opaque-ke](https://github.com/facebook/opaque-ke) maintained
|
||||
by [VexaHub](https://github.com/vexahub), targeting the latest **RustCrypto ecosystem**.
|
||||
|
||||
Background
|
||||
----------
|
||||
|
||||
Asymmetric Password Authenticated Key Exchange (aPAKE) protocols are designed to provide password authentication and mutually authenticated key exchange without relying on PKI (except during user/password registration) and without disclosing passwords to servers or other entities other than the client machine.
|
||||
Augmented Password Authenticated Key Exchange (aPAKE) protocols are designed to provide password authentication and
|
||||
mutually authenticated key exchange without relying on PKI (except during user/password registration) and without
|
||||
disclosing passwords to servers or other entities other than the client machine.
|
||||
|
||||
OPAQUE is a PKI-free aPAKE that is secure against pre-computation attacks and capable of using a secret salt.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The API can be found [here](https://docs.rs/opaque-ke/) along with an example for usage.
|
||||
The API can be found [here](https://docs.rs/opaque-vx/) along with an example for usage. More examples can be found
|
||||
in
|
||||
the [examples](./examples) directory.
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -22,30 +30,57 @@ Installation
|
||||
Add the following line to the dependencies of your `Cargo.toml`:
|
||||
|
||||
```
|
||||
opaque-ke = "0.3.0"
|
||||
opaque-vx = "1.0.0-rc.0"
|
||||
```
|
||||
|
||||
### Minimum Supported Rust Version
|
||||
|
||||
Rust **1.90** or higher.
|
||||
|
||||
Audit
|
||||
-----
|
||||
|
||||
This library was audited by NCC Group in June of 2021. The audit was sponsored by WhatsApp for its use
|
||||
in [enabling end-to-end encrypted backups](https://engineering.fb.com/2021/09/10/security/whatsapp-e2ee-backups/).
|
||||
|
||||
The audit found issues in release `v0.5.0`, and the fixes were subsequently incorporated into release `v1.2.0`. See
|
||||
the [full audit report here](https://research.nccgroup.com/2021/12/13/public-report-whatsapp-opaque-ke-cryptographic-implementation-review/).
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
|
||||
- [draft-irtf-cfrg-opaque-02](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-02.html), containing a detailed (byte-level) specification for OPAQUE
|
||||
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
|
||||
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of
|
||||
security
|
||||
- [RFC 9807](https://datatracker.ietf.org/doc/rfc9807/), 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
|
||||
- [@serenity-kit/opaque](https://github.com/serenity-kit/opaque), a WebAssembly package for this library
|
||||
- [opaque-wasm](https://github.com/marucjmar/opaque-wasm), a WebAssembly package for this library. A comparison between
|
||||
`@serenity-kit/opaque` and `opaque-wasm` can be
|
||||
found [here](https://opaque-documentation.netlify.app/docs/faq#how-does-it-compare-to-opaque-wasm)
|
||||
- [react-native-opaque](https://github.com/serenity-kit/react-native-opaque), a React Native package for this library
|
||||
matching the API of `@serenity-kit/opaque`
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
The authors of this code are Kevin Lewi
|
||||
([@kevinlewi](https://github.com/kevinlewi)) and François Garillot ([@huitseeker](https://github.com/huitseeker)).
|
||||
This fork is maintained by [VexaHub](https://github.com/vexahub).
|
||||
|
||||
The original authors are Kevin Lewi ([@kevinlewi](https://github.com/kevinlewi)) and François
|
||||
Garillot ([@huitseeker](https://github.com/huitseeker)).
|
||||
To learn more about contributing to this project, [see this document](./CONTRIBUTING.md).
|
||||
|
||||
#### Acknowledgments
|
||||
|
||||
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving
|
||||
this implementation.
|
||||
|
||||
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for
|
||||
improving
|
||||
this implementation. Additional credit goes to @daxpedda for adding no_std support, p256 support, and making other
|
||||
general
|
||||
improvements to the library.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This project is [MIT licensed](./LICENSE).
|
||||
This project is dual-licensed under either the [MIT license](./LICENSE-MIT)
|
||||
or the [Apache License, Version 2.0](./LICENSE-APACHE).
|
||||
You may select, at your option, one of the above-listed licenses.
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#[macro_use]
|
||||
extern crate criterion;
|
||||
|
||||
use criterion::Criterion;
|
||||
use opaque_vx::*;
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rand::rngs::SysRng;
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
static SUFFIX: &str = "ristretto255";
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
static SUFFIX: &str = "p256";
|
||||
|
||||
struct Default;
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
impl CipherSuite for Default {
|
||||
type OprfCs = Ristretto255;
|
||||
type KeyExchange = TripleDh<Ristretto255, sha2::Sha512>;
|
||||
type Ksf = ksf::Identity;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
impl CipherSuite for Default {
|
||||
type OprfCs = p256::NistP256;
|
||||
type KeyExchange = TripleDh<p256::NistP256, sha2::Sha256>;
|
||||
type Ksf = ksf::Identity;
|
||||
}
|
||||
|
||||
fn server_setup(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
|
||||
c.bench_function(&format!("server setup ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
ServerSetup::<Default>::new(&mut rng);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn client_registration_start(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let password = b"password";
|
||||
|
||||
c.bench_function(&format!("client registration start ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn server_registration_start(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
|
||||
c.bench_function(&format!("server registration start ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
ServerRegistration::<Default>::start(
|
||||
&server_setup,
|
||||
client_registration_start_result.message.clone(),
|
||||
username,
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn client_registration_finish(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&server_setup,
|
||||
client_registration_start_result.message.clone(),
|
||||
username,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function(
|
||||
&format!("client registration finish ({SUFFIX})"),
|
||||
move |b| {
|
||||
b.iter(|| {
|
||||
client_registration_start_result
|
||||
.clone()
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_registration_start_result.message.clone(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn server_registration_finish(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&server_setup,
|
||||
client_registration_start_result.message.clone(),
|
||||
username,
|
||||
)
|
||||
.unwrap();
|
||||
let client_registration_finish_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function(
|
||||
&format!("server registration finish ({SUFFIX})"),
|
||||
move |b| {
|
||||
b.iter(|| {
|
||||
ServerRegistration::finish(client_registration_finish_result.clone().message);
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn client_login_start(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let password = b"password";
|
||||
|
||||
c.bench_function(&format!("client login start ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
ClientLogin::<Default>::start(&mut rng, password).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn server_login_start_real(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&server_setup,
|
||||
client_registration_start_result.message.clone(),
|
||||
username,
|
||||
)
|
||||
.unwrap();
|
||||
let client_registration_finish_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
|
||||
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
|
||||
|
||||
c.bench_function(&format!("server login start (real) ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
ServerLogin::start(
|
||||
&mut rng,
|
||||
&server_setup,
|
||||
Some(password_file.clone()),
|
||||
client_login_start_result.clone().message,
|
||||
username,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn server_login_start_fake(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
|
||||
|
||||
c.bench_function(&format!("server login start (fake) ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
ServerLogin::start(
|
||||
&mut rng,
|
||||
&server_setup,
|
||||
None,
|
||||
client_login_start_result.clone().message,
|
||||
username,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn client_login_finish(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&server_setup,
|
||||
client_registration_start_result.message.clone(),
|
||||
username,
|
||||
)
|
||||
.unwrap();
|
||||
let client_registration_finish_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
|
||||
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_login_start = ServerLogin::start(
|
||||
&mut rng,
|
||||
&server_setup,
|
||||
Some(password_file),
|
||||
client_login_start_result.clone().message,
|
||||
username,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function(&format!("client login finish ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
client_login_start_result
|
||||
.clone()
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_login_start.clone().message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn server_login_finish(c: &mut Criterion) {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let username = b"username";
|
||||
let password = b"password";
|
||||
let server_setup = ServerSetup::<Default>::new(&mut rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_registration_start_result = ServerRegistration::<Default>::start(
|
||||
&server_setup,
|
||||
client_registration_start_result.message.clone(),
|
||||
username,
|
||||
)
|
||||
.unwrap();
|
||||
let client_registration_finish_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
|
||||
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
|
||||
let server_login_start_result = ServerLogin::start(
|
||||
&mut rng,
|
||||
&server_setup,
|
||||
Some(password_file),
|
||||
client_login_start_result.clone().message,
|
||||
username,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let client_login_finish_result = client_login_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut rng,
|
||||
password,
|
||||
server_login_start_result.clone().message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
c.bench_function(&format!("server login finish ({SUFFIX})"), move |b| {
|
||||
b.iter(|| {
|
||||
server_login_start_result
|
||||
.clone()
|
||||
.state
|
||||
.finish(
|
||||
client_login_finish_result.clone().message,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
opaque_benches,
|
||||
server_setup,
|
||||
client_registration_start,
|
||||
server_registration_start,
|
||||
client_registration_finish,
|
||||
server_registration_finish,
|
||||
client_login_start,
|
||||
server_login_start_real,
|
||||
server_login_start_fake,
|
||||
client_login_finish,
|
||||
server_login_finish,
|
||||
);
|
||||
criterion_main!(opaque_benches);
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
#[macro_use]
|
||||
extern crate criterion;
|
||||
|
||||
use criterion::Criterion;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::arr;
|
||||
use opaque_ke::{
|
||||
group::Group,
|
||||
oprf::{blind_shim, evaluate_shim, unblind_and_finalize_shim},
|
||||
};
|
||||
use rand::{prelude::ThreadRng, thread_rng};
|
||||
use sha2::Sha512;
|
||||
|
||||
fn oprf1(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
c.bench_function("blind with Ristretto", move |b| {
|
||||
b.iter(|| {
|
||||
blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn oprf2(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
let (_, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
|
||||
|
||||
c.bench_function("evaluate with Ristretto", move |b| {
|
||||
b.iter(|| {
|
||||
let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn oprf3(c: &mut Criterion) {
|
||||
let mut csprng: ThreadRng = thread_rng();
|
||||
let input = b"hunter2";
|
||||
|
||||
let (token, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
|
||||
let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
|
||||
|
||||
c.bench_function("unblind_and_finalize with Ristretto", move |b| {
|
||||
b.iter(|| {
|
||||
let _res = unblind_and_finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(oprf_benches, oprf1, oprf2, oprf3);
|
||||
criterion_main!(oprf_benches);
|
||||
@@ -0,0 +1 @@
|
||||
doc-valid-idents = ["HashEdDSA", "PureEdDSA", ".."]
|
||||
@@ -9,6 +9,7 @@
|
||||
# The values provided in this template are the default values that will be used
|
||||
# when any section or field is not specified in your own configuration
|
||||
|
||||
[graph]
|
||||
# If 1 or more target triples (and optionally, target_features) are specified,
|
||||
# only the specified targets will be checked when running `cargo deny check`.
|
||||
# This means, if a particular package is only ever used as a target specific
|
||||
@@ -17,14 +18,15 @@
|
||||
# this list would mean the nix crate, as well as any of its exclusive
|
||||
# dependencies not shared by any other crates, would be ignored, as the target
|
||||
# list here is effectively saying which targets you are building for.
|
||||
all-features = true
|
||||
targets = [
|
||||
# The triple can be any string, but only the target triples built in to
|
||||
# rustc (as of 1.40) can be checked against actual config expressions
|
||||
#{ triple = "x86_64-unknown-linux-musl" },
|
||||
# You can also specify which target_features you promise are enabled for a
|
||||
# particular target. target_features are currently not validated against
|
||||
# the actual valid features supported by the target architecture.
|
||||
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||
# The triple can be any string, but only the target triples built in to
|
||||
# rustc (as of 1.40) can be checked against actual config expressions
|
||||
#{ triple = "x86_64-unknown-linux-musl" },
|
||||
# You can also specify which target_features you promise are enabled for a
|
||||
# particular target. target_features are currently not validated against
|
||||
# the actual valid features supported by the target architecture.
|
||||
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check advisories`
|
||||
@@ -34,21 +36,16 @@ 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"
|
||||
# The lint level for security vulnerabilities
|
||||
vulnerability = "deny"
|
||||
# The lint level for unmaintained crates
|
||||
unmaintained = "warn"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
# The lint level for crates that have been yanked from their source registry
|
||||
yanked = "warn"
|
||||
# The lint level for crates with security notices. Note that as of
|
||||
# 2019-12-17 there are no security notice advisories in
|
||||
# https://github.com/rustsec/advisory-db
|
||||
notice = "warn"
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
#"RUSTSEC-0000-0000",
|
||||
# dev-dependency
|
||||
"RUSTSEC-2024-0436",
|
||||
# bincode is unmaintained but only used as dev-dependency for tests
|
||||
"RUSTSEC-2025-0141",
|
||||
]
|
||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||
# lower than the range specified will be ignored. Note that ignored advisories
|
||||
@@ -64,47 +61,26 @@ ignore = [
|
||||
# More documentation for the licenses section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||
[licenses]
|
||||
# The lint level for crates which do not have a detectable license
|
||||
unlicensed = "deny"
|
||||
# List of explictly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
|
||||
allow = [
|
||||
#"MIT",
|
||||
#"Apache-2.0",
|
||||
#"Apache-2.0 WITH LLVM-exception",
|
||||
"MIT",
|
||||
"BSD-3-Clause",
|
||||
"Apache-2.0",
|
||||
#"Apache-2.0 WITH LLVM-exception",
|
||||
]
|
||||
# List of explictly disallowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
|
||||
deny = [
|
||||
#"Nokia",
|
||||
]
|
||||
# Lint level for licenses considered copyleft
|
||||
copyleft = "warn"
|
||||
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
|
||||
# * both - The license will be approved if it is both OSI-approved *AND* FSF
|
||||
# * either - The license will be approved if it is either OSI-approved *OR* FSF
|
||||
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
|
||||
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
|
||||
# * neither - This predicate is ignored and the default lint level is used
|
||||
allow-osi-fsf-free = "neither"
|
||||
# Lint level used when no other predicates are matched
|
||||
# 1. License isn't in the allow or deny lists
|
||||
# 2. License isn't copyleft
|
||||
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
|
||||
default = "allow"
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
# canonical license text of a valid SPDX license file.
|
||||
# [possible values: any between 0.0 and 1.0].
|
||||
confidence-threshold = 0.8
|
||||
confidence-threshold = 0.95
|
||||
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
|
||||
# aren't accepted for every possible crate as with the normal allow list
|
||||
exceptions = [
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
#{ allow = ["Zlib"], name = "adler32", version = "*" },
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
{ allow = ["Unicode-3.0"], name = "unicode-ident", version = "*" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
@@ -123,8 +99,8 @@ exceptions = [
|
||||
# and the crate will be checked normally, which may produce warnings or errors
|
||||
# depending on the rest of your configuration
|
||||
#license-files = [
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
#]
|
||||
|
||||
[licenses.private]
|
||||
@@ -135,7 +111,7 @@ ignore = false
|
||||
# is only published to private registries, and ignore is true, the crate will
|
||||
# not have its license(s) checked
|
||||
registries = [
|
||||
#"https://sekretz.com/registry
|
||||
#"https://sekretz.com/registry
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check bans`.
|
||||
@@ -152,24 +128,24 @@ multiple-versions = "warn"
|
||||
highlight = "all"
|
||||
# List of crates that are allowed. Use with care!
|
||||
allow = [
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
]
|
||||
# List of crates to deny
|
||||
deny = [
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
]
|
||||
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||
skip = [
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
]
|
||||
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||
# by default infinite
|
||||
skip-tree = [
|
||||
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
|
||||
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
|
||||
+111
-95
@@ -1,53 +1,62 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! 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.
|
||||
//! 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 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.
|
||||
//! 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.
|
||||
//! 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 rand_core::{OsRng, RngCore};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
use std::convert::TryFrom;
|
||||
use std::process::exit;
|
||||
|
||||
use opaque_ke::{
|
||||
ciphersuite::CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters,
|
||||
ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization,
|
||||
CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
|
||||
RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration,
|
||||
use chacha20poly1305::aead::{Aead, KeyInit};
|
||||
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
|
||||
use opaque_vx::ciphersuite::CipherSuite;
|
||||
use opaque_vx::rand::Rng;
|
||||
use opaque_vx::rand::rngs::SysRng;
|
||||
use opaque_vx::{
|
||||
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
|
||||
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
|
||||
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
|
||||
ServerLoginParameters, ServerRegistration, ServerSetup,
|
||||
};
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rustyline::Editor;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::history::DefaultHistory;
|
||||
|
||||
// The ciphersuite trait allows to specify the underlying primitives
|
||||
// that will be used in the OPAQUE protocol
|
||||
// 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 DefaultCipherSuite;
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
impl CipherSuite for DefaultCipherSuite {
|
||||
type OprfCs = opaque_vx::Ristretto255;
|
||||
type KeyExchange = opaque_vx::TripleDh<opaque_vx::Ristretto255, sha2::Sha512>;
|
||||
type Ksf = opaque_vx::ksf::Identity;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
impl CipherSuite for DefaultCipherSuite {
|
||||
type OprfCs = p256::NistP256;
|
||||
type KeyExchange = opaque_vx::TripleDh<p256::NistP256, sha2::Sha256>;
|
||||
type Ksf = opaque_vx::ksf::Identity;
|
||||
}
|
||||
|
||||
struct Locker {
|
||||
@@ -57,46 +66,48 @@ struct Locker {
|
||||
|
||||
// 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 cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
|
||||
|
||||
let mut rng = OsRng;
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
let nonce = Nonce::try_from(&nonce_bytes[..]).unwrap();
|
||||
|
||||
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
|
||||
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
|
||||
// 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]));
|
||||
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
|
||||
cipher
|
||||
.decrypt(
|
||||
Nonce::from_slice(&ciphertext[..12]),
|
||||
&Nonce::try_from(&ciphertext[..12]).unwrap(),
|
||||
ciphertext[12..].as_ref(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Password-based registration and encryption of client secret message between a client and server
|
||||
// Password-based registration and encryption of client secret message between a
|
||||
// client and server
|
||||
fn register_locker(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
server_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
locker_id: usize,
|
||||
password: String,
|
||||
secret_message: String,
|
||||
) -> Locker {
|
||||
let mut client_rng = OsRng;
|
||||
let mut client_rng = UnwrapErr(SysRng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
|
||||
ClientRegistration::<DefaultCipherSuite>::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(),
|
||||
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
|
||||
server_setup,
|
||||
RegistrationRequest::deserialize(®istration_request_bytes).unwrap(),
|
||||
&locker_id.to_be_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
let registration_response_bytes = server_registration_start_result.message.serialize();
|
||||
@@ -107,7 +118,8 @@ fn register_locker(
|
||||
.state
|
||||
.finish(
|
||||
&mut client_rng,
|
||||
RegistrationResponse::deserialize(®istration_response_bytes[..]).unwrap(),
|
||||
password.as_bytes(),
|
||||
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -121,42 +133,40 @@ fn register_locker(
|
||||
|
||||
// Client sends message_bytes to server
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
|
||||
.unwrap();
|
||||
let password_file = ServerRegistration::finish(
|
||||
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
|
||||
);
|
||||
|
||||
Locker {
|
||||
contents: ciphertext,
|
||||
password_file: password_file.to_bytes(),
|
||||
password_file: password_file.serialize().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
// Open the contents of a locker with a password between a client and server
|
||||
fn open_locker(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
server_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
locker_id: usize,
|
||||
password: String,
|
||||
locker: &Locker,
|
||||
) -> Result<String, String> {
|
||||
let mut client_rng = OsRng;
|
||||
let client_login_start_result = ClientLogin::<Default>::start(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut client_rng = UnwrapErr(SysRng);
|
||||
let client_login_start_result =
|
||||
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||
|
||||
// Client sends credential_request_bytes to server
|
||||
|
||||
let password_file = ServerRegistration::<Default>::try_from(&locker.password_file[..]).unwrap();
|
||||
let mut server_rng = OsRng;
|
||||
let password_file =
|
||||
ServerRegistration::<DefaultCipherSuite>::deserialize(&locker.password_file).unwrap();
|
||||
let mut server_rng = UnwrapErr(SysRng);
|
||||
let server_login_start_result = ServerLogin::start(
|
||||
&mut server_rng,
|
||||
password_file,
|
||||
&server_kp.private(),
|
||||
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
|
||||
ServerLoginStartParameters::default(),
|
||||
server_setup,
|
||||
Some(password_file),
|
||||
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
|
||||
&locker_id.to_be_bytes(),
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize();
|
||||
@@ -164,7 +174,9 @@ fn open_locker(
|
||||
// Server sends credential_response_bytes to client
|
||||
|
||||
let result = client_login_start_result.state.finish(
|
||||
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
@@ -179,14 +191,17 @@ fn open_locker(
|
||||
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
|
||||
.finish(
|
||||
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.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
|
||||
// Client decrypts contents of locker, first under the session key, and then
|
||||
let plaintext = decrypt(
|
||||
&client_login_finish_result.export_key,
|
||||
&decrypt(
|
||||
@@ -198,10 +213,10 @@ fn open_locker(
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = OsRng;
|
||||
let server_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
|
||||
|
||||
let mut rl = Editor::<()>::new();
|
||||
let mut rl = Editor::<(), _>::new().unwrap();
|
||||
let mut registered_lockers: Vec<Locker> = vec![];
|
||||
loop {
|
||||
display_lockers(®istered_lockers);
|
||||
@@ -224,8 +239,10 @@ fn main() {
|
||||
&mut rl,
|
||||
None,
|
||||
);
|
||||
let locker_id = registered_lockers.len();
|
||||
registered_lockers.push(register_locker(
|
||||
&server_kp,
|
||||
&server_setup,
|
||||
locker_id,
|
||||
password,
|
||||
secret_message,
|
||||
));
|
||||
@@ -251,15 +268,17 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match open_locker(&server_kp, password, ®istered_lockers[locker_index]) {
|
||||
match open_locker(
|
||||
&server_setup,
|
||||
locker_index,
|
||||
password,
|
||||
®istered_lockers[locker_index],
|
||||
) {
|
||||
Ok(contents) => {
|
||||
println!("\n\nSuccess! Contents: {}\n\n", contents);
|
||||
println!("\n\nSuccess! Contents: {contents}\n\n");
|
||||
}
|
||||
Err(err) => {
|
||||
println!(
|
||||
"\n\nError encountered, could not open locker: {}\n\n",
|
||||
err
|
||||
);
|
||||
println!("\n\nError encountered, could not open locker: {err}\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,16 +295,13 @@ fn main() {
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn display_lockers(lockers: &Vec<Locker>) {
|
||||
fn display_lockers(lockers: &[Locker]) {
|
||||
let mut locker_numbers = vec![];
|
||||
for (i, _) in lockers.iter().enumerate() {
|
||||
locker_numbers.push(i);
|
||||
}
|
||||
|
||||
println!(
|
||||
"\nCurrently registered locker numbers: {:?}\n",
|
||||
locker_numbers
|
||||
);
|
||||
println!("\nCurrently registered locker numbers: {locker_numbers:?}\n");
|
||||
}
|
||||
|
||||
// Handle readline errors
|
||||
@@ -298,7 +314,7 @@ fn handle_error(err: ReadlineError) {
|
||||
println!("CTRL-D");
|
||||
}
|
||||
err => {
|
||||
println!("Error: {:?}", err);
|
||||
println!("Error: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,11 +323,11 @@ fn handle_error(err: ReadlineError) {
|
||||
fn get_two_strings(
|
||||
s1: &str,
|
||||
s2: &str,
|
||||
rl: &mut Editor<()>,
|
||||
rl: &mut Editor<(), DefaultHistory>,
|
||||
string1: Option<String>,
|
||||
) -> (String, String) {
|
||||
let query = if string1.is_none() { s1 } else { s2 };
|
||||
let readline = rl.readline(&format!("{}: ", query));
|
||||
let readline = rl.readline(&format!("{query}: "));
|
||||
match readline {
|
||||
Ok(line) => match string1 {
|
||||
Some(x) => (x, line),
|
||||
|
||||
+102
-81
@@ -1,67 +1,79 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Demonstrates a simple client-server password-based login protocol
|
||||
//! using OPAQUE, over a command-line interface
|
||||
//! 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 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.
|
||||
//! 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.
|
||||
//! 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 rand_core::OsRng;
|
||||
use rustyline::error::ReadlineError;
|
||||
use opaque_vx::argon2::Argon2;
|
||||
use opaque_vx::ciphersuite::CipherSuite;
|
||||
use opaque_vx::hybrid_array::Array;
|
||||
use opaque_vx::rand::rngs::SysRng;
|
||||
use opaque_vx::{
|
||||
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
|
||||
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
|
||||
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
|
||||
ServerLoginParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
|
||||
};
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rustyline::Editor;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::history::DefaultHistory;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::process::exit;
|
||||
|
||||
use opaque_ke::{
|
||||
ciphersuite::CipherSuite, 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
|
||||
// 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 DefaultCipherSuite;
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
impl CipherSuite for DefaultCipherSuite {
|
||||
type OprfCs = opaque_vx::Ristretto255;
|
||||
type KeyExchange = opaque_vx::TripleDh<opaque_vx::Ristretto255, sha2::Sha512>;
|
||||
|
||||
type Ksf = Argon2<'static>;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
impl CipherSuite for DefaultCipherSuite {
|
||||
type OprfCs = p256::NistP256;
|
||||
type KeyExchange = opaque_vx::TripleDh<p256::NistP256, sha2::Sha256>;
|
||||
|
||||
type Ksf = Argon2<'static>;
|
||||
}
|
||||
|
||||
// Password-based registration between a client and server
|
||||
fn account_registration(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
server_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Vec<u8> {
|
||||
let mut client_rng = OsRng;
|
||||
) -> Array<u8, ServerRegistrationLen<DefaultCipherSuite>> {
|
||||
let mut client_rng = UnwrapErr(SysRng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
|
||||
ClientRegistration::<DefaultCipherSuite>::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(),
|
||||
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
|
||||
server_setup,
|
||||
RegistrationRequest::deserialize(®istration_request_bytes).unwrap(),
|
||||
username.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
let registration_response_bytes = server_registration_start_result.message.serialize();
|
||||
@@ -72,7 +84,8 @@ fn account_registration(
|
||||
.state
|
||||
.finish(
|
||||
&mut client_rng,
|
||||
RegistrationResponse::deserialize(®istration_response_bytes[..]).unwrap(),
|
||||
password.as_bytes(),
|
||||
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -80,38 +93,36 @@ fn account_registration(
|
||||
|
||||
// Client sends message_bytes to server
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
|
||||
.unwrap();
|
||||
password_file.to_bytes()
|
||||
let password_file = ServerRegistration::finish(
|
||||
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
|
||||
);
|
||||
password_file.serialize().into_ha0_4()
|
||||
}
|
||||
|
||||
// Password-based login between a client and server
|
||||
fn account_login(
|
||||
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
|
||||
server_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
username: String,
|
||||
password: String,
|
||||
password_file_bytes: &[u8],
|
||||
) -> bool {
|
||||
let mut client_rng = OsRng;
|
||||
let client_login_start_result = ClientLogin::<Default>::start(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut client_rng = UnwrapErr(SysRng);
|
||||
let client_login_start_result =
|
||||
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||
|
||||
// Client sends credential_request_bytes to server
|
||||
|
||||
let password_file = ServerRegistration::<Default>::try_from(password_file_bytes).unwrap();
|
||||
let mut server_rng = OsRng;
|
||||
let password_file =
|
||||
ServerRegistration::<DefaultCipherSuite>::deserialize(password_file_bytes).unwrap();
|
||||
let mut server_rng = UnwrapErr(SysRng);
|
||||
let server_login_start_result = ServerLogin::start(
|
||||
&mut server_rng,
|
||||
password_file,
|
||||
&server_kp.private(),
|
||||
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
|
||||
ServerLoginStartParameters::default(),
|
||||
server_setup,
|
||||
Some(password_file),
|
||||
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
|
||||
username.as_bytes(),
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize();
|
||||
@@ -119,7 +130,9 @@ fn account_login(
|
||||
// Server sends credential_response_bytes to client
|
||||
|
||||
let result = client_login_start_result.state.finish(
|
||||
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
@@ -134,18 +147,22 @@ fn account_login(
|
||||
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
|
||||
.finish(
|
||||
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.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 rng = UnwrapErr(SysRng);
|
||||
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
|
||||
|
||||
let mut rl = Editor::<()>::new();
|
||||
let mut registered_users = HashMap::<String, Vec<u8>>::new();
|
||||
let mut rl = Editor::<(), _>::new().unwrap();
|
||||
let mut registered_users =
|
||||
HashMap::<String, Array<u8, ServerRegistrationLen<DefaultCipherSuite>>>::new();
|
||||
loop {
|
||||
println!(
|
||||
"\nCurrently registered usernames: {:?}\n",
|
||||
@@ -165,20 +182,24 @@ fn main() {
|
||||
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
|
||||
match line.as_ref() {
|
||||
"1" => {
|
||||
registered_users
|
||||
.insert(username, account_registration(&server_kp, password));
|
||||
registered_users.insert(
|
||||
username.clone(),
|
||||
account_registration(&server_setup, username, password),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
"2" => match registered_users.get(&username) {
|
||||
Some(password_file_bytes) => {
|
||||
if account_login(&server_kp, password, password_file_bytes) {
|
||||
if account_login(&server_setup, username, password, password_file_bytes)
|
||||
{
|
||||
println!("\nLogin success!");
|
||||
} else {
|
||||
// Note that at this point, the client knows whether or not the login
|
||||
// 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.
|
||||
// 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.");
|
||||
}
|
||||
}
|
||||
@@ -207,7 +228,7 @@ fn handle_error(err: ReadlineError) {
|
||||
println!("CTRL-D");
|
||||
}
|
||||
err => {
|
||||
println!("Error: {:?}", err);
|
||||
println!("Error: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,11 +237,11 @@ fn handle_error(err: ReadlineError) {
|
||||
fn get_two_strings(
|
||||
s1: &str,
|
||||
s2: &str,
|
||||
rl: &mut Editor<()>,
|
||||
rl: &mut Editor<(), DefaultHistory>,
|
||||
string1: Option<String>,
|
||||
) -> (String, String) {
|
||||
let query = if string1.is_none() { s1 } else { s2 };
|
||||
let readline = rl.readline(&format!("{}: ", query));
|
||||
let readline = rl.readline(&format!("{query}: "));
|
||||
match readline {
|
||||
Ok(line) => match string1 {
|
||||
Some(x) => (x, line),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
],
|
||||
"dependencyDashboard": true,
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"rangeStrategy": "auto",
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"cargo"
|
||||
],
|
||||
"groupName": "rust deps"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"cargo"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"major"
|
||||
],
|
||||
"automerge": false
|
||||
}
|
||||
],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true
|
||||
},
|
||||
"configMigration": true
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
newline_style = "Unix"
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/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.
|
||||
|
||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
# Copyright (c) VexaHub and contributors.
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
set timeout 1
|
||||
spawn cargo run --example digital_locker
|
||||
|
||||
Executable → Regular
+5
-5
@@ -1,11 +1,11 @@
|
||||
#!/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.
|
||||
|
||||
# SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
# Copyright (c) VexaHub and contributors.
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
set timeout 1
|
||||
spawn cargo run --example simple_login
|
||||
spawn cargo run --example simple_login --features argon2
|
||||
match_max 100000
|
||||
sleep 1
|
||||
expect "*
|
||||
|
||||
+46
-32
@@ -1,41 +1,55 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE
|
||||
//! Defines the [`CipherSuite`] trait to specify the underlying primitives for
|
||||
//! OPAQUE
|
||||
|
||||
use crate::{
|
||||
hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
|
||||
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
|
||||
};
|
||||
use digest::Digest;
|
||||
use core::ops::Add;
|
||||
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use digest::block_api::{CoreProxy, EagerHash, SmallBlockSizeUser};
|
||||
use generic_array::ArrayLength;
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
|
||||
|
||||
use crate::envelope::NonceLen;
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::KeyExchange;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::ksf::Ksf;
|
||||
use crate::opaque::MaskedResponseLen;
|
||||
|
||||
/// 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`.
|
||||
/// * `OprfCs`: A VOPRF ciphersuite, see [`voprf::CipherSuite`].
|
||||
/// * `KeGroup`: A `Group` used for the `KeyExchange`.
|
||||
/// * `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
|
||||
pub trait CipherSuite {
|
||||
/// 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`.
|
||||
type Group: GroupWithMapToCurve<UniformBytesLen = <Self::Hash as Digest>::OutputSize>;
|
||||
/// * `Ksf`: A key stretching function, typically used for password hashing
|
||||
pub trait CipherSuite
|
||||
where
|
||||
OprfHash<Self>: Hash + EagerHash,
|
||||
<OprfHash<Self> as CoreProxy>::Core: ProxyHash,
|
||||
<<OprfHash<Self> as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<OprfHash<Self> as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
// Envelope: Nonce + Hash
|
||||
// MaskedResponse: (Nonce + Hash) + KePk
|
||||
// TODO: migrate fully to after hybrid-array v0.5 releases
|
||||
// https://github.com/RustCrypto/hybrid-array/issues/66
|
||||
OutputSize<OprfHash<Self>>: Add<NonceLen> + ArrayLength,
|
||||
Sum<OutputSize<OprfHash<Self>>, NonceLen>: ArrayLength + Add<<KeGroup<Self> as Group>::PkLen>,
|
||||
MaskedResponseLen<Self>: ArrayLength,
|
||||
// hybrid-array interop bounds
|
||||
<OprfGroup<Self> as voprf::Group>::ScalarLen: ArrayLength,
|
||||
<OprfGroup<Self> as voprf::Group>::ElemLen: ArrayLength,
|
||||
{
|
||||
/// A VOPRF ciphersuite, see [`voprf::CipherSuite`].
|
||||
type OprfCs: voprf::CipherSuite;
|
||||
/// A key exchange protocol
|
||||
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) -> KeyPair<Self::Group> {
|
||||
KeyPair::<Self::Group>::generate_random(rng)
|
||||
}
|
||||
type KeyExchange: KeyExchange;
|
||||
/// A key stretching function, typically used for password hashing
|
||||
type Ksf: Ksf;
|
||||
}
|
||||
|
||||
pub(crate) type OprfGroup<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Group;
|
||||
pub(crate) type OprfHash<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Hash;
|
||||
pub(crate) type KeGroup<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Group;
|
||||
pub(crate) type KeHash<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Hash;
|
||||
|
||||
+245
-279
@@ -1,344 +1,310 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use crate::{
|
||||
errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
|
||||
hash::Hash,
|
||||
serialization::{serialize, tokenize},
|
||||
};
|
||||
use digest::Digest;
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::Output;
|
||||
use generic_array::GenericArray;
|
||||
use generic_array::typenum::{Sum, U32};
|
||||
use hkdf::SimpleHkdf as Hkdf;
|
||||
use hmac::{KeyInit, Mac, SimpleHmac};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, OprfHash};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::SerializedIdentifiers;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::{GenericArrayExt, SliceExt, UpdateExt};
|
||||
|
||||
// Constant string used as salt for HKDF computation
|
||||
const STR_PAD: &[u8] = b"Pad";
|
||||
const STR_AUTH_KEY: &[u8] = b"AuthKey";
|
||||
const STR_EXPORT_KEY: &[u8] = b"ExportKey";
|
||||
const STR_AUTH_KEY: [u8; 7] = *b"AuthKey";
|
||||
const STR_EXPORT_KEY: [u8; 9] = *b"ExportKey";
|
||||
const STR_PRIVATE_KEY: [u8; 10] = *b"PrivateKey";
|
||||
pub(crate) type NonceLen = U32;
|
||||
|
||||
const NONCE_LEN: usize = 32;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub(crate) enum InnerEnvelopeMode {
|
||||
Base = 1,
|
||||
CustomIdentifier = 2,
|
||||
Zero = 0,
|
||||
Internal = 1,
|
||||
}
|
||||
|
||||
impl Zeroize for InnerEnvelopeMode {
|
||||
fn zeroize(&mut self) {
|
||||
*self = Self::Zero
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for InnerEnvelopeMode {
|
||||
type Error = PakeError;
|
||||
type Error = ProtocolError;
|
||||
fn try_from(x: u8) -> Result<Self, Self::Error> {
|
||||
match x {
|
||||
1 => Ok(InnerEnvelopeMode::Base),
|
||||
2 => Ok(InnerEnvelopeMode::CustomIdentifier),
|
||||
_ => Err(PakeError::SerializationError),
|
||||
1 => Ok(InnerEnvelopeMode::Internal),
|
||||
_ => Err(ProtocolError::SerializationError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InnerEnvelope {
|
||||
mode: InnerEnvelopeMode,
|
||||
nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
}
|
||||
|
||||
impl InnerEnvelope {
|
||||
pub(crate) fn serialize(&self) -> Vec<u8> {
|
||||
[
|
||||
&[self.mode as u8],
|
||||
&self.nonce[..],
|
||||
&serialize(&self.ciphertext, 2)[..],
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
|
||||
if input.is_empty() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::SerializationError,
|
||||
));
|
||||
}
|
||||
let mode = InnerEnvelopeMode::try_from(input[0])?;
|
||||
|
||||
let bytes = &input[1..];
|
||||
if bytes.len() < NONCE_LEN {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::SerializationError,
|
||||
));
|
||||
}
|
||||
|
||||
let nonce = &bytes[..NONCE_LEN];
|
||||
let (ciphertext, remainder) = tokenize(&bytes[NONCE_LEN..], 2)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
mode,
|
||||
nonce: nonce.to_vec(),
|
||||
ciphertext,
|
||||
},
|
||||
remainder,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is an instantiation of the envelope as described in
|
||||
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
|
||||
/// This struct is an instantiation of the envelope.
|
||||
///
|
||||
/// Note that earlier versions of this specification described an
|
||||
/// implementation of this envelope using an encryption scheme that
|
||||
/// satisfied random-key robustness
|
||||
/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4).
|
||||
/// The specification update has simplified this assumption by taking
|
||||
/// an XOR-based approach without compromising on security, and to avoid
|
||||
/// the confusion around the implementation of an RKR-secure encryption.
|
||||
pub(crate) struct Envelope<D: Hash> {
|
||||
inner_envelope: InnerEnvelope,
|
||||
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
/// Note that earlier versions of this specification described an implementation
|
||||
/// of this envelope using an encryption scheme that satisfied random-key
|
||||
/// robustness.
|
||||
/// The specification update has simplified this assumption by taking an
|
||||
/// XOR-based approach without compromising on security, and to avoid the
|
||||
/// confusion around the implementation of an RKR-secure encryption.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
|
||||
pub(crate) struct Envelope<CS: CipherSuite> {
|
||||
pub(crate) mode: InnerEnvelopeMode,
|
||||
nonce: GenericArray<u8, NonceLen>,
|
||||
hmac: Output<OprfHash<CS>>,
|
||||
}
|
||||
|
||||
// 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>,
|
||||
// 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<'a, CS: CipherSuite> {
|
||||
pub(crate) client_static_keypair: KeyPair<KeGroup<CS>>,
|
||||
pub(crate) export_key: Output<OprfHash<CS>>,
|
||||
pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
}
|
||||
|
||||
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
|
||||
pub(crate) plaintext: Vec<u8>,
|
||||
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
pub(crate) struct OpenedInnerEnvelope<CS: CipherSuite> {
|
||||
pub(crate) export_key: Output<OprfHash<CS>>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>);
|
||||
#[cfg(test)]
|
||||
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>, Output<OprfHash<CS>>);
|
||||
#[cfg(not(test))]
|
||||
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<KeGroup<CS>>, Output<OprfHash<CS>>);
|
||||
#[cfg(test)]
|
||||
type SealResult<CS: CipherSuite> = (
|
||||
Envelope<CS>,
|
||||
PublicKey<KeGroup<CS>>,
|
||||
Output<OprfHash<CS>>,
|
||||
Output<OprfHash<CS>>,
|
||||
);
|
||||
|
||||
fn hmac_key_size() -> usize {
|
||||
<D as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<OutputSize<OprfHash<CS>>, NonceLen>;
|
||||
|
||||
fn export_key_size() -> usize {
|
||||
<D as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
pub(crate) fn get_mode(&self) -> InnerEnvelopeMode {
|
||||
self.inner_envelope.mode
|
||||
}
|
||||
|
||||
/// The format of the output is:
|
||||
/// mode | nonce | ciphertext | hmac
|
||||
/// u8 | nonce_size bytes | variable length | hmac_size bytes
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let (result, remainder) = Self::deserialize(bytes)
|
||||
.map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?;
|
||||
if !remainder.is_empty() {
|
||||
return Err(InternalPakeError::InvalidEnvelopeStructureError);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
self.serialize()
|
||||
}
|
||||
|
||||
pub(crate) fn 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>(
|
||||
impl<CS: CipherSuite> Envelope<CS> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn seal<R: Rng + 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 plaintext = serialize(&client_s_sk, 2);
|
||||
let aad = construct_aad(server_s_pk, &optional_ids);
|
||||
Self::seal_raw(rng, key, &plaintext, &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_raw<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
key: &[u8],
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
mode: InnerEnvelopeMode,
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
|
||||
let mut nonce = vec![0u8; NONCE_LEN];
|
||||
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
|
||||
server_s_pk: &PublicKey<KeGroup<CS>>,
|
||||
ids: Identifiers,
|
||||
) -> Result<SealResult<CS>, ProtocolError> {
|
||||
let mut nonce = GenericArray::default();
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
let h = Hkdf::<D>::new(Some(&nonce), &key);
|
||||
let mut xor_key = vec![0u8; plaintext.len()];
|
||||
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
|
||||
let mut export_key = vec![0u8; Self::export_key_size()];
|
||||
let (mode, client_s_pk) = (
|
||||
InnerEnvelopeMode::Internal,
|
||||
build_inner_envelope_internal::<CS>(randomized_pwd_hasher, nonce)?,
|
||||
);
|
||||
|
||||
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 server_s_pk_bytes = server_s_pk.serialize();
|
||||
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
|
||||
ids,
|
||||
client_s_pk.serialize(),
|
||||
server_s_pk_bytes.clone(),
|
||||
)?;
|
||||
let aad = construct_aad(
|
||||
identifiers.client.iter(),
|
||||
identifiers.server.iter(),
|
||||
&server_s_pk_bytes,
|
||||
);
|
||||
|
||||
let ciphertext: Vec<u8> = xor_key
|
||||
.iter()
|
||||
.zip(plaintext.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
|
||||
Ok((
|
||||
result.0,
|
||||
client_s_pk,
|
||||
result.1,
|
||||
#[cfg(test)]
|
||||
result.2,
|
||||
))
|
||||
}
|
||||
|
||||
let inner_envelope = InnerEnvelope {
|
||||
mode,
|
||||
nonce,
|
||||
ciphertext,
|
||||
};
|
||||
/// Uses a key to convert the plaintext into an envelope, authenticated by
|
||||
/// the aad field. Note that a new nonce is sampled for each call to seal.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn seal_raw<'a>(
|
||||
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
|
||||
nonce: GenericArray<u8, NonceLen>,
|
||||
aad: impl Iterator<Item = &'a [u8]>,
|
||||
mode: InnerEnvelopeMode,
|
||||
) -> Result<SealRawResult<CS>, InternalError> {
|
||||
let mut hmac_key = Output::<OprfHash<CS>>::default();
|
||||
let mut export_key = Output::<OprfHash<CS>>::default();
|
||||
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
hmac.update(&inner_envelope.serialize());
|
||||
hmac.update(&aad);
|
||||
randomized_pwd_hasher
|
||||
.expand_multi_info(&[&nonce, &STR_AUTH_KEY], &mut hmac_key)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
randomized_pwd_hasher
|
||||
.expand_multi_info(&[&nonce, &STR_EXPORT_KEY], &mut export_key)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
|
||||
let mut hmac = SimpleHmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
|
||||
.map_err(|_| InternalError::HmacError)?;
|
||||
hmac.update(&nonce);
|
||||
hmac.update_iter(aad);
|
||||
|
||||
let hmac_bytes = hmac.finalize().into_bytes();
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
inner_envelope,
|
||||
mode,
|
||||
nonce,
|
||||
hmac: hmac_bytes,
|
||||
},
|
||||
GenericArray::clone_from_slice(&export_key),
|
||||
export_key,
|
||||
#[cfg(test)]
|
||||
hmac_key,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn open(
|
||||
pub(crate) fn open<'a>(
|
||||
&self,
|
||||
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);
|
||||
}
|
||||
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
|
||||
server_s_pk: PublicKey<KeGroup<CS>>,
|
||||
optional_ids: Identifiers<'a>,
|
||||
) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
|
||||
let client_static_keypair = match self.mode {
|
||||
InnerEnvelopeMode::Zero => {
|
||||
return Err(InternalError::IncompatibleEnvelopeModeError.into());
|
||||
}
|
||||
InnerEnvelopeMode::Internal => {
|
||||
recover_keys_internal::<CS>(randomized_pwd_hasher, self.nonce)?
|
||||
}
|
||||
};
|
||||
|
||||
let aad = construct_aad(server_s_pk, optional_ids);
|
||||
let opened = self.open_raw(key, &aad)?;
|
||||
let server_s_pk_bytes = server_s_pk.serialize();
|
||||
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
|
||||
optional_ids,
|
||||
client_static_keypair.public().serialize(),
|
||||
server_s_pk_bytes.clone(),
|
||||
)?;
|
||||
let aad = construct_aad(
|
||||
identifiers.client.iter(),
|
||||
identifiers.server.iter(),
|
||||
&server_s_pk_bytes,
|
||||
);
|
||||
|
||||
let (client_s_sk, remainder) = tokenize(&opened.plaintext, 2)
|
||||
.map_err(|_| InternalPakeError::UnexpectedEnvelopeContentsError)?;
|
||||
if !remainder.is_empty() {
|
||||
// Should not have anything else in plaintext
|
||||
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
|
||||
}
|
||||
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
|
||||
|
||||
Ok(OpenedEnvelope {
|
||||
client_s_sk,
|
||||
client_static_keypair,
|
||||
export_key: opened.export_key,
|
||||
identifiers,
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to decrypt the envelope using a key, which is successful only if the key and
|
||||
/// aad used to construct the envelope are the same.
|
||||
pub(crate) fn open_raw(
|
||||
/// Attempts to decrypt the envelope using a key, which is successful only
|
||||
/// if the key and aad used to construct the envelope are the same.
|
||||
pub(crate) fn open_raw<'a>(
|
||||
&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()];
|
||||
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
|
||||
aad: impl Iterator<Item = &'a [u8]>,
|
||||
) -> Result<OpenedInnerEnvelope<CS>, InternalError> {
|
||||
let mut hmac_key = Output::<OprfHash<CS>>::default();
|
||||
let mut export_key = Output::<OprfHash<CS>>::default();
|
||||
|
||||
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)?;
|
||||
randomized_pwd_hasher
|
||||
.expand_multi_info(&[&self.nonce, &STR_AUTH_KEY], &mut hmac_key)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
randomized_pwd_hasher
|
||||
.expand_multi_info(&[&self.nonce, &STR_EXPORT_KEY], &mut export_key)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
|
||||
let mut hmac =
|
||||
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
hmac.update(&self.inner_envelope.serialize());
|
||||
hmac.update(aad);
|
||||
if hmac.verify(&self.hmac).is_err() {
|
||||
return Err(InternalPakeError::SealOpenHmacError);
|
||||
let mut hmac = SimpleHmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
|
||||
.map_err(|_| InternalError::HmacError)?;
|
||||
hmac.update(&self.nonce);
|
||||
hmac.update_iter(aad);
|
||||
hmac.verify(&self.hmac)
|
||||
.map_err(|_| InternalError::SealOpenHmacError)?;
|
||||
|
||||
Ok(OpenedInnerEnvelope { export_key })
|
||||
}
|
||||
|
||||
// Creates a dummy envelope object that serializes to the all-zeros byte string
|
||||
pub(crate) fn dummy() -> Self {
|
||||
Self {
|
||||
mode: InnerEnvelopeMode::Zero,
|
||||
nonce: GenericArray::default(),
|
||||
hmac: GenericArray::default().into_ha0_4(),
|
||||
}
|
||||
}
|
||||
|
||||
let plaintext: Vec<u8> = xor_key
|
||||
.iter()
|
||||
.zip(self.inner_envelope.ciphertext.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
Ok(OpenedInnerEnvelope {
|
||||
plaintext,
|
||||
export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
|
||||
&export_key,
|
||||
),
|
||||
#[cfg(test)]
|
||||
pub(crate) fn len() -> usize {
|
||||
use generic_array::typenum::Unsigned;
|
||||
|
||||
OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>> {
|
||||
self.nonce
|
||||
.concat_ext(&GenericArray::from_ha0_4(self.hmac.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
mode: InnerEnvelopeMode::Internal,
|
||||
nonce: bytes.take_array("nonce")?,
|
||||
hmac: bytes
|
||||
.take_array::<OutputSize<OprfHash<CS>>>("hmac")?
|
||||
.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
[serialize(server_s_pk, 2), ids].concat()
|
||||
fn build_inner_envelope_internal<CS: CipherSuite>(
|
||||
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
|
||||
nonce: GenericArray<u8, NonceLen>,
|
||||
) -> Result<PublicKey<KeGroup<CS>>, ProtocolError> {
|
||||
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
|
||||
randomized_pwd_hasher
|
||||
.expand_multi_info(&[&nonce, &STR_PRIVATE_KEY], &mut keypair_seed)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
|
||||
|
||||
Ok(client_s_sk.public_key())
|
||||
}
|
||||
|
||||
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
|
||||
match optional_ids {
|
||||
Some(_) => InnerEnvelopeMode::CustomIdentifier,
|
||||
None => InnerEnvelopeMode::Base,
|
||||
}
|
||||
fn recover_keys_internal<CS: CipherSuite>(
|
||||
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
|
||||
nonce: GenericArray<u8, NonceLen>,
|
||||
) -> Result<KeyPair<KeGroup<CS>>, ProtocolError> {
|
||||
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
|
||||
randomized_pwd_hasher
|
||||
.expand_multi_info(&[&nonce, &STR_PRIVATE_KEY], &mut keypair_seed)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
|
||||
let client_s_pk = client_s_sk.public_key();
|
||||
|
||||
Ok(KeyPair::new(client_s_sk, client_s_pk))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[test]
|
||||
fn seal_and_open() {
|
||||
let mut rng = OsRng;
|
||||
let mut key = [0u8; 32];
|
||||
rng.fill_bytes(&mut key);
|
||||
|
||||
let mut msg = [0u8; 100];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
|
||||
&mut rng,
|
||||
&key,
|
||||
&msg,
|
||||
b"aad",
|
||||
InnerEnvelopeMode::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
|
||||
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
|
||||
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
|
||||
}
|
||||
fn construct_aad<'a>(
|
||||
id_u: impl Iterator<Item = &'a [u8]>,
|
||||
id_s: impl Iterator<Item = &'a [u8]>,
|
||||
server_s_pk: &'a [u8],
|
||||
) -> impl Iterator<Item = &'a [u8]> {
|
||||
[server_s_pk].into_iter().chain(id_s).chain(id_u)
|
||||
}
|
||||
|
||||
+135
-148
@@ -1,18 +1,90 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! A list of error types which are produced during an execution of the protocol
|
||||
use core::convert::Infallible;
|
||||
use core::error::Error;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use displaydoc::Display;
|
||||
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}.
|
||||
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum InternalError {
|
||||
/// Size of input is empty or longer then [`u16::MAX`].
|
||||
HashToScalar,
|
||||
/// Computing HKDF failed while deriving subkeys
|
||||
HkdfError,
|
||||
/// Computing HMAC failed while supplying a secret key
|
||||
HmacError,
|
||||
/// Computing the key stretching function failed
|
||||
KsfError,
|
||||
/// Error while performing a KEM operation
|
||||
KemError,
|
||||
/** This error occurs when the envelope seal open hmac check fails
|
||||
HMAC check in seal open failed. */
|
||||
SealOpenHmacError,
|
||||
/** This error occurs when attempting to open an envelope of the wrong
|
||||
type (base mode, custom identifier) */
|
||||
IncompatibleEnvelopeModeError,
|
||||
/// Error from the OPRF evaluation
|
||||
OprfError(voprf::Error),
|
||||
/// Error from the OPRF evaluation
|
||||
OprfInternalError(voprf::InternalError),
|
||||
}
|
||||
|
||||
impl Debug for InternalError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::HashToScalar => f.debug_tuple("HashToScalar").finish(),
|
||||
Self::HkdfError => f.debug_tuple("HkdfError").finish(),
|
||||
Self::HmacError => f.debug_tuple("HmacError").finish(),
|
||||
Self::KsfError => f.debug_tuple("KsfError").finish(),
|
||||
Self::KemError => f.debug_tuple("KemError").finish(),
|
||||
Self::SealOpenHmacError => f.debug_tuple("SealOpenHmacError").finish(),
|
||||
Self::IncompatibleEnvelopeModeError => {
|
||||
f.debug_tuple("IncompatibleEnvelopeModeError").finish()
|
||||
}
|
||||
Self::OprfError(error) => f.debug_tuple("OprfError").field(error).finish(),
|
||||
Self::OprfInternalError(error) => {
|
||||
f.debug_tuple("OprfInternalError").field(error).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for InternalError {}
|
||||
|
||||
impl From<voprf::Error> for InternalError {
|
||||
fn from(voprf_error: voprf::Error) -> Self {
|
||||
Self::OprfError(voprf_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<voprf::Error> for ProtocolError {
|
||||
fn from(voprf_error: voprf::Error) -> Self {
|
||||
Self::LibraryError(InternalError::OprfError(voprf_error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<voprf::InternalError> for ProtocolError {
|
||||
fn from(voprf_error: voprf::InternalError) -> Self {
|
||||
Self::LibraryError(InternalError::OprfInternalError(voprf_error))
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error in protocol handling
|
||||
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum ProtocolError<T = Infallible> {
|
||||
/// Internal error encountered
|
||||
LibraryError(InternalError),
|
||||
/// Error in validating credentials
|
||||
InvalidLoginError,
|
||||
/// Error with serializing / deserializing protocol messages
|
||||
SerializationError,
|
||||
/** Invalid length for `{name}`: expected {len}, actual {actual_len} */
|
||||
#[allow(clippy::doc_markdown, unused_assignments)]
|
||||
SizeError {
|
||||
/// name
|
||||
name: &'static str,
|
||||
@@ -21,159 +93,74 @@ pub enum InternalPakeError {
|
||||
/// actual
|
||||
actual_len: usize,
|
||||
},
|
||||
/// Could not decompress point.
|
||||
PointError,
|
||||
/// Key belongs to a small subgroup!
|
||||
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
|
||||
HmacError,
|
||||
/// Computing the slow hashing function failed
|
||||
SlowHashError,
|
||||
/// This error occurs when the envelope seal fails
|
||||
/// Constructing the envelope seal failed.
|
||||
SealError,
|
||||
/// This error occurs when the envelope seal open fails
|
||||
/// Opening the envelope seal failed.
|
||||
SealOpenError,
|
||||
/// 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,
|
||||
/** This error occurs when the client detects that the server has
|
||||
reflected the OPRF value (beta == alpha) */
|
||||
ReflectedValueError,
|
||||
/// Custom [`SecretKey`](crate::keypair::PrivateKeySerialization) error type
|
||||
Custom(T),
|
||||
}
|
||||
|
||||
/// Represents an error in password checking
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum PakeError {
|
||||
/// This error results from an internal error during PRF construction
|
||||
///
|
||||
/// Internal error during PRF verification: {0}
|
||||
CryptoError(InternalPakeError),
|
||||
/// This error occurs when the server object that is being called finish() on is malformed
|
||||
/// Incomplete set of keys passed into finish() function
|
||||
IncompleteKeysError,
|
||||
/// The provided server public key doesn't match the sealed one
|
||||
IncompatibleServerStaticPublicKeyError,
|
||||
/// Error in key exchange protocol when attempting to validate MACs
|
||||
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
|
||||
// internal error into a PakeError
|
||||
impl From<InternalPakeError> for PakeError {
|
||||
fn from(e: InternalPakeError) -> PakeError {
|
||||
PakeError::CryptoError(e)
|
||||
impl<T: Debug> Debug for ProtocolError<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::LibraryError(pake_error) => {
|
||||
f.debug_tuple("LibraryError").field(pake_error).finish()
|
||||
}
|
||||
Self::InvalidLoginError => f.debug_tuple("InvalidLoginError").finish(),
|
||||
Self::SerializationError => f.debug_tuple("SerializationError").finish(),
|
||||
Self::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
} => f
|
||||
.debug_struct("SizeError")
|
||||
.field("name", name)
|
||||
.field("len", len)
|
||||
.field("actual_len", actual_len)
|
||||
.finish(),
|
||||
Self::ReflectedValueError => f.debug_tuple("ReflectedValueError").finish(),
|
||||
Self::Custom(custom) => f.debug_tuple("Custom").field(custom).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error in protocol handling
|
||||
#[derive(Debug, Display, Error)]
|
||||
pub enum ProtocolError {
|
||||
/// This error results from an error during password verification
|
||||
///
|
||||
/// Internal error during password verification: {0}
|
||||
VerificationError(PakeError),
|
||||
/// This error occurs when the 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,
|
||||
}
|
||||
|
||||
// This is meant to express future(ly) non-trivial ways of converting the
|
||||
// Pake error into a ProtocolError
|
||||
impl From<PakeError> for ProtocolError {
|
||||
fn from(e: PakeError) -> ProtocolError {
|
||||
ProtocolError::VerificationError(e)
|
||||
}
|
||||
}
|
||||
impl<T: Error> Error for ProtocolError<T> {}
|
||||
|
||||
// This is meant to express future(ly) non-trivial ways of converting the
|
||||
// internal error into a ProtocolError
|
||||
impl From<InternalPakeError> for ProtocolError {
|
||||
fn from(e: InternalPakeError) -> ProtocolError {
|
||||
ProtocolError::VerificationError(e.into())
|
||||
impl<T> From<InternalError> for ProtocolError<T> {
|
||||
fn from(e: InternalError) -> ProtocolError<T> {
|
||||
Self::LibraryError(e)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/rust-lang/rust/issues/64715 and remove this when
|
||||
// merged, and https://github.com/dtolnay/thiserror/issues/62 for why this
|
||||
// comes up in our doc tests.
|
||||
impl From<::std::convert::Infallible> for ProtocolError {
|
||||
fn from(_: ::std::convert::Infallible) -> Self {
|
||||
// See https://github.com/rust-lang/rust/issues/64715 and remove this when merged,
|
||||
// and https://github.com/dtolnay/thiserror/issues/62 for why this comes up in our
|
||||
// doc tests.
|
||||
impl<T> From<Infallible> for ProtocolError<T> {
|
||||
fn from(_: Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
||||
pub fn check_slice_size<'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(),
|
||||
});
|
||||
impl ProtocolError {
|
||||
/// Convert `ProtocolError<Infallible>` into `ProtocolError<T>`
|
||||
pub fn into_custom<T>(self) -> ProtocolError<T> {
|
||||
match self {
|
||||
Self::LibraryError(internal_error) => ProtocolError::LibraryError(internal_error),
|
||||
Self::InvalidLoginError => ProtocolError::InvalidLoginError,
|
||||
Self::SerializationError => ProtocolError::SerializationError,
|
||||
Self::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
} => ProtocolError::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
},
|
||||
Self::ReflectedValueError => ProtocolError::ReflectedValueError,
|
||||
Self::Custom(infallible) => match infallible {},
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
-137
@@ -1,137 +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.
|
||||
|
||||
//! Defines the Group trait to specify the underlying prime order group used in
|
||||
//! OPAQUE's OPRF
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::RISTRETTO_BASEPOINT_POINT,
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
use generic_array::{
|
||||
typenum::{U32, U64},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use rand_core::{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> {
|
||||
/// The type of base field scalars
|
||||
type Scalar: Zeroize + Clone;
|
||||
/// The byte length necessary to represent scalars
|
||||
type ScalarLen: ArrayLength<u8>;
|
||||
/// Return a scalar from its fixed-length bytes representation
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError>;
|
||||
/// picks a scalar at random
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
|
||||
/// Serializes a scalar to bytes
|
||||
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
|
||||
/// The multiplicative inverse of this scalar
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
|
||||
|
||||
/// The byte length necessary to represent group elements
|
||||
type ElemLen: ArrayLength<u8>;
|
||||
/// Return an element from its fixed-length bytes representation
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError>;
|
||||
/// Serializes the `self` group element
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
|
||||
|
||||
/// Hashes points presumed to be uniformly random to the curve. The
|
||||
/// impl is allowed to perform additional hashes if it needs to, but this
|
||||
/// may not be necessary as this function is going to be called with the
|
||||
/// output of a kdf.
|
||||
type UniformBytesLen: ArrayLength<u8>;
|
||||
|
||||
/// 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
|
||||
impl Group for RistrettoPoint {
|
||||
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 {
|
||||
#[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())
|
||||
}
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
||||
scalar.invert()
|
||||
}
|
||||
|
||||
// The byte length necessary to represent group elements
|
||||
type ElemLen = U32;
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError> {
|
||||
CompressedRistretto::from_slice(element_bits)
|
||||
.decompress()
|
||||
.ok_or(InternalPakeError::PointError)
|
||||
}
|
||||
// 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 = U64;
|
||||
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
|
||||
// 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)
|
||||
}
|
||||
|
||||
fn base_point() -> Self {
|
||||
RISTRETTO_BASEPOINT_POINT
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
+72
-10
@@ -1,15 +1,77 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! A convenience trait for digest bounds used throughout the library
|
||||
|
||||
use digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use digest::block_api::{
|
||||
BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, SmallBlockSizeUser,
|
||||
};
|
||||
use digest::block_buffer::Eager;
|
||||
use digest::{Digest, FixedOutputReset, HashMarker, OutputSizeUser};
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, U256};
|
||||
|
||||
/// 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 {}
|
||||
pub(crate) type OutputSize<H> = <<H as CoreProxy>::Core as OutputSizeUser>::OutputSize;
|
||||
|
||||
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
|
||||
/// Trait to simplify requirements for [`Hash`].
|
||||
pub trait ProxyHash:
|
||||
HashMarker + FixedOutputCore + BufferKindUser<BufferKind = Eager> + OutputSizeUser + Default + Clone
|
||||
where
|
||||
<Self as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<Self as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
{
|
||||
}
|
||||
|
||||
impl<
|
||||
T: HashMarker
|
||||
+ FixedOutputCore
|
||||
+ BufferKindUser<BufferKind = Eager>
|
||||
+ OutputSizeUser
|
||||
+ Default
|
||||
+ Clone,
|
||||
> ProxyHash for T
|
||||
where
|
||||
<T as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<T as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
{
|
||||
}
|
||||
|
||||
/// Trait inheriting the requirements from [`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:
|
||||
Default
|
||||
+ HashMarker
|
||||
+ Digest
|
||||
+ OutputSizeUser<OutputSize = OutputSize<Self>>
|
||||
+ BlockSizeUser
|
||||
+ FixedOutputReset
|
||||
+ CoreProxy
|
||||
+ Clone
|
||||
+ zeroize::ZeroizeOnDrop
|
||||
where
|
||||
<Self as CoreProxy>::Core: ProxyHash,
|
||||
<<Self as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<Self as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<Self>: generic_array::ArrayLength,
|
||||
{
|
||||
}
|
||||
|
||||
impl<
|
||||
T: Default
|
||||
+ HashMarker
|
||||
+ Digest
|
||||
+ OutputSizeUser<OutputSize = OutputSize<Self>>
|
||||
+ BlockSizeUser
|
||||
+ FixedOutputReset
|
||||
+ CoreProxy
|
||||
+ Clone
|
||||
+ zeroize::ZeroizeOnDrop,
|
||||
> Hash for T
|
||||
where
|
||||
<T as CoreProxy>::Core: ProxyHash,
|
||||
<<T as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<T as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<T>: generic_array::ArrayLength,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Key Exchange group implementation for Curve25519
|
||||
|
||||
pub use curve25519_dalek;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar;
|
||||
use curve25519_dalek::traits::IsIdentity;
|
||||
use generic_array::GenericArray;
|
||||
use generic_array::typenum::U32;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use subtle::ConstantTimeEq;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
use super::Group;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Implementation for Curve25519.
|
||||
pub struct Curve25519;
|
||||
|
||||
/// The implementation of such a subgroup for Curve25519
|
||||
impl Group for Curve25519 {
|
||||
type Pk = NonIdentity;
|
||||
type PkLen = U32;
|
||||
type Sk = Scalar;
|
||||
type SkLen = U32;
|
||||
|
||||
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
pk.0.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
bytes
|
||||
.take_array::<U32>("public key")
|
||||
.and_then(|bytes| NonIdentity::from_bytes(bytes.into()))
|
||||
}
|
||||
|
||||
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
// Sample 32 random bytes and then clamp, as described in https://cr.yp.to/ecdh.html
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let scalar = scalar::clamp_integer(scalar_bytes);
|
||||
|
||||
Scalar(scalar)
|
||||
}
|
||||
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
Ok(Scalar(scalar::clamp_integer(seed.into())))
|
||||
}
|
||||
|
||||
fn public_key(sk: &Self::Sk) -> Self::Pk {
|
||||
NonIdentity(MontgomeryPoint::mul_base_clamped(sk.0))
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.take_array::<U32>("secret key")
|
||||
.and_then(|bytes| Scalar::from_bytes(bytes.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffieHellman<Curve25519> for Scalar {
|
||||
fn diffie_hellman(&self, pk: &NonIdentity) -> GenericArray<u8, U32> {
|
||||
Curve25519::serialize_pk(&NonIdentity(pk.0.mul_clamped(self.0)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-identity point wrapper for [`MontgomeryPoint`].
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct NonIdentity(
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_pk"))]
|
||||
MontgomeryPoint,
|
||||
);
|
||||
|
||||
impl NonIdentity {
|
||||
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
|
||||
let point = MontgomeryPoint(bytes);
|
||||
|
||||
if point.is_identity() {
|
||||
Err(ProtocolError::SerializationError)
|
||||
} else {
|
||||
Ok(NonIdentity(point))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
fn serde_deserialize_pk<'de, D>(deserializer: D) -> Result<MontgomeryPoint, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Deserialize, Error};
|
||||
|
||||
let point = MontgomeryPoint::deserialize(deserializer)?;
|
||||
|
||||
NonIdentity::from_bytes(point.0)
|
||||
.map(|point| point.0)
|
||||
.map_err(Error::custom)
|
||||
}
|
||||
|
||||
/// Curve25519 scalar.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct Scalar(
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_sk"))] [u8; 32],
|
||||
);
|
||||
|
||||
impl Scalar {
|
||||
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
|
||||
let scalar = scalar::clamp_integer(bytes);
|
||||
|
||||
if scalar.ct_eq(&bytes).into() {
|
||||
Ok(Self(scalar))
|
||||
} else {
|
||||
Err(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
fn serde_deserialize_sk<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Deserialize, Error};
|
||||
|
||||
Scalar::from_bytes(<[u8; 32]>::deserialize(deserializer)?)
|
||||
.map(|scalar| scalar.0)
|
||||
.map_err(D::Error::custom)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_zero_scalar() {
|
||||
use std::vec;
|
||||
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
|
||||
let mut rng = CycleRng::new(vec![0]);
|
||||
let sk = Curve25519::random_sk(&mut rng);
|
||||
assert_ne!(sk.0, curve25519_dalek::Scalar::ZERO.to_bytes());
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Key Exchange group implementation for Ed25519
|
||||
|
||||
use core::iter;
|
||||
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use curve25519_dalek::traits::IsIdentity;
|
||||
use curve25519_dalek::{EdwardsPoint, Scalar};
|
||||
use digest::Digest;
|
||||
pub use ed25519_dalek;
|
||||
use ed25519_dalek::hazmat::ExpandedSecretKey;
|
||||
use ed25519_dalek::{SecretKey, Sha512};
|
||||
use generic_array::GenericArray;
|
||||
use generic_array::typenum::{U32, U64};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use super::Group;
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::sigma_i::hash_eddsa::implementation::HashEddsaImpl;
|
||||
use crate::key_exchange::sigma_i::pure_eddsa::implementation::PureEddsaImpl;
|
||||
pub use crate::key_exchange::sigma_i::shared::PreHash;
|
||||
use crate::key_exchange::sigma_i::{CachedMessage, Message, MessageBuilder};
|
||||
use crate::serialization::{ConcatExt, SliceExt, UpdateExt};
|
||||
|
||||
/// Implementation for Ed25519.
|
||||
pub struct Ed25519;
|
||||
|
||||
impl Group for Ed25519 {
|
||||
type Pk = VerifyingKey;
|
||||
type PkLen = U32;
|
||||
type Sk = SigningKey;
|
||||
type SkLen = U32;
|
||||
|
||||
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
pk.compressed.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
let bytes = bytes.take_array::<U32>("public key")?;
|
||||
|
||||
VerifyingKey::from_bytes(bytes.into())
|
||||
}
|
||||
|
||||
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
let mut sk = <[u8; 32]>::default();
|
||||
rng.fill_bytes(&mut sk);
|
||||
|
||||
SigningKey::from_bytes(sk)
|
||||
}
|
||||
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
Ok(SigningKey::from_bytes(seed.into()))
|
||||
}
|
||||
|
||||
fn public_key(sk: &Self::Sk) -> Self::Pk {
|
||||
sk.verifying_key
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.sk.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
Ok(SigningKey::from_bytes(
|
||||
bytes.take_array::<U32>("secret key")?.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl PureEddsaImpl for Ed25519 {
|
||||
type Signature = Signature;
|
||||
type SignatureLen = U64;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, CachedMessage<CS, KE>) {
|
||||
(sign(sk, false, message.sign_message()), message.to_cached())
|
||||
}
|
||||
|
||||
/// Validates that the signature was created by signing the given message
|
||||
/// with the corresponding private key.
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: CachedMessage<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(
|
||||
pk,
|
||||
false,
|
||||
message_builder.build::<KE>(state).verify_message(),
|
||||
signature,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
Signature::deserialize_take(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
impl HashEddsaImpl for Ed25519 {
|
||||
type Signature = Signature;
|
||||
type SignatureLen = U64;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<Sha512>;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
let hash = message.hash::<Sha512>();
|
||||
|
||||
(
|
||||
sign(sk, true, iter::once(hash.sign.finalize().as_slice())),
|
||||
PreHash(hash.verify.finalize()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Validates that the signature was created by signing the given message
|
||||
/// with the corresponding private key.
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(pk, true, iter::once(state.0.as_slice()), signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
Signature::deserialize_take(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
// This contains a manual implementation of EdDSA because `ed25519-dalek`
|
||||
// doesn't support message streaming. See
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
fn sign<'a>(
|
||||
sk: &SigningKey,
|
||||
pre_hash: bool,
|
||||
message: impl Clone + Iterator<Item = &'a [u8]>,
|
||||
) -> Signature {
|
||||
let mut h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(sk.hash_prefix);
|
||||
h.update_iter(message.clone());
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
#[allow(non_snake_case)]
|
||||
let R = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(R.as_bytes());
|
||||
h.update(sk.verifying_key.compressed.0);
|
||||
h.update_iter(message);
|
||||
|
||||
let k = Scalar::from_hash(h);
|
||||
let s: Scalar = (k * sk.scalar) + r;
|
||||
|
||||
Signature { R, s }
|
||||
}
|
||||
|
||||
fn verify<'a>(
|
||||
pk: &VerifyingKey,
|
||||
pre_hash: bool,
|
||||
message: impl Iterator<Item = &'a [u8]>,
|
||||
signature: &Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(signature.R.as_bytes());
|
||||
h.update(pk.compressed.as_bytes());
|
||||
h.update_iter(message);
|
||||
let k = Scalar::from_hash(h);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let minus_A: EdwardsPoint = -pk.point;
|
||||
#[allow(non_snake_case)]
|
||||
let expected_R =
|
||||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s).compress();
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 verifying key.
|
||||
// `ed25519_dalek::VerifyingKey` doesn't implement `Zeroize`.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
|
||||
// Required for manual implementation of EdDSA.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Zeroize)]
|
||||
pub struct VerifyingKey {
|
||||
point: EdwardsPoint,
|
||||
compressed: CompressedEdwardsY,
|
||||
}
|
||||
|
||||
impl VerifyingKey {
|
||||
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
|
||||
let compressed = CompressedEdwardsY(bytes);
|
||||
|
||||
if let Some(point) = compressed.decompress().filter(|point| !point.is_identity()) {
|
||||
Ok(Self { point, compressed })
|
||||
} else {
|
||||
Err(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for VerifyingKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use core::fmt::{self, Formatter};
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor};
|
||||
|
||||
struct VerifyingKeyVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for VerifyingKeyVisitor {
|
||||
type Value = VerifyingKey;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
Formatter::write_str(formatter, "tuple struct VerifyingKey")
|
||||
}
|
||||
|
||||
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let compressed = CompressedEdwardsY::deserialize(deserializer)?;
|
||||
VerifyingKey::from_bytes(compressed.0).map_err(Error::custom)
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let compressed: CompressedEdwardsY = seq.next_element()?.ok_or_else(|| {
|
||||
Error::invalid_length(0, &"tuple struct VerifyingKey with 1 element")
|
||||
})?;
|
||||
VerifyingKey::from_bytes(compressed.0).map_err(Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_newtype_struct("VerifyingKey", VerifyingKeyVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for VerifyingKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_newtype_struct("VerifyingKey", &self.compressed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 signing key.
|
||||
// We store the `ExpandedSecret` in memory to avoid computing it on demand and then discarding it
|
||||
// again.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct SigningKey {
|
||||
// `ed25519_dalek::SigningKey` doesn't implement `Zeroize`. See
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747
|
||||
// Required for manual implementation of EdDSA.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
sk: SecretKey,
|
||||
verifying_key: VerifyingKey,
|
||||
// `ed25519_dalek::ExpandedSecret` doesn't implement traits we need. See
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/748 and
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
|
||||
scalar: Scalar,
|
||||
hash_prefix: [u8; 32],
|
||||
}
|
||||
|
||||
impl SigningKey {
|
||||
fn from_bytes(sk: [u8; 32]) -> Self {
|
||||
let ExpandedSecretKey {
|
||||
scalar,
|
||||
hash_prefix,
|
||||
} = ExpandedSecretKey::from(&sk);
|
||||
let point = EdwardsPoint::mul_base(&scalar);
|
||||
let verifying_key = VerifyingKey {
|
||||
point,
|
||||
compressed: point.compress(),
|
||||
};
|
||||
|
||||
SigningKey {
|
||||
sk,
|
||||
verifying_key,
|
||||
scalar,
|
||||
hash_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for SigningKey {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use core::fmt::{self, Formatter};
|
||||
|
||||
use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor};
|
||||
|
||||
struct SigningKeyVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for SigningKeyVisitor {
|
||||
type Value = SigningKey;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
Formatter::write_str(formatter, "tuple struct SigningKey")
|
||||
}
|
||||
|
||||
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let sk = Scalar::deserialize(deserializer)?;
|
||||
Ok(SigningKey::from_bytes(sk.to_bytes()))
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let sk: Scalar = seq.next_element()?.ok_or_else(|| {
|
||||
Error::invalid_length(0, &"tuple struct SigningKey with 1 element")
|
||||
})?;
|
||||
Ok(SigningKey::from_bytes(sk.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_newtype_struct("SigningKey", SigningKeyVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for SigningKey {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_newtype_struct("SigningKey", &self.sk)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 Signature.
|
||||
// `ed25519_dalek::Signature` doesn't implement validation with Serde de/serialization.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct Signature {
|
||||
R: CompressedEdwardsY,
|
||||
s: Scalar,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Expects the `R` and `s` components of an Ed25519 signature with no added
|
||||
/// framing.
|
||||
pub fn from_slice(mut bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::deserialize_take(&mut bytes)
|
||||
}
|
||||
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
#[allow(non_snake_case)]
|
||||
let R = CompressedEdwardsY(bytes.take_array::<U32>("signature R")?.into());
|
||||
|
||||
let s = Scalar::from_canonical_bytes(bytes.take_array::<U32>("signature s")?.into())
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::SerializationError)?;
|
||||
|
||||
Ok(Self { R, s })
|
||||
}
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, U64> {
|
||||
GenericArray::<u8, U32>::from(self.R.0)
|
||||
.cat(GenericArray::<u8, U32>::from(self.s.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for Signature {
|
||||
fn zeroize(&mut self) {
|
||||
self.R.0 = [0; 32];
|
||||
self.s = Scalar::default();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::iter;
|
||||
|
||||
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rand::rngs::SysRng;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pure_eddsa() {
|
||||
let mut message = [0; 1024];
|
||||
UnwrapErr(SysRng).fill_bytes(&mut message);
|
||||
|
||||
let mut sk = SecretKey::default();
|
||||
UnwrapErr(SysRng).fill_bytes(&mut sk);
|
||||
let signing_key = SigningKey::from_bytes(&sk);
|
||||
|
||||
let signature = signing_key.sign(&message);
|
||||
|
||||
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
|
||||
let custom_signature = sign(&custom_sk, false, iter::once(message.as_slice()));
|
||||
|
||||
assert_eq!(
|
||||
signature.to_bytes(),
|
||||
custom_signature.serialize().as_slice()
|
||||
);
|
||||
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
verifying_key.verify(&message, &signature).unwrap();
|
||||
|
||||
let custom_pk = Ed25519::public_key(&custom_sk);
|
||||
verify(
|
||||
&custom_pk,
|
||||
false,
|
||||
iter::once(message.as_slice()),
|
||||
&custom_signature,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_eddsa() {
|
||||
let mut message = [0; 1024];
|
||||
UnwrapErr(SysRng).fill_bytes(&mut message);
|
||||
let message = Sha512::new_with_prefix(message);
|
||||
let pre_hash = message.clone().finalize();
|
||||
|
||||
let mut sk = SecretKey::default();
|
||||
UnwrapErr(SysRng).fill_bytes(&mut sk);
|
||||
let signing_key = SigningKey::from_bytes(&sk);
|
||||
|
||||
let signature = signing_key.sign_prehashed(message.clone(), None).unwrap();
|
||||
|
||||
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
|
||||
let custom_signature = sign(&custom_sk, true, iter::once(pre_hash.as_slice()));
|
||||
|
||||
assert_eq!(
|
||||
signature.to_bytes(),
|
||||
custom_signature.serialize().as_slice()
|
||||
);
|
||||
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
verifying_key
|
||||
.verify_prehashed(message, None, &signature)
|
||||
.unwrap();
|
||||
|
||||
let custom_pk = Ed25519::public_key(&custom_sk);
|
||||
verify(
|
||||
&custom_pk,
|
||||
true,
|
||||
iter::once(pre_hash.as_slice()),
|
||||
&custom_signature,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Implementation for EC curves via [`elliptic_curve`] traits.
|
||||
|
||||
use core::ops::Mul;
|
||||
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use elliptic_curve::point::NonIdentity;
|
||||
use elliptic_curve::sec1::{ModulusSize, ToSec1Point};
|
||||
use elliptic_curve::{
|
||||
CurveArithmetic, FieldBytesSize, Generate, NonZeroScalar, ProjectivePoint, Scalar, SecretKey,
|
||||
};
|
||||
use generic_array::typenum::U2;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use voprf::Mode;
|
||||
|
||||
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
impl<G> Group for G
|
||||
where
|
||||
Self: CurveArithmetic + voprf::CipherSuite<Group = Self> + voprf::Group<Scalar = Scalar<Self>>,
|
||||
FieldBytesSize<Self>: ModulusSize + ArrayLength,
|
||||
<FieldBytesSize<Self> as ModulusSize>::CompressedPointSize: ArrayLength,
|
||||
ProjectivePoint<Self>: GroupEncoding<
|
||||
Repr = hybrid_array::Array<
|
||||
u8,
|
||||
<FieldBytesSize<Self> as ModulusSize>::CompressedPointSize,
|
||||
>,
|
||||
> + ToSec1Point<Self>,
|
||||
// Bounds required by voprf::CipherSuite
|
||||
<Self as voprf::Group>::SecurityLevel: Mul<U2>,
|
||||
{
|
||||
// We don't use `elliptic_curve::PublicKey` because it stores its internals in a
|
||||
// format ideal for serialization and not computation. This is inconsistent with
|
||||
// our other implementations.
|
||||
type Pk = NonIdentity<ProjectivePoint<Self>>;
|
||||
|
||||
type PkLen = <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize;
|
||||
|
||||
type Sk = SecretKey<Self>;
|
||||
|
||||
type SkLen = FieldBytesSize<Self>;
|
||||
|
||||
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
GenericArray::from_slice(pk.to_sec1_point(true).as_bytes()).clone()
|
||||
}
|
||||
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
NonIdentity::<ProjectivePoint<Self>>::from_bytes(
|
||||
&bytes.take_array("public key")?.into_ha0_4(),
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
SecretKey::<Self>::generate_from_rng(rng)
|
||||
}
|
||||
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
|
||||
.map(|scalar| {
|
||||
NonZeroScalar::new(scalar).expect("`voprf::derive_key()` returned a zero scalar")
|
||||
})
|
||||
.map(SecretKey::from)
|
||||
.map_err(InternalError::from)
|
||||
}
|
||||
|
||||
fn public_key(sk: &Self::Sk) -> Self::Pk {
|
||||
NonIdentity::<ProjectivePoint<Self>>::mul_by_generator(&sk.to_nonzero_scalar())
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
GenericArray::from(sk.to_bytes())
|
||||
}
|
||||
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
SecretKey::<Self>::from_bytes(&bytes.take_array("secret key")?.into_ha0_4())
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> DiffieHellman<G> for SecretKey<G>
|
||||
where
|
||||
G: CurveArithmetic + voprf::CipherSuite<Group = G> + voprf::Group<Scalar = Scalar<G>>,
|
||||
FieldBytesSize<G>: ModulusSize + ArrayLength,
|
||||
<FieldBytesSize<G> as ModulusSize>::CompressedPointSize: ArrayLength,
|
||||
ProjectivePoint<G>: GroupEncoding<
|
||||
Repr = hybrid_array::Array<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize>,
|
||||
> + ToSec1Point<G>,
|
||||
// Bounds required by voprf::CipherSuite
|
||||
<G as voprf::Group>::SecurityLevel: Mul<U2>,
|
||||
{
|
||||
fn diffie_hellman(
|
||||
&self,
|
||||
pk: &NonIdentity<ProjectivePoint<G>>,
|
||||
) -> GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize> {
|
||||
GenericArray::from_slice(
|
||||
(pk * self.to_nonzero_scalar())
|
||||
.to_sec1_point(true)
|
||||
.as_bytes(),
|
||||
)
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Includes the [`Group`] trait and definitions for the key exchange groups
|
||||
|
||||
#[cfg(feature = "curve25519")]
|
||||
pub mod curve25519;
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub mod ed25519;
|
||||
pub mod elliptic_curve;
|
||||
#[cfg(feature = "ristretto255")]
|
||||
pub mod ristretto255;
|
||||
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hybrid_array::ArraySize;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
|
||||
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 33] = *b"OPAQUE-DeriveDiffieHellmanKeyPair";
|
||||
|
||||
/// A group representation for use in the key exchange
|
||||
pub trait Group {
|
||||
/// Public key
|
||||
type Pk: Clone;
|
||||
/// Length of the public key
|
||||
type PkLen: ArrayLength + ArraySize;
|
||||
/// Secret key
|
||||
type Sk: Clone + ZeroizeOnDrop;
|
||||
/// Length of the secret key
|
||||
type SkLen: ArrayLength + ArraySize;
|
||||
|
||||
/// Serializes `self`
|
||||
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen>;
|
||||
|
||||
/// Return a public key from its fixed-length bytes representation
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError>;
|
||||
|
||||
/// Generate a random secret key
|
||||
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk;
|
||||
|
||||
/// Deterministically derive a [`Self::Sk`] from `seed`.
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError>;
|
||||
|
||||
/// Return a public key from its secret key
|
||||
fn public_key(sk: &Self::Sk) -> Self::Pk;
|
||||
|
||||
/// Serializes `self`
|
||||
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen>;
|
||||
|
||||
/// Return a public key from its fixed-length bytes representation
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError>;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Key Exchange group implementation for ristretto255
|
||||
|
||||
pub use curve25519_dalek;
|
||||
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use curve25519_dalek::traits::IsIdentity;
|
||||
use digest::block_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker};
|
||||
use generic_array::GenericArray;
|
||||
use generic_array::typenum::{IsGreaterOrEqual, IsLess, IsLessOrEqual, Prod, True, U2, U32, U256};
|
||||
use hybrid_array::Array;
|
||||
use rand::{CryptoRng, Rng, TryCryptoRng, TryRng};
|
||||
use voprf::Mode;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Implementation for Ristretto255.
|
||||
// This is necessary because Rust lacks specialization, otherwise we could
|
||||
// implement `KeGroup` for `voprf::Ristretto255`.
|
||||
pub struct Ristretto255;
|
||||
|
||||
impl Group for Ristretto255 {
|
||||
type Pk = NonIdentity;
|
||||
type PkLen = U32;
|
||||
type Sk = NonZeroScalar;
|
||||
type SkLen = U32;
|
||||
|
||||
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
pk.0.compress().to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
CompressedRistretto(bytes.take_array::<U32>("public key")?.into())
|
||||
.decompress()
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
.and_then(NonIdentity::from_point)
|
||||
}
|
||||
|
||||
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
loop {
|
||||
let mut bytes = [0u8; 64];
|
||||
|
||||
rng.fill_bytes(&mut bytes);
|
||||
|
||||
let scalar = Scalar::from_bytes_mod_order_wide(&bytes);
|
||||
|
||||
if scalar != Scalar::ZERO {
|
||||
break NonZeroScalar(scalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
|
||||
.map(NonZeroScalar)
|
||||
.map_err(InternalError::from)
|
||||
}
|
||||
|
||||
fn public_key(sk: &Self::Sk) -> Self::Pk {
|
||||
NonIdentity(RISTRETTO_BASEPOINT_POINT * sk.0)
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.0.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
Scalar::from_canonical_bytes(bytes.take_array::<U32>("secret key")?.into())
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
.and_then(NonZeroScalar::from_scalar)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffieHellman<Ristretto255> for NonZeroScalar {
|
||||
fn diffie_hellman(&self, pk: &NonIdentity) -> GenericArray<u8, U32> {
|
||||
Ristretto255::serialize_pk(&NonIdentity(pk.0 * self.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-identity point wrapper for [`RistrettoPoint`].
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct NonIdentity(
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_pk"))] RistrettoPoint,
|
||||
);
|
||||
|
||||
impl NonIdentity {
|
||||
fn from_point(point: RistrettoPoint) -> Result<Self, ProtocolError> {
|
||||
if point.is_identity() {
|
||||
Err(ProtocolError::SerializationError)
|
||||
} else {
|
||||
Ok(NonIdentity(point))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
fn serde_deserialize_pk<'de, D>(deserializer: D) -> Result<RistrettoPoint, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Deserialize, Error};
|
||||
|
||||
let point = RistrettoPoint::deserialize(deserializer)?;
|
||||
|
||||
NonIdentity::from_point(point)
|
||||
.map(|point| point.0)
|
||||
.map_err(Error::custom)
|
||||
}
|
||||
|
||||
/// Non-zero scalar wrapper for [`Scalar`]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct NonZeroScalar(
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_sk"))] Scalar,
|
||||
);
|
||||
|
||||
impl NonZeroScalar {
|
||||
fn from_scalar(scalar: Scalar) -> Result<Self, ProtocolError> {
|
||||
if scalar == Scalar::ZERO {
|
||||
Err(ProtocolError::SerializationError)
|
||||
} else {
|
||||
Ok(Self(scalar))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
fn serde_deserialize_sk<'de, D>(deserializer: D) -> Result<Scalar, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::{Deserialize, Error};
|
||||
|
||||
let scalar = Scalar::deserialize(deserializer)?;
|
||||
|
||||
NonZeroScalar::from_scalar(scalar)
|
||||
.map(|scalar| scalar.0)
|
||||
.map_err(Error::custom)
|
||||
}
|
||||
|
||||
impl voprf::CipherSuite for Ristretto255 {
|
||||
const ID: &'static [u8] = voprf::Ristretto255::ID;
|
||||
|
||||
type Group = <voprf::Ristretto255 as voprf::CipherSuite>::Group;
|
||||
|
||||
type Hash = <voprf::Ristretto255 as voprf::CipherSuite>::Hash;
|
||||
}
|
||||
|
||||
impl voprf::Group for Ristretto255 {
|
||||
type Elem = <voprf::Ristretto255 as voprf::Group>::Elem;
|
||||
|
||||
type ElemLen = <voprf::Ristretto255 as voprf::Group>::ElemLen;
|
||||
|
||||
type Scalar = <voprf::Ristretto255 as voprf::Group>::Scalar;
|
||||
|
||||
type ScalarLen = <voprf::Ristretto255 as voprf::Group>::ScalarLen;
|
||||
|
||||
type SecurityLevel = <voprf::Ristretto255 as voprf::Group>::SecurityLevel;
|
||||
|
||||
fn hash_to_curve<H>(
|
||||
input: &[&[u8]],
|
||||
dst: &[&[u8]],
|
||||
) -> voprf::Result<Self::Elem, voprf::InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256>
|
||||
+ IsLessOrEqual<H::BlockSize, Output = True>
|
||||
+ IsGreaterOrEqual<Prod<<Self as voprf::Group>::SecurityLevel, U2>, Output = True>,
|
||||
{
|
||||
<voprf::Ristretto255 as voprf::Group>::hash_to_curve::<H>(input, dst)
|
||||
}
|
||||
|
||||
fn hash_to_scalar<H>(
|
||||
input: &[&[u8]],
|
||||
dst: &[&[u8]],
|
||||
) -> voprf::Result<Self::Scalar, voprf::InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256>
|
||||
+ IsLessOrEqual<H::BlockSize, Output = True>
|
||||
+ IsGreaterOrEqual<Prod<<Self as voprf::Group>::SecurityLevel, U2>, Output = True>,
|
||||
{
|
||||
<voprf::Ristretto255 as voprf::Group>::hash_to_scalar::<H>(input, dst)
|
||||
}
|
||||
|
||||
fn base_elem() -> Self::Elem {
|
||||
<voprf::Ristretto255 as voprf::Group>::base_elem()
|
||||
}
|
||||
|
||||
fn identity_elem() -> Self::Elem {
|
||||
<voprf::Ristretto255 as voprf::Group>::identity_elem()
|
||||
}
|
||||
|
||||
fn serialize_elem(elem: Self::Elem) -> Array<u8, Self::ElemLen> {
|
||||
<voprf::Ristretto255 as voprf::Group>::serialize_elem(elem)
|
||||
}
|
||||
|
||||
fn deserialize_elem(element_bits: &[u8]) -> voprf::Result<Self::Elem> {
|
||||
<voprf::Ristretto255 as voprf::Group>::deserialize_elem(element_bits)
|
||||
}
|
||||
|
||||
fn random_scalar<R: TryRng + TryCryptoRng>(rng: &mut R) -> voprf::Result<Self::Scalar> {
|
||||
<voprf::Ristretto255 as voprf::Group>::random_scalar(rng)
|
||||
}
|
||||
|
||||
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
|
||||
<voprf::Ristretto255 as voprf::Group>::invert_scalar(scalar)
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
|
||||
<voprf::Ristretto255 as voprf::Group>::is_zero_scalar(scalar)
|
||||
}
|
||||
|
||||
fn serialize_scalar(scalar: Self::Scalar) -> Array<u8, Self::ScalarLen> {
|
||||
<voprf::Ristretto255 as voprf::Group>::serialize_scalar(scalar)
|
||||
}
|
||||
|
||||
fn deserialize_scalar(scalar_bits: &[u8]) -> voprf::Result<Self::Scalar> {
|
||||
<voprf::Ristretto255 as voprf::Group>::deserialize_scalar(scalar_bits)
|
||||
}
|
||||
}
|
||||
+409
-7
@@ -1,10 +1,412 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Includes instantiations of key exchange protocols used in the
|
||||
//! login step for OPAQUE
|
||||
//! Includes instantiations of key exchange protocols used in the login step for
|
||||
//! OPAQUE
|
||||
|
||||
pub(crate) mod traits;
|
||||
pub mod group;
|
||||
pub(crate) mod shared;
|
||||
pub mod sigma_i;
|
||||
pub mod tripledh;
|
||||
#[cfg(feature = "kem")]
|
||||
pub mod tripledh_kem;
|
||||
|
||||
use core::iter;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::Output;
|
||||
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U2, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hybrid_array::Array;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use voprf::{BlindedElement, EvaluationElement};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::ciphersuite::KeHash;
|
||||
use crate::ciphersuite::{CipherSuite, OprfGroup};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{NonceLen, STR_CONTEXT};
|
||||
use crate::keypair::{PrivateKey, PublicKey};
|
||||
use crate::opaque::{Identifiers, MaskedResponse, MaskedResponseLen};
|
||||
use crate::serialization::{ConcatExt, SliceExt, i2osp};
|
||||
|
||||
/// The key exchange trait.
|
||||
pub trait KeyExchange
|
||||
where
|
||||
<Self::Hash as CoreProxy>::Core: ProxyHash,
|
||||
<<Self::Hash as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<Self::Hash as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<Self::Hash>: ArrayLength,
|
||||
{
|
||||
/// The group used for the key exchange.
|
||||
type Group: Group;
|
||||
/// The hash used for the key exchange.
|
||||
type Hash: Hash;
|
||||
|
||||
/// Client state.
|
||||
type KE1State: ZeroizeOnDrop + Clone;
|
||||
/// Server state.
|
||||
type KE2State<CS: CipherSuite>: ZeroizeOnDrop + Clone;
|
||||
/// First message sent by the client.
|
||||
type KE1Message: ZeroizeOnDrop + Clone;
|
||||
/// Server state builder.
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>>: ZeroizeOnDrop + Clone;
|
||||
/// Server data for the remote key interaction.
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite>;
|
||||
/// Server remote key input.
|
||||
type KE2BuilderInput<CS: CipherSuite>;
|
||||
/// Message sent by the server.
|
||||
type KE2Message: ZeroizeOnDrop + Clone;
|
||||
/// Second message sent by the client.
|
||||
type KE3Message: ZeroizeOnDrop + Clone;
|
||||
|
||||
/// Client generates [`KE1Message`](Self::KE1Message) and
|
||||
/// [`KE1State`](Self::KE1State).
|
||||
fn generate_ke1<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<GenerateKe1Result<Self>, ProtocolError>;
|
||||
|
||||
/// Server generates [`KE2Builder`](Self::KE2Builder).
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
client_s_pk: PublicKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'a, Self::Group>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError>;
|
||||
|
||||
/// Server returns the data for the remote key interaction.
|
||||
fn ke2_builder_data<'a, CS: CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS>;
|
||||
|
||||
/// Server generates the input without a remote key.
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
rng: &mut R,
|
||||
server_s_sk: &PrivateKey<Self::Group>,
|
||||
) -> Self::KE2BuilderInput<CS>;
|
||||
|
||||
/// Server generates [`KE2Message`](Self::KE2Message) and
|
||||
/// [`KE2State`](Self::KE2State).
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
builder: Self::KE2Builder<'_, CS>,
|
||||
input: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError>;
|
||||
|
||||
/// Client generates [`KE3Message`](Self::KE3Message) and the session key.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
ke1_state: &Self::KE1State,
|
||||
ke2_message: Self::KE2Message,
|
||||
server_s_pk: PublicKey<Self::Group>,
|
||||
client_s_sk: PrivateKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'_, Self::Group>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError>;
|
||||
|
||||
/// Server generates the session key.
|
||||
fn finish_ke<CS: CipherSuite<KeyExchange = Self>>(
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
ke3_message: Self::KE3Message,
|
||||
identifiers: Identifiers<'_>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<Output<Self::Hash>, ProtocolError>;
|
||||
}
|
||||
|
||||
/// Serialized form of [`CredentialRequest`](crate::CredentialRequest).
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
|
||||
pub struct SerializedCredentialRequest<CS: CipherSuite>(
|
||||
Array<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>,
|
||||
);
|
||||
|
||||
impl<CS: CipherSuite> SerializedCredentialRequest<CS> {
|
||||
pub(crate) fn new(blinded_element: &BlindedElement<CS::OprfCs>) -> Self {
|
||||
Self(blinded_element.serialize())
|
||||
}
|
||||
|
||||
/// Returns the serialized form of
|
||||
/// [`CredentialRequest`](crate::CredentialRequest) in multiple byte slices.
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
iter::once(self.0.as_slice())
|
||||
}
|
||||
|
||||
/// Returns a [`SerializedCredentialRequest`] deserialized from the given
|
||||
/// `bytes`.
|
||||
pub fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self(bytes.take_array("blinded element")?.into_ha0_4()))
|
||||
}
|
||||
}
|
||||
|
||||
type SerializedCredentialRequestLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
|
||||
|
||||
impl<CS: CipherSuite> Serialize for SerializedCredentialRequest<CS>
|
||||
where
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: ArrayLength,
|
||||
{
|
||||
type Len = SerializedCredentialRequestLen<CS>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
GenericArray::from_slice(self.0.as_slice()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialized form of [`CredentialResponse`](crate::CredentialResponse).
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct SerializedCredentialResponse<CS: CipherSuite> {
|
||||
evaluation_element: Array<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>,
|
||||
masking_nonce: GenericArray<u8, NonceLen>,
|
||||
masked_response: MaskedResponse<CS>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> SerializedCredentialResponse<CS> {
|
||||
pub(crate) fn new(
|
||||
evaluation_element: &EvaluationElement<CS::OprfCs>,
|
||||
masking_nonce: GenericArray<u8, NonceLen>,
|
||||
masked_response: MaskedResponse<CS>,
|
||||
) -> Self {
|
||||
Self {
|
||||
evaluation_element: evaluation_element.serialize(),
|
||||
masking_nonce,
|
||||
masked_response,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the serialized form of
|
||||
/// [`CredentialResponse`](crate::CredentialResponse) in multiple byte
|
||||
/// slices.
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
[self.evaluation_element.as_slice(), &self.masking_nonce]
|
||||
.into_iter()
|
||||
.chain(self.masked_response.iter())
|
||||
}
|
||||
|
||||
/// Returns a [`SerializedCredentialRequest`] deserialized from the given
|
||||
/// `bytes`.
|
||||
pub fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
evaluation_element: input.take_array("evaluation element")?.into_ha0_4(),
|
||||
masking_nonce: input.take_array("masking nonce")?,
|
||||
masked_response: MaskedResponse::deserialize_take(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type SerializedCredentialResponseLen<CS: CipherSuite> =
|
||||
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
|
||||
|
||||
impl<CS: CipherSuite> Serialize for SerializedCredentialResponse<CS>
|
||||
where
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength + Add<MaskedResponseLen<CS>>,
|
||||
SerializedCredentialResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
type Len = SerializedCredentialResponseLen<CS>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
let elem = GenericArray::<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>::from_slice(
|
||||
self.evaluation_element.as_slice(),
|
||||
)
|
||||
.clone();
|
||||
|
||||
elem.cat(self.masking_nonce)
|
||||
.cat(self.masked_response.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialized form of a `context` given in
|
||||
/// [`ClientLoginFinishParameters`](crate::ClientLoginFinishParameters) or
|
||||
/// [`ServerLoginParameters`](crate::ServerLoginParameters).
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
#[allow(unused_assignments)]
|
||||
pub struct SerializedContext<'a> {
|
||||
length: GenericArray<u8, U2>,
|
||||
#[zeroize(skip)]
|
||||
context: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> SerializedContext<'a> {
|
||||
pub(crate) fn from(context: Option<&'a [u8]>) -> Result<Self, ProtocolError> {
|
||||
let context = context.unwrap_or(&[]);
|
||||
|
||||
Ok(Self {
|
||||
length: i2osp::<U2>(context.len())?,
|
||||
context,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the serialized form of `context` in multiple byte slices.
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
iter::once(STR_CONTEXT).chain([self.length.as_slice(), self.context])
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialized form of [`Identifiers`].
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct SerializedIdentifiers<'a, G: Group> {
|
||||
/// Client identifiers.
|
||||
pub client: SerializedIdentifier<'a, G>,
|
||||
/// Server identifiers.
|
||||
pub server: SerializedIdentifier<'a, G>,
|
||||
}
|
||||
|
||||
/// Serialized form of a single identifier from [`Identifiers`].
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct SerializedIdentifier<'a, G: Group> {
|
||||
length: GenericArray<u8, U2>,
|
||||
identifier: Identifier<'a, G>,
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
enum Identifier<'a, G: Group> {
|
||||
Owned(GenericArray<u8, G::PkLen>),
|
||||
#[derive_where(skip_inner(Zeroize))]
|
||||
Borrowed(&'a [u8]),
|
||||
}
|
||||
|
||||
impl<'a, G: Group> SerializedIdentifiers<'a, G> {
|
||||
pub(crate) fn from_identifiers(
|
||||
ids: Identifiers<'a>,
|
||||
client_s_pk: GenericArray<u8, G::PkLen>,
|
||||
server_s_pk: GenericArray<u8, G::PkLen>,
|
||||
) -> Result<Self, ProtocolError> {
|
||||
let client = SerializedIdentifier::from_identifier(ids.client, client_s_pk)?;
|
||||
let server = SerializedIdentifier::from_identifier(ids.server, server_s_pk)?;
|
||||
|
||||
Ok(Self { client, server })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, G: Group> SerializedIdentifier<'a, G> {
|
||||
/// Creates a [`SerializedIdentifier`] an identifier or the corresponding
|
||||
/// static public key.
|
||||
pub fn from_identifier(
|
||||
id: Option<&'a [u8]>,
|
||||
s_pk: GenericArray<u8, G::PkLen>,
|
||||
) -> Result<Self, ProtocolError> {
|
||||
if let Some(id) = id {
|
||||
Ok(SerializedIdentifier {
|
||||
length: i2osp::<U2>(id.len())?,
|
||||
identifier: Identifier::Borrowed(id),
|
||||
})
|
||||
} else {
|
||||
Ok(SerializedIdentifier {
|
||||
length: i2osp::<U2>(s_pk.len())?,
|
||||
identifier: Identifier::Owned(s_pk),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the serialized form of an identifier in multiple byte slices.
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
[self.length.as_slice()]
|
||||
.into_iter()
|
||||
.chain(match &self.identifier {
|
||||
Identifier::Owned(bytes) => [bytes.as_slice()],
|
||||
Identifier::Borrowed(bytes) => [*bytes],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserialization trait for key exchange types.
|
||||
pub trait Deserialize: Sized {
|
||||
/// Deserialize [`Self`] from the given `bytes`.
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError>;
|
||||
}
|
||||
|
||||
/// Serialization trait for key exchange types.
|
||||
pub trait Serialize {
|
||||
/// The length of the serialized types.
|
||||
type Len: ArrayLength;
|
||||
|
||||
/// Serialize [`Self`] to a fixed-length byte array.
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len>;
|
||||
}
|
||||
|
||||
/// Result type of [`KeyExchange::generate_ke1()`].
|
||||
pub struct GenerateKe1Result<KE: KeyExchange + ?Sized> {
|
||||
/// The client state.
|
||||
pub state: KE::KE1State,
|
||||
/// The first client message.
|
||||
pub message: KE::KE1Message,
|
||||
}
|
||||
|
||||
/// Result type of [`KeyExchange::build_ke2()`].
|
||||
pub struct GenerateKe2Result<CS: CipherSuite> {
|
||||
/// The server state.
|
||||
pub state: <CS::KeyExchange as KeyExchange>::KE2State<CS>,
|
||||
/// The server message.
|
||||
pub message: <CS::KeyExchange as KeyExchange>::KE2Message,
|
||||
#[cfg(test)]
|
||||
pub(crate) handshake_secret: Output<KeHash<CS>>,
|
||||
#[cfg(test)]
|
||||
pub(crate) km2: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
/// Result type of [`KeyExchange::generate_ke3()`].
|
||||
pub struct GenerateKe3Result<KE: KeyExchange + ?Sized> {
|
||||
/// The session key.
|
||||
pub session_key: Output<KE::Hash>,
|
||||
/// The second client message.
|
||||
pub message: KE::KE3Message,
|
||||
#[cfg(test)]
|
||||
pub(crate) handshake_secret: Output<KE::Hash>,
|
||||
#[cfg(test)]
|
||||
pub(crate) km3: Output<KE::Hash>,
|
||||
}
|
||||
|
||||
pub(crate) type Ke1StateLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange>::KE1State as Serialize>::Len;
|
||||
pub(crate) type Ke1MessageLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange>::KE1Message as Serialize>::Len;
|
||||
pub(crate) type Ke2StateLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange>::KE2State<CS> as Serialize>::Len;
|
||||
pub(crate) type Ke2MessageLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange>::KE2Message as Serialize>::Len;
|
||||
pub(crate) type Ke3MessageLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange>::KE3Message as Serialize>::Len;
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
|
||||
use digest::{Digest, Mac, Output, OutputSizeUser, Update};
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U1, U2, U32, U256, Unsigned};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hkdf::SimpleHkdf as Hkdf;
|
||||
use hkdf::SimpleHkdfExtract as HkdfExtract;
|
||||
use hmac::{KeyInit, SimpleHmac};
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
use super::{
|
||||
Deserialize, GenerateKe1Result, KeyExchange, Serialize, SerializedContext,
|
||||
SerializedCredentialRequest, SerializedCredentialResponse, SerializedIdentifiers,
|
||||
};
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::serialization::{ConcatExt, SliceExt, UpdateExt, i2osp};
|
||||
|
||||
///////////////
|
||||
// Constants //
|
||||
// ========= //
|
||||
///////////////
|
||||
|
||||
pub(crate) type NonceLen = U32;
|
||||
pub(super) static STR_CONTEXT: &[u8] = b"OPAQUEv1-";
|
||||
static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
|
||||
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
|
||||
static STR_SERVER_MAC: &[u8] = b"ServerMAC";
|
||||
static STR_SESSION_KEY: &[u8] = b"SessionKey";
|
||||
static STR_OPAQUE: &[u8] = b"OPAQUE-";
|
||||
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
// ====================== //
|
||||
////////////////////////////
|
||||
|
||||
/// Trait required by [`Group::Sk`] to be compatible with
|
||||
/// [`TripleDh`](crate::TripleDh) and [`SigmaI`](crate::SigmaI).
|
||||
pub trait DiffieHellman<G: Group> {
|
||||
/// Diffie-Hellman key exchange.
|
||||
fn diffie_hellman(&self, pk: &G::Pk) -> GenericArray<u8, G::PkLen>;
|
||||
}
|
||||
|
||||
/// The client state produced after the first key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "G::Sk: serde::Deserialize<'de>",
|
||||
serialize = "G::Sk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)]
|
||||
pub struct Ke1State<G: Group> {
|
||||
pub(super) client_e_sk: PrivateKey<G>,
|
||||
pub(super) client_nonce: GenericArray<u8, NonceLen>,
|
||||
}
|
||||
|
||||
/// The first key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "G::Pk: serde::Deserialize<'de>",
|
||||
serialize = "G::Pk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
|
||||
pub struct Ke1Message<G: Group> {
|
||||
pub(super) client_nonce: GenericArray<u8, NonceLen>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
pub(super) client_e_pk: PublicKey<G>,
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Convenience Structs //
|
||||
//==================== //
|
||||
/////////////////////////
|
||||
|
||||
// Consists of a session key, followed by two mac keys: (session_key, km2, km3)
|
||||
pub(super) struct DerivedKeys<H: OutputSizeUser> {
|
||||
pub(super) session_key: Output<H>,
|
||||
pub(super) km2: Output<H>,
|
||||
pub(super) km3: Output<H>,
|
||||
#[cfg(test)]
|
||||
pub(super) handshake_secret: Output<H>,
|
||||
}
|
||||
|
||||
/// Helper bundle containing the common `TripleDH` server state that both
|
||||
/// `TripleDh` and `TripleDhKem` builders need.
|
||||
pub(super) struct Ke2BuilderCommon<G: Group, H: Hash>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
G::Sk: DiffieHellman<G>,
|
||||
{
|
||||
pub(super) server_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(super) transcript_hasher: H,
|
||||
pub(super) client_e_pk: PublicKey<G>,
|
||||
pub(super) server_e_pk: PublicKey<G>,
|
||||
pub(super) shared_secret_1: GenericArray<u8, G::PkLen>,
|
||||
pub(super) shared_secret_3: GenericArray<u8, G::PkLen>,
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Helper functions and Trait Implementations //
|
||||
// ========================================== //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Helper functions
|
||||
|
||||
pub(super) fn generate_ke1<
|
||||
R: Rng + CryptoRng,
|
||||
KE: KeyExchange<KE1State = Ke1State<G>, KE1Message = Ke1Message<G>>,
|
||||
G: Group,
|
||||
>(
|
||||
rng: &mut R,
|
||||
) -> Result<GenerateKe1Result<KE>, ProtocolError> {
|
||||
let client_e_kp = KeyPair::<G>::derive_random(rng);
|
||||
let client_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let ke1_message = Ke1Message {
|
||||
client_nonce,
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
Ok(GenerateKe1Result {
|
||||
state: Ke1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce,
|
||||
},
|
||||
message: ke1_message,
|
||||
})
|
||||
}
|
||||
|
||||
// Generate a random nonce up to NonceLen::USIZE bytes.
|
||||
pub(super) fn generate_nonce<R: Rng + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
|
||||
let mut nonce_bytes = GenericArray::default();
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
nonce_bytes
|
||||
}
|
||||
|
||||
pub(super) fn transcript<CS: CipherSuite, KE: Group>(
|
||||
context: &SerializedContext<'_>,
|
||||
identifiers: &SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
credential_request: &SerializedCredentialRequest<CS>,
|
||||
ke1_message: &Ke1MessageIter<KE>,
|
||||
credential_response: &SerializedCredentialResponse<CS>,
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: &GenericArray<u8, KE::PkLen>,
|
||||
) -> KeHash<CS> {
|
||||
KeHash::<CS>::new()
|
||||
.chain_iter(context.iter())
|
||||
.chain_iter(identifiers.client.iter())
|
||||
.chain_iter(credential_request.iter())
|
||||
.chain_iter(ke1_message.iter())
|
||||
.chain_iter(identifiers.server.iter())
|
||||
.chain_iter(credential_response.iter())
|
||||
.chain(server_nonce)
|
||||
.chain(server_e_pk)
|
||||
}
|
||||
|
||||
/// Generates the server-side `TripleDH` transcript state shared by multiple
|
||||
/// key-exchange variants.
|
||||
pub(super) fn ke2_builder_common<'a, G, H, CS, R>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Ke1Message<G>,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
client_s_pk: PublicKey<G>,
|
||||
identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Ke2BuilderCommon<G, H>, ProtocolError>
|
||||
where
|
||||
G: Group,
|
||||
H: Hash,
|
||||
R: Rng + CryptoRng,
|
||||
CS: CipherSuite,
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
G::Sk: DiffieHellman<G>,
|
||||
CS::KeyExchange: KeyExchange<Group = G, Hash = H>,
|
||||
{
|
||||
let server_ephemeral = KeyPair::<G>::derive_random(rng);
|
||||
let server_nonce = generate_nonce::<R>(rng);
|
||||
let server_e_pk_bytes = server_ephemeral.public().serialize();
|
||||
|
||||
let ke1_iter = ke1_message.to_iter();
|
||||
let client_e_pk = ke1_message.client_e_pk.clone();
|
||||
|
||||
let transcript_hasher = transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_iter,
|
||||
&credential_response,
|
||||
server_nonce,
|
||||
&server_e_pk_bytes,
|
||||
);
|
||||
|
||||
let shared_secret_1 = server_ephemeral
|
||||
.private()
|
||||
.ke_diffie_hellman(&ke1_message.client_e_pk);
|
||||
let shared_secret_3 = server_ephemeral.private().ke_diffie_hellman(&client_s_pk);
|
||||
|
||||
Ok(Ke2BuilderCommon {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk,
|
||||
server_e_pk: server_ephemeral.public().clone(),
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
})
|
||||
}
|
||||
|
||||
// Internal function which takes computed shared secrets, along with some
|
||||
// auxiliary metadata, to produce the session key and two MAC keys
|
||||
pub(super) fn derive_keys<'a, H: Hash>(
|
||||
ikms: impl Iterator<Item = &'a [u8]>,
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<DerivedKeys<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
let mut hkdf = HkdfExtract::<H>::new(None);
|
||||
|
||||
for ikm in ikms {
|
||||
hkdf.input_ikm(ikm);
|
||||
}
|
||||
|
||||
let (_, extracted_ikm) = hkdf.finalize();
|
||||
let handshake_secret = derive_secrets::<H>(
|
||||
&extracted_ikm,
|
||||
STR_HANDSHAKE_SECRET,
|
||||
hashed_derivation_transcript,
|
||||
)?;
|
||||
let session_key = derive_secrets::<H>(
|
||||
&extracted_ikm,
|
||||
STR_SESSION_KEY,
|
||||
hashed_derivation_transcript,
|
||||
)?;
|
||||
|
||||
let km2 = hkdf_expand_label::<H>(&handshake_secret, STR_SERVER_MAC, b"")?;
|
||||
let km3 = hkdf_expand_label::<H>(&handshake_secret, STR_CLIENT_MAC, b"")?;
|
||||
|
||||
Ok(DerivedKeys {
|
||||
session_key,
|
||||
km2,
|
||||
km3,
|
||||
#[cfg(test)]
|
||||
handshake_secret,
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper function for shared functionality in KE2 MAC computation
|
||||
/// for both `TripleDH` and TripleDH-KEM
|
||||
pub(super) fn compute_ke2_macs<H: Hash>(
|
||||
transcript_hasher: &mut H,
|
||||
derived_keys: &DerivedKeys<H>,
|
||||
transcript_digest: &[u8],
|
||||
) -> Result<(Output<H>, Output<H>), ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
let mut mac_hasher =
|
||||
SimpleHmac::<H>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(&mut mac_hasher, transcript_digest);
|
||||
let mac = mac_hasher.finalize().into_bytes();
|
||||
|
||||
Update::update(transcript_hasher, &mac);
|
||||
let finalized_transcript = transcript_hasher.clone().finalize();
|
||||
|
||||
let mut expected_mac_hasher =
|
||||
SimpleHmac::<H>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(&mut expected_mac_hasher, &finalized_transcript);
|
||||
let expected_mac = expected_mac_hasher.finalize().into_bytes();
|
||||
|
||||
Ok((mac, expected_mac))
|
||||
}
|
||||
|
||||
/// Finalizes the KE3 transcript by deriving session material from the provided
|
||||
/// shared secrets and verifying the server's MAC, returning both the derived
|
||||
/// keys and the client's MAC response. Callers are expected to supply any
|
||||
/// protocol-specific shared secrets (e.g. classic Diffie-Hellman results or
|
||||
/// KEM outputs) as byte slices.
|
||||
pub(super) fn finalize_ke3_transcript<'a, H: Hash>(
|
||||
transcript_hasher: &mut H,
|
||||
shared_secrets: impl Iterator<Item = &'a [u8]>,
|
||||
server_mac: &Output<H>,
|
||||
) -> Result<(DerivedKeys<H>, Output<H>), ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
let transcript_digest = transcript_hasher.clone().finalize();
|
||||
let derived_keys = derive_keys::<H>(shared_secrets, &transcript_digest)?;
|
||||
let mut server_mac_hasher =
|
||||
SimpleHmac::<H>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(&mut server_mac_hasher, &transcript_digest);
|
||||
server_mac_hasher
|
||||
.verify(server_mac)
|
||||
.map_err(|_| ProtocolError::InvalidLoginError)?;
|
||||
|
||||
Update::update(transcript_hasher, server_mac);
|
||||
let finalized_transcript = transcript_hasher.clone().finalize();
|
||||
|
||||
let mut client_mac_hasher =
|
||||
SimpleHmac::<H>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(&mut client_mac_hasher, &finalized_transcript);
|
||||
|
||||
let client_mac = client_mac_hasher.finalize().into_bytes();
|
||||
|
||||
Ok((derived_keys, client_mac))
|
||||
}
|
||||
|
||||
fn hkdf_expand_label<H: Hash>(
|
||||
secret: &[u8],
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<Output<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
let h = Hkdf::<H>::from_prk(secret).map_err(|_| InternalError::HkdfError)?;
|
||||
hkdf_expand_label_extracted(&h, label, context)
|
||||
}
|
||||
|
||||
fn hkdf_expand_label_extracted<H: Hash>(
|
||||
hkdf: &Hkdf<H>,
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<Output<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
let mut okm = GenericArray::default().into_ha0_4();
|
||||
|
||||
let length = i2osp::<U2>(OutputSize::<H>::USIZE)?;
|
||||
let label_length = i2osp::<U1>(STR_OPAQUE.len() + label.len())?;
|
||||
let context_len = i2osp::<U1>(context.len())?;
|
||||
|
||||
let hkdf_label = [
|
||||
length.as_slice(),
|
||||
&label_length,
|
||||
STR_OPAQUE,
|
||||
label,
|
||||
&context_len,
|
||||
context,
|
||||
];
|
||||
|
||||
hkdf.expand_multi_info(&hkdf_label, &mut okm)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
fn derive_secrets<H: Hash>(
|
||||
hkdf: &Hkdf<H>,
|
||||
label: &[u8],
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<Output<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
hkdf_expand_label_extracted::<H>(hkdf, label, hashed_derivation_transcript)
|
||||
}
|
||||
|
||||
// Serialization and deserialization implementations
|
||||
|
||||
impl<G: Group> Deserialize for Ke1State<G> {
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_e_sk: PrivateKey::deserialize_take(bytes)?,
|
||||
client_nonce: bytes.take_array("client nonce")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Serialize for Ke1State<G>
|
||||
where
|
||||
// Ke1State: KeSk + Nonce
|
||||
G::SkLen: Add<NonceLen>,
|
||||
Sum<G::SkLen, NonceLen>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<G::SkLen, NonceLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_e_sk.serialize().cat(self.client_nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Deserialize for Ke1Message<G> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_nonce: input.take_array("client nonce")?,
|
||||
client_e_pk: PublicKey::deserialize_take(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Serialize for Ke1Message<G>
|
||||
where
|
||||
// Ke1Message: Nonce + KePk
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Sum<NonceLen, G::PkLen>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<NonceLen, G::PkLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_nonce.cat(self.client_e_pk.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Ke1Message<G> {
|
||||
pub(crate) fn to_iter(&self) -> Ke1MessageIter<G> {
|
||||
Ke1MessageIter {
|
||||
client_nonce: self.client_nonce,
|
||||
client_e_pk: self.client_e_pk.serialize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
|
||||
pub(crate) struct Ke1MessageIter<G: Group> {
|
||||
client_nonce: GenericArray<u8, NonceLen>,
|
||||
client_e_pk: GenericArray<u8, G::PkLen>,
|
||||
}
|
||||
|
||||
pub(crate) type Ke1MessageIterLen<G: Group> = Sum<NonceLen, G::PkLen>;
|
||||
|
||||
impl<G: Group> Ke1MessageIter<G> {
|
||||
pub(crate) fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
[self.client_nonce.as_slice(), self.client_e_pk.as_slice()].into_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Ke1MessageIter {
|
||||
client_nonce: input.take_array("client nonce")?,
|
||||
client_e_pk: input.take_array("client ephemeral public key")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Ke1MessageIter<G>
|
||||
where
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Ke1MessageIterLen<G>: ArrayLength,
|
||||
{
|
||||
pub(crate) fn serialize(&self) -> GenericArray<u8, Ke1MessageIterLen<G>> {
|
||||
self.client_nonce.cat(self.client_e_pk.clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! ECDSA implementation for [`elliptic_curve`] [`Group`] implementations to
|
||||
//! support [`SigmaI`](crate::SigmaI).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use digest::block_api::{BlockSizeUser, EagerHash};
|
||||
use digest::{Digest, FixedOutputReset, HashMarker};
|
||||
use ecdsa::{EcdsaCurve, SignatureSize};
|
||||
use elliptic_curve::point::NonIdentity;
|
||||
use elliptic_curve::{CurveArithmetic, FieldBytes, ProjectivePoint, SecretKey};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hybrid_array::ArraySize;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
use super::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::Group;
|
||||
pub use crate::key_exchange::sigma_i::shared::PreHash;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// ECDSA for [`SigmaI`](crate::SigmaI).
|
||||
///
|
||||
/// The ["verification state"](Self::VerifyState) is the pre-hash for the
|
||||
/// message to be verified.
|
||||
pub struct Ecdsa<G, H>(PhantomData<(G, H)>);
|
||||
|
||||
impl<G, H> SignatureProtocol for Ecdsa<G, H>
|
||||
where
|
||||
G: CurveArithmetic
|
||||
+ Group<Sk = SecretKey<G>, Pk = NonIdentity<ProjectivePoint<G>>>
|
||||
+ EcdsaCurve,
|
||||
SignatureSize<G>: ArrayLength + ArraySize,
|
||||
H: EagerHash + FixedOutputReset + BlockSizeUser + HashMarker + Digest + Clone + Default,
|
||||
{
|
||||
type Group = G;
|
||||
type Signature = ecdsa::Signature<G>;
|
||||
type SignatureLen = SignatureSize<G>;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<H>;
|
||||
|
||||
// We use a manual implementation of `RandomizedPrehashSigner` to use the same
|
||||
// hash for the message as for generating `k`. See
|
||||
// https://github.com/RustCrypto/signatures/issues/949.
|
||||
fn sign<'a, R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
|
||||
sk: &<Self::Group as Group>::Sk,
|
||||
rng: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
let hash = message.hash::<H>();
|
||||
|
||||
(
|
||||
sign::<_, G, H>(sk, rng, &hash.sign.finalize_fixed()),
|
||||
PreHash(hash.verify.finalize_fixed()),
|
||||
)
|
||||
}
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &<Self::Group as Group>::Pk,
|
||||
_: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(pk, &state.0, signature)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
GenericArray::from_slice(signature.to_bytes().as_slice()).clone()
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
ecdsa::Signature::from_bytes(&bytes.take_array("signature")?.into_ha0_4())
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
fn sign<R, C, H>(sk: &SecretKey<C>, rng: &mut R, pre_hash: &[u8]) -> ecdsa::Signature<C>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
C: CurveArithmetic + EcdsaCurve,
|
||||
SignatureSize<C>: ArraySize,
|
||||
H: Digest + BlockSizeUser + FixedOutputReset,
|
||||
{
|
||||
let mut ad = FieldBytes::<C>::default();
|
||||
rng.fill_bytes(&mut ad);
|
||||
ecdsa::hazmat::sign_prehashed_rfc6979::<C, H>(&sk.to_nonzero_scalar(), pre_hash, &ad).0
|
||||
}
|
||||
|
||||
fn verify<C>(
|
||||
pk: &NonIdentity<ProjectivePoint<C>>,
|
||||
pre_hash: &[u8],
|
||||
signature: &ecdsa::Signature<C>,
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
C: CurveArithmetic + EcdsaCurve,
|
||||
SignatureSize<C>: ArraySize,
|
||||
{
|
||||
ecdsa::hazmat::verify_prehashed(&pk.to_point(), pre_hash, signature)
|
||||
.map_err(|_| ProtocolError::InvalidLoginError)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ecdsa() {
|
||||
use std::vec;
|
||||
|
||||
use digest::Digest;
|
||||
use ecdsa::signature::hazmat::PrehashVerifier;
|
||||
use p256::ecdsa::signature::RandomizedDigestSigner;
|
||||
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
|
||||
use p256::{NistP256, PublicKey};
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rand::rngs::SysRng;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
|
||||
let mut rng = CycleRng::new(vec![1; 32]);
|
||||
|
||||
let mut message = [0; 1024];
|
||||
UnwrapErr(SysRng).fill_bytes(&mut message);
|
||||
let hash = Sha256::new_with_prefix(message);
|
||||
|
||||
let sk = NistP256::random_sk(&mut UnwrapErr(SysRng));
|
||||
let signing_key = SigningKey::from(sk.clone());
|
||||
|
||||
let signature: Signature = signing_key.sign_digest_with_rng(&mut rng, |d: &mut Sha256| {
|
||||
d.update(message);
|
||||
});
|
||||
let custom_signature = sign::<_, _, Sha256>(&sk, &mut rng, &hash.clone().finalize());
|
||||
|
||||
assert_eq!(signature, custom_signature);
|
||||
|
||||
let pk = NistP256::public_key(&sk);
|
||||
let verifying_key = VerifyingKey::from(PublicKey::from(&pk));
|
||||
|
||||
verifying_key
|
||||
.verify_prehash(&hash.clone().finalize(), &signature)
|
||||
.unwrap();
|
||||
verify(&pk, &hash.finalize(), &custom_signature).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! HashEdDSA implementation for [`SigmaI`](crate::SigmaI). Currently only
|
||||
//! supports [`Ed25519`](crate::Ed25519).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use self::implementation::HashEddsaImpl;
|
||||
use super::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::Group;
|
||||
|
||||
/// HashEdDSA for [`SigmaI`](crate::SigmaI).
|
||||
///
|
||||
/// The ["verification state"](Self::VerifyState) is the pre-hash for the
|
||||
/// message to be verified.
|
||||
pub struct HashEddsa<G>(PhantomData<G>);
|
||||
|
||||
impl<G: HashEddsaImpl> SignatureProtocol for HashEddsa<G> {
|
||||
type Group = G;
|
||||
type Signature = G::Signature;
|
||||
type SignatureLen = G::SignatureLen;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = G::VerifyState<CS, KE>;
|
||||
|
||||
fn sign<'a, R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
|
||||
sk: &<Self::Group as Group>::Sk,
|
||||
_: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
G::sign(sk, message)
|
||||
}
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &<Self::Group as Group>::Pk,
|
||||
_: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
G::verify(pk, state, signature)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
G::serialize_signature(signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
G::deserialize_take_signature(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) mod implementation {
|
||||
use generic_array::ArrayLength;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub trait HashEddsaImpl: Group {
|
||||
type Signature: Clone + Zeroize;
|
||||
type SignatureLen: ArrayLength;
|
||||
type VerifyState<CS: CipherSuite, KE: Group>: Clone + Zeroize;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>);
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError>;
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature)
|
||||
-> GenericArray<u8, Self::SignatureLen>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::{FixedOutput, Output, Update};
|
||||
use generic_array::typenum::Sum;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash, OprfGroup};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{Ke1MessageIter, Ke1MessageIterLen, NonceLen};
|
||||
use crate::key_exchange::{
|
||||
Deserialize, Serialize, SerializedContext, SerializedCredentialRequest,
|
||||
SerializedCredentialRequestLen, SerializedCredentialResponse, SerializedCredentialResponseLen,
|
||||
SerializedIdentifier, SerializedIdentifiers,
|
||||
};
|
||||
use crate::opaque::MaskedResponseLen;
|
||||
use crate::serialization::{ConcatExt, SliceExt, UpdateExt};
|
||||
|
||||
/// This holds the message to be signed and the message to be verified.
|
||||
///
|
||||
/// If your signature protocol requires pre-hashes, you can call [`hash()`].
|
||||
///
|
||||
/// If you require the actual message, call [`sign_message()`]. To get the
|
||||
/// message to verify, call [`to_cached()`] to create a [`CachedMessage`] and
|
||||
/// save it in [`SignatureProtocol::VerifyState`], which you can then use in
|
||||
/// [`SignatureProtocol::verify()`] with [`MessageBuilder`] to create
|
||||
/// [`VerifyMessage`].
|
||||
///
|
||||
/// [`hash()`]: super::Message::hash
|
||||
/// [`sign_message()`]: super::Message::sign_message
|
||||
/// [`to_cached()`]: super::Message::to_cached
|
||||
/// [`SignatureProtocol::sign()`]: super::SignatureProtocol::sign
|
||||
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct Message<'a, CS: CipherSuite, KE: Group> {
|
||||
pub(super) role: Role,
|
||||
pub(super) context: SerializedContext<'a>,
|
||||
pub(super) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
pub(super) cache: CachedMessage<CS, KE>,
|
||||
}
|
||||
|
||||
/// This holds the message to be verified.
|
||||
///
|
||||
/// Create it by using [`MessageBuilder::build()`] with [`CachedMessage`].
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct VerifyMessage<'a, CS: CipherSuite, KE: Group> {
|
||||
role: Role,
|
||||
context: SerializedContext<'a>,
|
||||
identifier: SerializedIdentifier<'a, KeGroup<CS>>,
|
||||
pub(super) cache: CachedMessage<CS, KE>,
|
||||
}
|
||||
|
||||
/// Used to build [`VerifyMessage`]. It is only available in
|
||||
/// [`SignatureProtocol::verify()`].
|
||||
///
|
||||
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct MessageBuilder<'a, CS: CipherSuite> {
|
||||
pub(super) role: Role,
|
||||
pub(super) context: SerializedContext<'a>,
|
||||
pub(super) identifier: SerializedIdentifier<'a, KeGroup<CS>>,
|
||||
}
|
||||
|
||||
/// Created by [`Message::to_cached()`]. This is used to save the message to be
|
||||
/// verified in [`SignatureProtocol::VerifyState`].
|
||||
///
|
||||
/// Use [`MessageBuilder::build()`] to create [`VerifyMessage`] in
|
||||
/// [`SignatureProtocol::verify()`].
|
||||
///
|
||||
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct CachedMessage<CS: CipherSuite, KE: Group> {
|
||||
pub(super) credential_request: SerializedCredentialRequest<CS>,
|
||||
pub(super) ke1_message: Ke1MessageIter<KE>,
|
||||
pub(super) credential_response: SerializedCredentialResponse<CS>,
|
||||
pub(super) server_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(super) server_e_pk: GenericArray<u8, KE::PkLen>,
|
||||
pub(super) server_mac: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> Message<'_, CS, KE> {
|
||||
/// Returns the message to be signed.
|
||||
pub fn sign_message(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
self.context.iter().chain(self.post_message(Stage::Sign))
|
||||
}
|
||||
|
||||
/// Returns the hash of both messages.
|
||||
pub fn hash<KEH: Default + Clone + FixedOutput + Update>(&self) -> HashOutput<KEH> {
|
||||
let mut context = KEH::default();
|
||||
context.update_iter(self.context.iter());
|
||||
|
||||
let sign = context.clone().chain_iter(self.post_message(Stage::Sign));
|
||||
let verify = context.chain_iter(self.post_message(Stage::Verify));
|
||||
|
||||
HashOutput { sign, verify }
|
||||
}
|
||||
|
||||
fn post_message(&self, stage: Stage) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
let transcript = match (self.role, stage) {
|
||||
(Role::Server, Stage::Sign) => Role::Server,
|
||||
(Role::Server, Stage::Verify) => Role::Client,
|
||||
(Role::Client, Stage::Sign) => Role::Client,
|
||||
(Role::Client, Stage::Verify) => Role::Server,
|
||||
};
|
||||
let identifier = match transcript {
|
||||
Role::Server => &self.identifiers.server,
|
||||
Role::Client => &self.identifiers.client,
|
||||
};
|
||||
|
||||
self.cache.post_message(transcript, identifier)
|
||||
}
|
||||
|
||||
/// Create a [`CachedMessage`], which can be saved in
|
||||
/// [`SignatureProtocol::VerifyState`] and create a [`VerifyMessage`] with
|
||||
/// [`MessageBuilder::build()`].
|
||||
///
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
pub fn to_cached(&self) -> CachedMessage<CS, KE> {
|
||||
self.cache.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> VerifyMessage<'_, CS, KE> {
|
||||
/// Returns the message to be verified.
|
||||
pub fn verify_message(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
let transcript = match self.role {
|
||||
Role::Server => Role::Client,
|
||||
Role::Client => Role::Server,
|
||||
};
|
||||
|
||||
self.context
|
||||
.iter()
|
||||
.chain(self.cache.post_message(transcript, &self.identifier))
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> CachedMessage<CS, KE> {
|
||||
fn post_message<'a>(
|
||||
&'a self,
|
||||
transcript: Role,
|
||||
identifier: &'a SerializedIdentifier<'_, KeGroup<CS>>,
|
||||
) -> impl Clone + Iterator<Item = &'a [u8]> {
|
||||
Some(identifier.iter())
|
||||
.filter(|_| matches!(transcript, Role::Client))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(self.credential_request.iter())
|
||||
.chain(self.ke1_message.iter())
|
||||
.chain(
|
||||
Some(identifier.iter())
|
||||
.filter(|_| matches!(transcript, Role::Server))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(self.credential_response.iter())
|
||||
.chain([self.server_nonce.as_slice(), &self.server_e_pk])
|
||||
.chain(Some(self.server_mac.as_slice()).filter(|_| matches!(transcript, Role::Client)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub(super) enum Role {
|
||||
Server,
|
||||
Client,
|
||||
}
|
||||
|
||||
impl Zeroize for Role {
|
||||
fn zeroize(&mut self) {
|
||||
*self = Self::Server;
|
||||
}
|
||||
}
|
||||
|
||||
enum Stage {
|
||||
Sign,
|
||||
Verify,
|
||||
}
|
||||
|
||||
/// Returned by [`Message::hash()`] containing the hash of the message to be
|
||||
/// signed and the message to be verified.
|
||||
pub struct HashOutput<H> {
|
||||
/// The hash of the message to be signed.
|
||||
pub sign: H,
|
||||
/// The hash of the message to be verified.
|
||||
pub verify: H,
|
||||
}
|
||||
|
||||
impl<'a, CS: CipherSuite> MessageBuilder<'a, CS> {
|
||||
/// Creates a [`VerifyMessage`]. [`CachedMessage`] can be created by
|
||||
/// [`Message::to_cached()`] and stored in
|
||||
/// [`SignatureProtocol::VerifyState`].
|
||||
///
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
pub fn build<KE: Group>(self, cache: CachedMessage<CS, KE>) -> VerifyMessage<'a, CS, KE> {
|
||||
VerifyMessage {
|
||||
role: self.role,
|
||||
context: self.context.clone(),
|
||||
identifier: self.identifier.clone(),
|
||||
cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> Deserialize for CachedMessage<CS, KE> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
credential_request: SerializedCredentialRequest::deserialize_take(input)?,
|
||||
ke1_message: Ke1MessageIter::deserialize_take(input)?,
|
||||
credential_response: SerializedCredentialResponse::deserialize_take(input)?,
|
||||
server_nonce: input.take_array("server nonce")?,
|
||||
server_e_pk: input.take_array("serialized server ephemeral key")?,
|
||||
server_mac: input.take_array("server mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`CachedMessage`].
|
||||
type CachedMessageLen<CS: CipherSuite, KE: Group> = Sum<
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
SerializedCredentialResponseLen<CS>,
|
||||
>,
|
||||
NonceLen,
|
||||
>,
|
||||
KE::PkLen,
|
||||
>,
|
||||
OutputSize<KeHash<CS>>,
|
||||
>;
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> Serialize for CachedMessage<CS, KE>
|
||||
where
|
||||
SerializedCredentialRequestLen<CS>: ArrayLength + Add<Ke1MessageIterLen<KE>>,
|
||||
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>:
|
||||
ArrayLength + Add<SerializedCredentialResponseLen<CS>>,
|
||||
Sum<
|
||||
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
SerializedCredentialResponseLen<CS>,
|
||||
>: ArrayLength + Add<NonceLen>,
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
SerializedCredentialResponseLen<CS>,
|
||||
>,
|
||||
NonceLen,
|
||||
>: ArrayLength + Add<KE::PkLen>,
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
SerializedCredentialResponseLen<CS>,
|
||||
>,
|
||||
NonceLen,
|
||||
>,
|
||||
KE::PkLen,
|
||||
>: ArrayLength + Add<OutputSize<KeHash<CS>>>,
|
||||
CachedMessageLen<CS, KE>: ArrayLength,
|
||||
// Ke1MessageIter
|
||||
NonceLen: Add<KE::PkLen>,
|
||||
Ke1MessageIterLen<KE>: ArrayLength,
|
||||
// CredentialResponseParts
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength + Add<MaskedResponseLen<CS>>,
|
||||
SerializedCredentialResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
type Len = CachedMessageLen<CS, KE>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.credential_request
|
||||
.serialize()
|
||||
.cat(self.ke1_message.serialize())
|
||||
.cat(self.credential_response.serialize())
|
||||
.cat(self.server_nonce)
|
||||
.cat(self.server_e_pk.clone())
|
||||
.cat(GenericArray::from_slice(self.server_mac.as_slice()).clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! An implementation of the SIGMA-I key exchange protocol
|
||||
//!
|
||||
//! ⚠️ **Warning**: This implementation has not been audited. Use at your own
|
||||
//! risk!
|
||||
|
||||
#[cfg(feature = "ecdsa")]
|
||||
pub mod ecdsa;
|
||||
pub mod hash_eddsa;
|
||||
mod message;
|
||||
pub mod pure_eddsa;
|
||||
pub(super) mod shared;
|
||||
|
||||
use core::iter;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::block_api::{BlockSizeUser, CoreProxy, SmallBlockSizeUser};
|
||||
use digest::{Mac, Output, OutputSizeUser};
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hmac::{KeyInit, SimpleHmac};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use self::message::Role;
|
||||
pub use self::message::{CachedMessage, HashOutput, Message, MessageBuilder, VerifyMessage};
|
||||
use super::{
|
||||
Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
|
||||
SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
|
||||
SerializedIdentifier, SerializedIdentifiers,
|
||||
};
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash};
|
||||
use crate::envelope::NonceLen;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
|
||||
use crate::key_exchange::shared::{derive_keys, generate_ke1, generate_nonce, transcript};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::{ConcatExt, SliceExt, UpdateExt};
|
||||
|
||||
/// The SIGMA-I key exchange implementation
|
||||
///
|
||||
/// `SIG` determines the algorithm used for the signature. `KE` determines the
|
||||
/// algorithm used for establishing the shared secret. `KEH` determines the hash
|
||||
/// used for the key exchange.
|
||||
///
|
||||
/// # Remote Key
|
||||
///
|
||||
/// [`ServerLoginBuilder::data()`](crate::ServerLoginBuilder::data()) will
|
||||
/// return [`Message`].
|
||||
///
|
||||
/// [`ServerLoginBuilder::build()`](crate::ServerLoginBuilder::build()) expects
|
||||
/// a signature from signing the [message](Message::sign_message) with the
|
||||
/// servers private key, and a ["verification
|
||||
/// state"](SignatureProtocol::VerifyState).
|
||||
///
|
||||
/// To understand what kind of "verification state" is expected here exactly,
|
||||
/// refer to the documentation of your chosen [`SignatureProtocol`] `SIG`. E.g.
|
||||
/// [`Ecdsa`](ecdsa::Ecdsa), [`PureEddsa`](pure_eddsa::PureEddsa) or
|
||||
/// [`HashEddsa`](hash_eddsa::HashEddsa).
|
||||
pub struct SigmaI<SIG, KE, KEH>(PhantomData<(SIG, KE, KEH)>);
|
||||
|
||||
/// Trait to implement for `SIG` used in [`SigmaI`].
|
||||
///
|
||||
/// The [`sign()`] and [`verify()`] methods do not function independent of each
|
||||
/// other. [`sign()`] is always called first and receives a [Message] containing
|
||||
/// the message for both signing and verifying. A ["verification
|
||||
/// state"](Self::VerifyState) is created by [`sign()`] and then passed onto
|
||||
/// [`verify()`].
|
||||
///
|
||||
/// The most straightforward implementation would simply store the message for
|
||||
/// verifying in [`VerifyState`](Self::VerifyState). However, protocols that
|
||||
/// allow for pre-hashing don't need to store the whole message and can
|
||||
/// preemptively hash the verification message and only store that instead,
|
||||
/// getting rid of the much larger message.
|
||||
///
|
||||
/// [`sign()`]: Self::sign
|
||||
/// [`verify()`]: Self::verify
|
||||
pub trait SignatureProtocol {
|
||||
/// The [`Group`] used to generate and derive keys.
|
||||
type Group: Group;
|
||||
/// The signature.
|
||||
type Signature: Clone + Zeroize;
|
||||
/// Length of a serialized [`Signature`](Self::Signature).
|
||||
type SignatureLen: ArrayLength;
|
||||
/// The state required to run the verification. This is used to cache the
|
||||
/// pre-hash for curves that support that, otherwise the [`Message`] to
|
||||
/// verify is stored via [`CachedMessage`].
|
||||
type VerifyState<CS: CipherSuite, KE: Group>: Clone + Zeroize;
|
||||
|
||||
/// Returns a signature from the given message signed by the given private
|
||||
/// key.
|
||||
///
|
||||
/// [`Message`] contains both signature messages for signing and
|
||||
/// verification. If you need it again during verification, consider
|
||||
/// using [`CachedMessage`].
|
||||
///
|
||||
/// The returned [`VerifyState`](Self::VerifyState) will be passed to
|
||||
/// [`verify()`](Self::verify) and must contain the necessary
|
||||
/// information to verify the incoming signature.
|
||||
fn sign<R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
|
||||
sk: &<Self::Group as Group>::Sk,
|
||||
rng: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>);
|
||||
|
||||
/// Validates that the signature was created by signing the message with the
|
||||
/// corresponding private key.
|
||||
///
|
||||
/// The [`MessageBuilder`] can be used with [`CachedMessage`] to create
|
||||
/// [`VerifyMessage`] which contains the message of the given `signature`.
|
||||
///
|
||||
/// The `state` is created by [`sign()`](Self::sign()).
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &<Self::Group as Group>::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError>;
|
||||
|
||||
/// Serialize [`Signature`](Self::Signature) into a fixed-sized byte array.
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen>;
|
||||
|
||||
/// Deserialize [`Signature`](Self::Signature) from the given `bytes`.
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
|
||||
}
|
||||
|
||||
/// Builder for the second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "'de: 'a, <KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>, KE::Pk: \
|
||||
serde::Deserialize<'de>",
|
||||
serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize, KE::Pk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; <KeGroup<CS> as Group>::Pk, KE::Pk)]
|
||||
pub struct Ke2Builder<'a, CS: CipherSuite, KE: Group> {
|
||||
transcript: Message<'a, CS, KE>,
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
client_s_pk: PublicKey<KeGroup<CS>>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
server_e_pk: PublicKey<KE>,
|
||||
expected_mac: Output<KeHash<CS>>,
|
||||
session_key: Output<KeHash<CS>>,
|
||||
#[cfg(test)]
|
||||
handshake_secret: Output<KeHash<CS>>,
|
||||
#[cfg(test)]
|
||||
km2: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
/// The server state produced after the second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "<SIG::Group as Group>::Pk: serde::Deserialize<'de>, SIG::VerifyState<CS, \
|
||||
KE>: serde::Deserialize<'de>",
|
||||
serialize = "<SIG::Group as Group>::Pk: serde::Serialize, SIG::VerifyState<CS, KE>: \
|
||||
serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; <SIG::Group as Group>::Pk, SIG::VerifyState<CS, KE>)]
|
||||
pub struct Ke2State<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> {
|
||||
#[derive_where(skip(Zeroize))]
|
||||
client_s_pk: PublicKey<SIG::Group>,
|
||||
session_key: Output<KeHash<CS>>,
|
||||
verify_state: SIG::VerifyState<CS, KE>,
|
||||
expected_mac: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "KE::Pk: serde::Deserialize<'de>, SIG::Signature: serde::Deserialize<'de>",
|
||||
serialize = "KE::Pk: serde::Serialize, SIG::Signature: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KE::Pk, SIG::Signature)]
|
||||
pub struct Ke2Message<SIG: SignatureProtocol, KE: Group, KEH: Hash>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<KEH>: ArrayLength,
|
||||
{
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
server_e_pk: PublicKey<KE>,
|
||||
signature: SIG::Signature,
|
||||
mac: Output<KEH>,
|
||||
}
|
||||
|
||||
/// The third key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "SIG::Signature: serde::Deserialize<'de>",
|
||||
serialize = "SIG::Signature: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; SIG::Signature)]
|
||||
pub struct Ke3Message<SIG: SignatureProtocol, KEH: OutputSizeUser>
|
||||
where
|
||||
<KEH as OutputSizeUser>::OutputSize: ArrayLength,
|
||||
{
|
||||
signature: SIG::Signature,
|
||||
mac: Output<KEH>,
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: 'static + Group, KEH: Hash + BlockSizeUser> KeyExchange
|
||||
for SigmaI<SIG, KE, KEH>
|
||||
where
|
||||
KE::Sk: DiffieHellman<KE>,
|
||||
KEH::Core: ProxyHash,
|
||||
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<KEH>: ArrayLength,
|
||||
{
|
||||
type Group = SIG::Group;
|
||||
type Hash = KEH;
|
||||
|
||||
type KE1State = Ke1State<KE>;
|
||||
type KE2State<CS: CipherSuite> = Ke2State<CS, SIG, KE>;
|
||||
type KE1Message = Ke1Message<KE>;
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<'a, CS, KE>;
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a Message<'a, CS, KE>;
|
||||
type KE2BuilderInput<CS: CipherSuite> = (SIG::Signature, SIG::VerifyState<CS, KE>);
|
||||
type KE2Message = Ke2Message<SIG, KE, KEH>;
|
||||
type KE3Message = Ke3Message<SIG, KEH>;
|
||||
|
||||
fn generate_ke1<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<GenerateKe1Result<Self>, ProtocolError> {
|
||||
generate_ke1(rng)
|
||||
}
|
||||
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
client_s_pk: PublicKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
|
||||
let server_e = KeyPair::<KE>::derive_random(rng);
|
||||
let server_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let ke1_message_iter = ke1_message.to_iter();
|
||||
let server_e_pk = server_e.public().serialize();
|
||||
|
||||
let transcript_hasher = transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message_iter,
|
||||
&credential_response,
|
||||
server_nonce,
|
||||
&server_e_pk,
|
||||
);
|
||||
|
||||
let shared_secret = server_e
|
||||
.private()
|
||||
.ke_diffie_hellman(&ke1_message.client_e_pk);
|
||||
|
||||
let derived_keys = derive_keys::<KEH>(
|
||||
iter::once(shared_secret.as_slice()),
|
||||
&transcript_hasher.finalize(),
|
||||
)?;
|
||||
|
||||
let mut server_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km2)
|
||||
.map_err(|_| InternalError::HmacError)?;
|
||||
server_mac.update_iter(identifiers.server.iter());
|
||||
let server_mac = server_mac.finalize().into_bytes();
|
||||
|
||||
let mut client_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km3)
|
||||
.map_err(|_| InternalError::HmacError)?;
|
||||
client_mac.update_iter(identifiers.client.iter());
|
||||
let client_mac = client_mac.finalize().into_bytes();
|
||||
|
||||
let message = Message {
|
||||
role: Role::Server,
|
||||
context,
|
||||
identifiers,
|
||||
cache: CachedMessage {
|
||||
credential_request,
|
||||
ke1_message: ke1_message_iter,
|
||||
credential_response,
|
||||
server_nonce,
|
||||
server_e_pk,
|
||||
server_mac,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Ke2Builder {
|
||||
transcript: message,
|
||||
server_nonce,
|
||||
client_s_pk,
|
||||
server_e_pk: server_e.public().clone(),
|
||||
expected_mac: client_mac,
|
||||
session_key: derived_keys.session_key,
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
km2: derived_keys.km2,
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS> {
|
||||
&builder.transcript
|
||||
}
|
||||
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
rng: &mut R,
|
||||
server_s_sk: &PrivateKey<Self::Group>,
|
||||
) -> Self::KE2BuilderInput<CS> {
|
||||
server_s_sk.sign::<_, CS, SIG, KE>(rng, &builder.transcript)
|
||||
}
|
||||
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
builder: Self::KE2Builder<'_, CS>,
|
||||
input: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
|
||||
Ok(GenerateKe2Result {
|
||||
state: Ke2State {
|
||||
client_s_pk: builder.client_s_pk.clone(),
|
||||
session_key: builder.session_key.clone(),
|
||||
verify_state: input.1,
|
||||
expected_mac: builder.expected_mac.clone(),
|
||||
},
|
||||
message: Ke2Message {
|
||||
server_nonce: builder.server_nonce,
|
||||
server_e_pk: builder.server_e_pk.clone(),
|
||||
signature: input.0,
|
||||
mac: builder.transcript.cache.server_mac.clone(),
|
||||
},
|
||||
#[cfg(test)]
|
||||
handshake_secret: builder.handshake_secret.clone(),
|
||||
#[cfg(test)]
|
||||
km2: builder.km2.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
ke1_state: &Self::KE1State,
|
||||
ke2_message: Self::KE2Message,
|
||||
server_s_pk: PublicKey<Self::Group>,
|
||||
client_s_sk: PrivateKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
|
||||
let ke1_message_iter = ke1_message.to_iter();
|
||||
let server_e_pk = ke2_message.server_e_pk.serialize();
|
||||
|
||||
let transcript_hasher = transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message_iter,
|
||||
&credential_response,
|
||||
ke2_message.server_nonce,
|
||||
&server_e_pk,
|
||||
);
|
||||
|
||||
let shared_secret = ke1_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&ke2_message.server_e_pk);
|
||||
|
||||
let derived_keys = derive_keys::<KEH>(
|
||||
iter::once(shared_secret.as_slice()),
|
||||
&transcript_hasher.finalize(),
|
||||
)?;
|
||||
|
||||
let mut server_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km2)
|
||||
.map_err(|_| InternalError::HmacError)?;
|
||||
server_mac.update_iter(identifiers.server.iter());
|
||||
let server_mac = server_mac.finalize().into_bytes();
|
||||
|
||||
bool::from(server_mac.ct_eq(&ke2_message.mac))
|
||||
.then_some(())
|
||||
.ok_or(ProtocolError::InvalidLoginError)?;
|
||||
|
||||
let mut client_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km3)
|
||||
.map_err(|_| InternalError::HmacError)?;
|
||||
client_mac.update_iter(identifiers.client.iter());
|
||||
let client_mac = client_mac.finalize().into_bytes();
|
||||
|
||||
let message = Message {
|
||||
role: Role::Client,
|
||||
context: context.clone(),
|
||||
identifiers: identifiers.clone(),
|
||||
cache: CachedMessage {
|
||||
credential_request,
|
||||
ke1_message: ke1_message_iter,
|
||||
credential_response,
|
||||
server_nonce: ke2_message.server_nonce,
|
||||
server_e_pk,
|
||||
server_mac,
|
||||
},
|
||||
};
|
||||
|
||||
let (signature, state) = client_s_sk.sign::<_, CS, SIG, KE>(rng, &message);
|
||||
|
||||
server_s_pk.verify::<CS, SIG, KE>(
|
||||
MessageBuilder {
|
||||
role: Role::Client,
|
||||
context,
|
||||
identifier: identifiers.server,
|
||||
},
|
||||
state,
|
||||
&ke2_message.signature,
|
||||
)?;
|
||||
|
||||
Ok(GenerateKe3Result {
|
||||
session_key: derived_keys.session_key,
|
||||
message: Ke3Message {
|
||||
signature,
|
||||
mac: client_mac,
|
||||
},
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
km3: derived_keys.km3,
|
||||
})
|
||||
}
|
||||
|
||||
fn finish_ke<CS: CipherSuite<KeyExchange = Self>>(
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
ke3_message: Self::KE3Message,
|
||||
identifiers: Identifiers<'_>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<Output<KEH>, ProtocolError> {
|
||||
ke2_state.client_s_pk.verify::<CS, SIG, KE>(
|
||||
MessageBuilder {
|
||||
role: Role::Server,
|
||||
context,
|
||||
identifier: SerializedIdentifier::from_identifier(
|
||||
identifiers.client,
|
||||
ke2_state.client_s_pk.serialize(),
|
||||
)?,
|
||||
},
|
||||
ke2_state.verify_state.clone(),
|
||||
&ke3_message.signature,
|
||||
)?;
|
||||
|
||||
CtOption::new(
|
||||
ke2_state.session_key.clone(),
|
||||
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> Deserialize for Ke2State<CS, SIG, KE>
|
||||
where
|
||||
SIG::VerifyState<CS, KE>: Deserialize,
|
||||
OutputSize<KeHash<CS>>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_s_pk: PublicKey::deserialize_take(input)?,
|
||||
session_key: input.take_array("session key")?.into_ha0_4(),
|
||||
verify_state: SIG::VerifyState::<CS, KE>::deserialize_take(input)?,
|
||||
expected_mac: input.take_array("expected mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type Ke2StateLen<CS, SIG: SignatureProtocol, KE> = Sum<
|
||||
Sum<Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>, VerifyStateLen<CS, SIG, KE>>,
|
||||
OutputSize<KeHash<CS>>,
|
||||
>;
|
||||
|
||||
type VerifyStateLen<CS, SIG: SignatureProtocol, KE> = <SIG::VerifyState<CS, KE> as Serialize>::Len;
|
||||
|
||||
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> Serialize for Ke2State<CS, SIG, KE>
|
||||
where
|
||||
SIG::VerifyState<CS, KE>: Serialize,
|
||||
OutputSize<KeHash<CS>>: ArrayLength,
|
||||
// Ke2State: ((SigPk + Hash) + VerifyState) + Hash
|
||||
<SIG::Group as Group>::PkLen: Add<OutputSize<KeHash<CS>>>,
|
||||
Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>:
|
||||
ArrayLength + Add<VerifyStateLen<CS, SIG, KE>>,
|
||||
Sum<Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>, VerifyStateLen<CS, SIG, KE>>:
|
||||
ArrayLength + Add<OutputSize<KeHash<CS>>>,
|
||||
Ke2StateLen<CS, SIG, KE>: ArrayLength,
|
||||
{
|
||||
type Len = Ke2StateLen<CS, SIG, KE>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_s_pk
|
||||
.serialize()
|
||||
.cat(GenericArray::from_slice(self.session_key.as_slice()).clone())
|
||||
.cat(self.verify_state.serialize())
|
||||
.cat(GenericArray::from_slice(self.expected_mac.as_slice()).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: Group, KEH: Hash> Deserialize for Ke2Message<SIG, KE, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<KEH>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
server_nonce: input.take_array("server nonce")?,
|
||||
server_e_pk: PublicKey::deserialize_take(input)?,
|
||||
signature: SIG::deserialize_take_signature(input)?,
|
||||
mac: input.take_array("mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: Group, KEH: Hash> Serialize for Ke2Message<SIG, KE, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<KEH>: ArrayLength,
|
||||
// Ke2Message: ((Nonce + KePk) + Signature) + Hash
|
||||
NonceLen: Add<KE::PkLen>,
|
||||
Sum<NonceLen, KE::PkLen>: ArrayLength + Add<SIG::SignatureLen>,
|
||||
Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>: ArrayLength + Add<OutputSize<KEH>>,
|
||||
Sum<Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>, OutputSize<KEH>>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>, OutputSize<KEH>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.server_nonce
|
||||
.cat(self.server_e_pk.serialize())
|
||||
.cat(SIG::serialize_signature(&self.signature))
|
||||
.cat(GenericArray::from_slice(self.mac.as_slice()).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KEH: Hash> Deserialize for Ke3Message<SIG, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<KEH>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
signature: SIG::deserialize_take_signature(input)?,
|
||||
mac: input.take_array("mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KEH: Hash> Serialize for Ke3Message<SIG, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<KEH>: ArrayLength,
|
||||
// Ke2Message: Signature + Hash
|
||||
SIG::SignatureLen: Add<OutputSize<KEH>>,
|
||||
Sum<SIG::SignatureLen, OutputSize<KEH>>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<SIG::SignatureLen, OutputSize<KEH>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
SIG::serialize_signature(&self.signature)
|
||||
.cat(GenericArray::from_slice(self.mac.as_slice()).clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! PureEdDSA implementation for [`SigmaI`](crate::SigmaI). Currently only
|
||||
//! supports [`Ed25519`](crate::Ed25519).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use self::implementation::PureEddsaImpl;
|
||||
use super::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::sigma_i::CachedMessage;
|
||||
|
||||
/// PureEdDSA for [`SigmaI`](crate::SigmaI).
|
||||
///
|
||||
/// The ["verification state"](Self::VerifyState) is a [`CachedMessage`],
|
||||
/// created by calling [`Message::to_cached()`].
|
||||
pub struct PureEddsa<G>(PhantomData<G>);
|
||||
|
||||
impl<G: PureEddsaImpl> SignatureProtocol for PureEddsa<G> {
|
||||
type Group = G;
|
||||
type Signature = G::Signature;
|
||||
type SignatureLen = G::SignatureLen;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = CachedMessage<CS, KE>;
|
||||
|
||||
fn sign<'a, R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
|
||||
sk: &G::Sk,
|
||||
_: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
G::sign(sk, message)
|
||||
}
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &G::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
G::verify(pk, message_builder, state, signature)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
G::serialize_signature(signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
G::deserialize_take_signature(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) mod implementation {
|
||||
use generic_array::ArrayLength;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub trait PureEddsaImpl: Group {
|
||||
type Signature: Clone + Zeroize;
|
||||
type SignatureLen: ArrayLength;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, CachedMessage<CS, KE>);
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: CachedMessage<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError>;
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature)
|
||||
-> GenericArray<u8, Self::SignatureLen>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::{Output, OutputSizeUser};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::{Deserialize, Serialize};
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Pre-hash of the message to be verified.
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[allow(dead_code)]
|
||||
pub struct PreHash<H: OutputSizeUser>(pub Output<H>);
|
||||
|
||||
impl<H: OutputSizeUser> Deserialize for PreHash<H>
|
||||
where
|
||||
H::OutputSize: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self(input.take_array("pre-hash")?.into_ha0_4()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: OutputSizeUser> Serialize for PreHash<H>
|
||||
where
|
||||
H::OutputSize: ArrayLength,
|
||||
{
|
||||
type Len = H::OutputSize;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
GenericArray::from_slice(self.0.as_slice()).clone()
|
||||
}
|
||||
}
|
||||
@@ -1,64 +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::{
|
||||
errors::{PakeError, ProtocolError},
|
||||
group::Group,
|
||||
hash::Hash,
|
||||
keypair::Key,
|
||||
};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
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>(
|
||||
info: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
|
||||
|
||||
#[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: 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>;
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
||||
fn generate_ke3(
|
||||
l2_component: Vec<u8>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
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 ke2_message_size() -> usize;
|
||||
}
|
||||
|
||||
pub trait ToBytes {
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
}
|
||||
+383
-519
@@ -1,554 +1,418 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! An implementation of the Triple Diffie-Hellman key exchange protocol
|
||||
use crate::{
|
||||
errors::{
|
||||
utils::{check_slice_size, check_slice_size_atleast},
|
||||
InternalPakeError, PakeError, ProtocolError,
|
||||
},
|
||||
group::Group,
|
||||
hash::Hash,
|
||||
key_exchange::traits::{KeyExchange, ToBytes},
|
||||
keypair::{Key, KeyPair, SizedBytesExt},
|
||||
serialization::{serialize, tokenize},
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
|
||||
use digest::{Output, OutputSizeUser};
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
|
||||
use super::{
|
||||
Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
|
||||
SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
|
||||
SerializedIdentifiers,
|
||||
};
|
||||
use digest::{Digest, FixedOutput};
|
||||
use generic_array::{
|
||||
typenum::{Unsigned, U32},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use generic_bytes::SizedBytes;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{self, NonceLen};
|
||||
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
|
||||
use crate::keypair::{PrivateKey, PublicKey};
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::{ConcatExt, SliceExt};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const KEY_LEN: usize = 32;
|
||||
pub(crate) type NonceLen = U32;
|
||||
|
||||
static STR_3DH: &[u8] = b"3DH keys";
|
||||
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_SERVER_ENC: &[u8] = b"handshake enc";
|
||||
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
|
||||
static STR_SESSION_KEY: &[u8] = b"session secret";
|
||||
static STR_OPAQUE: &[u8] = b"OPAQUE ";
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
// ====================== //
|
||||
////////////////////////////
|
||||
|
||||
/// The Triple Diffie-Hellman key exchange implementation
|
||||
pub struct TripleDH;
|
||||
///
|
||||
/// # Remote Key
|
||||
///
|
||||
/// [`ServerLoginBuilder::data()`](crate::ServerLoginBuilder::data()) will
|
||||
/// return the client's ephemeral public key.
|
||||
///
|
||||
/// [`ServerLoginBuilder::build()`](crate::ServerLoginBuilder::build()) expects
|
||||
/// a shared secret computed through Diffie-Hellman from the servers private key
|
||||
/// and the given public key.
|
||||
pub struct TripleDh<G, H>(PhantomData<(G, H)>);
|
||||
|
||||
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>(
|
||||
info: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
|
||||
let client_e_kp = KeyPair::<G>::generate_random(rng);
|
||||
let client_nonce: GenericArray<u8, NonceLen> = {
|
||||
let mut client_nonce_bytes = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut client_nonce_bytes);
|
||||
GenericArray::clone_from_slice(&client_nonce_bytes)
|
||||
};
|
||||
|
||||
let ke1_message = KE1Message {
|
||||
client_nonce,
|
||||
info,
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
Ok((
|
||||
KE1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce,
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn generate_ke2<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
serialized_credential_request: Vec<u8>,
|
||||
l2_bytes: Vec<u8>,
|
||||
ke1_message: Self::KE1Message,
|
||||
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: GenericArray<u8, NonceLen> = {
|
||||
let mut server_nonce_bytes = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut server_nonce_bytes);
|
||||
GenericArray::clone_from_slice(&server_nonce_bytes)
|
||||
};
|
||||
|
||||
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
|
||||
TripleDHComponents {
|
||||
pk1: ke1_message.client_e_pk.clone(),
|
||||
sk1: server_e_kp.private().clone(),
|
||||
pk2: ke1_message.client_e_pk,
|
||||
sk2: server_s_sk,
|
||||
pk3: client_s_pk,
|
||||
sk3: server_e_kp.private().clone(),
|
||||
},
|
||||
&ke1_message.client_nonce,
|
||||
&server_nonce,
|
||||
&id_u,
|
||||
&id_s,
|
||||
)?;
|
||||
|
||||
// Compute encryption of e_info
|
||||
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let mut encryption_pad = vec![0u8; e_info.len()];
|
||||
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let ciphertext: Vec<u8> = encryption_pad
|
||||
.iter()
|
||||
.zip(e_info.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
|
||||
let transcript2: Vec<u8> = [
|
||||
&serialized_credential_request[..],
|
||||
&l2_bytes[..],
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public().to_arr(),
|
||||
&serialize(&ciphertext, 2),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher = D::new();
|
||||
hasher.update(&transcript2);
|
||||
let hashed_transcript_without_mac = hasher.finalize_reset();
|
||||
|
||||
let mut mac_hasher =
|
||||
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac_hasher.update(&hashed_transcript_without_mac);
|
||||
let mac = mac_hasher.finalize().into_bytes();
|
||||
|
||||
hasher.update(&transcript2);
|
||||
hasher.update(&mac);
|
||||
let hashed_transcript = hasher.finalize();
|
||||
|
||||
Ok((
|
||||
ke1_message.info,
|
||||
KE2State {
|
||||
km3,
|
||||
hashed_transcript,
|
||||
session_key,
|
||||
},
|
||||
KE2Message {
|
||||
server_nonce,
|
||||
server_e_pk: server_e_kp.public().clone(),
|
||||
e_info: ciphertext,
|
||||
mac,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn generate_ke3(
|
||||
l2_component: Vec<u8>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
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 (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,
|
||||
sk2: ke1_state.client_e_sk.clone(),
|
||||
pk3: ke2_message.server_e_pk.clone(),
|
||||
sk3: client_s_sk,
|
||||
},
|
||||
&ke1_state.client_nonce,
|
||||
&ke2_message.server_nonce,
|
||||
&id_u,
|
||||
&id_s,
|
||||
)?;
|
||||
|
||||
let transcript: Vec<u8> = [
|
||||
&serialized_credential_request,
|
||||
&l2_component[..],
|
||||
&ke2_message.to_bytes_without_mac(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher = D::new();
|
||||
hasher.update(&transcript);
|
||||
let hashed_transcript_without_mac = hasher.finalize_reset();
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
server_mac.update(&hashed_transcript_without_mac);
|
||||
|
||||
if ke2_message.mac != server_mac.finalize().into_bytes() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
hasher.update(transcript);
|
||||
hasher.update(ke2_message.mac.to_vec());
|
||||
let hashed_transcript = hasher.finalize();
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.update(&hashed_transcript);
|
||||
|
||||
// Compute decryption of e_info
|
||||
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
|
||||
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
let plaintext: Vec<u8> = encryption_pad
|
||||
.iter()
|
||||
.zip(ke2_message.e_info.iter())
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
|
||||
Ok((
|
||||
plaintext,
|
||||
session_key.to_vec(),
|
||||
KE3Message {
|
||||
mac: client_mac.finalize().into_bytes(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn finish_ke(
|
||||
ke3_message: Self::KE3Message,
|
||||
ke2_state: &Self::KE2State,
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let mut client_mac =
|
||||
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() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ke2_state.session_key.to_vec())
|
||||
}
|
||||
|
||||
fn ke2_message_size() -> usize {
|
||||
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 {
|
||||
client_e_sk: Key,
|
||||
client_nonce: GenericArray<u8, NonceLen>,
|
||||
}
|
||||
|
||||
/// The first key exchange message
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct KE1Message {
|
||||
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(crate) info: Vec<u8>,
|
||||
pub(crate) client_e_pk: Key,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE1State {
|
||||
type Error = PakeError;
|
||||
|
||||
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: GenericArray::clone_from_slice(
|
||||
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes for KE1State {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes for KE1Message {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[
|
||||
&self.client_nonce[..],
|
||||
&serialize(&self.info, 2),
|
||||
&self.client_e_pk.to_arr(),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE1Message {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
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: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
|
||||
info,
|
||||
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
/// The server state produced after the second key exchange message
|
||||
pub struct KE2State<HashLen: ArrayLength<u8>> {
|
||||
km3: GenericArray<u8, HashLen>,
|
||||
hashed_transcript: GenericArray<u8, HashLen>,
|
||||
session_key: GenericArray<u8, HashLen>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
|
||||
pub struct Ke2State<H: OutputSizeUser> {
|
||||
pub(super) session_key: Output<H>,
|
||||
pub(super) expected_mac: Output<H>,
|
||||
}
|
||||
|
||||
/// Builder for the second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "H: serde::Deserialize<'de>, PublicKey<G>: serde::Deserialize<'de>",
|
||||
serialize = "H: serde::Serialize, PublicKey<G>: serde::Serialize",
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; H, PublicKey<G>)]
|
||||
pub struct Ke2Builder<G: Group, H: Hash>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
transcript_hasher: H,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
client_e_pk: PublicKey<G>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
server_e_pk: PublicKey<G>,
|
||||
shared_secret_1: GenericArray<u8, G::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, G::PkLen>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
pub struct KE2Message<HashLen: ArrayLength<u8>> {
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: Key,
|
||||
e_info: Vec<u8>,
|
||||
mac: GenericArray<u8, HashLen>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "G::Pk: serde::Deserialize<'de>",
|
||||
serialize = "G::Pk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
|
||||
pub struct Ke2Message<G: Group, H: Hash>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
pub(super) server_nonce: GenericArray<u8, NonceLen>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
pub(super) server_e_pk: PublicKey<G>,
|
||||
pub(super) mac: Output<H>,
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for KE2State<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.session_key[..],
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE2State<HashLen> {
|
||||
type Error = PakeError;
|
||||
|
||||
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: 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<HashLen: ArrayLength<u8>> ToBytes for KE2Message<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.to_bytes_without_mac(), &self.mac[..]].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> KE2Message<HashLen> {
|
||||
fn to_bytes_without_mac(&self) -> Vec<u8> {
|
||||
[
|
||||
&self.server_nonce[..],
|
||||
&self.server_e_pk.to_arr(),
|
||||
&serialize(&self.e_info, 2),
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
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: 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The triple of public and private components used in the 3DH computation
|
||||
struct TripleDHComponents {
|
||||
pk1: Key,
|
||||
sk1: Key,
|
||||
pk2: Key,
|
||||
sk2: Key,
|
||||
pk3: Key,
|
||||
sk3: Key,
|
||||
}
|
||||
|
||||
// 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 FixedOutput>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
GenericArray<u8, <D as FixedOutput>::OutputSize>,
|
||||
);
|
||||
|
||||
/// The third key exchange message
|
||||
pub struct KE3Message<HashLen: ArrayLength<u8>> {
|
||||
mac: GenericArray<u8, HashLen>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
|
||||
pub struct Ke3Message<H: Hash>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
pub(super) mac: Output<H>,
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.mac.to_vec()
|
||||
////////////////////////////////
|
||||
// High-level Implementations //
|
||||
// ========================== //
|
||||
////////////////////////////////
|
||||
|
||||
impl<G: Group + 'static, H: Hash> KeyExchange for TripleDh<G, H>
|
||||
where
|
||||
G::Sk: DiffieHellman<G>,
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
type Group = G;
|
||||
type Hash = H;
|
||||
|
||||
type KE1State = Ke1State<G>;
|
||||
type KE2State<CS: CipherSuite> = Ke2State<H>;
|
||||
type KE1Message = Ke1Message<G>;
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<G, H>;
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a PublicKey<G>;
|
||||
type KE2BuilderInput<CS: CipherSuite> = GenericArray<u8, G::PkLen>;
|
||||
type KE2Message = Ke2Message<G, H>;
|
||||
type KE3Message = Ke3Message<H>;
|
||||
|
||||
fn generate_ke1<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<GenerateKe1Result<Self>, ProtocolError> {
|
||||
shared::generate_ke1(rng)
|
||||
}
|
||||
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
client_s_pk: PublicKey<G>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
|
||||
let shared::Ke2BuilderCommon {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk,
|
||||
server_e_pk,
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
} = shared::ke2_builder_common::<G, H, CS, R>(
|
||||
rng,
|
||||
credential_request,
|
||||
ke1_message,
|
||||
credential_response,
|
||||
client_s_pk,
|
||||
identifiers,
|
||||
context,
|
||||
)?;
|
||||
|
||||
Ok(Ke2Builder {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk,
|
||||
server_e_pk,
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS> {
|
||||
&builder.client_e_pk
|
||||
}
|
||||
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
_: &mut R,
|
||||
server_s_sk: &PrivateKey<G>,
|
||||
) -> Self::KE2BuilderInput<CS> {
|
||||
server_s_sk.ke_diffie_hellman(&builder.client_e_pk)
|
||||
}
|
||||
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
mut builder: Self::KE2Builder<'_, CS>,
|
||||
shared_secret_2: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
|
||||
let transcript_digest = builder.transcript_hasher.clone().finalize();
|
||||
let derived_keys = shared::derive_keys::<H>(
|
||||
[
|
||||
builder.shared_secret_1.as_slice(),
|
||||
&shared_secret_2,
|
||||
&builder.shared_secret_3,
|
||||
]
|
||||
.into_iter(),
|
||||
&transcript_digest,
|
||||
)?;
|
||||
|
||||
let (mac, expected_mac) = shared::compute_ke2_macs(
|
||||
&mut builder.transcript_hasher,
|
||||
&derived_keys,
|
||||
&transcript_digest,
|
||||
)?;
|
||||
|
||||
Ok(GenerateKe2Result {
|
||||
state: Ke2State {
|
||||
session_key: derived_keys.session_key,
|
||||
expected_mac,
|
||||
},
|
||||
message: Ke2Message {
|
||||
server_nonce: builder.server_nonce,
|
||||
server_e_pk: builder.server_e_pk.clone(),
|
||||
mac,
|
||||
},
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
km2: derived_keys.km2,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
_: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
ke1_state: &Self::KE1State,
|
||||
ke2_message: Self::KE2Message,
|
||||
server_s_pk: PublicKey<G>,
|
||||
client_s_sk: PrivateKey<G>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
|
||||
let mut transcript_hasher = shared::transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message.to_iter(),
|
||||
&credential_response,
|
||||
ke2_message.server_nonce,
|
||||
&ke2_message.server_e_pk.serialize(),
|
||||
);
|
||||
|
||||
let shared_secret_1 = ke1_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&ke2_message.server_e_pk);
|
||||
let shared_secret_2 = ke1_state.client_e_sk.ke_diffie_hellman(&server_s_pk);
|
||||
let shared_secret_3 = client_s_sk.ke_diffie_hellman(&ke2_message.server_e_pk);
|
||||
|
||||
let (derived_keys, client_mac) = shared::finalize_ke3_transcript(
|
||||
&mut transcript_hasher,
|
||||
[
|
||||
shared_secret_1.as_slice(),
|
||||
shared_secret_2.as_slice(),
|
||||
shared_secret_3.as_slice(),
|
||||
]
|
||||
.into_iter(),
|
||||
&ke2_message.mac,
|
||||
)?;
|
||||
|
||||
Ok(GenerateKe3Result {
|
||||
session_key: derived_keys.session_key,
|
||||
message: Ke3Message { mac: client_mac },
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
km3: derived_keys.km3,
|
||||
})
|
||||
}
|
||||
|
||||
fn finish_ke<CS: CipherSuite>(
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
ke3_message: Self::KE3Message,
|
||||
_: Identifiers<'_>,
|
||||
_: SerializedContext<'_>,
|
||||
) -> Result<Output<H>, ProtocolError> {
|
||||
CtOption::new(
|
||||
ke2_state.session_key.clone(),
|
||||
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE3Message<HashLen> {
|
||||
type Error = PakeError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&bytes, HashLen::to_usize(), "ke3_message")?;
|
||||
////////////////////////////////////////////////
|
||||
// Trait Implementations //
|
||||
// ========================================== //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
impl<H: Hash> Deserialize for Ke2State<H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
mac: GenericArray::clone_from_slice(&checked_bytes),
|
||||
session_key: input.take_array("session key")?.into_ha0_4(),
|
||||
expected_mac: input.take_array("expected mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
impl<H: Hash> Serialize for Ke2State<H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
// Ke2State: Hash + Hash
|
||||
OutputSize<H>: Add<OutputSize<H>>,
|
||||
Sum<OutputSize<H>, OutputSize<H>>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<OutputSize<H>, OutputSize<H>>;
|
||||
|
||||
// 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,
|
||||
client_nonce: &GenericArray<u8, NonceLen>,
|
||||
server_nonce: &GenericArray<u8, NonceLen>,
|
||||
id_u: &[u8],
|
||||
id_s: &[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();
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
let sk: GenericArray<u8, OutputSize<H>> =
|
||||
GenericArray::from_slice(self.session_key.as_slice()).clone();
|
||||
let mac: GenericArray<u8, OutputSize<H>> =
|
||||
GenericArray::from_slice(self.expected_mac.as_slice()).clone();
|
||||
|
||||
let info: Vec<u8> = [
|
||||
STR_3DH,
|
||||
&serialize(&client_nonce, 2),
|
||||
&serialize(&server_nonce, 2),
|
||||
&serialize(id_u, 2),
|
||||
&serialize(id_s, 2),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
|
||||
let handshake_secret = derive_secrets::<D>(&extracted_ikm, &STR_HANDSHAKE_SECRET, &info)?;
|
||||
let session_key = derive_secrets::<D>(&extracted_ikm, &STR_SESSION_KEY, &info)?;
|
||||
|
||||
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_SERVER_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),
|
||||
))
|
||||
sk.cat(mac)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
impl<G: Group, H: Hash> Deserialize for Ke2Message<G, H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
server_nonce: input.take_array("server nonce")?,
|
||||
server_e_pk: PublicKey::deserialize_take(input)?,
|
||||
mac: input.take_array("mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
impl<H: Hash, G: Group> Serialize for Ke2Message<G, H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
// Ke2Message: (Nonce + KePk) + Hash
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Sum<NonceLen, G::PkLen>: ArrayLength + Add<OutputSize<H>>,
|
||||
Sum<Sum<NonceLen, G::PkLen>, OutputSize<H>>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<Sum<NonceLen, G::PkLen>, OutputSize<H>>;
|
||||
|
||||
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 serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.server_nonce
|
||||
.cat(self.server_e_pk.serialize())
|
||||
.cat(GenericArray::from_slice(self.mac.as_slice()).clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_secrets<D: Hash>(
|
||||
hkdf: &Hkdf<D>,
|
||||
label: &[u8],
|
||||
transcript: &[u8],
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let hashed_transcript = D::digest(transcript);
|
||||
hkdf_expand_label_extracted::<D>(
|
||||
hkdf,
|
||||
label,
|
||||
&hashed_transcript,
|
||||
<D as Digest>::OutputSize::to_usize(),
|
||||
)
|
||||
impl<H: Hash> Deserialize for Ke3Message<H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
mac: bytes.take_array("mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hash> Serialize for Ke3Message<H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
type Len = OutputSize<H>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
GenericArray::from_slice(self.mac.as_slice()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,706 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! TripleDH-KEM is a variant of the OPAQUE Triple Diffie-Hellman handshake in
|
||||
//! which the client supplies a KEM public key in KE1 and the server performs a
|
||||
//! KEM encapsulation in KE2 instead of relying solely on the final Diffie-
|
||||
//! Hellman hop. The server bundles the KEM ciphertext alongside the classic
|
||||
//! `TripleDH` payload, both parties absorb the ciphertext into the transcript
|
||||
//! and mix the encapsulated shared secret with the three Diffie-Hellman
|
||||
//! products when deriving handshake keys, and the client decapsulates during
|
||||
//! KE3 to recover that shared secret before validating the server MAC. This
|
||||
//! file contains the data model and trait glue that layer
|
||||
//! the generic `ml-kem` abstractions into the existing OPAQUE key-exchange
|
||||
//! pipeline.
|
||||
|
||||
use core::fmt::Debug;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::Output;
|
||||
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
|
||||
use generic_array::typenum::{Cmp, IsLess, Le, NonZero, Sum, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hybrid_array::ArraySize;
|
||||
use ml_kem::kem::{
|
||||
Ciphertext as MlKemCiphertext, Decapsulate, Encapsulate, Kem as MlKemTrait, KeyExport, KeyInit,
|
||||
KeySizeUser, TryKeyInit,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
|
||||
use super::shared::{self, Ke1Message, Ke1State, NonceLen};
|
||||
use super::{
|
||||
Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
|
||||
SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
|
||||
SerializedIdentifiers,
|
||||
};
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::keypair::{PrivateKey, PublicKey};
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::{ConcatExt, SliceExt};
|
||||
|
||||
/// Adapter trait that augments the `ml-kem` core traits with the metadata
|
||||
/// required by OPAQUE (e.g. fixed lengths and serialization hooks).
|
||||
pub trait KemCoreWrapper {
|
||||
/// Public key type used for encapsulation operations.
|
||||
type EncapsulationKey: Clone;
|
||||
|
||||
/// Secret key type used for decapsulation operations.
|
||||
type DecapsulationKey: Clone + zeroize::ZeroizeOnDrop;
|
||||
|
||||
/// Length (in bytes) of the serialized public key.
|
||||
type EncapsulationKeyLen: ArrayLength + ArraySize;
|
||||
/// Length (in bytes) of the serialized secret key.
|
||||
type DecapsulationKeyLen: ArrayLength + ArraySize;
|
||||
/// Length (in bytes) of the encapsulated ciphertext.
|
||||
type CiphertextLen: ArrayLength + ArraySize;
|
||||
/// Length (in bytes) of the shared secret output by the KEM.
|
||||
type SharedSecretLen: ArrayLength + ArraySize;
|
||||
|
||||
/// Generates a fresh KEM key pair.
|
||||
fn generate<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError>;
|
||||
|
||||
/// Serializes the public encapsulation key.
|
||||
fn serialize_encapsulation_key(
|
||||
key: &Self::EncapsulationKey,
|
||||
) -> GenericArray<u8, Self::EncapsulationKeyLen>;
|
||||
|
||||
/// Deserializes the public encapsulation key, advancing the input slice.
|
||||
fn deserialize_encapsulation_key(
|
||||
input: &mut &[u8],
|
||||
) -> Result<Self::EncapsulationKey, ProtocolError>;
|
||||
|
||||
/// Serializes the secret decapsulation key.
|
||||
fn serialize_decapsulation_key(
|
||||
key: &Self::DecapsulationKey,
|
||||
) -> GenericArray<u8, Self::DecapsulationKeyLen>;
|
||||
|
||||
/// Deserializes the secret decapsulation key, advancing the input slice.
|
||||
fn deserialize_decapsulation_key(
|
||||
input: &mut &[u8],
|
||||
) -> Result<Self::DecapsulationKey, ProtocolError>;
|
||||
|
||||
/// Encapsulates to the given public key, returning the ciphertext and
|
||||
/// shared secret.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn encapsulate<R: Rng + CryptoRng>(
|
||||
key: &Self::EncapsulationKey,
|
||||
rng: &mut R,
|
||||
) -> Result<
|
||||
(
|
||||
GenericArray<u8, Self::CiphertextLen>,
|
||||
GenericArray<u8, Self::SharedSecretLen>,
|
||||
),
|
||||
ProtocolError,
|
||||
>;
|
||||
|
||||
/// Decapsulates the shared secret from the provided ciphertext.
|
||||
fn decapsulate(
|
||||
key: &Self::DecapsulationKey,
|
||||
encapsulated_key: &GenericArray<u8, Self::CiphertextLen>,
|
||||
) -> Result<GenericArray<u8, Self::SharedSecretLen>, ProtocolError>;
|
||||
}
|
||||
|
||||
/// Adapter to bridge `rand 0.8` (`rand_core 0.6`) RNGs to `rand_core 0.10`
|
||||
/// which is required by `ml-kem 0.3.x`.
|
||||
struct RngCompat<'a, R>(&'a mut R);
|
||||
|
||||
impl<R: Rng> rand::rand_core::TryRng for RngCompat<'_, R> {
|
||||
type Error = core::convert::Infallible;
|
||||
|
||||
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
|
||||
Ok(self.0.next_u32())
|
||||
}
|
||||
|
||||
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
|
||||
Ok(self.0.next_u64())
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
|
||||
self.0.fill_bytes(dst);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Rng + CryptoRng> rand::rand_core::TryCryptoRng for RngCompat<'_, R> {}
|
||||
|
||||
type RcEncapsulationKeyLen<K> = <<K as MlKemTrait>::EncapsulationKey as KeySizeUser>::KeySize;
|
||||
type RcDecapsulationKeyLen<K> = <<K as MlKemTrait>::DecapsulationKey as KeySizeUser>::KeySize;
|
||||
type RcCiphertextLen<K> = <K as MlKemTrait>::CiphertextSize;
|
||||
type RcSharedSecretLen<K> = <K as MlKemTrait>::SharedKeySize;
|
||||
|
||||
impl<K> KemCoreWrapper for K
|
||||
where
|
||||
K: MlKemTrait,
|
||||
K::EncapsulationKey: Encapsulate<Kem = K> + KeyExport + TryKeyInit + Clone,
|
||||
K::DecapsulationKey:
|
||||
Decapsulate<Kem = K> + KeyExport + KeyInit + Clone + zeroize::ZeroizeOnDrop,
|
||||
RcEncapsulationKeyLen<K>: ArrayLength + ArraySize,
|
||||
RcDecapsulationKeyLen<K>: ArrayLength + ArraySize,
|
||||
RcCiphertextLen<K>: ArrayLength + ArraySize,
|
||||
RcSharedSecretLen<K>: ArrayLength + ArraySize,
|
||||
{
|
||||
type EncapsulationKey = K::EncapsulationKey;
|
||||
type DecapsulationKey = K::DecapsulationKey;
|
||||
type EncapsulationKeyLen = RcEncapsulationKeyLen<K>;
|
||||
type DecapsulationKeyLen = RcDecapsulationKeyLen<K>;
|
||||
type CiphertextLen = RcCiphertextLen<K>;
|
||||
type SharedSecretLen = RcSharedSecretLen<K>;
|
||||
|
||||
fn generate<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError> {
|
||||
Ok(K::generate_keypair_from_rng(&mut RngCompat(rng)))
|
||||
}
|
||||
|
||||
fn serialize_encapsulation_key(
|
||||
key: &Self::EncapsulationKey,
|
||||
) -> GenericArray<u8, Self::EncapsulationKeyLen> {
|
||||
GenericArray::from_slice(key.to_bytes().as_slice()).clone()
|
||||
}
|
||||
|
||||
fn deserialize_encapsulation_key(
|
||||
input: &mut &[u8],
|
||||
) -> Result<Self::EncapsulationKey, ProtocolError> {
|
||||
let bytes: GenericArray<u8, RcEncapsulationKeyLen<K>> =
|
||||
input.take_array("kem encapsulation key")?;
|
||||
let key = ml_kem::array::Array::try_from(bytes.as_slice())
|
||||
.map_err(|_| ProtocolError::SerializationError)?;
|
||||
TryKeyInit::new(&key).map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn serialize_decapsulation_key(
|
||||
key: &Self::DecapsulationKey,
|
||||
) -> GenericArray<u8, Self::DecapsulationKeyLen> {
|
||||
GenericArray::from_slice(key.to_bytes().as_slice()).clone()
|
||||
}
|
||||
|
||||
fn deserialize_decapsulation_key(
|
||||
input: &mut &[u8],
|
||||
) -> Result<Self::DecapsulationKey, ProtocolError> {
|
||||
let bytes: GenericArray<u8, RcDecapsulationKeyLen<K>> =
|
||||
input.take_array("kem decapsulation key")?;
|
||||
let seed = ml_kem::array::Array::try_from(bytes.as_slice())
|
||||
.map_err(|_| ProtocolError::SerializationError)?;
|
||||
Ok(KeyInit::new(&seed))
|
||||
}
|
||||
|
||||
fn encapsulate<R: Rng + CryptoRng>(
|
||||
key: &Self::EncapsulationKey,
|
||||
rng: &mut R,
|
||||
) -> Result<
|
||||
(
|
||||
GenericArray<u8, Self::CiphertextLen>,
|
||||
GenericArray<u8, Self::SharedSecretLen>,
|
||||
),
|
||||
ProtocolError,
|
||||
> {
|
||||
let (ciphertext, shared) = key.encapsulate_with_rng(&mut RngCompat(rng));
|
||||
Ok((
|
||||
GenericArray::from_slice(ciphertext.as_slice()).clone(),
|
||||
GenericArray::from_slice(shared.as_slice()).clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn decapsulate(
|
||||
key: &Self::DecapsulationKey,
|
||||
encapsulated_key: &GenericArray<u8, Self::CiphertextLen>,
|
||||
) -> Result<GenericArray<u8, Self::SharedSecretLen>, ProtocolError> {
|
||||
let ciphertext = MlKemCiphertext::<K>::try_from(encapsulated_key.as_slice())
|
||||
.map_err(|_| ProtocolError::SerializationError)?;
|
||||
let shared = key.decapsulate(&ciphertext);
|
||||
Ok(GenericArray::from_slice(shared.as_slice()).clone())
|
||||
}
|
||||
}
|
||||
/// Triple Diffie-Hellman-style key exchange that offloads the second hop to a
|
||||
/// generic KEM.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TripleDhKem<G, H, K>(PhantomData<(G, H, K)>);
|
||||
|
||||
/// Client state combining the classic `TripleDH` state with a KEM secret key.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "Ke1State<G>: serde::Deserialize<'de>, K::DecapsulationKey: \
|
||||
serde::Deserialize<'de>",
|
||||
serialize = "Ke1State<G>: serde::Serialize, K::DecapsulationKey: serde::Serialize",
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; Ke1State<G>, K::DecapsulationKey)]
|
||||
pub struct KemKe1State<G: Group, K: KemCoreWrapper> {
|
||||
dh_state: Ke1State<G>,
|
||||
kem_decapsulation_key: K::DecapsulationKey,
|
||||
}
|
||||
|
||||
/// Client message including the ephemeral Diffie-Hellman component alongside a
|
||||
/// serialized KEM public key.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "Ke1Message<G>: serde::Deserialize<'de>",
|
||||
serialize = "Ke1Message<G>: serde::Serialize",
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; Ke1Message<G>)]
|
||||
pub struct KemKe1Message<G: Group, K: KemCoreWrapper> {
|
||||
dh_message: Ke1Message<G>,
|
||||
kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
|
||||
}
|
||||
|
||||
/// Server state mirrors the `TripleDH` state and carries the client’s KEM
|
||||
/// public key for later use.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct KemKe2State<K: KemCoreWrapper, H: Hash>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
base_state: super::tripledh::Ke2State<H>,
|
||||
kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
|
||||
server_kem_ciphertext: GenericArray<u8, K::CiphertextLen>,
|
||||
}
|
||||
|
||||
/// Server builder placeholder capturing the data needed to finish the KEM
|
||||
/// exchange.
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
pub struct KemKe2Builder<G: Group, H: Hash, K: KemCoreWrapper>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
transcript_hasher: H,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
client_e_pk: PublicKey<G>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
server_e_pk: PublicKey<G>,
|
||||
shared_secret_1: GenericArray<u8, G::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, G::PkLen>,
|
||||
#[derive_where(skip(Zeroize))]
|
||||
kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
|
||||
kem_ciphertext: GenericArray<u8, K::CiphertextLen>,
|
||||
kem_shared_secret: GenericArray<u8, K::SharedSecretLen>,
|
||||
}
|
||||
|
||||
/// Server message bundles the `TripleDH` payload with the KEM encapsulation.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "super::tripledh::Ke2Message<G, H>: serde::Deserialize<'de>",
|
||||
serialize = "super::tripledh::Ke2Message<G, H>: serde::Serialize",
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; super::tripledh::Ke2Message<G, H>)]
|
||||
pub struct KemKe2Message<G: Group, H: Hash, K: KemCoreWrapper>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
dh_message: super::tripledh::Ke2Message<G, H>,
|
||||
kem_ciphertext: GenericArray<u8, K::CiphertextLen>,
|
||||
}
|
||||
|
||||
/// Third message remains the same as `TripleDH`.
|
||||
pub type KemKe3Message<H> = super::tripledh::Ke3Message<H>;
|
||||
|
||||
impl<G, H, K> KeyExchange for TripleDhKem<G, H, K>
|
||||
where
|
||||
G: Group + 'static,
|
||||
G::Sk: shared::DiffieHellman<G>,
|
||||
H: Hash,
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
K: KemCoreWrapper,
|
||||
NonceLen: Add<K::EncapsulationKeyLen>,
|
||||
Sum<NonceLen, K::EncapsulationKeyLen>: ArrayLength,
|
||||
{
|
||||
type Group = G;
|
||||
type Hash = H;
|
||||
|
||||
type KE1State = KemKe1State<G, K>;
|
||||
type KE2State<CS: CipherSuite> = KemKe2State<K, H>;
|
||||
type KE1Message = KemKe1Message<G, K>;
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = KemKe2Builder<G, H, K>;
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite> = (
|
||||
&'a PublicKey<G>,
|
||||
&'a GenericArray<u8, K::EncapsulationKeyLen>,
|
||||
);
|
||||
type KE2BuilderInput<CS: CipherSuite> = GenericArray<u8, G::PkLen>;
|
||||
type KE2Message = KemKe2Message<G, H, K>;
|
||||
type KE3Message = KemKe3Message<H>;
|
||||
|
||||
fn generate_ke1<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<GenerateKe1Result<Self>, ProtocolError> {
|
||||
let base = super::tripledh::TripleDh::<G, H>::generate_ke1(rng)?;
|
||||
let (kem_secret, kem_public) = K::generate(rng)?;
|
||||
let kem_encapsulation_key = K::serialize_encapsulation_key(&kem_public);
|
||||
|
||||
Ok(GenerateKe1Result {
|
||||
state: KemKe1State {
|
||||
dh_state: base.state,
|
||||
kem_decapsulation_key: kem_secret,
|
||||
},
|
||||
message: KemKe1Message {
|
||||
dh_message: base.message,
|
||||
kem_encapsulation_key,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
client_s_pk: PublicKey<G>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
|
||||
let shared::Ke2BuilderCommon {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk,
|
||||
server_e_pk,
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
} = shared::ke2_builder_common::<G, H, CS, R>(
|
||||
rng,
|
||||
credential_request,
|
||||
ke1_message.dh_message.clone(),
|
||||
credential_response,
|
||||
client_s_pk,
|
||||
identifiers,
|
||||
context,
|
||||
)?;
|
||||
|
||||
let mut kem_bytes_slice: &[u8] = ke1_message.kem_encapsulation_key.as_slice();
|
||||
let encapsulation_key = K::deserialize_encapsulation_key(&mut kem_bytes_slice)?;
|
||||
let (kem_ciphertext, kem_shared_secret) = K::encapsulate(&encapsulation_key, rng)?;
|
||||
|
||||
let mut transcript_hasher = transcript_hasher;
|
||||
digest::Digest::update(
|
||||
&mut transcript_hasher,
|
||||
ke1_message.kem_encapsulation_key.as_slice(),
|
||||
);
|
||||
digest::Digest::update(&mut transcript_hasher, kem_ciphertext.as_slice());
|
||||
|
||||
Ok(KemKe2Builder {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk,
|
||||
server_e_pk,
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
kem_encapsulation_key: ke1_message.kem_encapsulation_key.clone(),
|
||||
kem_ciphertext,
|
||||
kem_shared_secret,
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS> {
|
||||
(&builder.client_e_pk, &builder.kem_encapsulation_key)
|
||||
}
|
||||
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
_: &mut R,
|
||||
server_s_sk: &PrivateKey<G>,
|
||||
) -> Self::KE2BuilderInput<CS> {
|
||||
server_s_sk.ke_diffie_hellman(&builder.client_e_pk)
|
||||
}
|
||||
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
mut builder: Self::KE2Builder<'_, CS>,
|
||||
shared_secret_2: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
|
||||
let transcript_digest = builder.transcript_hasher.clone().finalize();
|
||||
let derived_keys = shared::derive_keys::<H>(
|
||||
[
|
||||
builder.shared_secret_1.as_slice(),
|
||||
shared_secret_2.as_slice(),
|
||||
builder.shared_secret_3.as_slice(),
|
||||
builder.kem_shared_secret.as_slice(),
|
||||
]
|
||||
.into_iter(),
|
||||
&transcript_digest,
|
||||
)?;
|
||||
|
||||
let (mac, expected_mac) = shared::compute_ke2_macs(
|
||||
&mut builder.transcript_hasher,
|
||||
&derived_keys,
|
||||
&transcript_digest,
|
||||
)?;
|
||||
|
||||
Ok(GenerateKe2Result {
|
||||
state: KemKe2State {
|
||||
base_state: super::tripledh::Ke2State {
|
||||
session_key: derived_keys.session_key.clone(),
|
||||
expected_mac,
|
||||
},
|
||||
kem_encapsulation_key: builder.kem_encapsulation_key.clone(),
|
||||
server_kem_ciphertext: builder.kem_ciphertext.clone(),
|
||||
},
|
||||
message: KemKe2Message {
|
||||
dh_message: super::tripledh::Ke2Message {
|
||||
server_nonce: builder.server_nonce,
|
||||
server_e_pk: builder.server_e_pk.clone(),
|
||||
mac,
|
||||
},
|
||||
kem_ciphertext: builder.kem_ciphertext.clone(),
|
||||
},
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
km2: derived_keys.km2,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
|
||||
_rng: &mut R,
|
||||
credential_request: SerializedCredentialRequest<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: SerializedCredentialResponse<CS>,
|
||||
ke1_state: &Self::KE1State,
|
||||
ke2_message: Self::KE2Message,
|
||||
server_s_pk: PublicKey<G>,
|
||||
client_s_sk: PrivateKey<G>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
|
||||
let mut transcript_hasher = shared::transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message.dh_message.to_iter(),
|
||||
&credential_response,
|
||||
ke2_message.dh_message.server_nonce,
|
||||
&ke2_message.dh_message.server_e_pk.serialize(),
|
||||
);
|
||||
digest::Digest::update(
|
||||
&mut transcript_hasher,
|
||||
ke1_message.kem_encapsulation_key.as_slice(),
|
||||
);
|
||||
digest::Digest::update(
|
||||
&mut transcript_hasher,
|
||||
ke2_message.kem_ciphertext.as_slice(),
|
||||
);
|
||||
|
||||
let shared_secret_1 = ke1_state
|
||||
.dh_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&ke2_message.dh_message.server_e_pk);
|
||||
let shared_secret_2 = ke1_state
|
||||
.dh_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&server_s_pk);
|
||||
let shared_secret_3 = client_s_sk.ke_diffie_hellman(&ke2_message.dh_message.server_e_pk);
|
||||
let kem_shared_secret = K::decapsulate(
|
||||
&ke1_state.kem_decapsulation_key,
|
||||
&ke2_message.kem_ciphertext,
|
||||
)?;
|
||||
|
||||
let (derived_keys, client_mac) = shared::finalize_ke3_transcript(
|
||||
&mut transcript_hasher,
|
||||
[
|
||||
shared_secret_1.as_slice(),
|
||||
shared_secret_2.as_slice(),
|
||||
shared_secret_3.as_slice(),
|
||||
kem_shared_secret.as_slice(),
|
||||
]
|
||||
.into_iter(),
|
||||
&ke2_message.dh_message.mac,
|
||||
)?;
|
||||
|
||||
Ok(GenerateKe3Result {
|
||||
session_key: derived_keys.session_key,
|
||||
message: super::tripledh::Ke3Message { mac: client_mac },
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
km3: derived_keys.km3,
|
||||
})
|
||||
}
|
||||
|
||||
fn finish_ke<CS: CipherSuite>(
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
ke3_message: Self::KE3Message,
|
||||
_identifiers: Identifiers<'_>,
|
||||
_context: SerializedContext<'_>,
|
||||
) -> Result<Output<Self::Hash>, ProtocolError> {
|
||||
CtOption::new(
|
||||
ke2_state.base_state.session_key.clone(),
|
||||
ke2_state.base_state.expected_mac.ct_eq(&ke3_message.mac),
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialization logic will be implemented once the concrete KEM wiring is in
|
||||
/// place.
|
||||
impl<G: Group, K: KemCoreWrapper> Deserialize for KemKe1State<G, K> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
dh_state: Ke1State::<G>::deserialize_take(input)?,
|
||||
kem_decapsulation_key: K::deserialize_decapsulation_key(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group, K: KemCoreWrapper> Serialize for KemKe1State<G, K>
|
||||
where
|
||||
Ke1State<G>: Serialize,
|
||||
<Ke1State<G> as Serialize>::Len: Add<K::DecapsulationKeyLen>,
|
||||
Sum<<Ke1State<G> as Serialize>::Len, K::DecapsulationKeyLen>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<<Ke1State<G> as Serialize>::Len, K::DecapsulationKeyLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.dh_state
|
||||
.serialize()
|
||||
.cat(K::serialize_decapsulation_key(&self.kem_decapsulation_key))
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group, K: KemCoreWrapper> Deserialize for KemKe1Message<G, K> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
dh_message: Ke1Message::<G>::deserialize_take(input)?,
|
||||
kem_encapsulation_key: input.take_array("kem encapsulation key")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group, K: KemCoreWrapper> Serialize for KemKe1Message<G, K>
|
||||
where
|
||||
Ke1Message<G>: Serialize,
|
||||
<Ke1Message<G> as Serialize>::Len: Add<K::EncapsulationKeyLen>,
|
||||
Sum<<Ke1Message<G> as Serialize>::Len, K::EncapsulationKeyLen>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<<Ke1Message<G> as Serialize>::Len, K::EncapsulationKeyLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.dh_message
|
||||
.serialize()
|
||||
.cat(self.kem_encapsulation_key.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: KemCoreWrapper, H: Hash> Deserialize for KemKe2State<K, H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
base_state: super::tripledh::Ke2State::<H>::deserialize_take(input)?,
|
||||
kem_encapsulation_key: input.take_array("kem encapsulation key")?,
|
||||
server_kem_ciphertext: input.take_array("kem ciphertext")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: KemCoreWrapper, H: Hash> Serialize for KemKe2State<K, H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
super::tripledh::Ke2State<H>: Serialize,
|
||||
<super::tripledh::Ke2State<H> as Serialize>::Len: Add<K::EncapsulationKeyLen>,
|
||||
Sum<<super::tripledh::Ke2State<H> as Serialize>::Len, K::EncapsulationKeyLen>:
|
||||
ArrayLength + Add<K::CiphertextLen>,
|
||||
Sum<
|
||||
Sum<<super::tripledh::Ke2State<H> as Serialize>::Len, K::EncapsulationKeyLen>,
|
||||
K::CiphertextLen,
|
||||
>: ArrayLength,
|
||||
{
|
||||
type Len = Sum<
|
||||
Sum<<super::tripledh::Ke2State<H> as Serialize>::Len, K::EncapsulationKeyLen>,
|
||||
K::CiphertextLen,
|
||||
>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.base_state
|
||||
.serialize()
|
||||
.cat(self.kem_encapsulation_key.clone())
|
||||
.cat(self.server_kem_ciphertext.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group, H: Hash, K: KemCoreWrapper> Deserialize for KemKe2Message<G, H, K>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
dh_message: super::tripledh::Ke2Message::<G, H>::deserialize_take(input)?,
|
||||
kem_ciphertext: input.take_array("kem ciphertext")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group, H: Hash, K: KemCoreWrapper> Serialize for KemKe2Message<G, H, K>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
|
||||
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
|
||||
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
|
||||
OutputSize<H>: ArrayLength,
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Sum<NonceLen, G::PkLen>: ArrayLength + Add<OutputSize<H>>,
|
||||
Sum<Sum<NonceLen, G::PkLen>, OutputSize<H>>: ArrayLength,
|
||||
super::tripledh::Ke2Message<G, H>: Serialize,
|
||||
<super::tripledh::Ke2Message<G, H> as Serialize>::Len: Add<K::CiphertextLen>,
|
||||
<<super::tripledh::Ke2Message<G, H> as Serialize>::Len as Add<K::CiphertextLen>>::Output:
|
||||
ArrayLength,
|
||||
{
|
||||
type Len = Sum<<super::tripledh::Ke2Message<G, H> as Serialize>::Len, K::CiphertextLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.dh_message.serialize().cat(self.kem_ciphertext.clone())
|
||||
}
|
||||
}
|
||||
+474
-144
@@ -1,186 +1,516 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Contains the keypair types that must be supplied for the OPAQUE API
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::group::Group;
|
||||
use generic_array::{typenum::U32, GenericArray};
|
||||
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
|
||||
use generic_bytes_derive::TryFromForSizedBytes;
|
||||
#[cfg(test)]
|
||||
use proptest::prelude::*;
|
||||
#[cfg(test)]
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
// Pub(crate) convenience extension trait of SizedBytes for our purposes
|
||||
pub(crate) trait SizedBytesExt: SizedBytes {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, TryFromSizedBytesError> {
|
||||
<Self as SizedBytes>::from_arr(GenericArray::from_slice(bytes))
|
||||
}
|
||||
}
|
||||
use derive_where::derive_where;
|
||||
use digest::{Output, OutputSizeUser};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
// blanket implementation
|
||||
impl<T> SizedBytesExt for T where T: SizedBytes {}
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::key_exchange::sigma_i::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// A Keypair trait with public-private verification
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct KeyPair<G> {
|
||||
pk: Key,
|
||||
sk: Key,
|
||||
_g: PhantomData<G>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "G::Pk: serde::Deserialize<'de>, SK: serde::Deserialize<'de>",
|
||||
serialize = "G::Pk: serde::Serialize, SK: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk, SK)]
|
||||
pub struct KeyPair<G: Group, SK: Clone = PrivateKey<G>> {
|
||||
pk: PublicKey<G>,
|
||||
sk: SK,
|
||||
}
|
||||
|
||||
impl<G: Group> KeyPair<G> {
|
||||
impl<G: Group, SK: Clone> KeyPair<G, SK> {
|
||||
/// Creates a new [`KeyPair`] from the given keys.
|
||||
pub fn new(sk: SK, pk: PublicKey<G>) -> Self {
|
||||
Self { pk, sk }
|
||||
}
|
||||
|
||||
/// The public key component
|
||||
pub fn public(&self) -> &Key {
|
||||
pub fn public(&self) -> &PublicKey<G> {
|
||||
&self.pk
|
||||
}
|
||||
|
||||
/// The private key component
|
||||
pub fn private(&self) -> &Key {
|
||||
pub fn private(&self) -> &SK {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
/// A constructor that receives public and private key independently as
|
||||
/// bytes
|
||||
pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
|
||||
Ok(Self {
|
||||
pk: public,
|
||||
sk: private,
|
||||
_g: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generating a random key pair given a cryptographic rng
|
||||
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()
|
||||
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
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> KeyPair<G> {
|
||||
pub(crate) fn random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
|
||||
let sk = G::random_sk(rng);
|
||||
let pk = G::public_key(&sk);
|
||||
Self {
|
||||
pk: PublicKey(pk),
|
||||
sk: PrivateKey(sk),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generating a random key pair given a cryptographic rng
|
||||
pub(crate) fn derive_random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
|
||||
let mut scalar_bytes = GenericArray::<_, <G as Group>::SkLen>::default();
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let sk = G::derive_scalar(scalar_bytes).unwrap();
|
||||
let pk = G::public_key(&sk);
|
||||
Self {
|
||||
pk: PublicKey(pk),
|
||||
sk: PrivateKey(sk),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around a Key to enforce that it's a private one.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "G::Sk: serde::Deserialize<'de>",
|
||||
serialize = "G::Sk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)]
|
||||
pub struct PrivateKey<G: Group>(G::Sk);
|
||||
|
||||
impl<G: Group> PrivateKey<G> {
|
||||
pub(crate) fn new(key: G::Sk) -> Self {
|
||||
Self(key)
|
||||
}
|
||||
|
||||
/// Returns public key from private key
|
||||
pub fn public_key(&self) -> PublicKey<G> {
|
||||
PublicKey(G::public_key(&self.0))
|
||||
}
|
||||
|
||||
/// Serializes this private key to a fixed-length byte array.
|
||||
pub fn serialize(&self) -> GenericArray<u8, G::SkLen> {
|
||||
G::serialize_sk(&self.0)
|
||||
}
|
||||
|
||||
/// Creates a [`PrivateKey`] from the given bytes.
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::deserialize_take(&mut input)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
G::deserialize_take_sk(input).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> PrivateKey<G>
|
||||
where
|
||||
G::Sk: DiffieHellman<G>,
|
||||
{
|
||||
/// Diffie-Hellman key exchange implementation
|
||||
pub(crate) fn ke_diffie_hellman(&self, pk: &PublicKey<G>) -> GenericArray<u8, G::PkLen> {
|
||||
self.0.diffie_hellman(&pk.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> PrivateKey<G> {
|
||||
/// Private-key signing implementation
|
||||
pub(crate) fn sign<
|
||||
R: CryptoRng + Rng,
|
||||
CS: CipherSuite,
|
||||
SIG: SignatureProtocol<Group = G>,
|
||||
KE: Group,
|
||||
>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (SIG::Signature, SIG::VerifyState<CS, KE>) {
|
||||
SIG::sign(&self.0, rng, message)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to facilitate
|
||||
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
|
||||
pub trait PrivateKeySerialization<G: Group>: Clone {
|
||||
/// Custom error type that can be passed down to `ProtocolError::Custom`
|
||||
type Error;
|
||||
/// Serialization size in bytes.
|
||||
type Len: ArrayLength;
|
||||
|
||||
/// Serialization into bytes
|
||||
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len>;
|
||||
|
||||
/// Deserialization from bytes
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_key_pair(
|
||||
bytes: &mut &[u8],
|
||||
) -> Result<KeyPair<G, Self>, ProtocolError<Self::Error>>;
|
||||
}
|
||||
|
||||
impl<G: Group> PrivateKeySerialization<G> for PrivateKey<G> {
|
||||
type Error = core::convert::Infallible;
|
||||
type Len = G::SkLen;
|
||||
|
||||
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len> {
|
||||
key_pair.private().serialize()
|
||||
}
|
||||
|
||||
fn deserialize_take_key_pair(input: &mut &[u8]) -> Result<KeyPair<G, Self>, ProtocolError> {
|
||||
let sk = PrivateKey::deserialize_take(input)?;
|
||||
let pk = sk.public_key();
|
||||
|
||||
Ok(KeyPair::new(sk, pk))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around a Key to enforce that it's a public one.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "G::Pk: serde::Deserialize<'de>",
|
||||
serialize = "G::Pk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
|
||||
pub struct PublicKey<G: Group + ?Sized>(G::Pk);
|
||||
|
||||
impl<G: Group> PublicKey<G> {
|
||||
/// Convert from bytes
|
||||
pub fn deserialize(mut key_bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::deserialize_take(&mut key_bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_take(key_bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
G::deserialize_take_pk(key_bytes).map(Self)
|
||||
}
|
||||
|
||||
/// Convert to bytes
|
||||
pub fn serialize(&self) -> GenericArray<u8, G::PkLen> {
|
||||
G::serialize_pk(&self.0)
|
||||
}
|
||||
|
||||
/// Returns the inner [`Group::Pk`].
|
||||
pub fn to_group_type(&self) -> &G::Pk {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> PublicKey<G> {
|
||||
/// Public-key verifying implementation
|
||||
pub(crate) fn verify<CS: CipherSuite, SIG: SignatureProtocol<Group = G>, KE: Group>(
|
||||
&self,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: SIG::VerifyState<CS, KE>,
|
||||
signature: &SIG::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
SIG::verify(&self.0, message_builder, state, signature)
|
||||
}
|
||||
}
|
||||
|
||||
/// Default OPRF seed container.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct OprfSeed<H: OutputSizeUser>(pub(crate) Output<H>);
|
||||
|
||||
/// A trait to facilitate
|
||||
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
|
||||
///
|
||||
/// Will be called with `E` being [`PrivateKeySerialization::Error`].
|
||||
pub trait OprfSeedSerialization<H, E>: Sized {
|
||||
/// Serialization size in bytes.
|
||||
type Len: ArrayLength;
|
||||
|
||||
/// Serialization into bytes
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len>;
|
||||
|
||||
/// Deserialization from bytes
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError<E>>;
|
||||
}
|
||||
|
||||
impl<H: OutputSizeUser, E> OprfSeedSerialization<H, E> for OprfSeed<H>
|
||||
where
|
||||
H::OutputSize: ArrayLength,
|
||||
{
|
||||
type Len = H::OutputSize;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
GenericArray::from_slice(self.0.as_slice()).clone()
|
||||
}
|
||||
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError<E>> {
|
||||
Ok(Self(
|
||||
input
|
||||
.take_array("OPRF seed")
|
||||
.map_err(ProtocolError::into_custom)?
|
||||
.into_ha0_4(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: Group + Debug> KeyPair<G> {
|
||||
impl<G: Group> KeyPair<G>
|
||||
where
|
||||
G::Pk: core::fmt::Debug,
|
||||
G::Sk: core::fmt::Debug,
|
||||
{
|
||||
/// Test-only strategy returning a proptest Strategy based on
|
||||
/// generate_random
|
||||
fn uniform_keypair_strategy() -> BoxedStrategy<Self> {
|
||||
// The no_shrink is because keypairs should be fixed -- shrinking would cause a different
|
||||
// keypair to be generated, which appears to not be very useful.
|
||||
/// [`Self::derive_random`]
|
||||
fn uniform_keypair_strategy() -> proptest::prelude::BoxedStrategy<Self> {
|
||||
use proptest::prelude::*;
|
||||
use rand::SeedableRng;
|
||||
use rand::rngs::StdRng;
|
||||
|
||||
// The no_shrink is because keypairs should be fixed -- shrinking would cause a
|
||||
// different keypair to be generated, which appears to not be very useful.
|
||||
any::<[u8; 32]>()
|
||||
.prop_filter_map("valid random keypair", |seed| {
|
||||
let mut rng = StdRng::from_seed(seed);
|
||||
Some(Self::generate_random(&mut rng))
|
||||
Some(Self::derive_random(&mut rng))
|
||||
})
|
||||
.no_shrink()
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimalist key type built around [u8;32]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, TryFromForSizedBytes)]
|
||||
#[ErrorType = "::generic_bytes::TryFromSizedBytesError"]
|
||||
#[repr(transparent)]
|
||||
pub struct Key(Vec<u8>);
|
||||
|
||||
impl Deref for Key {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedBytes for Key {
|
||||
type Len = U32;
|
||||
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
|
||||
GenericArray::clone_from_slice(&self.0[..])
|
||||
}
|
||||
|
||||
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;
|
||||
use crate::ciphersuite::{KeGroup, OprfHash};
|
||||
use crate::{
|
||||
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult,
|
||||
ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters,
|
||||
ClientRegistrationFinishResult, ClientRegistrationStartResult, ServerLogin,
|
||||
ServerLoginParameters, ServerLoginStartResult, ServerRegistration,
|
||||
ServerRegistrationStartResult, ServerSetup,
|
||||
};
|
||||
use hkdf::Hkdf;
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rand::rngs::SysRng;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_ristretto_check(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
|
||||
}
|
||||
macro_rules! test {
|
||||
($mod:ident, $point:ty) => {
|
||||
mod $mod {
|
||||
|
||||
#[test]
|
||||
fn test_ristretto_pub_from_priv(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
let sk = kp.private();
|
||||
prop_assert_eq!(&KeyPair::<RistrettoPoint>::public_from_private(sk), pk);
|
||||
}
|
||||
use proptest::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
|
||||
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
use super::*;
|
||||
|
||||
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
|
||||
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
let sk = kp.private();
|
||||
prop_assert_eq!(sk.public_key().serialize(), pk.serialize());
|
||||
}
|
||||
|
||||
prop_assert_eq!(dh1, dh2);
|
||||
}
|
||||
#[test]
|
||||
fn dh(kp1 in KeyPair::<$point>::uniform_keypair_strategy(),
|
||||
kp2 in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
|
||||
#[test]
|
||||
fn test_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
|
||||
let sk_bytes = kp.private().to_vec();
|
||||
let dh1 = kp2.private().ke_diffie_hellman(&kp1.public());
|
||||
let dh2 = kp1.private().ke_diffie_hellman(kp2.public());
|
||||
|
||||
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);
|
||||
}
|
||||
#[test]
|
||||
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
let sk_bytes = kp.private().serialize().to_vec();
|
||||
|
||||
let kp2 = PrivateKey::<$point>::deserialize_take_key_pair(&mut (sk_bytes.as_slice()))?;
|
||||
let kp2_private_bytes = kp2.private().serialize().to_vec();
|
||||
|
||||
prop_assert_eq!(sk_bytes, kp2_private_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
test!(ristretto, crate::Ristretto255);
|
||||
test!(p256, ::p256::NistP256);
|
||||
test!(p384, ::p384::NistP384);
|
||||
test!(p521, ::p521::NistP521);
|
||||
|
||||
struct Default;
|
||||
|
||||
impl CipherSuite for Default {
|
||||
#[cfg(feature = "ristretto255")]
|
||||
type OprfCs = crate::Ristretto255;
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
type OprfCs = ::p256::NistP256;
|
||||
#[cfg(feature = "ristretto255")]
|
||||
type KeyExchange = crate::TripleDh<crate::Ristretto255, sha2::Sha512>;
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
type KeyExchange = crate::TripleDh<::p256::NistP256, sha2::Sha256>;
|
||||
type Ksf = crate::ksf::Identity;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RemoteSeed<H: OutputSizeUser>(Output<H>);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RemoteKey(PrivateKey<KeGroup<Default>>);
|
||||
|
||||
const PASSWORD: &str = "password";
|
||||
|
||||
#[test]
|
||||
fn remote_key() {
|
||||
let sk = PrivateKey(KeGroup::<Default>::random_sk(&mut UnwrapErr(SysRng)));
|
||||
let pk = sk.public_key();
|
||||
let sk = RemoteKey(sk);
|
||||
let keypair = KeyPair::new(sk, pk);
|
||||
|
||||
let server_setup =
|
||||
ServerSetup::<Default, RemoteKey>::new_with_key_pair(&mut UnwrapErr(SysRng), keypair);
|
||||
|
||||
let ClientRegistrationStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientRegistration::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes())
|
||||
.unwrap();
|
||||
let ServerRegistrationStartResult { message, .. } =
|
||||
ServerRegistration::start(&server_setup, message, &[]).unwrap();
|
||||
let ClientRegistrationFinishResult { message, .. } = client
|
||||
.finish(
|
||||
&mut UnwrapErr(SysRng),
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let file = ServerRegistration::finish(message);
|
||||
|
||||
let ClientLoginStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientLogin::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes()).unwrap();
|
||||
let builder = ServerLogin::builder(
|
||||
&mut UnwrapErr(SysRng),
|
||||
&server_setup,
|
||||
Some(file),
|
||||
message,
|
||||
&[],
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let shared_secret = builder.private_key().0.ke_diffie_hellman(builder.data());
|
||||
let ServerLoginStartResult {
|
||||
message,
|
||||
state: server,
|
||||
..
|
||||
} = builder.build(shared_secret).unwrap();
|
||||
let ClientLoginFinishResult { message, .. } = client
|
||||
.finish(
|
||||
&mut UnwrapErr(SysRng),
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
server
|
||||
.finish(message, ServerLoginParameters::default())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_seed() {
|
||||
let mut oprf_seed = RemoteSeed::<OprfHash<Default>>(GenericArray::default().into_ha0_4());
|
||||
UnwrapErr(SysRng).fill_bytes(&mut oprf_seed.0);
|
||||
|
||||
let sk = PrivateKey(KeGroup::<Default>::random_sk(&mut UnwrapErr(SysRng)));
|
||||
let pk = sk.public_key();
|
||||
let sk = RemoteKey(sk);
|
||||
let keypair = KeyPair::new(sk, pk);
|
||||
|
||||
let server_setup = ServerSetup::<Default, _, _>::new_with_key_pair_and_seed(
|
||||
&mut UnwrapErr(SysRng),
|
||||
keypair,
|
||||
oprf_seed,
|
||||
);
|
||||
|
||||
let ClientRegistrationStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientRegistration::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes())
|
||||
.unwrap();
|
||||
let km = server_setup.key_material_info(&[]);
|
||||
let mut ikm = GenericArray::default();
|
||||
Hkdf::<OprfHash<Default>>::from_prk(&km.ikm.0)
|
||||
.unwrap()
|
||||
.expand_multi_info(&km.info, &mut ikm)
|
||||
.unwrap();
|
||||
let ServerRegistrationStartResult { message, .. } =
|
||||
ServerRegistration::start_with_key_material(&server_setup, ikm, message).unwrap();
|
||||
let ClientRegistrationFinishResult { message, .. } = client
|
||||
.finish(
|
||||
&mut UnwrapErr(SysRng),
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let file = ServerRegistration::finish(message);
|
||||
|
||||
let ClientLoginStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientLogin::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes()).unwrap();
|
||||
let km = server_setup.key_material_info(&[]);
|
||||
let mut ikm = GenericArray::default();
|
||||
Hkdf::<OprfHash<Default>>::from_prk(&km.ikm.0)
|
||||
.unwrap()
|
||||
.expand_multi_info(&km.info, &mut ikm)
|
||||
.unwrap();
|
||||
let builder = ServerLogin::builder_with_key_material(
|
||||
&mut UnwrapErr(SysRng),
|
||||
&server_setup,
|
||||
ikm,
|
||||
Some(file),
|
||||
message,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let shared_secret = builder.private_key().0.ke_diffie_hellman(builder.data());
|
||||
let ServerLoginStartResult {
|
||||
message,
|
||||
state: server,
|
||||
..
|
||||
} = builder.build(shared_secret).unwrap();
|
||||
let ClientLoginFinishResult { message, .. } = client
|
||||
.finish(
|
||||
&mut UnwrapErr(SysRng),
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
server
|
||||
.finish(message, ServerLoginParameters::default())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! Trait specifying a key stretching function
|
||||
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
|
||||
use crate::errors::InternalError;
|
||||
|
||||
/// Used for the key stretching function in OPAQUE
|
||||
pub trait Ksf: Default {
|
||||
/// Computes the key stretching function
|
||||
fn hash<L: ArrayLength>(
|
||||
&self,
|
||||
input: GenericArray<u8, L>,
|
||||
) -> Result<GenericArray<u8, L>, InternalError>;
|
||||
}
|
||||
|
||||
/// A no-op hash which simply returns its input
|
||||
#[derive(Default)]
|
||||
pub struct Identity;
|
||||
|
||||
impl Ksf for Identity {
|
||||
fn hash<L: ArrayLength>(
|
||||
&self,
|
||||
input: GenericArray<u8, L>,
|
||||
) -> Result<GenericArray<u8, L>, InternalError> {
|
||||
Ok(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "argon2")]
|
||||
impl Ksf for argon2::Argon2<'_> {
|
||||
fn hash<L: ArrayLength>(
|
||||
&self,
|
||||
input: GenericArray<u8, L>,
|
||||
) -> Result<GenericArray<u8, L>, InternalError> {
|
||||
let mut output = GenericArray::default();
|
||||
self.hash_password_into(&input, &[0; argon2::RECOMMENDED_SALT_LEN], &mut output)
|
||||
.map_err(|_| InternalError::KsfError)?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
+1075
-405
File diff suppressed because it is too large
Load Diff
@@ -1,225 +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.
|
||||
|
||||
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
|
||||
//! curve point
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::group::Group;
|
||||
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;
|
||||
|
||||
/// A subtrait of Group specifying how to hash a password into a point
|
||||
pub trait GroupWithMapToCurve: Group {
|
||||
/// 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 {
|
||||
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[..]),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+443
-309
@@ -1,360 +1,494 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! 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,
|
||||
hash::Hash,
|
||||
key_exchange::traits::{KeyExchange, ToBytes},
|
||||
keypair::{Key, KeyPair, SizedBytesExt},
|
||||
serialization::{serialize, tokenize},
|
||||
};
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use generic_bytes::SizedBytes;
|
||||
use std::convert::TryFrom;
|
||||
use std::marker::PhantomData;
|
||||
use core::ops::Add;
|
||||
|
||||
// Messages
|
||||
// =========
|
||||
use derive_where::derive_where;
|
||||
use digest::Output;
|
||||
use generic_array::typenum::{Sum, Unsigned};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hybrid_array::Array;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use voprf::{BlindedElement, BlindedElementLen, EvaluationElement, EvaluationElementLen};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
|
||||
use crate::envelope::{Envelope, EnvelopeLen};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::NonceLen;
|
||||
use crate::key_exchange::{
|
||||
Deserialize, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, Serialize,
|
||||
SerializedCredentialRequest, SerializedCredentialResponse,
|
||||
};
|
||||
use crate::keypair::PublicKey;
|
||||
use crate::opaque::{
|
||||
MaskedResponse, MaskedResponseLen, ServerLogin, ServerLoginStartResult, ServerSetup,
|
||||
};
|
||||
use crate::serialization::{ConcatExt, SliceExt};
|
||||
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
// ====================== //
|
||||
////////////////////////////
|
||||
|
||||
/// The message sent by the client to the server, to initiate registration
|
||||
pub struct RegistrationRequest<Grp> {
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::BlindedElement<CS::OprfCs>)]
|
||||
pub struct RegistrationRequest<CS: CipherSuite> {
|
||||
/// blinded password information
|
||||
pub(crate) alpha: Grp,
|
||||
}
|
||||
|
||||
impl<Grp: Group> TryFrom<&[u8]> for RegistrationRequest<Grp> {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let elem_len = Grp::ElemLen::to_usize();
|
||||
let checked_slice = check_slice_size(first_message_bytes, 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[checked_slice.len() - elem_len..]);
|
||||
let alpha = Grp::from_element_slice(arr)?;
|
||||
Ok(Self { alpha })
|
||||
}
|
||||
}
|
||||
|
||||
impl<Grp: Group> RegistrationRequest<Grp> {
|
||||
/// Byte representation for the registration request
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.alpha.to_arr().to_vec()
|
||||
}
|
||||
|
||||
/// 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 checked_slice =
|
||||
check_slice_size(&input, Grp::ElemLen::to_usize(), "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 = Grp::from_element_slice(arr)?;
|
||||
Ok(Self { alpha })
|
||||
}
|
||||
pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
|
||||
}
|
||||
|
||||
/// The answer sent by the server to the user, upon reception of the
|
||||
/// registration attempt
|
||||
pub struct RegistrationResponse<Grp> {
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>",
|
||||
serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::EvaluationElement<CS::OprfCs>, <KeGroup<CS> as Group>::Pk
|
||||
)]
|
||||
pub struct RegistrationResponse<CS: CipherSuite> {
|
||||
/// The server's oprf output
|
||||
pub(crate) beta: Grp,
|
||||
pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
|
||||
/// Server's static public key
|
||||
pub(crate) server_s_pk: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<Grp> TryFrom<&[u8]> for RegistrationResponse<Grp>
|
||||
where
|
||||
Grp: Group,
|
||||
{
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let elem_len = Grp::ElemLen::to_usize();
|
||||
let checked_slice = check_slice_size_atleast(bytes, elem_len, "second_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 beta = Grp::from_element_slice(arr)?;
|
||||
|
||||
// FIXME check public key bytes
|
||||
let server_s_pk = checked_slice[elem_len..].to_vec();
|
||||
|
||||
Ok(Self { beta, server_s_pk })
|
||||
}
|
||||
}
|
||||
|
||||
impl<Grp> RegistrationResponse<Grp>
|
||||
where
|
||||
Grp: Group,
|
||||
{
|
||||
/// Byte representation for the registration response message. This does not
|
||||
/// include the envelope credentials format
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.beta.to_arr().to_vec()[..], &self.server_s_pk[..]].concat()
|
||||
}
|
||||
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut registration_response: Vec<u8> = Vec::new();
|
||||
registration_response.extend_from_slice(&self.beta.to_arr());
|
||||
registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2));
|
||||
registration_response
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let checked_slice =
|
||||
check_slice_size_atleast(&input, Grp::ElemLen::to_usize(), "second_message_bytes")?;
|
||||
|
||||
// Check that the message is actually containing an element of the
|
||||
// correct subgroup
|
||||
let arr = GenericArray::from_slice(&checked_slice[..Grp::ElemLen::to_usize()]);
|
||||
let beta = Grp::from_element_slice(arr)?;
|
||||
|
||||
let (server_s_pk, remainder) = tokenize(&checked_slice[Grp::ElemLen::to_usize()..], 2)?;
|
||||
if !remainder.is_empty() {
|
||||
return Err(PakeError::SerializationError.into());
|
||||
}
|
||||
|
||||
Ok(Self { server_s_pk, beta })
|
||||
}
|
||||
pub(crate) server_s_pk: PublicKey<KeGroup<CS>>,
|
||||
}
|
||||
|
||||
/// The final message from the client, containing sealed cryptographic
|
||||
/// identifiers
|
||||
pub struct RegistrationUpload<D: Hash, G: Group> {
|
||||
/// The "envelope" generated by the user, containing sealed
|
||||
/// cryptographic identifiers
|
||||
pub(crate) envelope: Envelope<D>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>",
|
||||
serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk)]
|
||||
pub struct RegistrationUpload<CS: CipherSuite> {
|
||||
/// The "envelope" generated by the user, containing sealed cryptographic
|
||||
/// identifiers
|
||||
pub(crate) envelope: Envelope<CS>,
|
||||
/// The masking key used to mask the envelope
|
||||
pub(crate) masking_key: Output<OprfHash<CS>>,
|
||||
/// The user's public key
|
||||
pub(crate) client_s_pk: Key,
|
||||
pub(crate) _g: PhantomData<G>,
|
||||
}
|
||||
|
||||
impl<D: Hash, G: Group> TryFrom<&[u8]> for RegistrationUpload<D, G> {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(third_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let key_len = <Key as SizedBytes>::Len::to_usize();
|
||||
let envelope_size = key_len + Envelope::<D>::additional_size();
|
||||
let checked_bytes = check_slice_size(
|
||||
third_message_bytes,
|
||||
envelope_size + key_len,
|
||||
"third_message",
|
||||
)?;
|
||||
let unchecked_client_s_pk = Key::from_bytes(&checked_bytes[envelope_size..])?;
|
||||
let client_s_pk = KeyPair::<G>::check_public_key(unchecked_client_s_pk)?;
|
||||
|
||||
Ok(Self {
|
||||
envelope: Envelope::<D>::from_bytes(&checked_bytes[..envelope_size])?,
|
||||
client_s_pk,
|
||||
_g: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash, G: Group> RegistrationUpload<D, G> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let mut message: Vec<u8> = Vec::new();
|
||||
message.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2));
|
||||
message.extend_from_slice(&self.envelope.serialize());
|
||||
message
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let (client_s_pk, remainder) = tokenize(&input, 2)?;
|
||||
let (envelope, remainder) = Envelope::<D>::deserialize(&remainder)?;
|
||||
|
||||
if !remainder.is_empty() {
|
||||
return Err(PakeError::SerializationError.into());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
envelope,
|
||||
client_s_pk: KeyPair::<G>::check_public_key(Key::from_bytes(&client_s_pk)?)?,
|
||||
_g: PhantomData,
|
||||
})
|
||||
}
|
||||
#[derive_where(skip(Zeroize))]
|
||||
pub(crate) client_s_pk: PublicKey<KeGroup<CS>>,
|
||||
}
|
||||
|
||||
/// The message sent by the user to the server, to initiate registration
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Deserialize<'de>",
|
||||
serialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(
|
||||
Debug, Eq, Hash, PartialEq;
|
||||
voprf::BlindedElement<CS::OprfCs>,
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message,
|
||||
)]
|
||||
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,
|
||||
pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
|
||||
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> TryFrom<&[u8]> for CredentialRequest<CS> {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::deserialize(first_message_bytes)
|
||||
/// Builder for [`ServerLogin`] when using remote keys.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "SK: serde::Deserialize<'de>, <CS::KeyExchange as \
|
||||
KeyExchange>::KE2Builder<'a, CS>: serde::Deserialize<'de>",
|
||||
serialize = "SK: serde::Serialize, <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>: \
|
||||
serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(
|
||||
Debug, Eq, PartialEq;
|
||||
<KeGroup<CS> as Group>::Pk,
|
||||
SK,
|
||||
voprf::EvaluationElement<CS::OprfCs>,
|
||||
<CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>,
|
||||
)]
|
||||
pub struct ServerLoginBuilder<'a, CS: CipherSuite, SK: Clone> {
|
||||
pub(crate) server_s_sk: SK,
|
||||
pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
|
||||
pub(crate) masking_nonce: Zeroizing<GenericArray<u8, NonceLen>>,
|
||||
pub(crate) masked_response: MaskedResponse<CS>,
|
||||
#[cfg(test)]
|
||||
pub(crate) oprf_key: Zeroizing<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>>,
|
||||
pub(crate) ke2_builder: <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, SK: Clone> ServerLoginBuilder<'_, CS, SK> {
|
||||
/// The returned data here has to be processed and the result given as an
|
||||
/// input to [`ServerLoginBuilder::build()`]. To understand what kind of
|
||||
/// output is expected here and how to process it, refer to the
|
||||
/// documentation of your chosen [`CipherSuite::KeyExchange`].
|
||||
pub fn data(&self) -> <CS::KeyExchange as KeyExchange>::KE2BuilderData<'_, CS> {
|
||||
CS::KeyExchange::ke2_builder_data(&self.ke2_builder)
|
||||
}
|
||||
|
||||
/// The handle to the corresponding [`ServerSetup`]s private key.
|
||||
pub fn private_key(&self) -> &SK {
|
||||
&self.server_s_sk
|
||||
}
|
||||
|
||||
/// Build [`ServerLogin`] after attaining the input for the key exchange. To
|
||||
/// understand what kind of input is expected here, refer to the
|
||||
/// documentation of your chosen [`CipherSuite::KeyExchange`].
|
||||
///
|
||||
/// See [`ServerLogin::start()`] for the regular path.
|
||||
pub fn build(
|
||||
self,
|
||||
input: <CS::KeyExchange as KeyExchange>::KE2BuilderInput<CS>,
|
||||
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
|
||||
ServerLogin::build(self, input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> CredentialRequest<CS> {
|
||||
/// byte representation for the login request
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat()
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// The answer sent by the server to the user, upon reception of the login
|
||||
/// attempt
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Deserialize<'de>",
|
||||
serialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(
|
||||
Debug, Eq, Hash, PartialEq;
|
||||
EvaluationElement<CS::OprfCs>,
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message,
|
||||
)]
|
||||
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,
|
||||
pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
|
||||
pub(crate) masking_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(crate) masked_response: MaskedResponse<CS>,
|
||||
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange>::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()
|
||||
/// The answer sent by the client to the server, upon reception of the sealed
|
||||
/// envelope
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Deserialize<'de>",
|
||||
serialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(
|
||||
Debug, Eq, Hash, PartialEq;
|
||||
<CS::KeyExchange as KeyExchange>::KE3Message,
|
||||
)]
|
||||
pub struct CredentialFinalization<CS: CipherSuite> {
|
||||
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message,
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// High-level Implementations //
|
||||
// ========================== //
|
||||
////////////////////////////////
|
||||
|
||||
/// Length of [`RegistrationRequest`] in bytes for serialization.
|
||||
pub type RegistrationRequestLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
|
||||
|
||||
impl<CS: CipherSuite> RegistrationRequest<CS> {
|
||||
/// Only used for testing purposes
|
||||
#[cfg(test)]
|
||||
pub(crate) fn get_blinded_element_for_testing(&self) -> BlindedElement<CS::OprfCs> {
|
||||
self.blinded_element.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_without_ke(
|
||||
beta: &CS::Group,
|
||||
server_s_pk: &Key,
|
||||
envelope: &Envelope<CS::Hash>,
|
||||
) -> Vec<u8> {
|
||||
[
|
||||
&beta.to_arr(),
|
||||
&serialize(&server_s_pk.to_arr().to_vec(), 2)[..],
|
||||
&envelope.to_bytes(),
|
||||
]
|
||||
.concat()
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Array<u8, RegistrationRequestLen<CS>> {
|
||||
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.blinded_element.value())
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::try_from(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> TryFrom<&[u8]> for CredentialResponse<CS> {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
|
||||
let checked_slice =
|
||||
check_slice_size_atleast(second_message_bytes, elem_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 (serialized_server_s_pk, remainder) = tokenize(&checked_slice[elem_len..], 2)?;
|
||||
let sized_server_s_pk = check_slice_size(
|
||||
&serialized_server_s_pk[..],
|
||||
<Key as SizedBytes>::Len::to_usize(),
|
||||
"server_s_pk in credential_response",
|
||||
)?;
|
||||
let unchecked_server_s_pk = Key::from_bytes(&sized_server_s_pk[..])?;
|
||||
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
|
||||
|
||||
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&remainder)?;
|
||||
|
||||
let 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,
|
||||
)?;
|
||||
|
||||
let elem_len = <OprfGroup<CS> as voprf::Group>::ElemLen::USIZE;
|
||||
if input.len() < elem_len {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
Ok(Self {
|
||||
beta,
|
||||
server_s_pk,
|
||||
envelope,
|
||||
ke2_message,
|
||||
blinded_element: BlindedElement::deserialize(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
/// Length of [`RegistrationResponse`] in bytes for serialization.
|
||||
pub type RegistrationResponseLen<CS: CipherSuite> =
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, <KeGroup<CS> as Group>::PkLen>;
|
||||
|
||||
impl<CS: CipherSuite> TryFrom<&[u8]> for CredentialFinalization<CS> {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let ke3_message =
|
||||
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(bytes)?;
|
||||
Ok(Self { ke3_message })
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> CredentialFinalization<CS> {
|
||||
impl<CS: CipherSuite> RegistrationResponse<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
self.ke3_message.to_bytes()
|
||||
pub fn serialize(&self) -> GenericArray<u8, RegistrationResponseLen<CS>>
|
||||
where
|
||||
// RegistrationResponse: KgPk + KePk
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen> + ArrayLength,
|
||||
RegistrationResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
|
||||
self.evaluation_element.value(),
|
||||
));
|
||||
|
||||
elem.cat(self.server_s_pk.serialize())
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::try_from(&input[..])
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let elem_len = EvaluationElementLen::<CS::OprfCs>::USIZE;
|
||||
if input.len() < elem_len {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
let evaluation_element = EvaluationElement::deserialize(&input[..elem_len])?;
|
||||
input = &input[elem_len..];
|
||||
|
||||
Ok(Self {
|
||||
evaluation_element,
|
||||
server_s_pk: PublicKey::deserialize_take(&mut input)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// byte representation for the login finalization
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.ke3_message.to_bytes()
|
||||
#[cfg(test)]
|
||||
/// Only used for tests, where we can set the beta value to test for the
|
||||
/// reflection error case
|
||||
pub(crate) fn set_evaluation_element_for_testing(
|
||||
&self,
|
||||
beta: <OprfGroup<CS> as voprf::Group>::Elem,
|
||||
) -> Self {
|
||||
Self {
|
||||
evaluation_element: EvaluationElement::from_value_unchecked(beta),
|
||||
server_s_pk: self.server_s_pk.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`RegistrationUpload`] in bytes for serialization.
|
||||
pub type RegistrationUploadLen<CS: CipherSuite> =
|
||||
Sum<Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>, EnvelopeLen<CS>>;
|
||||
|
||||
impl<CS: CipherSuite> RegistrationUpload<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> GenericArray<u8, RegistrationUploadLen<CS>>
|
||||
where
|
||||
// RegistrationUpload: (KePk + Hash) + Envelope
|
||||
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
|
||||
ArrayLength + Add<EnvelopeLen<CS>>,
|
||||
RegistrationUploadLen<CS>: ArrayLength,
|
||||
{
|
||||
self.client_s_pk
|
||||
.serialize()
|
||||
.cat(GenericArray::from_slice(self.masking_key.as_slice()).clone())
|
||||
.cat(self.envelope.serialize())
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_s_pk: PublicKey::deserialize_take(&mut input)?,
|
||||
masking_key: input.take_array("masking key")?.into_ha0_4(),
|
||||
envelope: Envelope::deserialize_take(&mut input)?,
|
||||
})
|
||||
}
|
||||
|
||||
// Creates a dummy instance used for faking a [CredentialResponse]
|
||||
pub(crate) fn dummy<R: Rng + CryptoRng, SK: Clone, OS: Clone>(
|
||||
rng: &mut R,
|
||||
server_setup: &ServerSetup<CS, SK, OS>,
|
||||
) -> Self {
|
||||
let mut masking_key = Output::<OprfHash<CS>>::default();
|
||||
rng.fill_bytes(&mut masking_key);
|
||||
|
||||
Self {
|
||||
envelope: Envelope::<CS>::dummy(),
|
||||
masking_key,
|
||||
client_s_pk: server_setup.dummy_pk.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`CredentialRequest`] in bytes for serialization.
|
||||
pub type CredentialRequestLen<CS: CipherSuite> =
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, Ke1MessageLen<CS>>;
|
||||
|
||||
impl<CS: CipherSuite> CredentialRequest<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> GenericArray<u8, CredentialRequestLen<CS>>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
|
||||
// CredentialRequest: KgPk + Ke1Message
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>> + ArrayLength,
|
||||
CredentialRequestLen<CS>: ArrayLength,
|
||||
{
|
||||
let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
|
||||
self.blinded_element.value(),
|
||||
));
|
||||
|
||||
elem.cat(self.ke1_message.serialize())
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
|
||||
{
|
||||
Self::deserialize_take(&mut input)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
|
||||
{
|
||||
let elem_len = BlindedElementLen::<CS::OprfCs>::USIZE;
|
||||
if input.len() < elem_len {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
let blinded_element = BlindedElement::deserialize(&input[..elem_len])?;
|
||||
*input = &input[elem_len..];
|
||||
|
||||
Ok(Self {
|
||||
blinded_element,
|
||||
ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message::deserialize_take(input)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_parts(&self) -> SerializedCredentialRequest<CS> {
|
||||
SerializedCredentialRequest::new(&self.blinded_element)
|
||||
}
|
||||
|
||||
/// Only used for testing purposes
|
||||
#[cfg(test)]
|
||||
pub(crate) fn get_blinded_element_for_testing(&self) -> BlindedElement<CS::OprfCs> {
|
||||
self.blinded_element.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`CredentialResponse`] in bytes for serialization.
|
||||
pub type CredentialResponseLen<CS: CipherSuite> =
|
||||
Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
|
||||
|
||||
pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
|
||||
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
|
||||
|
||||
impl<CS: CipherSuite> CredentialResponse<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> GenericArray<u8, CredentialResponseLen<CS>>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
|
||||
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen> + ArrayLength,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength + Add<MaskedResponseLen<CS>>,
|
||||
CredentialResponseWithoutKeLen<CS>: ArrayLength,
|
||||
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
|
||||
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
|
||||
CredentialResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
|
||||
self.evaluation_element.value(),
|
||||
));
|
||||
|
||||
elem.cat(self.masking_nonce)
|
||||
.cat(self.masked_response.serialize())
|
||||
.cat(self.ke2_message.serialize())
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize,
|
||||
{
|
||||
let elem_len = EvaluationElementLen::<CS::OprfCs>::USIZE;
|
||||
if input.len() < elem_len {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
let evaluation_element = EvaluationElement::deserialize(&input[..elem_len])?;
|
||||
input = &input[elem_len..];
|
||||
|
||||
Ok(Self {
|
||||
evaluation_element,
|
||||
masking_nonce: input.take_array("masking nonce")?,
|
||||
masked_response: MaskedResponse::deserialize_take(&mut input)?,
|
||||
ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message::deserialize_take(
|
||||
&mut input,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_parts(&self) -> SerializedCredentialResponse<CS> {
|
||||
SerializedCredentialResponse::new(
|
||||
&self.evaluation_element,
|
||||
self.masking_nonce,
|
||||
self.masked_response.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Only used for tests, where we can set the beta value to test for the
|
||||
/// reflection error case
|
||||
pub(crate) fn set_evaluation_element_for_testing(
|
||||
&self,
|
||||
beta: <OprfGroup<CS> as voprf::Group>::Elem,
|
||||
) -> Self {
|
||||
Self {
|
||||
evaluation_element: EvaluationElement::from_value_unchecked(beta),
|
||||
masking_nonce: self.masking_nonce,
|
||||
masked_response: self.masked_response.clone(),
|
||||
ke2_message: self.ke2_message.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`CredentialFinalization`] in bytes for serialization.
|
||||
pub type CredentialFinalizationLen<CS: CipherSuite> = Ke3MessageLen<CS>;
|
||||
|
||||
impl<CS: CipherSuite> CredentialFinalization<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
|
||||
{
|
||||
self.ke3_message.serialize()
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
|
||||
{
|
||||
Ok(Self {
|
||||
ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(
|
||||
&mut input,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1047
-759
File diff suppressed because it is too large
Load Diff
-166
@@ -1,166 +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::{
|
||||
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
|
||||
serialization::serialize,
|
||||
};
|
||||
use digest::Digest;
|
||||
use generic_array::GenericArray;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
/// 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 blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> 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 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 unblind<G: Group>(token: &Token<G>, point: G) -> Vec<u8> {
|
||||
let unblinded = point * &G::scalar_invert(&token.blind);
|
||||
unblinded.to_arr().to_vec()
|
||||
}
|
||||
|
||||
pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
|
||||
token_data: &[u8],
|
||||
issued_token: &[u8],
|
||||
info: &[u8],
|
||||
) -> GenericArray<u8, <H as Digest>::OutputSize> {
|
||||
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
|
||||
let hash_input = [
|
||||
serialize(token_data, 2),
|
||||
serialize(issued_token, 2),
|
||||
serialize(info, 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],
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<(Token<G>, G), InternalPakeError> {
|
||||
blind::<R, G, H>(input, blinding_factor_rng)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
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 unblind_and_finalize_shim<G: GroupWithMapToCurve, H: Hash>(
|
||||
token: &Token<G>,
|
||||
point: G,
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
|
||||
Ok(finalize::<G, H>(
|
||||
&token.data,
|
||||
&unblind::<G>(token, point),
|
||||
b"",
|
||||
))
|
||||
}
|
||||
|
||||
///////////
|
||||
// Tests //
|
||||
// ===== //
|
||||
///////////
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::group::Group;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::{arr, GenericArray};
|
||||
use rand_core::OsRng;
|
||||
use sha2::Sha512;
|
||||
|
||||
fn prf(input: &[u8], oprf_key: &[u8; 32]) -> GenericArray<u8, <Sha512 as Digest>::OutputSize> {
|
||||
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
|
||||
let point = RistrettoPoint::map_to_curve::<Sha512>(input, &dst).unwrap();
|
||||
let scalar =
|
||||
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
|
||||
let res = point * scalar;
|
||||
|
||||
finalize::<RistrettoPoint, sha2::Sha512>(&input, &res.to_arr().to_vec(), b"")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oprf_retrieval() -> Result<(), InternalPakeError> {
|
||||
let input = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng)?;
|
||||
let oprf_key_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
|
||||
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
|
||||
let res =
|
||||
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &unblind(&token, beta), b"");
|
||||
let res2 = prf(&input[..], &oprf_key.as_bytes());
|
||||
assert_eq!(res, res2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oprf_inversion_unsalted() {
|
||||
let mut rng = OsRng;
|
||||
let mut input = vec![0u8; 64];
|
||||
rng.fill_bytes(&mut input);
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
|
||||
let res =
|
||||
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &unblind(&token, alpha), b"");
|
||||
|
||||
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
|
||||
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
|
||||
let res2 = finalize::<RistrettoPoint, sha2::Sha512>(&input, &point.to_arr().to_vec(), b"");
|
||||
|
||||
assert_eq!(res, res2);
|
||||
}
|
||||
}
|
||||
+134
-35
@@ -1,57 +1,156 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use crate::errors::PakeError;
|
||||
use core::ops::Add;
|
||||
|
||||
use crate::errors::ProtocolError;
|
||||
use digest::Update;
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::Sum;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hybrid_array::{Array, ArraySize};
|
||||
|
||||
// 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();
|
||||
pub(crate) fn i2osp<L: ArrayLength>(input: usize) -> Result<GenericArray<u8, L>, ProtocolError> {
|
||||
const SIZEOF_USIZE: usize = size_of::<usize>();
|
||||
|
||||
// Make sure input fits in output.
|
||||
if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
|
||||
let mut output = vec![0u8; length];
|
||||
output.splice(
|
||||
length - std::mem::size_of::<usize>()..length,
|
||||
input.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
output
|
||||
let mut output = GenericArray::default();
|
||||
output[L::USIZE.saturating_sub(SIZEOF_USIZE)..]
|
||||
.copy_from_slice(&input.to_be_bytes()[SIZEOF_USIZE.saturating_sub(L::USIZE)..]);
|
||||
Ok(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);
|
||||
#[cfg(test)]
|
||||
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, ProtocolError> {
|
||||
if input.len() > size_of::<usize>() {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
|
||||
let mut output_array = [0u8; std::mem::size_of::<usize>()];
|
||||
output_array[std::mem::size_of::<usize>() - input.len()..].copy_from_slice(input);
|
||||
let mut output_array = [0u8; size_of::<usize>()];
|
||||
output_array[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()
|
||||
pub(crate) trait UpdateExt {
|
||||
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>);
|
||||
|
||||
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self;
|
||||
}
|
||||
|
||||
// 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);
|
||||
impl<T: Update> UpdateExt for T {
|
||||
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>) {
|
||||
for bytes in iter {
|
||||
self.update(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
let size = os2ip(&input[..size_bytes])?;
|
||||
if size_bytes + size > input.len() {
|
||||
return Err(PakeError::SerializationError);
|
||||
}
|
||||
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self {
|
||||
let mut self_ = self;
|
||||
|
||||
Ok((
|
||||
input[size_bytes..size_bytes + size].to_vec(),
|
||||
input[size_bytes + size..].to_vec(),
|
||||
))
|
||||
for bytes in iter {
|
||||
self_ = self_.chain(bytes);
|
||||
}
|
||||
|
||||
self_
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait SliceExt {
|
||||
fn take_array<L: ArrayLength + ArraySize>(
|
||||
self: &mut &Self,
|
||||
name: &'static str,
|
||||
) -> Result<GenericArray<u8, L>, ProtocolError>;
|
||||
}
|
||||
|
||||
impl SliceExt for [u8] {
|
||||
fn take_array<L: ArrayLength + ArraySize>(
|
||||
self: &mut &Self,
|
||||
name: &'static str,
|
||||
) -> Result<GenericArray<u8, L>, ProtocolError> {
|
||||
if L::USIZE > self.len() {
|
||||
return Err(ProtocolError::SizeError {
|
||||
name,
|
||||
len: L::USIZE,
|
||||
actual_len: self.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let (front, back) = self.split_at(L::USIZE);
|
||||
*self = back;
|
||||
let arr: Array<u8, L> = Array::try_from(front).unwrap();
|
||||
Ok(GenericArray::from(arr))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait GenericArrayExt<O: ArrayLength> {
|
||||
type Output: ArrayLength;
|
||||
|
||||
/// This allows us to concat two [`GenericArray`]s but with `where` bounds
|
||||
/// `Other + Self`. Because sometimes `Self + Other` doesn't imply the
|
||||
/// bounds, and we have to add them to every call.
|
||||
fn concat_ext(&self, rest: &GenericArray<u8, O>) -> GenericArray<u8, Self::Output>;
|
||||
}
|
||||
|
||||
impl<L: ArrayLength, O: ArrayLength> GenericArrayExt<O> for GenericArray<u8, L>
|
||||
where
|
||||
O: Add<L>,
|
||||
Sum<O, L>: ArrayLength,
|
||||
{
|
||||
type Output = Sum<O, L>;
|
||||
|
||||
fn concat_ext(&self, other: &GenericArray<u8, O>) -> GenericArray<u8, Self::Output> {
|
||||
let mut output = GenericArray::<u8, O>::default().concat(GenericArray::<u8, L>::default());
|
||||
output[..L::USIZE].copy_from_slice(self);
|
||||
output[L::USIZE..].copy_from_slice(other);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ConcatExt<N: ArrayLength>: Sized {
|
||||
fn cat<M: ArrayLength>(self, other: GenericArray<u8, M>) -> GenericArray<u8, Sum<N, M>>
|
||||
where
|
||||
N: Add<M>,
|
||||
Sum<N, M>: ArrayLength;
|
||||
}
|
||||
|
||||
impl<N: ArrayLength> ConcatExt<N> for GenericArray<u8, N> {
|
||||
fn cat<M: ArrayLength>(self, other: GenericArray<u8, M>) -> GenericArray<u8, Sum<N, M>>
|
||||
where
|
||||
N: Add<M>,
|
||||
Sum<N, M>: ArrayLength,
|
||||
{
|
||||
Concat::concat(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use generic_array::typenum::{U1, U2};
|
||||
|
||||
use super::*;
|
||||
|
||||
// Test the error condition for I2OSP
|
||||
#[test]
|
||||
fn test_i2osp_err_check() {
|
||||
assert!(i2osp::<U1>(0).is_ok());
|
||||
|
||||
assert!(i2osp::<U1>(255).is_ok());
|
||||
assert!(i2osp::<U1>(256).is_err());
|
||||
assert!(i2osp::<U1>(257).is_err());
|
||||
|
||||
assert!(i2osp::<U2>(256 * 256 - 1).is_ok());
|
||||
assert!(i2osp::<U2>(256 * 256).is_err());
|
||||
assert!(i2osp::<U2>(256 * 256 + 1).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+933
-316
File diff suppressed because it is too large
Load Diff
@@ -1,45 +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.
|
||||
|
||||
//! Trait specifying a slow hashing function
|
||||
|
||||
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
|
||||
pub trait SlowHash<D: Hash> {
|
||||
/// Computes the slow hashing function
|
||||
fn hash(
|
||||
input: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, InternalPakeError>;
|
||||
}
|
||||
|
||||
/// A no-op hash which simply returns its input
|
||||
pub struct NoOpHash;
|
||||
|
||||
impl<D: Hash> SlowHash<D> for NoOpHash {
|
||||
fn hash(
|
||||
input: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
Ok(input.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[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).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)
|
||||
}
|
||||
}
|
||||
+1140
-431
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+33
-27
@@ -1,24 +1,24 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use rand_core::{CryptoRng, Error, RngCore};
|
||||
use std::cmp::min;
|
||||
use core::cmp::min;
|
||||
use std::vec::Vec;
|
||||
|
||||
/// A simple implementation of `RngCore` for testing purposes.
|
||||
use core::convert::Infallible;
|
||||
use rand::rand_core::{TryCryptoRng, TryRng};
|
||||
|
||||
/// A simple implementation of `Rng` for testing purposes.
|
||||
///
|
||||
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
|
||||
///
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CycleRng {
|
||||
v: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CycleRng {
|
||||
/// Create a `CycleRng`, yielding a sequence starting with
|
||||
/// `initial` and looping thereafter
|
||||
/// Create a `CycleRng`, yielding a sequence starting with `initial` and
|
||||
/// looping thereafter
|
||||
pub fn new(initial: Vec<u8>) -> Self {
|
||||
CycleRng { v: initial }
|
||||
}
|
||||
@@ -35,29 +35,35 @@ fn rotate_left<T>(data: &mut [T], steps: usize) {
|
||||
data.reverse();
|
||||
}
|
||||
|
||||
impl RngCore for CycleRng {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
unimplemented!()
|
||||
impl TryRng for CycleRng {
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
|
||||
let mut buf = [0u8; 4];
|
||||
|
||||
self.try_fill_bytes(&mut buf)?;
|
||||
|
||||
Ok(u32::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
unimplemented!()
|
||||
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
|
||||
let mut buf = [0u8; 8];
|
||||
|
||||
self.try_fill_bytes(&mut buf)?;
|
||||
|
||||
Ok(u64::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
|
||||
let len = min(self.v.len(), dest.len());
|
||||
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
|
||||
rotate_left(&mut self.v, len);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
|
||||
self.fill_bytes(dest);
|
||||
dest[..len].copy_from_slice(&self.v[..len]);
|
||||
|
||||
rotate_left(&mut self.v, len);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// This is meant for testing only
|
||||
impl CryptoRng for CycleRng {}
|
||||
impl TryCryptoRng for CycleRng {}
|
||||
|
||||
+16
-6
@@ -1,9 +1,19 @@
|
||||
// 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.
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use serde_json::Value;
|
||||
use std::vec::Vec;
|
||||
|
||||
mod full_test;
|
||||
#[rustfmt::skip]
|
||||
#[allow(dead_code)]
|
||||
mod full_test_vectors;
|
||||
pub mod mock_rng;
|
||||
mod opaque_test_vectors;
|
||||
mod voprf_test_vectors;
|
||||
mod parser;
|
||||
mod rfc9807_vectors;
|
||||
mod test_opaque_vectors;
|
||||
|
||||
pub(crate) fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
values[key].as_str().and_then(|s| hex::decode(s).ok())
|
||||
}
|
||||
|
||||
@@ -1,574 +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::*, key_exchange::tripledh::TripleDH, keypair::Key, opaque::*,
|
||||
slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
|
||||
};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// 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>,
|
||||
}
|
||||
|
||||
static TEST_VECTORS: &[&str] = &[
|
||||
r#"
|
||||
Group: ristretto255
|
||||
EnvelopeMode: 01
|
||||
OPRF: 0001
|
||||
SlowHash: Identity
|
||||
Hash: SHA512
|
||||
server_nonce: a4997137a8fa0d4baf7052a499bf877057f9404e03c889d641a0d7c
|
||||
807b6a518
|
||||
oprf_key: 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be03
|
||||
7e50b
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
blind_login: ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63
|
||||
e3263503
|
||||
server_private_keyshare: 31587dff30b8001d9d43584decc22e358fa7f9d6e606
|
||||
29fb1223081c3bae7103
|
||||
client_nonce: 75a1ad27ab77578bc08b44c4318f09b31d53145c9ba3b42abf0ea08
|
||||
a781277a6
|
||||
server_info: 6772656574696e677320616c696365
|
||||
client_info: 68656c6c6f20626f62
|
||||
client_private_keyshare: fbbf4ad24119f08a35bf999f8ae0c779ed7b3e266bf3
|
||||
3f793f6bf9ebf4578005
|
||||
envelope_nonce: 6c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e037
|
||||
585783444c0
|
||||
server_keyshare: 82be40ef93bf7c6edd43d4ed9f52fa19827649b819de39c52a22
|
||||
43e985b75d62
|
||||
blind_registration: c604c785ada70d77a5256ae21767de8c3304115237d262134
|
||||
f5e46e512cf8e03
|
||||
client_public_key: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3ef
|
||||
caba4f1c4ae861
|
||||
client_private_key: f0f56cfb488649fe28691dd9aa5dc9ff4c0e6028075baa3c5
|
||||
615398a2cb12304
|
||||
client_keyshare: 484e47e31b3132f4ee512e41805a1690891111a7b885bc526198
|
||||
22c14cabf360
|
||||
server_public_key: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdc
|
||||
eeeb935924a903
|
||||
server_private_key: d63d709e3a739a128929a9f289ff263fdcbc457f2f47f7c43
|
||||
ccafbbfee72290c
|
||||
auth_key: 22198da4ad73b1d35cd8bb875e64ce1a9fc2edeb073d760e114d1d7a2f8
|
||||
6d47411caf1787907e2ff96cd3190b14d101101d74cff234259d9f19a18f2cfe29d0b
|
||||
server_mac_key: 9de28e2f7107afe266570934c033dd6a403fb2b09a9f1a1357a81
|
||||
9fb072d25e6651626638d77bd7f0adf4b2b715d0a0bee2ac531fd0f7da699aba6e9ed
|
||||
717466
|
||||
envelope: 016c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e0375857
|
||||
83444c00022e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92c73838c043
|
||||
fc316deeb83deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23775aa54633
|
||||
ed75665cd88f451e044cac1b282d890269476d1ac18ff9a4c0cb832e1143ffa7d447a
|
||||
prk: 732303f65e76c39f30876aec31af1f5bcd8861626c922baa2e842209c7d5bf2e
|
||||
024912db2d6cac00b1260c437d34ec588a664a55fc7a0a40251915d2b15d8ec9
|
||||
client_mac_key: 1536168d48218d08dcbed3438e897d98eff566894240d8d136be0
|
||||
4ba46b2c788883fd165ce7614c52a6a9c926bf55d249e6c29bc4a23d5a1775ad294a7
|
||||
858922
|
||||
pseudorandom_pad: e99274b5cd27d14aeeaf16e7aa8d7e7a03071d58229c5dc96d0
|
||||
46ed57a761ddccdbc
|
||||
handshake_encrypt_key: c646def7a8ca75282ac1a0ad15630834cba2772837d7a3
|
||||
a26d00b36923e55b3f5d065679c715e4c799654f261c865f1a55bc94cabc29a6e2e72
|
||||
296dfc494a984
|
||||
handshake_secret: cc887e128c28064106d4101ac3de633e9096e170f2c4a9913d3
|
||||
50f306274b1665a5b251f761672c12db4c403a615c22cee96adb3539fa62662a17f2e
|
||||
18cd5fed
|
||||
registration_response: 1867301bcc67bdf8e640b7d6edcbe2a65488446417b50d
|
||||
30cdba66ccb379e57200205442a6f57333a332b4c6f07308f6fa846bde3ed27425820
|
||||
cdceeeb935924a903
|
||||
export_key: 6ca2c344763e5bc9e3d2bbfe3d982b826b709da597e28e85f9594ec54
|
||||
2a20c697d55de277ccce1d1af7c48ab7fea1467ac1e3a99c71dcf6326a909d280bd2f
|
||||
6f
|
||||
registration_upload: 0020b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9a
|
||||
a2c3efcaba4f1c4ae861016c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc5
|
||||
56e037585783444c00022e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92
|
||||
c73838c043fc316deeb83deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23
|
||||
775aa54633ed75665cd88f451e044cac1b282d890269476d1ac18ff9a4c0cb832e114
|
||||
3ffa7d447a
|
||||
registration_request: 241b621c417c0705b5ea7a8b7cdd5039fd61e6b63effe2a
|
||||
44418164c4d49003e
|
||||
session_key: ee9f1ef224d498858f6c9b3a121016a38bad7816055c452b1c7edf3d
|
||||
d439c42a4cc78cbd672e985a20910df14f8f1af4ce5793303ffe6954ff5f1a264e3fd
|
||||
515
|
||||
KE3: e771daf28bc5e8068dead67c3db19f9ad03ee919e52f6c7a6e79cf1085bd7448
|
||||
1e76512c77f37762578eb2faff8fe98e4185ca2d01957216c556d33a6fba3028
|
||||
KE2: e83812f06568d57b8cdfdcc90fe91454e21bd25dd2a1c32dd1599a2e4a4b6c35
|
||||
00205442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdceeeb935924a9030
|
||||
16c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e037585783444c00022
|
||||
e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92c73838c043fc316deeb83
|
||||
deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23775aa54633ed75665cd88
|
||||
f451e044cac1b282d890269476d1ac18ff9a4c0cb832e1143ffa7d447aa4997137a8f
|
||||
a0d4baf7052a499bf877057f9404e03c889d641a0d7c807b6a51882be40ef93bf7c6e
|
||||
dd43d4ed9f52fa19827649b819de39c52a2243e985b75d62000f13aed85ae30aee2f8
|
||||
9ac5e1c5dd53609b890267ef2d765bb56000bc704c2ba4e4256107befee6655cdb084
|
||||
64e6c75d8b251642e0d721b3fce38f245568b50e7f6c98825d54cc1a26d6eafadcc1d
|
||||
344
|
||||
KE1: b68e0e356f8490fa9c3bed952e16cc02db21eda686b3c484f3d9d912caa41f76
|
||||
75a1ad27ab77578bc08b44c4318f09b31d53145c9ba3b42abf0ea08a781277a600096
|
||||
8656c6c6f20626f62484e47e31b3132f4ee512e41805a1690891111a7b885bc526198
|
||||
22c14cabf360
|
||||
"#,
|
||||
r#"
|
||||
Group: ristretto255
|
||||
EnvelopeMode: 02
|
||||
OPRF: 0001
|
||||
SlowHash: Identity
|
||||
Hash: SHA512
|
||||
server_nonce: 0f3a6da8b667bc7a383c987586bee749c5f2787691baca68757e78b
|
||||
6128b0a0f
|
||||
oprf_key: 89c61a42c8191a5ca41f2fe959843d333bcf43173b7de4c5c119e0e0d8b
|
||||
0e707
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
blind_login: e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd
|
||||
4171ea02
|
||||
server_private_keyshare: 70c944dcb7f4dddde168ecb48dd9488c62b6fc7e9bb4
|
||||
2a16d291afca9dd25b07
|
||||
client_nonce: 480917b09c6720680b4a7a0ba9f54b69d870f640a4a7994b47ad07d
|
||||
1a95c984f
|
||||
server_info: 6772656574696e677320616c696365
|
||||
server_identity: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdcee
|
||||
eb935924a903
|
||||
client_info: 68656c6c6f20626f62
|
||||
client_private_keyshare: a4ba6cb7e16ab76eccdb4c0b9261eedd426d7863f00b
|
||||
fc4a0e09476d3121e70c
|
||||
envelope_nonce: e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7dee
|
||||
b8cfb42d13f
|
||||
server_public_key: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdc
|
||||
eeeb935924a903
|
||||
client_identity: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3efca
|
||||
ba4f1c4ae861
|
||||
blind_registration: 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9db
|
||||
cec831b8c681a09
|
||||
client_public_key: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3ef
|
||||
caba4f1c4ae861
|
||||
client_private_key: f0f56cfb488649fe28691dd9aa5dc9ff4c0e6028075baa3c5
|
||||
615398a2cb12304
|
||||
client_keyshare: 4ae7d50bb80cc8f5034d36c1c27edc30caca0983677a941bc0ac
|
||||
e5e10b18300a
|
||||
server_keyshare: a2e9e0809b270a1d5c8208f3498a3188538265e6a9e6b274cb38
|
||||
c4c5d9b1792d
|
||||
server_private_key: d63d709e3a739a128929a9f289ff263fdcbc457f2f47f7c43
|
||||
ccafbbfee72290c
|
||||
auth_key: 4c7c0ae950ad7e9c518266f953d4a01bd2232695f92fb028aa6c124996e
|
||||
31a205f621305fd4997edf8a5fce04a51252ba8430227c134b81d7093a58e01c70752
|
||||
server_mac_key: 974bde939ef1d30ba29f9ec2addcaff1eeb105a1f534e04113f9f
|
||||
0b5c3b9a0797e3fa5f7006e63dfb2b0ce74d002bd1161767c361507bfb1fa0fed5063
|
||||
1cdcb7
|
||||
envelope: 02e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7deeb8cf
|
||||
b42d13f00227ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26ecd566d7688
|
||||
095f9478a721e5a592be9dd3bda76ed97421819aca4a30752813223d33bc7ea443be9
|
||||
76754605116278c7fffda4de142674ed154f540bd3285080637eb4b929e1378336b11
|
||||
prk: e3bb74ca5f88a95571578e921489e1b6119b438e4efce6e955ae9b6453f24aa4
|
||||
e3a34fa22bc5f470cfc134ca0784a9cd7df64be46ff3b325fc19f4009979bf2e
|
||||
client_mac_key: 1c55ecba64d98dc2db8f45faa72b8fdd63cad8677b8665cc575cf
|
||||
8ad36ef38ec63c9dfc5552007573215983f55d8f5cfa1651da8417e3f27094dde5254
|
||||
7f5b89
|
||||
pseudorandom_pad: 7ef495b828a97c896f381824d3371ba012eb63c3f19bb535676
|
||||
a3b63b18373255ba3
|
||||
handshake_encrypt_key: 0852fb826109073be6e7c065b6e2d1f872c16f9977c177
|
||||
cd2f945b26bb933fe6252f226f40483f7aa52f545f801d1e4430f30f80fd42070494e
|
||||
a77fdcecd6595
|
||||
handshake_secret: bfd3bfbe451520880975d0e568cbd3b5155b23c02de504fccb9
|
||||
dd5a8195266cd94d49d040530b3d6b0a585d542eb24da708a2b6f6dc34dee4652d0c1
|
||||
c62c4e59
|
||||
registration_response: 088ac01ebf5700f0c96bc2988509343cb7e2dd6f0df820
|
||||
d0fb807faa11a26f5600205442a6f57333a332b4c6f07308f6fa846bde3ed27425820
|
||||
cdceeeb935924a903
|
||||
export_key: 0effa605dc47ba4fe565c423b782b8b6697b26ee2ede7059b0e17510d
|
||||
f8b11554ce053409671480a56ffbe77b91edc95205c213caeaf9dcb0841790ff834a0
|
||||
09
|
||||
registration_upload: 0020b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9a
|
||||
a2c3efcaba4f1c4ae86102e38fb444afe3df13ae05e6876d10eca7661196375518eb6
|
||||
6d7deeb8cfb42d13f00227ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26e
|
||||
cd566d7688095f9478a721e5a592be9dd3bda76ed97421819aca4a30752813223d33b
|
||||
c7ea443be976754605116278c7fffda4de142674ed154f540bd3285080637eb4b929e
|
||||
1378336b11
|
||||
registration_request: c8d2e9ba503bf3f8821226653314427edb1ec8a3ecc94a5
|
||||
dfbbe33d59d07b645
|
||||
session_key: 8dc21ff264f2774de95955d35544ba314e92d07f4a3b32c89ead5e70
|
||||
c83ac4c0221deadd34ed11d43fc4d3651aec612d696c63979c96bf1ddd1ee44da5d0c
|
||||
d68
|
||||
KE3: c178e45ed9b314653685cdbf5f7730e3e40f8652ceb9b10f47d1c784fdd75dd1
|
||||
07c4ae3f7683de5a692359178c8f13f41a043fc1dcfc14b1fb7cb411514efc6c
|
||||
KE2: 5079b16709b195b3b63257b419efb752bd0603170160fa72b828ce9ff9209c0c
|
||||
00205442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdceeeb935924a9030
|
||||
2e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7deeb8cfb42d13f0022
|
||||
7ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26ecd566d7688095f9478a72
|
||||
1e5a592be9dd3bda76ed97421819aca4a30752813223d33bc7ea443be976754605116
|
||||
278c7fffda4de142674ed154f540bd3285080637eb4b929e1378336b110f3a6da8b66
|
||||
7bc7a383c987586bee749c5f2787691baca68757e78b6128b0a0fa2e9e0809b270a1d
|
||||
5c8208f3498a3188538265e6a9e6b274cb38c4c5d9b1792d000f5a1a0b34573bf728b
|
||||
14f53485f3bf62fd91154dc9ca01b21945b2204f96adc87bd80e8283ecf3522b6d893
|
||||
7527f2e9b9782a94fedab0fa304590d1d4f03d72fb1664fca1523a3be95f6c3a97d0a
|
||||
2ed
|
||||
KE1: 7024ca0d5423176294fbb9ca968d8ce3fc879a231f1ceef69e672c89e02ded59
|
||||
480917b09c6720680b4a7a0ba9f54b69d870f640a4a7994b47ad07d1a95c984f00096
|
||||
8656c6c6f20626f624ae7d50bb80cc8f5034d36c1c27edc30caca0983677a941bc0ac
|
||||
e5e10b18300a
|
||||
"#,
|
||||
];
|
||||
|
||||
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::try_from(¶meters.server_public_key[..]).unwrap(),
|
||||
)?;
|
||||
|
||||
let password_file = server_registration_start_result
|
||||
.state
|
||||
.finish(RegistrationUpload::deserialize(¶meters.registration_upload[..]).unwrap())?;
|
||||
|
||||
Ok(password_file.to_bytes())
|
||||
}
|
||||
|
||||
#[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::try_from(¶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::try_from(&password_file_bytes[..]).unwrap(),
|
||||
&Key::try_from(¶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.to_bytes())
|
||||
);
|
||||
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::try_from(&password_file_bytes[..]).unwrap(),
|
||||
&Key::try_from(¶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::try_from(¶meters.KE3[..])?)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.session_key),
|
||||
hex::encode(server_login_result.session_key)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use std::string::{String, ToString};
|
||||
use std::vec::Vec;
|
||||
use std::{format, vec};
|
||||
|
||||
pub(crate) fn rfc_to_json(input: &str) -> String {
|
||||
format!("{{\n{}\n}}", parse_vector_types(input))
|
||||
}
|
||||
|
||||
fn parse_vector_types(input: &str) -> String {
|
||||
let re = regex::Regex::new(r" {2}(?P<type>.+?) Test Vectors").unwrap();
|
||||
let mut vector_types = vec![];
|
||||
|
||||
let chunks: Vec<&str> = re.split(input).collect();
|
||||
|
||||
for (count, caps) in (1..).zip(re.captures_iter(input)) {
|
||||
let vector_type = format!(
|
||||
"\"{}\": [\n {} \n]",
|
||||
&caps["type"].trim(),
|
||||
parse_ciphersuites(chunks[count])
|
||||
);
|
||||
vector_types.push(vector_type);
|
||||
}
|
||||
|
||||
vector_types.join(",\n")
|
||||
}
|
||||
|
||||
fn parse_ciphersuites(input: &str) -> String {
|
||||
let re = regex::Regex::new(
|
||||
r" Configuration\n([\s\S])*?OPRF: (?P<oprf>.*?)\n([\s\S])*?Group: (?P<group>.*?)\n",
|
||||
)
|
||||
.unwrap();
|
||||
let mut ciphersuites = vec![];
|
||||
|
||||
let chunks: Vec<&str> = re.split(input).collect();
|
||||
|
||||
for (count, caps) in (1..).zip(re.captures_iter(input)) {
|
||||
let ciphersuite = format!(
|
||||
"{{ \"{}, {}\": {{ {} }} }}",
|
||||
&caps["oprf"],
|
||||
&caps["group"],
|
||||
parse_params(chunks[count])
|
||||
);
|
||||
ciphersuites.push(ciphersuite);
|
||||
}
|
||||
|
||||
ciphersuites.join(",\n")
|
||||
}
|
||||
|
||||
fn parse_params(input: &str) -> String {
|
||||
let mut params = vec![];
|
||||
let mut param = String::new();
|
||||
|
||||
let mut lines = input.lines();
|
||||
|
||||
loop {
|
||||
match lines.next() {
|
||||
None => {
|
||||
// Clear out any existing string and flush to params
|
||||
param += "\"";
|
||||
params.push(param);
|
||||
|
||||
return params.join(",\n");
|
||||
}
|
||||
Some(line) => {
|
||||
// First, trim out any whitespace
|
||||
let line = line.trim();
|
||||
|
||||
// If line contains :, then
|
||||
if line.contains(':') {
|
||||
// Clear out any existing string and flush to params
|
||||
if !param.is_empty() {
|
||||
param += "\"";
|
||||
params.push(param);
|
||||
}
|
||||
|
||||
let mut iter = line.split(':');
|
||||
let key = iter.next().unwrap().split_whitespace().next().unwrap();
|
||||
let val = iter.next().unwrap().split_whitespace().next().unwrap();
|
||||
|
||||
param = format!(" \"{key}\": \"{val}");
|
||||
} else {
|
||||
let s = line.trim().to_string();
|
||||
if s.contains('~') || s.contains('#') {
|
||||
// Ignore comment lines
|
||||
continue;
|
||||
}
|
||||
if s.contains("C.") {
|
||||
// Ignore section lines
|
||||
continue;
|
||||
}
|
||||
if !s.is_empty() {
|
||||
param += &s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,806 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
//! The OPAQUE test vectors taken from Appendix C of
|
||||
//! [RFC 9807](https://www.rfc-editor.org/rfc/rfc9807.txt)
|
||||
|
||||
pub(crate) static VECTORS: &str = r#"
|
||||
C.1. Real Test Vectors
|
||||
|
||||
C.1.1. OPAQUE-3DH Real Test Vector 1
|
||||
|
||||
C.1.1.1. Configuration
|
||||
|
||||
OPRF: ristretto255-SHA512
|
||||
Hash: SHA512
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA512
|
||||
MAC: HMAC-SHA512
|
||||
Group: ristretto255
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
Nm: 64
|
||||
Nx: 64
|
||||
Nok: 32
|
||||
|
||||
C.1.1.2. Input Values
|
||||
|
||||
oprf_seed: f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b0
|
||||
54a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989e
|
||||
f
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d2
|
||||
3ba7a38dfec
|
||||
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
|
||||
f612fdfc6d
|
||||
server_private_key: 47451a85372f8b3537e249d7b54188091fb18edde78094b43
|
||||
e2ba42b5eb89f0d
|
||||
server_public_key: b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a
|
||||
382c9b79df1a78
|
||||
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
|
||||
138e3d4a1
|
||||
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
|
||||
0cae6a6cc
|
||||
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
|
||||
1091933541936304b
|
||||
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
|
||||
1e85ff80da12f982f
|
||||
blind_registration: 76cfbfe758db884bebb33582331ba9f159720ca8784a2a070
|
||||
a265d9c2d6abe01
|
||||
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
|
||||
b0790308
|
||||
|
||||
C.1.1.3. Intermediate Values
|
||||
|
||||
client_public_key: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbac
|
||||
cafb57ac5c3675
|
||||
auth_key: 6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa9003773
|
||||
0494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822
|
||||
randomized_password: aac48c25ab036e30750839d31d6e73007344cb1155289fb7
|
||||
d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf15
|
||||
0031d1435d9
|
||||
envelope: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a3
|
||||
8dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b
|
||||
8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5
|
||||
handshake_secret: 81263cb85a0cfa12450f0f388de4e92291ec4c7c7a0878b6245
|
||||
50ff528726332f1298fc6cc822a432c89504347c7a2ccd70316ae3da6a15e0399e6db
|
||||
3f7c1b12
|
||||
server_mac_key: 0d36b26cfe38f51f804f0a9361818f32ee1ce2a4e5578653b5271
|
||||
84af058d3b2d8075c296fd84d24677913d1baa109290cd81a13ed383f9091a3804e65
|
||||
298dfc
|
||||
client_mac_key: 91750adbac54a5e8e53b4c233cc8d369fe83b0de1b6a3cd85575e
|
||||
eb0bb01a6a90a086a2cf5fe75fff2a9379c30ba9049510a33b5b0b1444a88800fc3ee
|
||||
e2260d
|
||||
oprf_key: 5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4
|
||||
c6d05
|
||||
|
||||
C.1.1.4. Output Values
|
||||
|
||||
registration_request: 5059ff249eb1551b7ce4991f3336205bde44a105a032e74
|
||||
7d21bf382e75f7a71
|
||||
registration_response: 7408a268083e03abc7097fc05b587834539065e86fb0c7
|
||||
b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a3
|
||||
82c9b79df1a78
|
||||
registration_upload: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccb
|
||||
accafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e
|
||||
66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa8
|
||||
2785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfe
|
||||
c634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e
|
||||
9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5
|
||||
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
|
||||
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29b
|
||||
ee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326
|
||||
KE2: 7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb471
|
||||
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec6
|
||||
0bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced
|
||||
4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fedc80188ca46743c
|
||||
52786e0382f95ad85c08f6afcd1ccfbff95e2bdeb015b166c6b20b92f832cc6df01e0
|
||||
b86a7efd92c1c804ff865781fa93f2f20b446c8371b671cd9960ecef2fe0d0f749498
|
||||
6fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f197
|
||||
1b29eb1d5d0bd733e40816c91f7912cc4a660c48dae03e57aaa38f3d0cffcfc21852e
|
||||
bc8b405d15bd6744945ba1a93438a162b6111699d98a16bb55b7bdddfe0fc5608b23d
|
||||
a246e7bd73b47369169c5c90
|
||||
KE3: 4455df4f810ac31a6748835888564b536e6da5d9944dfea9e34defb9575fe5e2
|
||||
661ef61d2ae3929bcf57e53d464113d364365eb7d1a57b629707ca48da18e442
|
||||
export_key: 1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a
|
||||
970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f
|
||||
16
|
||||
session_key: 42afde6f5aca0cfa5c163763fbad55e73a41db6b41bc87b8e7b62214
|
||||
a8eedc6731fa3cb857d657ab9b3764b89a84e91ebcb4785166fbb02cedfcbdfda215b
|
||||
96f
|
||||
|
||||
C.1.2. OPAQUE-3DH Real Test Vector 2
|
||||
|
||||
C.1.2.1. Configuration
|
||||
|
||||
OPRF: ristretto255-SHA512
|
||||
Hash: SHA512
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA512
|
||||
MAC: HMAC-SHA512
|
||||
Group: ristretto255
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
Nm: 64
|
||||
Nx: 64
|
||||
Nok: 32
|
||||
|
||||
C.1.2.2. Input Values
|
||||
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b0
|
||||
54a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989e
|
||||
f
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d2
|
||||
3ba7a38dfec
|
||||
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
|
||||
f612fdfc6d
|
||||
server_private_key: 47451a85372f8b3537e249d7b54188091fb18edde78094b43
|
||||
e2ba42b5eb89f0d
|
||||
server_public_key: b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a
|
||||
382c9b79df1a78
|
||||
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
|
||||
138e3d4a1
|
||||
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
|
||||
0cae6a6cc
|
||||
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
|
||||
1091933541936304b
|
||||
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
|
||||
1e85ff80da12f982f
|
||||
blind_registration: 76cfbfe758db884bebb33582331ba9f159720ca8784a2a070
|
||||
a265d9c2d6abe01
|
||||
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
|
||||
b0790308
|
||||
|
||||
C.1.2.3. Intermediate Values
|
||||
|
||||
client_public_key: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbac
|
||||
cafb57ac5c3675
|
||||
auth_key: 6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa9003773
|
||||
0494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822
|
||||
randomized_password: aac48c25ab036e30750839d31d6e73007344cb1155289fb7
|
||||
d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf15
|
||||
0031d1435d9
|
||||
envelope: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a3
|
||||
8dfec1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b68
|
||||
0b2b3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c
|
||||
handshake_secret: 5e723bed1e5276de2503419eba9da61ead573109c4012268323
|
||||
98c7e08155b885bfe7bc93451f9d887a0c1d0c19233e40a8e47b347a9ac3907f94032
|
||||
a4cff64f
|
||||
server_mac_key: dad66bb9251073d17a13f8e5500f36e5998e3cde520ca0738e708
|
||||
5af62fd97812eb79a745c94d0bf8a6ac17f980cf435504cf64041eeb6bb237796d2c7
|
||||
f81e9a
|
||||
client_mac_key: f816fe2914f7c5b29852385615d7c7f31ac122adf202d7ccd4976
|
||||
06d7aabd48930323d1d02b1cc9ecd456c4de6f46c7950becb18bffd921dd5876381b5
|
||||
486ffe
|
||||
oprf_key: 5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4
|
||||
c6d05
|
||||
|
||||
C.1.2.4. Output Values
|
||||
|
||||
registration_request: 5059ff249eb1551b7ce4991f3336205bde44a105a032e74
|
||||
7d21bf382e75f7a71
|
||||
registration_response: 7408a268083e03abc7097fc05b587834539065e86fb0c7
|
||||
b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a3
|
||||
82c9b79df1a78
|
||||
registration_upload: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccb
|
||||
accafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e
|
||||
66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa8
|
||||
2785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfe
|
||||
c1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b680b2b
|
||||
3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c
|
||||
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
|
||||
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29b
|
||||
ee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326
|
||||
KE2: 7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb471
|
||||
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec6
|
||||
0bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced
|
||||
4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fea502150b67fe367
|
||||
95dd8914f164e49f81c7688a38928372134b7dccd50e09f8fed9518b7b2f94835b3c4
|
||||
fe4c8475e7513f20eb97ff0568a39caee3fd6251876f71cd9960ecef2fe0d0f749498
|
||||
6fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f197
|
||||
1b29eb1d5d0bd733e40816c91f7912cc4a292371e7809a9031743e943fb3b56f51de9
|
||||
03552fc91fba4e7419029951c3970b2e2f0a9dea218d22e9e4e0000855bb6421aa361
|
||||
0d6fc0f4033a6517030d4341
|
||||
KE3: 7a026de1d6126905736c3f6d92463a08d209833eb793e46d0f7f15b3e0f62c76
|
||||
43763c02bbc6b8d3d15b63250cae98171e9260f1ffa789750f534ac11a0176d5
|
||||
export_key: 1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a
|
||||
970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f
|
||||
16
|
||||
session_key: ae7951123ab5befc27e62e63f52cf472d6236cb386c968cc47b7e34f
|
||||
866aa4bc7638356a73cfce92becf39d6a7d32a1861f12130e824241fe6cab34fbd471
|
||||
a57
|
||||
|
||||
C.1.3. OPAQUE-3DH Real Test Vector 3
|
||||
|
||||
C.1.3.1. Configuration
|
||||
|
||||
OPRF: ristretto255-SHA512
|
||||
Hash: SHA512
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA512
|
||||
MAC: HMAC-SHA512
|
||||
Group: curve25519
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
Nm: 64
|
||||
Nx: 64
|
||||
Nok: 32
|
||||
|
||||
C.1.3.2. Input Values
|
||||
|
||||
oprf_seed: a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67
|
||||
a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1
|
||||
b
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cc
|
||||
a9bf44d6e0b
|
||||
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
|
||||
f612fdfc6d
|
||||
server_private_key: c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
|
||||
a7725bf0e79d564
|
||||
server_public_key: a41e28269b4e97a66468cc00c5a57753e192e1527669897706
|
||||
88aa90486ef031
|
||||
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
|
||||
138e3d4a1
|
||||
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
|
||||
0cae6a6cc
|
||||
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
|
||||
1091933541936304b
|
||||
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
|
||||
1e85ff80da12f982f
|
||||
blind_registration: c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee
|
||||
04baddb2034a70a
|
||||
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
|
||||
b0790308
|
||||
|
||||
C.1.3.3. Intermediate Values
|
||||
|
||||
client_public_key: 0936ea94ab030ec332e29050d266c520e916731a052d05ced7
|
||||
e0cfe751142b48
|
||||
auth_key: 7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d576
|
||||
4739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c
|
||||
randomized_password: 3a602c295a9c323d9362fe286f104567ed6862b25dbe30fa
|
||||
da844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8
|
||||
f3963fbb2e1
|
||||
envelope: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44
|
||||
d6e0b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac
|
||||
150b4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb
|
||||
handshake_secret: 178c8c15e025252380c3edb1c6ad8ac52573b38d536099e2f86
|
||||
5786f5e31c642608550c0c6f281c37ce259667dd72768af31630e0eb36f1096a2e642
|
||||
1c2aa163
|
||||
server_mac_key: f3c6a8e069c54bb0d8905139f723c9e22f5c662dc08848243a665
|
||||
4c8223800019b9823523d84da2ef67ca1c14277630aace464c113be8a0a658c39e181
|
||||
a8bb71
|
||||
client_mac_key: b1ee7ce52dbd0ab72872924ff11596cb196bbabfc319e74aca78a
|
||||
de54a0f74dd15dcf5621f6d2e79161b0c9b701381d494836dedbb86e584a65b34267a
|
||||
370e01
|
||||
oprf_key: 62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea14
|
||||
6030a
|
||||
|
||||
C.1.3.4. Output Values
|
||||
|
||||
registration_request: 26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f65538
|
||||
1a67de02c59fd5436
|
||||
registration_response: 506e8f1b89c098fb89b5b6210a05f7898cafdaea221761
|
||||
e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e15276698977068
|
||||
8aa90486ef031
|
||||
registration_upload: 0936ea94ab030ec332e29050d266c520e916731a052d05ce
|
||||
d7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e
|
||||
7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9e
|
||||
f53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0
|
||||
b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac150b
|
||||
4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb
|
||||
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
|
||||
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83
|
||||
b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911
|
||||
KE2: 9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d00
|
||||
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc3
|
||||
1127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151c
|
||||
eb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812fd9afddf98af8cd
|
||||
58067ca43b0633b6cadd0e9d987f89623fed4d3583bdf6910c425600e90dab3c6b351
|
||||
3188a465461a67f6bbc47aeba808f7f7e2c6d66f5c3271cd9960ecef2fe0d0f749498
|
||||
6fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad
|
||||
75865ee7efef95f4cb6c25d477f720502676f06a3b806da262139bf3fa76a1090b94d
|
||||
ac78bc3bc6f8747d5b35acf94eff3ec2ebe7d49b8cf16be64120b279fe92664e47be5
|
||||
da7e60f08f12e91192652f79
|
||||
KE3: 550e923829a544496d8316c490da2b979b78c730dd75be3a17f237a26432c19f
|
||||
bba54b6a0467b1c22ecbd6794bc5fa5b04215ba1ef974c6b090baa42c5bb984f
|
||||
export_key: 9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5
|
||||
070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c
|
||||
38
|
||||
session_key: fd2fdd07c1bcc88e81c1b1d1de5ad62dfdef1c0b8209ff9d671e1fac
|
||||
55ce9c34d381c1fb2703ff53a797f77daccbe33047ccc167b8105171e10ec962eea20
|
||||
3aa
|
||||
|
||||
C.1.4. OPAQUE-3DH Real Test Vector 4
|
||||
|
||||
C.1.4.1. Configuration
|
||||
|
||||
OPRF: ristretto255-SHA512
|
||||
Hash: SHA512
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA512
|
||||
MAC: HMAC-SHA512
|
||||
Group: curve25519
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
Nm: 64
|
||||
Nx: 64
|
||||
Nok: 32
|
||||
|
||||
C.1.4.2. Input Values
|
||||
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67
|
||||
a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1
|
||||
b
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cc
|
||||
a9bf44d6e0b
|
||||
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
|
||||
f612fdfc6d
|
||||
server_private_key: c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
|
||||
a7725bf0e79d564
|
||||
server_public_key: a41e28269b4e97a66468cc00c5a57753e192e1527669897706
|
||||
88aa90486ef031
|
||||
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
|
||||
138e3d4a1
|
||||
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
|
||||
0cae6a6cc
|
||||
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
|
||||
1091933541936304b
|
||||
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
|
||||
1e85ff80da12f982f
|
||||
blind_registration: c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee
|
||||
04baddb2034a70a
|
||||
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
|
||||
b0790308
|
||||
|
||||
C.1.4.3. Intermediate Values
|
||||
|
||||
client_public_key: 0936ea94ab030ec332e29050d266c520e916731a052d05ced7
|
||||
e0cfe751142b48
|
||||
auth_key: 7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d576
|
||||
4739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c
|
||||
randomized_password: 3a602c295a9c323d9362fe286f104567ed6862b25dbe30fa
|
||||
da844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8
|
||||
f3963fbb2e1
|
||||
envelope: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44
|
||||
d6e0bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d
|
||||
69061d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759
|
||||
handshake_secret: 13e7dc6afa5334b9dfffe26bee3caf744ef4add176caee464cd
|
||||
eb3d37303b90de35a8bf095df84471ac77d705f12fe232f1571de1d6a001d3e808998
|
||||
73a142dc
|
||||
server_mac_key: a58135acfb2bde92d506cf59119729a6404ad94eba294e4b52a63
|
||||
baf58cfe03f21bcf735222c7f2c27a60bd958be7f6aed50dc03a78f64e7ae4ac1ff07
|
||||
1b95aa
|
||||
client_mac_key: 1e1a8ba156aadc4a302f707d2193c9dab477b355f430d450dd407
|
||||
ce40dc75613f76ec33dec494f8a6bfdcf951eb060dac33e6572c693954fe92e33730c
|
||||
9ab0a2
|
||||
oprf_key: 62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea14
|
||||
6030a
|
||||
|
||||
C.1.4.4. Output Values
|
||||
|
||||
registration_request: 26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f65538
|
||||
1a67de02c59fd5436
|
||||
registration_response: 506e8f1b89c098fb89b5b6210a05f7898cafdaea221761
|
||||
e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e15276698977068
|
||||
8aa90486ef031
|
||||
registration_upload: 0936ea94ab030ec332e29050d266c520e916731a052d05ce
|
||||
d7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e
|
||||
7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9e
|
||||
f53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0
|
||||
bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d6906
|
||||
1d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759
|
||||
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
|
||||
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83
|
||||
b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911
|
||||
KE2: 9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d00
|
||||
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc3
|
||||
1127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151c
|
||||
eb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812699bff7663be3c5
|
||||
d59de94d8e7e58817c7da005b39c25d25555c929e1c5cf6c1b82837b1367c839aab56
|
||||
a422c0d97719426a79a16f9869cf852100597b23b5a071cd9960ecef2fe0d0f749498
|
||||
6fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad
|
||||
75865ee7efef95f4cb6c25d477f72050267cc22c87edbf3ecaca64cb33bc60dc3bfc5
|
||||
51e365f0d46a7fed0e09d96f9afbb48868f5bb3c3e05a86ed8c9476fc22c58306c5a2
|
||||
91be34388e09548ba9d70f39
|
||||
KE3: d16344e791c3f18594d22ba068984fa18ec1e9bead662b75f66826ffd627932f
|
||||
cd1ec40cd01dcf5f63f4055ebe45c7717a57a833aad360256cf1e1c20c0eae1c
|
||||
export_key: 9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5
|
||||
070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c
|
||||
38
|
||||
session_key: f6116d3aa0e4089a179713bad4d98ed5cb57e5443cae8d36ef78996f
|
||||
a60f3dc6e9fcdd63c001596b06dbc1285d80211035cc0e485506b3f7a650cbf78c5bf
|
||||
fc9
|
||||
|
||||
C.1.5. OPAQUE-3DH Real Test Vector 5
|
||||
|
||||
C.1.5.1. Configuration
|
||||
|
||||
OPRF: P256-SHA256
|
||||
Hash: SHA256
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA256
|
||||
MAC: HMAC-SHA256
|
||||
Group: P256_XMD:SHA-256_SSWU_RO_
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 32
|
||||
Npk: 33
|
||||
Nsk: 32
|
||||
Nm: 32
|
||||
Nx: 32
|
||||
Nok: 32
|
||||
|
||||
C.1.5.2. Input Values
|
||||
|
||||
oprf_seed: 62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0
|
||||
a8dce2
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebd
|
||||
cf65670e51f
|
||||
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
|
||||
f612fdfc6d
|
||||
server_private_key: c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
|
||||
a7725bf0e79d5e5
|
||||
server_public_key: 035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9
|
||||
f2092d6067784874
|
||||
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
|
||||
138e3d4a1
|
||||
client_nonce: ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f
|
||||
91fdaeeb1
|
||||
client_keyshare_seed: 633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d
|
||||
7e74d783b7cfab8dc
|
||||
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
|
||||
1e85ff80da12f982f
|
||||
blind_registration: 411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34
|
||||
ca527d597078153
|
||||
blind_login: c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9
|
||||
942256c1
|
||||
|
||||
C.1.5.3. Intermediate Values
|
||||
|
||||
client_public_key: 03b218507d978c3db570ca994aaf36695a731ddb2db272c817
|
||||
f79746fc37ae5214
|
||||
auth_key: 5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd
|
||||
0987b
|
||||
randomized_password: 06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa3
|
||||
19151b9382258fb0
|
||||
envelope: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6567
|
||||
0e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad67b8
|
||||
handshake_secret: 83a932431a8f25bad042f008efa2b07c6cd0faa8285f335b636
|
||||
3546a9f9b235f
|
||||
server_mac_key: 13e928581febfad28855e3e7f03306d61bd69489686f621535d44
|
||||
a1365b73b0d
|
||||
client_mac_key: afdc53910c25183b08b930e6953c35b3466276736d9de2e9c5efa
|
||||
f150f4082c5
|
||||
oprf_key: 2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66
|
||||
f7f31
|
||||
|
||||
C.1.5.4. Output Values
|
||||
|
||||
registration_request: 029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f507
|
||||
0d26a5315a1b50229f8
|
||||
registration_response: 0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2
|
||||
f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd
|
||||
9f2092d6067784874
|
||||
registration_upload: 03b218507d978c3db570ca994aaf36695a731ddb2db272c8
|
||||
17f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea0
|
||||
29148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6
|
||||
5670e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad6
|
||||
7b8
|
||||
KE1: 037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e
|
||||
07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022
|
||||
ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6
|
||||
KE2: 0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc147648
|
||||
1138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0
|
||||
c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe
|
||||
0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae26837b6ce688bf
|
||||
9af2542f04eec9ab96a1b9328812dc2f5c89182ed47fead61f09f71cd9960ecef2fe0
|
||||
d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf33
|
||||
7bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b50a73b145bc87a157e8c58c0
|
||||
342e2047ee22ae37b63db17e0a82a30fcc4ecf7b
|
||||
KE3: e97cab4433aa39d598e76f13e768bba61c682947bdcf9936035e8a3a3ebfb66e
|
||||
export_key: c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a
|
||||
853c64b
|
||||
session_key: 484ad345715ccce138ca49e4ea362c6183f0949aaaa1125dc3bc3f80
|
||||
876e7cd1
|
||||
|
||||
C.1.6. OPAQUE-3DH Real Test Vector 6
|
||||
|
||||
C.1.6.1. Configuration
|
||||
|
||||
OPRF: P256-SHA256
|
||||
Hash: SHA256
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA256
|
||||
MAC: HMAC-SHA256
|
||||
Group: P256_XMD:SHA-256_SSWU_RO_
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 32
|
||||
Npk: 33
|
||||
Nsk: 32
|
||||
Nm: 32
|
||||
Nx: 32
|
||||
Nok: 32
|
||||
|
||||
C.1.6.2. Input Values
|
||||
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: 62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0
|
||||
a8dce2
|
||||
credential_identifier: 31323334
|
||||
password: 436f7272656374486f72736542617474657279537461706c65
|
||||
envelope_nonce: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebd
|
||||
cf65670e51f
|
||||
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
|
||||
f612fdfc6d
|
||||
server_private_key: c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
|
||||
a7725bf0e79d5e5
|
||||
server_public_key: 035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9
|
||||
f2092d6067784874
|
||||
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
|
||||
138e3d4a1
|
||||
client_nonce: ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f
|
||||
91fdaeeb1
|
||||
client_keyshare_seed: 633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d
|
||||
7e74d783b7cfab8dc
|
||||
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
|
||||
1e85ff80da12f982f
|
||||
blind_registration: 411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34
|
||||
ca527d597078153
|
||||
blind_login: c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9
|
||||
942256c1
|
||||
|
||||
C.1.6.3. Intermediate Values
|
||||
|
||||
client_public_key: 03b218507d978c3db570ca994aaf36695a731ddb2db272c817
|
||||
f79746fc37ae5214
|
||||
auth_key: 5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd
|
||||
0987b
|
||||
randomized_password: 06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa3
|
||||
19151b9382258fb0
|
||||
envelope: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6567
|
||||
0e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0971
|
||||
handshake_secret: 80bdcc498f22de492e90ee8101fcc7c101e158dd49c77f7c283
|
||||
816ae329ed62f
|
||||
server_mac_key: 0f82432fbdb5b90daf27a91a3acc42299a9590dba1b77932c2207
|
||||
b4cb3d4a157
|
||||
client_mac_key: 7f629eb0b1b69979b07ca1f564b3e92ed22f07569fd1d11725d93
|
||||
e46731fbe71
|
||||
oprf_key: 2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66
|
||||
f7f31
|
||||
|
||||
C.1.6.4. Output Values
|
||||
|
||||
registration_request: 029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f507
|
||||
0d26a5315a1b50229f8
|
||||
registration_response: 0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2
|
||||
f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd
|
||||
9f2092d6067784874
|
||||
registration_upload: 03b218507d978c3db570ca994aaf36695a731ddb2db272c8
|
||||
17f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea0
|
||||
29148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6
|
||||
5670e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0
|
||||
971
|
||||
KE1: 037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e
|
||||
07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022
|
||||
ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6
|
||||
KE2: 0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc147648
|
||||
1138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0
|
||||
c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe
|
||||
0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae268d7f10604202
|
||||
1c80300e4c6f585980cf39fc51a4a6bba41b0729f9b240c729e5671cd9960ecef2fe0
|
||||
d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf33
|
||||
7bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b84922c7e5d074838a8f27859
|
||||
2c53f61fb59f031e85ad480c0c71086b871e1b24
|
||||
KE3: 46833578cee137775f6be3f01b80748daac5a694101ad0e9e7025480552da56a
|
||||
export_key: c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a
|
||||
853c64b
|
||||
session_key: 27766fabd8dd88ff37fbd0ef1a491e601d10d9f016c2b28c4bd1b0fb
|
||||
7511a3c3
|
||||
|
||||
C.2. Fake Test Vectors
|
||||
|
||||
C.2.1. OPAQUE-3DH Fake Test Vector 1
|
||||
|
||||
C.2.1.1. Configuration
|
||||
|
||||
OPRF: ristretto255-SHA512
|
||||
Hash: SHA512
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA512
|
||||
MAC: HMAC-SHA512
|
||||
Group: ristretto255
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
Nm: 64
|
||||
Nx: 64
|
||||
Nok: 32
|
||||
|
||||
C.2.1.2. Input Values
|
||||
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: 743fc168d1f826ad43738933e5adb23da6fb95f95a1b069f0daa0522d0
|
||||
a78b617f701fc6aa46d3e7981e70de7765dfcd6b1e13e3369a582eb8dc456b10aa53b
|
||||
0
|
||||
credential_identifier: 31323334
|
||||
masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61
|
||||
9e27b6e5a6
|
||||
client_private_key: 2b98980aa95ab53a0f39f0291903d2fdf04b00c167f081416
|
||||
9922df873002409
|
||||
client_public_key: 84f43f9492e19c22d8bdaa4447cc3d4db1cdb5427a9f852c47
|
||||
07921212c36251
|
||||
server_private_key: c788585ae8b5ba2942b693b849be0c0426384e41977c18d2e
|
||||
81fbe30fd7c9f06
|
||||
server_public_key: 825f832667480f08b0c9069da5083ac4d0e9ee31b49c4e0310
|
||||
031fea04d52966
|
||||
server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813
|
||||
055ae2d12
|
||||
client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276
|
||||
d1e15bdeb4c355e94
|
||||
server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc
|
||||
5b4f6b62df07f78c2
|
||||
masking_key: 39ebd51f0e39a07a1c2d2431995b0399bca9996c5d10014d6ebab445
|
||||
3dc10ce5cef38ed3df6e56bfff40c2d8dd4671c2b4cf63c3d54860f31fe40220d690b
|
||||
b71
|
||||
KE1: b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be448905
|
||||
42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ab641d
|
||||
7f20a245a09f1d4dbb6e301661af7f352beb0791d055e48d3645232f77f
|
||||
|
||||
C.2.1.3. Output Values
|
||||
|
||||
KE2: 928f79ad8df21963e91411b9f55165ba833dea918f441db967cdc09521d22925
|
||||
9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a632b5a
|
||||
b1bff96636144faa4f9f9afaac75dd88ea99cf5175902ae3f3b2195693f165f11929b
|
||||
a510a5978e64dcdabecbd7ee1e4380ce270e58fea58e6462d92964a1aaef72698bca1
|
||||
c673baeb04cc2bf7de5f3c2f5553464552d3a0f7698a9ca7f9c5e70c6cb1f706b2f17
|
||||
5ab9d04bbd13926e816b6811a50b4aafa9799d5ed7971e10f6eeab2a7a420bf09da9b
|
||||
27a4639645622c46358de9cf7ae813055ae2d1298251c5ba55f6b0b2d58d9ff0c88fe
|
||||
4176484be62a96db6e2a8c4d431bd1bf27fe6c1d0537603835217d42ebf7b25819827
|
||||
32e74892fd28211b31ed33863f0beaf75ba6f59474c0aaf9d78a60a9b2f4cd24d7ab5
|
||||
4131b3c8efa192df6b72db4c
|
||||
|
||||
C.2.2. OPAQUE-3DH Fake Test Vector 2
|
||||
|
||||
C.2.2.1. Configuration
|
||||
|
||||
OPRF: ristretto255-SHA512
|
||||
Hash: SHA512
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA512
|
||||
MAC: HMAC-SHA512
|
||||
Group: curve25519
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 64
|
||||
Npk: 32
|
||||
Nsk: 32
|
||||
Nm: 64
|
||||
Nx: 64
|
||||
Nok: 32
|
||||
|
||||
C.2.2.2. Input Values
|
||||
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: 66e650652a8266b2205f31fdd68adeb739a05b5e650b19e7edc75e734a
|
||||
1296d6088188ca46c31ae8ccbd42a52ed338c06e53645387a7efbc94b6a0449526155
|
||||
e
|
||||
credential_identifier: 31323334
|
||||
masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61
|
||||
9e27b6e5a6
|
||||
client_private_key: 288bf63470199221847bb035d99f96531adf8badd14cb1571
|
||||
b48f7a506649660
|
||||
client_public_key: 3c64a3153854cc9f0c23aab3c1a19106ec8bab4730736d1d00
|
||||
3880a1d5a59005
|
||||
server_private_key: 30fbe7e830be1fe8d2187c97414e3826040cbe49b893b6422
|
||||
9bab5e85a588846
|
||||
server_public_key: 78b3040047ff26572a7619617601a61b9c81899bee92f00cfc
|
||||
aa5eed96863555
|
||||
server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813
|
||||
055ae2d12
|
||||
client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276
|
||||
d1e15bdeb4c355e94
|
||||
server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc
|
||||
5b4f6b62df07f78c2
|
||||
masking_key: 79ad2621b0757a447dff7108a8ae20a068ce67872095620f415ea611
|
||||
c9dcc04972fa359538cd2fd6528775ca775487b2b56db642049b8a90526b975a38484
|
||||
c6a
|
||||
KE1: b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be448905
|
||||
42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ac059b
|
||||
7ba2aec863933ae48816360c7a9022e83d822704f3b0b86c0502a66e574
|
||||
|
||||
C.2.2.3. Output Values
|
||||
|
||||
KE2: 6606b6fedbb33f19a81a1feb5149c600fe77252f58acd3080d7504d3dad4922f
|
||||
9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a67db39
|
||||
8c0f65d8c298eac430abdae4c80e82b552fb940c00f0cbcea853c0f96c1c15099f3d4
|
||||
b0e83ecc249613116d605b8d77bb68bdf76994c2bc507e2dcae4176f00afed68ad25c
|
||||
f3040a0e991acece31ca532117f5c12816997372ff031ad04ebcdce06c501da24e7b4
|
||||
db95343456e2ed260895ec362694230a1fa20e24a9c71e10f6eeab2a7a420bf09da9b
|
||||
27a4639645622c46358de9cf7ae813055ae2d122d9055eb8f83e1b497370adad5cc2a
|
||||
417bf9be436a792def0c7b7ccb92b9e275d7c663104ea4655bd70570d975c05351655
|
||||
d55fbfb392286edb55600a23b55ce18f8c60e0d1960c960412dd08eabc81ba7ca8ae2
|
||||
b04aad65462321f51c298010
|
||||
|
||||
C.2.3. OPAQUE-3DH Fake Test Vector 3
|
||||
|
||||
C.2.3.1. Configuration
|
||||
|
||||
OPRF: P256-SHA256
|
||||
Hash: SHA256
|
||||
KSF: Identity
|
||||
KDF: HKDF-SHA256
|
||||
MAC: HMAC-SHA256
|
||||
Group: P256_XMD:SHA-256_SSWU_RO_
|
||||
Context: 4f50415155452d504f43
|
||||
Nh: 32
|
||||
Npk: 33
|
||||
Nsk: 32
|
||||
Nm: 32
|
||||
Nx: 32
|
||||
Nok: 32
|
||||
|
||||
C.2.3.2. Input Values
|
||||
|
||||
client_identity: 616c696365
|
||||
server_identity: 626f62
|
||||
oprf_seed: bb1cd59e16ac09bc0cb6d528541695d7eba2239b1613a3db3ade77b362
|
||||
80f725
|
||||
credential_identifier: 31323334
|
||||
masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61
|
||||
9e27b6e5a6
|
||||
client_private_key: d423b87899fc61d014fc8330a4e26190fcfa470a3afe59243
|
||||
24294af7dbbc1dd
|
||||
client_public_key: 03b81708eae026a9370616c22e1e8542fe9dbebd36ce8a2661
|
||||
b708e9628f4a57fc
|
||||
server_private_key: 34fbe7e830be1fe8d2187c97414e3826040cbe49b893b6422
|
||||
9bab5e85a5888c7
|
||||
server_public_key: 0221e034c0e202fe883dcfc96802a7624166fed4cfcab4ae30
|
||||
cf5f3290d01c88bf
|
||||
server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813
|
||||
055ae2d12
|
||||
client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276
|
||||
d1e15bdeb4c355e94
|
||||
server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc
|
||||
5b4f6b62df07f78c2
|
||||
masking_key: caecc6ccb4cae27cb54d8f3a1af1bac52a3d53107ce08497cdd362b1
|
||||
992e4e5e
|
||||
KE1: 0396875da2b4f7749bba411513aea02dc514a48d169d8a9531bd61d3af3fa9ba
|
||||
ae42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0a021
|
||||
47a6583983cc9973b5082db5f5070890cb373d70f7ac1b41ed2305361009784
|
||||
|
||||
C.2.3.3. Output Values
|
||||
|
||||
KE2: 0201198dcd13f9792eb75dcfa815f61b049abfe2e3e9456d4bbbceec5f442efd
|
||||
049c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6fac
|
||||
da65ce0a97b9085e7af07f61fd3fdd046d257cbf2183ce8766090b8041a8bf28d79dd
|
||||
4c9031ddc75bb6ddb4c291e639937840e3d39fc0d5a3d6e7723c09f7945df485bcf9a
|
||||
efe3fe82d149e84049e259bb5b33d6a2ff3b25e4bfb7eff0962821e10f6eeab2a7a42
|
||||
0bf09da9b27a4639645622c46358de9cf7ae813055ae2d12023f82bbb24e75b8683fd
|
||||
13b843cd566efae996cd0016cffdcc24ee2bc937d026f80144878749a69565b433c10
|
||||
40aff67e94f79345de888a877422b9bbe21ec329
|
||||
"#;
|
||||
@@ -0,0 +1,688 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
use core::ops::Add;
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
|
||||
use crate::envelope::EnvelopeLen;
|
||||
use crate::errors::*;
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::NonceLen;
|
||||
use crate::key_exchange::{Deserialize, Ke1MessageLen, Ke2MessageLen, KeyExchange, Serialize};
|
||||
use crate::ksf::Identity;
|
||||
use crate::messages::{
|
||||
CredentialRequestLen, CredentialResponseLen, CredentialResponseWithoutKeLen,
|
||||
RegistrationResponseLen, RegistrationUploadLen,
|
||||
};
|
||||
use crate::opaque::*;
|
||||
use crate::tests::decode;
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
use crate::*;
|
||||
use digest::OutputSizeUser;
|
||||
use generic_array::typenum::Sum;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::Rng;
|
||||
use rand::rand_core::UnwrapErr;
|
||||
use rand::rngs::SysRng;
|
||||
use serde_json::Value;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug)]
|
||||
pub struct OpaqueTestVectorParameters {
|
||||
pub dummy_public_key: Vec<u8>,
|
||||
pub dummy_masking_key: Vec<u8>,
|
||||
pub context: Vec<u8>,
|
||||
#[allow(dead_code)] // client_private_key is not tested in the test vectors
|
||||
pub client_private_key: Option<Vec<u8>>,
|
||||
pub client_keyshare_seed: Vec<u8>,
|
||||
pub server_public_key: Vec<u8>,
|
||||
pub server_private_key: Vec<u8>,
|
||||
pub server_keyshare_seed: Vec<u8>,
|
||||
pub client_identity: Option<Vec<u8>>,
|
||||
pub server_identity: Option<Vec<u8>>,
|
||||
pub credential_identifier: Vec<u8>,
|
||||
pub password: Vec<u8>,
|
||||
pub blind_registration: Vec<u8>,
|
||||
pub oprf_seed: Vec<u8>,
|
||||
pub masking_nonce: Vec<u8>,
|
||||
pub envelope_nonce: Vec<u8>,
|
||||
pub client_nonce: Vec<u8>,
|
||||
pub server_nonce: 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>,
|
||||
pub auth_key: Vec<u8>,
|
||||
pub randomized_pwd: Vec<u8>,
|
||||
pub handshake_secret: Vec<u8>,
|
||||
pub server_mac_key: Vec<u8>,
|
||||
pub client_mac_key: Vec<u8>,
|
||||
pub oprf_key: Vec<u8>,
|
||||
}
|
||||
|
||||
macro_rules! parse {
|
||||
( $v:ident, $s:expr ) => {
|
||||
parse_default!($v, $s, vec![])
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! parse_default {
|
||||
( $v:ident, $s:expr, $d:expr ) => {
|
||||
match decode(&$v, $s) {
|
||||
Some(x) => x,
|
||||
None => $d,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn populate_test_vectors<CS: CipherSuite>(values: &Value) -> OpaqueTestVectorParameters {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
|
||||
OpaqueTestVectorParameters {
|
||||
dummy_public_key: {
|
||||
decode(values, "client_public_key").unwrap_or_else(|| {
|
||||
KeGroup::<CS>::serialize_sk(&KeGroup::<CS>::random_sk(&mut UnwrapErr(SysRng)))
|
||||
.to_vec()
|
||||
})
|
||||
},
|
||||
dummy_masking_key: {
|
||||
match decode(values, "masking_key") {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
let mut bytes =
|
||||
GenericArray::<u8, <OprfHash<CS> as OutputSizeUser>::OutputSize>::default();
|
||||
rng.fill_bytes(&mut bytes);
|
||||
bytes.to_vec()
|
||||
}
|
||||
}
|
||||
},
|
||||
context: parse!(values, "Context"),
|
||||
client_private_key: decode(values, "client_private_key"),
|
||||
client_keyshare_seed: parse!(values, "client_keyshare_seed"),
|
||||
server_public_key: parse!(values, "server_public_key"),
|
||||
server_private_key: parse!(values, "server_private_key"),
|
||||
server_keyshare_seed: parse!(values, "server_keyshare_seed"),
|
||||
client_identity: decode(values, "client_identity"),
|
||||
server_identity: decode(values, "server_identity"),
|
||||
credential_identifier: parse!(values, "credential_identifier"),
|
||||
password: parse!(values, "password"),
|
||||
blind_registration: parse!(values, "blind_registration"),
|
||||
oprf_seed: parse!(values, "oprf_seed"),
|
||||
masking_nonce: parse!(values, "masking_nonce"),
|
||||
envelope_nonce: parse!(values, "envelope_nonce"),
|
||||
client_nonce: parse!(values, "client_nonce"),
|
||||
server_nonce: parse!(values, "server_nonce"),
|
||||
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"),
|
||||
auth_key: parse!(values, "auth_key"),
|
||||
randomized_pwd: parse!(values, "randomized_password"),
|
||||
handshake_secret: parse!(values, "handshake_secret"),
|
||||
server_mac_key: parse!(values, "server_mac_key"),
|
||||
client_mac_key: parse!(values, "client_mac_key"),
|
||||
oprf_key: parse!(values, "oprf_key"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_password_file_bytes<CS: CipherSuite>(parameters: &OpaqueTestVectorParameters) -> Vec<u8>
|
||||
where
|
||||
// RegistrationUpload: (KePk + Hash) + Envelope
|
||||
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
|
||||
ArrayLength + Add<EnvelopeLen<CS>>,
|
||||
RegistrationUploadLen<CS>: ArrayLength,
|
||||
// ServerRegistration = RegistrationUpload
|
||||
{
|
||||
let password_file = ServerRegistration::<CS>::finish(
|
||||
RegistrationUpload::deserialize(¶meters.registration_upload).unwrap(),
|
||||
);
|
||||
|
||||
password_file.serialize().to_vec()
|
||||
}
|
||||
|
||||
macro_rules! json_to_test_vectors {
|
||||
( $v:ident, $vector_type:expr, $cs:expr, $cs_ty:ty) => {
|
||||
$v[$vector_type]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|x| {
|
||||
if let Some(val) = x.get($cs) {
|
||||
Some(populate_test_vectors::<$cs_ty>(val))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<OpaqueTestVectorParameters>>()
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tests() -> Result<(), ProtocolError> {
|
||||
let rfc: Value =
|
||||
serde_json::from_str(super::parser::rfc_to_json(super::rfc9807_vectors::VECTORS).as_str())
|
||||
.expect("Could not parse json");
|
||||
|
||||
std::eprintln!("{}", serde_json::to_string_pretty(&rfc).unwrap());
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
{
|
||||
struct Ristretto255Sha512NoKsf;
|
||||
impl CipherSuite for Ristretto255Sha512NoKsf {
|
||||
type OprfCs = Ristretto255;
|
||||
type KeyExchange = TripleDh<Ristretto255, sha2::Sha512>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
let ristretto_real_tvs = json_to_test_vectors!(
|
||||
rfc,
|
||||
"Real",
|
||||
"ristretto255-SHA512, ristretto255",
|
||||
Ristretto255Sha512NoKsf
|
||||
);
|
||||
|
||||
let ristretto_fake_tvs = json_to_test_vectors!(
|
||||
rfc,
|
||||
"Fake",
|
||||
"ristretto255-SHA512, ristretto255",
|
||||
Ristretto255Sha512NoKsf
|
||||
);
|
||||
|
||||
assert!(
|
||||
!(ristretto_real_tvs.is_empty() || ristretto_fake_tvs.is_empty()),
|
||||
"Parsing error"
|
||||
);
|
||||
|
||||
// There should be 2 real test vectors and 1 fake test vector
|
||||
assert_eq!(ristretto_real_tvs.len(), 2);
|
||||
assert_eq!(ristretto_fake_tvs.len(), 1);
|
||||
|
||||
test_registration_request::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_registration_response::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_registration_upload::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_ke1::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_ke2::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_ke3::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_server_login_finish::<Ristretto255Sha512NoKsf>(&ristretto_real_tvs)?;
|
||||
test_fake_vectors::<Ristretto255Sha512NoKsf>(&ristretto_fake_tvs)?;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
|
||||
{
|
||||
struct Ristretto255Sha512Curve25519NoKsf;
|
||||
impl CipherSuite for Ristretto255Sha512Curve25519NoKsf {
|
||||
type OprfCs = crate::Ristretto255;
|
||||
type KeyExchange = TripleDh<crate::Curve25519, sha2::Sha512>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
let ristretto_real_tvs = json_to_test_vectors!(
|
||||
rfc,
|
||||
"Real",
|
||||
"ristretto255-SHA512, curve25519",
|
||||
Ristretto255Sha512Curve25519NoKsf
|
||||
);
|
||||
|
||||
let ristretto_fake_tvs = json_to_test_vectors!(
|
||||
rfc,
|
||||
"Fake",
|
||||
"ristretto255-SHA512, curve25519",
|
||||
Ristretto255Sha512Curve25519NoKsf
|
||||
);
|
||||
|
||||
assert!(
|
||||
!(ristretto_real_tvs.is_empty() || ristretto_fake_tvs.is_empty()),
|
||||
"Parsing error"
|
||||
);
|
||||
|
||||
// There should be 2 real test vectors and 1 fake test vector
|
||||
assert_eq!(ristretto_real_tvs.len(), 2);
|
||||
assert_eq!(ristretto_fake_tvs.len(), 1);
|
||||
|
||||
test_registration_request::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_registration_response::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_registration_upload::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_ke1::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_ke2::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_ke3::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_server_login_finish::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_real_tvs)?;
|
||||
test_fake_vectors::<Ristretto255Sha512Curve25519NoKsf>(&ristretto_fake_tvs)?;
|
||||
}
|
||||
|
||||
struct P256Sha256NoKsf;
|
||||
impl CipherSuite for P256Sha256NoKsf {
|
||||
type OprfCs = p256::NistP256;
|
||||
type KeyExchange = TripleDh<p256::NistP256, sha2::Sha256>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
let p256_real_tvs = json_to_test_vectors!(
|
||||
rfc,
|
||||
"Real",
|
||||
"P256-SHA256, P256_XMD:SHA-256_SSWU_RO_",
|
||||
P256Sha256NoKsf
|
||||
);
|
||||
let p256_fake_tvs = json_to_test_vectors!(
|
||||
rfc,
|
||||
"Fake",
|
||||
"P256-SHA256, P256_XMD:SHA-256_SSWU_RO_",
|
||||
P256Sha256NoKsf
|
||||
);
|
||||
|
||||
assert!(
|
||||
!(p256_real_tvs.is_empty() || p256_fake_tvs.is_empty()),
|
||||
"Parsing error"
|
||||
);
|
||||
|
||||
// There should be 2 real test vectors and 1 fake test vector
|
||||
assert_eq!(p256_real_tvs.len(), 2);
|
||||
assert_eq!(p256_fake_tvs.len(), 1);
|
||||
|
||||
test_registration_request::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_registration_response::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_registration_upload::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_ke1::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_ke2::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_ke3::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_server_login_finish::<P256Sha256NoKsf>(&p256_real_tvs)?;
|
||||
test_fake_vectors::<P256Sha256NoKsf>(&p256_fake_tvs)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_registration_request<CS: CipherSuite>(
|
||||
tvs: &[OpaqueTestVectorParameters],
|
||||
) -> Result<(), ProtocolError> {
|
||||
for parameters in tvs {
|
||||
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<CS>::start(&mut rng, ¶meters.password)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.registration_request),
|
||||
hex::encode(client_registration_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_registration_response<CS: CipherSuite>(
|
||||
tvs: &[OpaqueTestVectorParameters],
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
// RegistrationResponse: KgPk + KePk
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen>,
|
||||
RegistrationResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
for parameters in tvs {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
parameters.oprf_seed.as_slice(),
|
||||
¶meters.server_private_key,
|
||||
¶meters.dummy_public_key,
|
||||
]
|
||||
.concat(),
|
||||
)?;
|
||||
let server_registration_start_result = ServerRegistration::<CS>::start(
|
||||
&server_setup,
|
||||
RegistrationRequest::deserialize(¶meters.registration_request).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_public_key),
|
||||
hex::encode(server_setup.keypair().public().serialize()),
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.oprf_key),
|
||||
hex::encode(server_registration_start_result.oprf_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.registration_response),
|
||||
hex::encode(server_registration_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_registration_upload<CS: CipherSuite>(
|
||||
tvs: &[OpaqueTestVectorParameters],
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
// RegistrationUpload: (KePk + Hash) + Envelope
|
||||
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
|
||||
ArrayLength + Add<EnvelopeLen<CS>>,
|
||||
RegistrationUploadLen<CS>: ArrayLength,
|
||||
{
|
||||
for parameters in tvs {
|
||||
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<CS>::start(&mut rng, ¶meters.password)?;
|
||||
|
||||
let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce.to_vec());
|
||||
let result = client_registration_start_result.state.finish(
|
||||
&mut finish_registration_rng,
|
||||
¶meters.password,
|
||||
RegistrationResponse::deserialize(¶meters.registration_response).unwrap(),
|
||||
ClientRegistrationFinishParameters::new(
|
||||
Identifiers {
|
||||
client: parameters.client_identity.as_deref(),
|
||||
server: parameters.server_identity.as_deref(),
|
||||
},
|
||||
None,
|
||||
),
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.auth_key),
|
||||
hex::encode(result.auth_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.randomized_pwd),
|
||||
hex::encode(result.randomized_pwd)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.registration_upload),
|
||||
hex::encode(result.message.serialize())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.export_key),
|
||||
hex::encode(result.export_key)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_ke1<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
|
||||
where
|
||||
// CredentialRequest: KgPk + Ke1Message
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
|
||||
CredentialRequestLen<CS>: ArrayLength,
|
||||
{
|
||||
for parameters in tvs {
|
||||
let client_login_start = [
|
||||
parameters.blind_login.as_slice(),
|
||||
¶meters.client_keyshare_seed,
|
||||
¶meters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result =
|
||||
ClientLogin::<CS>::start(&mut client_login_start_rng, ¶meters.password)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE1),
|
||||
hex::encode(client_login_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
|
||||
// RegistrationUpload: (KePk + Hash) + Envelope
|
||||
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
|
||||
ArrayLength + Add<EnvelopeLen<CS>>,
|
||||
RegistrationUploadLen<CS>: ArrayLength,
|
||||
// ServerRegistration = RegistrationUpload
|
||||
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength + Add<MaskedResponseLen<CS>>,
|
||||
CredentialResponseWithoutKeLen<CS>: ArrayLength,
|
||||
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
|
||||
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
|
||||
CredentialResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
for parameters in tvs {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
parameters.oprf_seed.as_slice(),
|
||||
¶meters.server_private_key,
|
||||
¶meters.dummy_public_key,
|
||||
]
|
||||
.concat(),
|
||||
)?;
|
||||
|
||||
let record =
|
||||
ServerRegistration::<CS>::deserialize(&get_password_file_bytes::<CS>(parameters))?;
|
||||
|
||||
let mut server_keyshare_seed_and_nonce_rng = CycleRng::new(
|
||||
[
|
||||
parameters.dummy_masking_key.as_slice(),
|
||||
¶meters.masking_nonce,
|
||||
¶meters.server_keyshare_seed,
|
||||
¶meters.server_nonce,
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_keyshare_seed_and_nonce_rng,
|
||||
&server_setup,
|
||||
Some(record),
|
||||
CredentialRequest::<CS>::deserialize(¶meters.KE1).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
ServerLoginParameters {
|
||||
context: Some(¶meters.context),
|
||||
identifiers: Identifiers {
|
||||
client: parameters.client_identity.as_deref(),
|
||||
server: parameters.server_identity.as_deref(),
|
||||
},
|
||||
},
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.handshake_secret),
|
||||
hex::encode(server_login_start_result.handshake_secret)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_mac_key),
|
||||
hex::encode(server_login_start_result.server_mac_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.oprf_key),
|
||||
hex::encode(server_login_start_result.oprf_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE2),
|
||||
hex::encode(server_login_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize + Serialize,
|
||||
<CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
|
||||
{
|
||||
for parameters in tvs {
|
||||
let client_login_start = [
|
||||
parameters.blind_login.as_slice(),
|
||||
¶meters.client_keyshare_seed,
|
||||
¶meters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let client_login_start_result =
|
||||
ClientLogin::<CS>::start(&mut client_login_start_rng, ¶meters.password)?;
|
||||
|
||||
let client_login_finish_result = client_login_start_result.state.finish(
|
||||
&mut UnwrapErr(SysRng),
|
||||
¶meters.password,
|
||||
CredentialResponse::<CS>::deserialize(¶meters.KE2)?,
|
||||
ClientLoginFinishParameters::new(
|
||||
Some(¶meters.context.clone()),
|
||||
Identifiers {
|
||||
client: parameters.client_identity.as_deref(),
|
||||
server: parameters.server_identity.as_deref(),
|
||||
},
|
||||
None,
|
||||
),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.session_key),
|
||||
hex::encode(&client_login_finish_result.session_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.handshake_secret),
|
||||
hex::encode(&client_login_finish_result.handshake_secret)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_mac_key),
|
||||
hex::encode(&client_login_finish_result.client_mac_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(())
|
||||
}
|
||||
|
||||
fn test_server_login_finish<CS: CipherSuite>(
|
||||
tvs: &[OpaqueTestVectorParameters],
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
|
||||
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
|
||||
// RegistrationUpload: (KePk + Hash) + Envelope
|
||||
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
|
||||
ArrayLength + Add<EnvelopeLen<CS>>,
|
||||
RegistrationUploadLen<CS>: ArrayLength,
|
||||
// ServerRegistration = RegistrationUpload
|
||||
{
|
||||
for parameters in tvs {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
parameters.oprf_seed.as_slice(),
|
||||
¶meters.server_private_key,
|
||||
¶meters.dummy_public_key,
|
||||
]
|
||||
.concat(),
|
||||
)?;
|
||||
|
||||
let record =
|
||||
ServerRegistration::<CS>::deserialize(&get_password_file_bytes::<CS>(parameters))?;
|
||||
|
||||
let mut server_keyshare_seed_and_nonce_rng = CycleRng::new(
|
||||
[
|
||||
parameters.dummy_masking_key.as_slice(),
|
||||
¶meters.masking_nonce,
|
||||
¶meters.server_keyshare_seed,
|
||||
¶meters.server_nonce,
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_keyshare_seed_and_nonce_rng,
|
||||
&server_setup,
|
||||
Some(record),
|
||||
CredentialRequest::<CS>::deserialize(¶meters.KE1).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
ServerLoginParameters {
|
||||
context: Some(¶meters.context),
|
||||
identifiers: Identifiers {
|
||||
client: parameters.client_identity.as_deref(),
|
||||
server: parameters.server_identity.as_deref(),
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
let server_login_result = server_login_start_result.state.finish(
|
||||
CredentialFinalization::deserialize(¶meters.KE3)?,
|
||||
ServerLoginParameters {
|
||||
context: Some(¶meters.context),
|
||||
identifiers: Identifiers {
|
||||
client: parameters.client_identity.as_deref(),
|
||||
server: parameters.server_identity.as_deref(),
|
||||
},
|
||||
},
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.session_key),
|
||||
hex::encode(&server_login_result.session_key)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_fake_vectors<CS: CipherSuite>(
|
||||
tvs: &[OpaqueTestVectorParameters],
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
|
||||
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength + Add<MaskedResponseLen<CS>>,
|
||||
CredentialResponseWithoutKeLen<CS>: ArrayLength,
|
||||
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
|
||||
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
|
||||
CredentialResponseLen<CS>: ArrayLength,
|
||||
{
|
||||
for parameters in tvs {
|
||||
let server_setup = ServerSetup::<CS>::deserialize(
|
||||
&[
|
||||
parameters.oprf_seed.as_slice(),
|
||||
¶meters.server_private_key,
|
||||
¶meters.dummy_public_key,
|
||||
]
|
||||
.concat(),
|
||||
)?;
|
||||
|
||||
let mut server_keyshare_seed_and_nonce_rng = CycleRng::new(
|
||||
[
|
||||
parameters.dummy_masking_key.as_slice(),
|
||||
¶meters.masking_nonce,
|
||||
¶meters.server_keyshare_seed,
|
||||
¶meters.server_nonce,
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let server_login_start_result = ServerLogin::<CS>::start(
|
||||
&mut server_keyshare_seed_and_nonce_rng,
|
||||
&server_setup,
|
||||
None,
|
||||
CredentialRequest::<CS>::deserialize(¶meters.KE1).unwrap(),
|
||||
¶meters.credential_identifier,
|
||||
ServerLoginParameters {
|
||||
context: Some(¶meters.context),
|
||||
identifiers: Identifiers {
|
||||
client: parameters.client_identity.as_deref(),
|
||||
server: parameters.server_identity.as_deref(),
|
||||
},
|
||||
},
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE2),
|
||||
hex::encode(server_login_start_result.message.serialize())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,157 +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::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>,
|
||||
unblinded_element: Vec<u8>,
|
||||
info: 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": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
|
||||
"input": "00",
|
||||
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
|
||||
"blinded_element": "5cccd309ec729aebe398c53e19c0ab09c24a29f01036960bdad109852e7bdb44",
|
||||
"evaluation_element": "86bd5eeabf29a87cb4a5c7207cb3ade5297e65f9b74c979bd3551891f4b21515",
|
||||
"unblinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
|
||||
"info": "4f505246207465737420766563746f7273",
|
||||
"output": "0bb570873cc0402ca38f1a2c395301f2a3627616e305f2bc54bb08c3f6ea9871eb71074e52e36b90778ba7c3e3429ef7170245c9e01647f3827fdef84d3ba930"
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
{
|
||||
"sksm": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
|
||||
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
|
||||
"blind": "ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e3263503",
|
||||
"blinded_element": "227d63ca69e93bd062193c1e97fff3d5ebf628f646009d77c4e22ba6429be154",
|
||||
"evaluation_element": "063b91a12e7cbb98dfeb75d8a7eeb83aacf9fd6df7e0b4197466fb77a27fa631",
|
||||
"unblinded_element": "804ec6774764ed50a0bbad0a5f477aa04df7323acab8f98ca6e468b7790bca4c",
|
||||
"info": "4f505246207465737420766563746f7273",
|
||||
"output": "af7cc264dbc96a6b898ba0fa33bfa9e1407bf1dcfbf8772204d470d4458b8f047806679dbfa251f656b906edf9fa638e268adf979bd0e2380a092047d61f9db9"
|
||||
}
|
||||
"#,
|
||||
];
|
||||
|
||||
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(),
|
||||
unblinded_element: decode(&values, "unblinded_element").unwrap(),
|
||||
info: decode(&values, "info").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 sksm, evaluation_element -> evaluation_element
|
||||
#[test]
|
||||
fn test_unblind() -> Result<(), PakeError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
|
||||
let token = oprf::Token {
|
||||
data: parameters.input,
|
||||
blind: RistrettoPoint::from_scalar_slice(GenericArray::from_slice(
|
||||
¶meters.blind[..],
|
||||
))
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let unblinded_element = oprf::unblind::<RistrettoPoint>(
|
||||
&token,
|
||||
RistrettoPoint::from_element_slice(GenericArray::from_slice(
|
||||
¶meters.evaluation_element,
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
assert_eq!(¶meters.unblinded_element, &unblinded_element);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Tests input, unblinded_element, info -> 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,
|
||||
¶meters.unblinded_element,
|
||||
¶meters.info,
|
||||
);
|
||||
|
||||
assert_eq!(¶meters.output, &output.to_vec());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
[formatting]
|
||||
allowed_blank_lines = 1
|
||||
reorder_keys = true
|
||||
@@ -0,0 +1,653 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#![cfg(test_hsm)]
|
||||
#![allow(type_alias_bounds)]
|
||||
|
||||
use std::env;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
use std::vec::Vec;
|
||||
|
||||
#[cfg(feature = "ecdsa")]
|
||||
use ::ecdsa::SignatureSize;
|
||||
use cryptoki::context::{CInitializeArgs, Pkcs11};
|
||||
use cryptoki::mechanism::Mechanism;
|
||||
use cryptoki::mechanism::elliptic_curve::{EcKdf, Ecdh1DeriveParams};
|
||||
use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle};
|
||||
use cryptoki::session::{Session, UserType};
|
||||
use cryptoki::types::AuthPin;
|
||||
#[cfg(feature = "ecdsa")]
|
||||
use digest::Digest;
|
||||
use digest::OutputSizeUser;
|
||||
#[cfg(feature = "ecdsa")]
|
||||
use elliptic_curve::PrimeCurve;
|
||||
use elliptic_curve::group::Curve;
|
||||
use elliptic_curve::group::prime::PrimeCurveAffine;
|
||||
use elliptic_curve::pkcs8::der::asn1::{OctetString, OctetStringRef};
|
||||
use elliptic_curve::pkcs8::der::{Decode, Encode};
|
||||
use elliptic_curve::pkcs8::{AssociatedOid, ObjectIdentifier};
|
||||
use elliptic_curve::point::{AffineCoordinates, DecompressPoint};
|
||||
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, Tag, ToEncodedPoint};
|
||||
use elliptic_curve::{AffinePoint, CurveArithmetic, FieldBytesSize, Group as _, ProjectivePoint};
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use opaque_vx::key_exchange::KeyExchange;
|
||||
use opaque_vx::key_exchange::group::Group;
|
||||
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
|
||||
use opaque_vx::key_exchange::group::ed25519::{self, Ed25519};
|
||||
use opaque_vx::key_exchange::group::elliptic_curve::NonIdentity;
|
||||
#[cfg(feature = "ecdsa")]
|
||||
use opaque_vx::key_exchange::sigma_i::ecdsa::{self, Ecdsa, PreHash};
|
||||
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
|
||||
use opaque_vx::key_exchange::sigma_i::pure_eddsa::PureEddsa;
|
||||
#[cfg(feature = "ecdsa")]
|
||||
use opaque_vx::key_exchange::sigma_i::{CachedMessage, HashOutput, Message, SigmaI};
|
||||
use opaque_vx::key_exchange::tripledh::TripleDh;
|
||||
use opaque_vx::keypair::{KeyPair, PublicKey};
|
||||
use opaque_vx::ksf::Identity;
|
||||
use opaque_vx::{
|
||||
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginStartResult,
|
||||
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationStartResult,
|
||||
ServerLogin, ServerLoginParameters, ServerLoginStartResult, ServerRegistration, ServerSetup,
|
||||
};
|
||||
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
|
||||
use opaque_vx::{Curve25519, Ristretto255};
|
||||
use p256::NistP256;
|
||||
use p384::NistP384;
|
||||
use p521::NistP521;
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::{Sha256, Sha384, Sha512};
|
||||
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
|
||||
|
||||
type OprfGroup<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Group;
|
||||
type OprfHash<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Hash;
|
||||
type KeGroup<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Group;
|
||||
|
||||
#[test]
|
||||
fn triple_dh_p256() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP256;
|
||||
type KeyExchange = TripleDh<NistP256, Sha256>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
Mechanism::EccKeyPairGen,
|
||||
NistP256::OID,
|
||||
Attribute::Derive(true),
|
||||
Mechanism::Sha256Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_dh_p384() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP384;
|
||||
type KeyExchange = TripleDh<NistP384, Sha384>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
Mechanism::EccKeyPairGen,
|
||||
NistP384::OID,
|
||||
Attribute::Derive(true),
|
||||
Mechanism::Sha384Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_dh_p521() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP521;
|
||||
type KeyExchange = TripleDh<NistP521, Sha512>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
Mechanism::EccKeyPairGen,
|
||||
NistP521::OID,
|
||||
Attribute::Derive(true),
|
||||
Mechanism::Sha512Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
|
||||
fn triple_dh_curve25519() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = Ristretto255;
|
||||
type KeyExchange = TripleDh<Curve25519, Sha512>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
// This should be [`Mechanism::EccMontgomeryKeyPairGen`], but SoftHSM has an incorrect
|
||||
// implementation. See https://github.com/softhsm/SoftHSMv2/issues/647.
|
||||
Mechanism::EccEdwardsKeyPairGen,
|
||||
ObjectIdentifier::new("1.3.101.110").unwrap(),
|
||||
Attribute::Derive(true),
|
||||
Mechanism::Sha512Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "ecdsa")]
|
||||
fn sigma_i_p256() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP256;
|
||||
type KeyExchange = SigmaI<Ecdsa<NistP256, Sha256>, NistP256, Sha256>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
Mechanism::EccKeyPairGen,
|
||||
NistP256::OID,
|
||||
Attribute::Sign(true),
|
||||
Mechanism::Sha256Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "ecdsa")]
|
||||
fn sigma_i_p384() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP384;
|
||||
type KeyExchange = SigmaI<Ecdsa<NistP384, Sha384>, NistP384, Sha384>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
Mechanism::EccKeyPairGen,
|
||||
NistP384::OID,
|
||||
Attribute::Sign(true),
|
||||
Mechanism::Sha384Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
|
||||
fn sigma_i_ed25519() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = Ristretto255;
|
||||
type KeyExchange = SigmaI<PureEddsa<Ed25519>, Ristretto255, Sha512>;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
Mechanism::EccEdwardsKeyPairGen,
|
||||
ObjectIdentifier::new_unwrap("1.3.101.112"),
|
||||
Attribute::Sign(true),
|
||||
Mechanism::Sha512Hmac,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RemoteKey(ObjectHandle);
|
||||
|
||||
trait Pkcs11PublicKey
|
||||
where
|
||||
Self: Group,
|
||||
{
|
||||
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Self>;
|
||||
}
|
||||
|
||||
trait Pkcs11KeyExchange<KE: KeyExchange> {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
server_pk: &PublicKey<KE::Group>,
|
||||
data: KE::KE2BuilderData<'_, CS>,
|
||||
) -> KE::KE2BuilderInput<CS>;
|
||||
}
|
||||
|
||||
fn test<CS: 'static + CipherSuite>(
|
||||
dh_mechanism: Mechanism,
|
||||
oid: ObjectIdentifier,
|
||||
attribute: Attribute,
|
||||
hmac_mechanism: Mechanism,
|
||||
) where
|
||||
KeGroup<CS>: Pkcs11PublicKey,
|
||||
RemoteKey: Pkcs11KeyExchange<CS::KeyExchange>,
|
||||
{
|
||||
let (remote_key, pk) = pkcs11_generate_key_pair(dh_mechanism, oid, attribute);
|
||||
|
||||
let keypair = KeyPair::new(RemoteKey(remote_key), pk);
|
||||
let oprf_seed = pkcs11_generate_oprf_seed(<OprfHash<CS> as OutputSizeUser>::OutputSize::U64);
|
||||
let server_setup = ServerSetup::new_with_key_pair_and_seed(&mut OsRng, keypair, oprf_seed);
|
||||
|
||||
const PASSWORD: &str = "password";
|
||||
|
||||
let ClientRegistrationStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientRegistration::<CS>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
|
||||
let key_material_info = server_setup.key_material_info(&[]);
|
||||
let key_material = pkcs11_hkdf::<CS>(
|
||||
key_material_info.ikm,
|
||||
hmac_mechanism,
|
||||
Vec::from_iter(key_material_info.info.into_iter().flatten().copied()),
|
||||
);
|
||||
let message = ServerRegistration::start_with_key_material(&server_setup, key_material, message)
|
||||
.unwrap()
|
||||
.message;
|
||||
let message = client
|
||||
.finish(
|
||||
&mut OsRng,
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap()
|
||||
.message;
|
||||
let file = ServerRegistration::finish(message);
|
||||
|
||||
let ClientLoginStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientLogin::<CS>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
|
||||
let key_material_info = server_setup.key_material_info(&[]);
|
||||
let key_material = pkcs11_hkdf::<CS>(
|
||||
key_material_info.ikm,
|
||||
hmac_mechanism,
|
||||
Vec::from_iter(key_material_info.info.into_iter().flatten().copied()),
|
||||
);
|
||||
let builder = ServerLogin::builder_with_key_material(
|
||||
&mut OsRng,
|
||||
&server_setup,
|
||||
key_material,
|
||||
Some(file),
|
||||
message,
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let shared_secret = builder
|
||||
.private_key()
|
||||
.pkcs11_key_exchange(server_setup.keypair().public(), builder.data());
|
||||
|
||||
let ServerLoginStartResult {
|
||||
message,
|
||||
state: server,
|
||||
..
|
||||
} = builder.clone().build(shared_secret).unwrap();
|
||||
|
||||
let message = client
|
||||
.clone()
|
||||
.finish(
|
||||
&mut OsRng,
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)
|
||||
.map(|result| result.message);
|
||||
|
||||
message
|
||||
.map(|message| {
|
||||
server
|
||||
.finish(message, ServerLoginParameters::default())
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
static SESSION: LazyLock<Mutex<Session>> = LazyLock::new(|| {
|
||||
let module = env::var("PKCS11_MODULE").expect("`PKCS11_MODULE` environment variable");
|
||||
let pkcs11 = Pkcs11::new(module).unwrap();
|
||||
pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();
|
||||
|
||||
let slot = pkcs11.get_slots_with_token().unwrap()[0];
|
||||
|
||||
let so_pin = AuthPin::new("abcdef".into());
|
||||
pkcs11.init_token(slot, &so_pin, "Test Token").unwrap();
|
||||
|
||||
let user_pin = AuthPin::new("fedcba".into());
|
||||
|
||||
{
|
||||
let session = pkcs11.open_rw_session(slot).unwrap();
|
||||
session.login(UserType::So, Some(&so_pin)).unwrap();
|
||||
session.init_pin(&user_pin).unwrap();
|
||||
}
|
||||
|
||||
let session = pkcs11.open_rw_session(slot).unwrap();
|
||||
session.login(UserType::User, Some(&user_pin)).unwrap();
|
||||
|
||||
Mutex::new(session)
|
||||
});
|
||||
|
||||
fn pkcs11_generate_key_pair<G: Group + Pkcs11PublicKey>(
|
||||
mechanism: Mechanism,
|
||||
oid: ObjectIdentifier,
|
||||
attribute: Attribute,
|
||||
) -> (ObjectHandle, PublicKey<G>) {
|
||||
let session = SESSION.lock().unwrap();
|
||||
let (pk, remote_key) = session
|
||||
.generate_key_pair(
|
||||
&mechanism,
|
||||
&[
|
||||
Attribute::Token(false),
|
||||
Attribute::EcParams(oid.to_der().unwrap()),
|
||||
],
|
||||
&[Attribute::Token(false), attribute],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Attribute::EcPoint(pk) = session
|
||||
.get_attributes(pk, &[AttributeType::EcPoint])
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
drop(session);
|
||||
|
||||
let pk = OctetString::from_der(&pk).unwrap();
|
||||
let pk = G::pkcs11_public_key(pk.as_bytes());
|
||||
|
||||
(remote_key, pk)
|
||||
}
|
||||
|
||||
fn pkcs11_generate_oprf_seed(length: u64) -> ObjectHandle {
|
||||
SESSION
|
||||
.lock()
|
||||
.unwrap()
|
||||
.generate_key(
|
||||
&Mechanism::GenericSecretKeyGen,
|
||||
&[Attribute::Token(false), Attribute::ValueLen(length.into())],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// SoftHSM, nor any other popular HSM at the time of writing, supports HKDF. So
|
||||
// we instead implement HKDF by hand on top of the HSMs HMAC, which is supported
|
||||
// by almost all HSMs and still protects the OPRF seed.
|
||||
fn pkcs11_hkdf<CS: CipherSuite>(
|
||||
hmac: ObjectHandle,
|
||||
mechanism: Mechanism,
|
||||
info: Vec<u8>,
|
||||
) -> GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen> {
|
||||
let mut okm = GenericArray::default();
|
||||
let mut prev: Option<Vec<u8>> = None;
|
||||
let chunk_len = <OprfHash<CS> as OutputSizeUser>::OutputSize::USIZE;
|
||||
|
||||
if okm.len() > chunk_len * 255 {
|
||||
panic!("invalid length");
|
||||
}
|
||||
|
||||
let session = SESSION.lock().unwrap();
|
||||
|
||||
for (block_n, block) in (0..).zip(okm.chunks_mut(chunk_len)) {
|
||||
let mut data = Vec::new();
|
||||
|
||||
if let Some(ref prev) = prev {
|
||||
data.extend(prev.as_slice())
|
||||
};
|
||||
|
||||
data.extend(&info);
|
||||
data.extend(&[block_n + 1]);
|
||||
|
||||
let output = session.sign(&mechanism, hmac, &data).unwrap();
|
||||
block.copy_from_slice(&output[..block.len()]);
|
||||
prev = Some(output);
|
||||
}
|
||||
|
||||
okm
|
||||
}
|
||||
|
||||
impl Pkcs11PublicKey for NistP256 {
|
||||
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP256> {
|
||||
pkcs11_ec_public_key(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pkcs11PublicKey for NistP384 {
|
||||
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP384> {
|
||||
pkcs11_ec_public_key(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pkcs11PublicKey for NistP521 {
|
||||
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP521> {
|
||||
pkcs11_ec_public_key(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
|
||||
impl Pkcs11PublicKey for Curve25519 {
|
||||
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Curve25519> {
|
||||
PublicKey::deserialize(data).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
|
||||
impl Pkcs11PublicKey for Ed25519 {
|
||||
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Ed25519> {
|
||||
PublicKey::deserialize(data).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Pkcs11KeyExchange<TripleDh<NistP256, Sha256>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
server_pk: &PublicKey<NistP256>,
|
||||
client_pk: &PublicKey<NistP256>,
|
||||
) -> GenericArray<u8, <NistP256 as Group>::PkLen> {
|
||||
pkcs_11_ecdsa_derive_secret::<NistP256>(self.0, server_pk, client_pk)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pkcs11KeyExchange<TripleDh<NistP384, Sha384>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
server_pk: &PublicKey<NistP384>,
|
||||
client_pk: &PublicKey<NistP384>,
|
||||
) -> GenericArray<u8, <NistP384 as Group>::PkLen> {
|
||||
pkcs_11_ecdsa_derive_secret::<NistP384>(self.0, server_pk, client_pk)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pkcs11KeyExchange<TripleDh<NistP521, Sha512>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
server_pk: &PublicKey<NistP521>,
|
||||
client_pk: &PublicKey<NistP521>,
|
||||
) -> GenericArray<u8, <NistP521 as Group>::PkLen> {
|
||||
pkcs_11_ecdsa_derive_secret::<NistP521>(self.0, server_pk, client_pk)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
|
||||
impl Pkcs11KeyExchange<TripleDh<Curve25519, Sha512>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
_: &PublicKey<Curve25519>,
|
||||
pk: &PublicKey<Curve25519>,
|
||||
) -> GenericArray<u8, <Curve25519 as Group>::PkLen> {
|
||||
let shared_secret = pkcs_11_dh_derive_secret(self.0, &pk.serialize());
|
||||
|
||||
GenericArray::clone_from_slice(&shared_secret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ecdsa")]
|
||||
impl Pkcs11KeyExchange<SigmaI<Ecdsa<NistP256, Sha256>, NistP256, Sha256>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
_: &PublicKey<NistP256>,
|
||||
message: &Message<CS, NistP256>,
|
||||
) -> (ecdsa::Signature<NistP256>, PreHash<Sha256>) {
|
||||
pkcs_11_ecdsa_sign::<NistP256, Sha256>(self.0, message.hash())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ecdsa")]
|
||||
impl Pkcs11KeyExchange<SigmaI<Ecdsa<NistP384, Sha384>, NistP384, Sha384>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
_: &PublicKey<NistP384>,
|
||||
message: &Message<CS, NistP384>,
|
||||
) -> (ecdsa::Signature<NistP384>, PreHash<Sha384>) {
|
||||
pkcs_11_ecdsa_sign::<NistP384, Sha384>(self.0, message.hash())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
|
||||
impl Pkcs11KeyExchange<SigmaI<PureEddsa<Ed25519>, Ristretto255, Sha512>> for RemoteKey {
|
||||
fn pkcs11_key_exchange<CS: CipherSuite>(
|
||||
&self,
|
||||
_: &PublicKey<Ed25519>,
|
||||
message: &Message<CS, Ristretto255>,
|
||||
) -> (ed25519::Signature, CachedMessage<CS, Ristretto255>) {
|
||||
pkcs_11_eddsa_sign(self.0, message)
|
||||
}
|
||||
}
|
||||
|
||||
fn pkcs11_ec_public_key<G>(data: &[u8]) -> PublicKey<G>
|
||||
where
|
||||
G: Group<Pk = NonIdentity<G>> + CurveArithmetic,
|
||||
FieldBytesSize<G>: ModulusSize,
|
||||
AffinePoint<G>:
|
||||
FromEncodedPoint<G> + ToEncodedPoint<G> + PrimeCurveAffine<Curve = ProjectivePoint<G>>,
|
||||
{
|
||||
PublicKey::deserialize(
|
||||
elliptic_curve::PublicKey::<G>::from_sec1_bytes(data)
|
||||
.unwrap()
|
||||
.to_encoded_point(true)
|
||||
.as_bytes(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn pkcs_11_dh_derive_secret(sk: ObjectHandle, pk: &[u8]) -> Vec<u8> {
|
||||
let session = SESSION.lock().unwrap();
|
||||
let shared_secret = session
|
||||
.derive_key(
|
||||
&Mechanism::Ecdh1Derive(Ecdh1DeriveParams::new(EcKdf::null(), pk)),
|
||||
sk,
|
||||
&[
|
||||
Attribute::Token(false),
|
||||
Attribute::KeyType(KeyType::GENERIC_SECRET),
|
||||
Attribute::Class(ObjectClass::SECRET_KEY),
|
||||
Attribute::Extractable(true),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Attribute::Value(shared_secret) = session
|
||||
.get_attributes(shared_secret, &[AttributeType::Value])
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
drop(session);
|
||||
|
||||
shared_secret
|
||||
}
|
||||
|
||||
fn pkcs_11_ecdsa_derive_secret<G>(
|
||||
server_sk: ObjectHandle,
|
||||
server_pk: &PublicKey<G>,
|
||||
client_pk: &PublicKey<G>,
|
||||
) -> GenericArray<u8, <G as Group>::PkLen>
|
||||
where
|
||||
G: Group<Pk = NonIdentity<G>> + CurveArithmetic,
|
||||
AffinePoint<G>: DecompressPoint<G> + ToEncodedPoint<G>,
|
||||
FieldBytesSize<G>: ModulusSize,
|
||||
{
|
||||
let client_pk_point = client_pk.to_group_type();
|
||||
let client_pk = client_pk.serialize();
|
||||
let client_pk = OctetStringRef::new(&client_pk).unwrap();
|
||||
let client_pk = client_pk.to_der().unwrap();
|
||||
|
||||
let shared_secret_bytes = pkcs_11_dh_derive_secret(server_sk, &client_pk);
|
||||
let shared_secret_point = AffinePoint::<G>::decompress(
|
||||
&GenericArray::clone_from_slice(&shared_secret_bytes),
|
||||
Choice::from(0),
|
||||
)
|
||||
.unwrap();
|
||||
let mut shared_secret = GenericArray::default();
|
||||
shared_secret[1..].copy_from_slice(&shared_secret_bytes);
|
||||
|
||||
let shifted_client_pk = client_pk_point.0.to_point() + ProjectivePoint::<G>::generator();
|
||||
let shifted_client_pk = shifted_client_pk.to_affine().to_encoded_point(true);
|
||||
let shifted_client_pk = OctetStringRef::new(shifted_client_pk.as_bytes()).unwrap();
|
||||
let shifted_client_pk = shifted_client_pk.to_der().unwrap();
|
||||
|
||||
let check_point = pkcs_11_dh_derive_secret(server_sk, &shifted_client_pk);
|
||||
|
||||
let shifted_server_pk = server_pk.to_group_type().0.to_point() + shared_secret_point;
|
||||
let shifted_server_pk = shifted_server_pk.to_affine();
|
||||
|
||||
let tag = u8::conditional_select(
|
||||
&(Tag::CompressedEvenY as u8),
|
||||
&(Tag::CompressedOddY as u8),
|
||||
check_point.ct_ne(&shifted_server_pk.x()),
|
||||
);
|
||||
shared_secret[0] = tag;
|
||||
|
||||
shared_secret
|
||||
}
|
||||
|
||||
#[cfg(feature = "ecdsa")]
|
||||
fn pkcs_11_ecdsa_sign<G: CurveArithmetic + PrimeCurve, H: Clone + Digest>(
|
||||
sk: ObjectHandle,
|
||||
hashes: HashOutput<H>,
|
||||
) -> (ecdsa::Signature<G>, PreHash<H>)
|
||||
where
|
||||
SignatureSize<G>: ArrayLength<u8>,
|
||||
{
|
||||
let sign_pre_hash = hashes.sign.finalize();
|
||||
|
||||
let session = SESSION.lock().unwrap();
|
||||
let signature = session.sign(&Mechanism::Ecdsa, sk, &sign_pre_hash).unwrap();
|
||||
drop(session);
|
||||
|
||||
let signature = ::ecdsa::Signature::from_slice(&signature).unwrap();
|
||||
|
||||
(
|
||||
ecdsa::Signature(signature),
|
||||
PreHash(hashes.verify.finalize()),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
|
||||
fn pkcs_11_eddsa_sign<CS: CipherSuite>(
|
||||
sk: ObjectHandle,
|
||||
message: &Message<CS, Ristretto255>,
|
||||
) -> (ed25519::Signature, CachedMessage<CS, Ristretto255>) {
|
||||
use cryptoki::mechanism::eddsa::{EddsaParams, EddsaSignatureScheme};
|
||||
|
||||
let mut message_bytes = Vec::new();
|
||||
message
|
||||
.sign_message()
|
||||
.for_each(|bytes| message_bytes.extend_from_slice(bytes));
|
||||
|
||||
let session = SESSION.lock().unwrap();
|
||||
let signature = session
|
||||
.sign(
|
||||
&Mechanism::Eddsa(EddsaParams::new(EddsaSignatureScheme::Pure)),
|
||||
sk,
|
||||
&message_bytes,
|
||||
)
|
||||
.unwrap();
|
||||
drop(session);
|
||||
|
||||
let signature = ed25519::Signature::from_slice(&signature).unwrap();
|
||||
|
||||
(signature, message.to_cached())
|
||||
}
|
||||
Reference in New Issue
Block a user