Compare commits
339
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
1dbfed8763 | ||
|
|
d3204203ca | ||
|
|
160ac47ffa | ||
|
|
6307ea9eed | ||
|
|
e35410ab95 | ||
|
|
1f608e464c | ||
|
|
e32b240ce2 | ||
|
|
099f887530 | ||
|
|
9d3963f98e | ||
|
|
be6d042d80 | ||
|
|
d6ace87c21 | ||
|
|
6eeeb0db28 | ||
|
|
6dc1c8b212 | ||
|
|
e421aaad07 | ||
|
|
511d371c9a | ||
|
|
f73fc55254 | ||
|
|
2a2d0888a5 | ||
|
|
ba53089199 | ||
|
|
fa6752cdc5 | ||
|
|
9f6b32a5ea | ||
|
|
f5b5391ee0 | ||
|
|
605d2b639b | ||
|
|
9e3743a604 | ||
|
|
6b22064863 | ||
|
|
94630dff7c | ||
|
|
2794dfaaa9 | ||
|
|
9f414d4a82 | ||
|
|
6f5a1fc6aa | ||
|
|
4c82dfaf13 | ||
|
|
7fcde1c50d | ||
|
|
f8c976d659 | ||
|
|
95152c3b35 | ||
|
|
51b3781e5c | ||
|
|
f18eaea8cf | ||
|
|
2d7b4292ff | ||
|
|
f165310c96 | ||
|
|
09f7cfdf78 | ||
|
|
05514e49aa | ||
|
|
eb1a93c379 | ||
|
|
697cd7a788 | ||
|
|
d8906c092a | ||
|
|
3252ca96de | ||
|
|
552a546b6c | ||
|
|
230b1bcef6 | ||
|
|
3954cd23b8 | ||
|
|
aabd5266c3 | ||
|
|
bd37deb7a7 | ||
|
|
6deddbe33c | ||
|
|
c8cbf56336 | ||
|
|
28645a7cea | ||
|
|
07f8048a3d | ||
|
|
0fc3448777 | ||
|
|
7cc1c0992a | ||
|
|
9c06c98ad6 | ||
|
|
344e8ad8d1 | ||
|
|
c7f6abb591 | ||
|
|
85b0ef031d | ||
|
|
8ccb5ad510 | ||
|
|
18bcd23102 | ||
|
|
9b5f7f455d | ||
|
|
25ae7ff9bf | ||
|
|
aabdb53581 | ||
|
|
587ef935a7 | ||
|
|
288105861d | ||
|
|
6b9583e928 | ||
|
|
958d0b7a27 | ||
|
|
40ea015d6d | ||
|
|
39ec88674a | ||
|
|
c97ddaf5d2 | ||
|
|
ae890dbac4 | ||
|
|
56d38dba2c | ||
|
|
786bc51fdd | ||
|
|
887b4577fa | ||
|
|
3c2a208606 | ||
|
|
3c555e82ae | ||
|
|
84cc0e9b72 | ||
|
|
7945661f67 | ||
|
|
b673f840a5 | ||
|
|
81353cb844 | ||
|
|
48412c8ee4 | ||
|
|
ab8db3bf03 | ||
|
|
dede91a5b7 | ||
|
|
f1bdfcd3a0 | ||
|
|
cc4a292b6d | ||
|
|
2c9e47d14d | ||
|
|
195a698ad8 | ||
|
|
cdf0c7bf72 | ||
|
|
59af252168 | ||
|
|
6dde7c5351 | ||
|
|
694c51c982 | ||
|
|
07a702c1be | ||
|
|
29ba1b93b5 | ||
|
|
c2edb2d95e | ||
|
|
72a3928cbb | ||
|
|
6b1e485763 | ||
|
|
7b11da3bd6 | ||
|
|
4aae404e29 | ||
|
|
1f8d786be8 | ||
|
|
8885fe5aa1 | ||
|
|
b0b9cd0ab2 | ||
|
|
4fda240a7d | ||
|
|
5a7b60bec7 | ||
|
|
91137903c6 | ||
|
|
e93e8fd5a8 | ||
|
|
f8285c60ba | ||
|
|
2959290582 | ||
|
|
69449cca3e | ||
|
|
988b9bae77 | ||
|
|
9d40aa7659 | ||
|
|
41cd80ccb5 | ||
|
|
45ea7b6e84 | ||
|
|
52429dd2fa | ||
|
|
bb9c365998 | ||
|
|
3e41f2441d | ||
|
|
59d997900e | ||
|
|
6d02c72aae | ||
|
|
4a638b8a22 | ||
|
|
1a5e704cf6 | ||
|
|
d5e585db1d | ||
|
|
642d5188f5 | ||
|
|
c9d467e368 | ||
|
|
738b90188d | ||
|
|
7a2d309263 | ||
|
|
f989330807 | ||
|
|
4f9b7fe86f | ||
|
|
aab8ff8e19 | ||
|
|
2cf6808665 | ||
|
|
ab1b1d6209 | ||
|
|
0cf13c2266 | ||
|
|
f030fca1cb | ||
|
|
5cf29fba62 | ||
|
|
3ae1c58d46 | ||
|
|
e9fa474c58 | ||
|
|
e9add9fd7a | ||
|
|
b401cfc89c | ||
|
|
3a1aaab6cf | ||
|
|
5a374562d1 | ||
|
|
cd46a42634 | ||
|
|
ce1d97002c | ||
|
|
80fc493a60 | ||
|
|
8362804946 | ||
|
|
05fb76d5b7 | ||
|
|
af22bfa54f | ||
|
|
57e152b028 |
@@ -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,34 +0,0 @@
|
||||
name: Rust CI
|
||||
on: [push]
|
||||
jobs:
|
||||
combo:
|
||||
name: test + Clippy + rustfmt
|
||||
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
|
||||
|
||||
- name: Run cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
- name: Run cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
@@ -2,4 +2,5 @@
|
||||
.vscode/
|
||||
src/.DS_Store
|
||||
/target
|
||||
Cargo.lock
|
||||
**/*.rs.bk
|
||||
|
||||
+218
@@ -1,5 +1,223 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.0-pre.1 (July 1, 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 (June 29, 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
|
||||
* Compliance with RFC 8017 on data serialization functions (I2OSP / OS2IP)
|
||||
* Adherence to protocol format described in
|
||||
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-02
|
||||
* Added parameters for key exchange additional data
|
||||
* Added simple_login and digital_locker examples
|
||||
|
||||
## 0.2.1 (October 22, 2020)
|
||||
|
||||
* Changed visibility of hash module to be public
|
||||
|
||||
## 0.2.0 (September 3, 2020)
|
||||
|
||||
* Added CipherSuite API for specifying underlying primitives
|
||||
* Added support for specifying a slow password hashing function
|
||||
* Collapsed SignalKeyPair to X25519KeyPair
|
||||
* Updated the envelope implementation to match the suggested XOR-based
|
||||
construction in https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06
|
||||
* Included randomized tests for testing try_from crashes
|
||||
* Implemented Elligator2 map instead of try-and-increment for hash-to-curve
|
||||
* Added extensibility for supporting different key exchange protocols
|
||||
* Added benchmarks for the OPRF & switchable dalek backend depending on platform
|
||||
|
||||
## 0.1.0 (June 5, 2020)
|
||||
|
||||
* Initial release
|
||||
|
||||
@@ -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
|
||||
+3
-21
@@ -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 Ristretto255.js, you agree that your contributions will be
|
||||
licensed under the LICENSE file in the root directory of this source tree.
|
||||
By contributing to opaque-ke, you agree that your contributions will be
|
||||
licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root
|
||||
directory of this source tree.
|
||||
|
||||
Generated
-479
@@ -1,479 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aead 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ghash 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-soft"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aesni"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-cipher-trait"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aead 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chacha20 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"poly1305 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"polyval 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-ke"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aead 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"aes-gcm 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chacha20poly1305 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hkdf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"universal-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"universal-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stream-cipher"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum aead 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4"
|
||||
"checksum aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9"
|
||||
"checksum aes-gcm 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "834a6bda386024dbb7c8fc51322856c10ffe69559f972261c868485f5759c638"
|
||||
"checksum aes-soft 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d"
|
||||
"checksum aesni 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100"
|
||||
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
|
||||
"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
"checksum chacha20 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bea8b86bdf2f2b18a0f28fbfed740ee395e6ba1785b4b7123c021172eaab8ef9"
|
||||
"checksum chacha20poly1305 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "48901293601228db2131606f741db33561f7576b5d19c99cd66222380a7dc863"
|
||||
"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
||||
"checksum curve25519-dalek 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839"
|
||||
"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
"checksum ghash 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f0930ed19a7184089ea46d2fedead2f6dc2b674c5db4276b7da336c7cd83252"
|
||||
"checksum hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
||||
"checksum hkdf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3"
|
||||
"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
|
||||
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
|
||||
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
"checksum poly1305 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5829f50f48e9ddb79f3f7c3097029d0caee30f8286accb241416df603b080b8"
|
||||
"checksum polyval 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec3341498978de3bfd12d1b22f1af1de22818f5473a11e8a6ef997989e3a212"
|
||||
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
|
||||
"checksum serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)" = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff"
|
||||
"checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
|
||||
"checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0"
|
||||
"checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
|
||||
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
"checksum subtle 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
|
||||
"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
|
||||
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
"checksum thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db"
|
||||
"checksum thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4"
|
||||
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum universal-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01"
|
||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
"checksum x25519-dalek 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217"
|
||||
"checksum zeroize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8"
|
||||
"checksum zeroize_derive 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
|
||||
+123
-23
@@ -1,30 +1,130 @@
|
||||
[package]
|
||||
name = "opaque-ke"
|
||||
version = "0.1.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-pre.1"
|
||||
|
||||
[features]
|
||||
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]
|
||||
aead = "0.2.0"
|
||||
curve25519-dalek = "2.0.0"
|
||||
generic-array = "0.12.3"
|
||||
hkdf = "0.8.0"
|
||||
hmac = "0.7.1"
|
||||
rand_core = "0.5.1"
|
||||
sha2 = "0.8"
|
||||
thiserror = "1"
|
||||
x25519-dalek = "0.6.0"
|
||||
zeroize = "1.1"
|
||||
argon2 = { version = "0.6.0-rc", default-features = false, features = [
|
||||
"zeroize",
|
||||
"alloc",
|
||||
], optional = true }
|
||||
curve25519-dalek = { version = "5.0.0-rc", 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.0-rc.23", default-features = false, features = [
|
||||
"algorithm",
|
||||
], optional = true }
|
||||
ed25519-dalek = { version = "3.0.0-rc", 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-pre.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]
|
||||
aes-gcm = "0.5.0"
|
||||
base64 = "0.11.0"
|
||||
chacha20poly1305 = "0.4.1"
|
||||
hex = "0.4.2"
|
||||
lazy_static = "1.4.0"
|
||||
serde_json = "1.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.0-rc.15", default-features = false, features = [
|
||||
"ecdsa",
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"oprf",
|
||||
] }
|
||||
p384 = { version = "0.14.0-rc.15", 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]]
|
||||
harness = false
|
||||
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-ke-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.1.0"
|
||||
opaque-ke = { package = "opaque-vx", version = "1.0.0-pre.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-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05), containing a specification for the OPAQUE protocol
|
||||
- ["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 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);
|
||||
@@ -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`.
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
// 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.
|
||||
//!
|
||||
//! The client-server interactions are executed in a three-step protocol within
|
||||
//! the account_registration (for password registration) and account_login (for
|
||||
//! password login) functions. These steps must be performed in the specific
|
||||
//! sequence outlined in each of these functions.
|
||||
//!
|
||||
//! The CipherSuite trait allows the application to configure the primitives
|
||||
//! used by OPAQUE, but must be kept consistent across the steps of the
|
||||
//! protocol.
|
||||
//!
|
||||
//! In a more realistic client-server interaction, the client must send messages
|
||||
//! over "the wire" to the server. These bytes are serialized and explicitly
|
||||
//! annotated in the below functions.
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
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
|
||||
#[allow(dead_code)]
|
||||
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 {
|
||||
contents: Vec<u8>,
|
||||
password_file: Vec<u8>,
|
||||
}
|
||||
|
||||
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
|
||||
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
|
||||
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
|
||||
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
let nonce = Nonce::try_from(&nonce_bytes[..]).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
|
||||
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
|
||||
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
|
||||
cipher
|
||||
.decrypt(
|
||||
&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
|
||||
fn register_locker(
|
||||
server_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
locker_id: usize,
|
||||
password: String,
|
||||
secret_message: String,
|
||||
) -> Locker {
|
||||
let mut client_rng = UnwrapErr(SysRng);
|
||||
let client_registration_start_result =
|
||||
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 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();
|
||||
|
||||
// Server sends registration_response_bytes to client
|
||||
|
||||
let client_finish_registration_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let message_bytes = client_finish_registration_result.message.serialize();
|
||||
|
||||
// Client encrypts secret message using export key
|
||||
let ciphertext = encrypt(
|
||||
&client_finish_registration_result.export_key,
|
||||
secret_message.as_bytes(),
|
||||
);
|
||||
|
||||
// Client sends message_bytes to server
|
||||
|
||||
let password_file = ServerRegistration::finish(
|
||||
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
|
||||
);
|
||||
|
||||
Locker {
|
||||
contents: ciphertext,
|
||||
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_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
locker_id: usize,
|
||||
password: String,
|
||||
locker: &Locker,
|
||||
) -> Result<String, String> {
|
||||
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::<DefaultCipherSuite>::deserialize(&locker.password_file).unwrap();
|
||||
let mut server_rng = UnwrapErr(SysRng);
|
||||
let server_login_start_result = ServerLogin::start(
|
||||
&mut server_rng,
|
||||
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();
|
||||
|
||||
// Server sends credential_response_bytes to client
|
||||
|
||||
let result = client_login_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
// Client-detected login failure
|
||||
return Err(String::from("Incorrect password, please try again."));
|
||||
}
|
||||
let client_login_finish_result = result.unwrap();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize();
|
||||
|
||||
// Client sends credential_finalization_bytes to server
|
||||
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(
|
||||
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
|
||||
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
|
||||
let plaintext = decrypt(
|
||||
&client_login_finish_result.export_key,
|
||||
&decrypt(
|
||||
&client_login_finish_result.session_key,
|
||||
&encrypted_locker_contents,
|
||||
),
|
||||
);
|
||||
String::from_utf8(plaintext).map_err(|_| String::from("UTF8 error"))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
|
||||
|
||||
let mut rl = Editor::<(), _>::new().unwrap();
|
||||
let mut registered_lockers: Vec<Locker> = vec![];
|
||||
loop {
|
||||
display_lockers(®istered_lockers);
|
||||
|
||||
println!("Enter an option (1 or 2):");
|
||||
println!("1) Register a locker");
|
||||
println!("2) Open a locker\n");
|
||||
let readline = rl.readline("> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
if line != "1" && line != "2" {
|
||||
println!("Error: Invalid option (either specify 1 or 2)");
|
||||
continue;
|
||||
}
|
||||
match line.as_ref() {
|
||||
"1" => {
|
||||
let (password, secret_message) = get_two_strings(
|
||||
"Choose a password",
|
||||
"Set a secret message",
|
||||
&mut rl,
|
||||
None,
|
||||
);
|
||||
let locker_id = registered_lockers.len();
|
||||
registered_lockers.push(register_locker(
|
||||
&server_setup,
|
||||
locker_id,
|
||||
password,
|
||||
secret_message,
|
||||
));
|
||||
continue;
|
||||
}
|
||||
"2" => {
|
||||
let (locker, password) = get_two_strings(
|
||||
"Choose a locker number",
|
||||
"Enter the password",
|
||||
&mut rl,
|
||||
None,
|
||||
);
|
||||
let locker_index: usize = match locker.parse() {
|
||||
Ok(index) => index,
|
||||
Err(_) => {
|
||||
println!("Error: Could not find locker number");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if locker_index >= registered_lockers.len() {
|
||||
println!("Error: Could not find locker number");
|
||||
continue;
|
||||
}
|
||||
|
||||
match open_locker(
|
||||
&server_setup,
|
||||
locker_index,
|
||||
password,
|
||||
®istered_lockers[locker_index],
|
||||
) {
|
||||
Ok(contents) => {
|
||||
println!("\n\nSuccess! Contents: {contents}\n\n");
|
||||
}
|
||||
Err(err) => {
|
||||
println!("\n\nError encountered, could not open locker: {err}\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => exit(0),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
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: {locker_numbers:?}\n");
|
||||
}
|
||||
|
||||
// Handle readline errors
|
||||
fn handle_error(err: ReadlineError) {
|
||||
match err {
|
||||
ReadlineError::Interrupted => {
|
||||
println!("CTRL-C");
|
||||
}
|
||||
ReadlineError::Eof => {
|
||||
println!("CTRL-D");
|
||||
}
|
||||
err => {
|
||||
println!("Error: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A function run on the client which extracts two strings from the CLI
|
||||
fn get_two_strings(
|
||||
s1: &str,
|
||||
s2: &str,
|
||||
rl: &mut Editor<(), DefaultHistory>,
|
||||
string1: Option<String>,
|
||||
) -> (String, String) {
|
||||
let query = if string1.is_none() { s1 } else { s2 };
|
||||
let readline = rl.readline(&format!("{query}: "));
|
||||
match readline {
|
||||
Ok(line) => match string1 {
|
||||
Some(x) => (x, line),
|
||||
None => get_two_strings(s1, s2, rl, Some(line)),
|
||||
},
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// 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
|
||||
//!
|
||||
//! The client-server interactions are executed in a three-step protocol within
|
||||
//! the account_registration (for password registration) and account_login (for
|
||||
//! password login) functions. These steps must be performed in the specific
|
||||
//! sequence outlined in each of these functions.
|
||||
//!
|
||||
//! The CipherSuite trait allows the application to configure the primitives
|
||||
//! used by OPAQUE, but must be kept consistent across the steps of the
|
||||
//! protocol.
|
||||
//!
|
||||
//! In a more realistic client-server interaction, the client must send messages
|
||||
//! over "the wire" to the server. These bytes are serialized and explicitly
|
||||
//! annotated in the below functions.
|
||||
|
||||
use 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::process::exit;
|
||||
|
||||
// The ciphersuite trait allows to specify the underlying primitives that will
|
||||
// be used in the OPAQUE protocol
|
||||
#[allow(dead_code)]
|
||||
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_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Array<u8, ServerRegistrationLen<DefaultCipherSuite>> {
|
||||
let mut client_rng = UnwrapErr(SysRng);
|
||||
let client_registration_start_result =
|
||||
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 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();
|
||||
|
||||
// Server sends registration_response_bytes to client
|
||||
|
||||
let client_finish_registration_result = client_registration_start_result
|
||||
.state
|
||||
.finish(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
RegistrationResponse::deserialize(®istration_response_bytes).unwrap(),
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let message_bytes = client_finish_registration_result.message.serialize();
|
||||
|
||||
// Client sends message_bytes to server
|
||||
|
||||
let password_file = 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_setup: &ServerSetup<DefaultCipherSuite>,
|
||||
username: String,
|
||||
password: String,
|
||||
password_file_bytes: &[u8],
|
||||
) -> bool {
|
||||
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::<DefaultCipherSuite>::deserialize(password_file_bytes).unwrap();
|
||||
let mut server_rng = UnwrapErr(SysRng);
|
||||
let server_login_start_result = ServerLogin::start(
|
||||
&mut server_rng,
|
||||
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();
|
||||
|
||||
// Server sends credential_response_bytes to client
|
||||
|
||||
let result = client_login_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
password.as_bytes(),
|
||||
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
// Client-detected login failure
|
||||
return false;
|
||||
}
|
||||
let client_login_finish_result = result.unwrap();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize();
|
||||
|
||||
// Client sends credential_finalization_bytes to server
|
||||
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(
|
||||
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
|
||||
ServerLoginParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
client_login_finish_result.session_key == server_login_finish_result.session_key
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rng = UnwrapErr(SysRng);
|
||||
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
|
||||
|
||||
let mut rl = Editor::<(), _>::new().unwrap();
|
||||
let mut registered_users =
|
||||
HashMap::<String, Array<u8, ServerRegistrationLen<DefaultCipherSuite>>>::new();
|
||||
loop {
|
||||
println!(
|
||||
"\nCurrently registered usernames: {:?}\n",
|
||||
registered_users.keys()
|
||||
);
|
||||
|
||||
println!("Enter an option (1 or 2):");
|
||||
println!("1) Register a user");
|
||||
println!("2) Login as a user\n");
|
||||
let readline = rl.readline("> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
if line != "1" && line != "2" {
|
||||
println!("Error: Invalid option (either specify 1 or 2)");
|
||||
continue;
|
||||
}
|
||||
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
|
||||
match line.as_ref() {
|
||||
"1" => {
|
||||
registered_users.insert(
|
||||
username.clone(),
|
||||
account_registration(&server_setup, username, password),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
"2" => match registered_users.get(&username) {
|
||||
Some(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.
|
||||
println!("\nIncorrect password, please try again.");
|
||||
}
|
||||
}
|
||||
None => println!("Error: Could not find username registered"),
|
||||
},
|
||||
_ => exit(0),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Handle readline errors
|
||||
fn handle_error(err: ReadlineError) {
|
||||
match err {
|
||||
ReadlineError::Interrupted => {
|
||||
println!("CTRL-C");
|
||||
}
|
||||
ReadlineError::Eof => {
|
||||
println!("CTRL-D");
|
||||
}
|
||||
err => {
|
||||
println!("Error: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A function run on the client which extracts two strings from the CLI
|
||||
fn get_two_strings(
|
||||
s1: &str,
|
||||
s2: &str,
|
||||
rl: &mut Editor<(), DefaultHistory>,
|
||||
string1: Option<String>,
|
||||
) -> (String, String) {
|
||||
let query = if string1.is_none() { s1 } else { s2 };
|
||||
let readline = rl.readline(&format!("{query}: "));
|
||||
match readline {
|
||||
Ok(line) => match string1 {
|
||||
Some(x) => (x, line),
|
||||
None => get_two_strings(s1, s2, rl, Some(line)),
|
||||
},
|
||||
Err(err) => {
|
||||
handle_error(err);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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"
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/bin/expect -f
|
||||
|
||||
# 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
|
||||
match_max 100000
|
||||
sleep 1
|
||||
expect "*
|
||||
\r
|
||||
Currently registered locker numbers: \\\[\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "1\r"
|
||||
expect "Choose a password: \r"
|
||||
send -- "foo\r"
|
||||
expect "Set a secret message: \r"
|
||||
send -- "bar\r"
|
||||
expect "*
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Choose a locker number: \r"
|
||||
send -- "1\r"
|
||||
expect "Password: \r"
|
||||
send -- "foo\r"
|
||||
expect "*Error: Could not find locker number\r
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Choose a locker number: \r"
|
||||
send -- "0\r"
|
||||
expect "Password: \r"
|
||||
send -- "baz\r"
|
||||
expect "*Error encountered, could not open locker: Incorrect password, please try again.\r
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Choose a locker number: \r"
|
||||
send -- "0\r"
|
||||
expect "Password: \r"
|
||||
send -- "foo\r"
|
||||
expect "*
|
||||
\r
|
||||
*Success! Contents: bar\r
|
||||
\r
|
||||
Currently registered locker numbers: \\\[0\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a locker\r
|
||||
2) Open a locker\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- ""
|
||||
expect eof
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/bin/expect -f
|
||||
|
||||
# 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 --features argon2
|
||||
match_max 100000
|
||||
sleep 1
|
||||
expect "*
|
||||
\r
|
||||
Currently registered usernames: \\\[\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "1\r"
|
||||
expect "Username: \r"
|
||||
send -- "foo\r"
|
||||
expect "Password: \r"
|
||||
send -- "bar\r"
|
||||
expect "*
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Username: \r"
|
||||
send -- "baz\r"
|
||||
expect "Password: \r"
|
||||
send -- "quux\r"
|
||||
expect "*Error: Could not find username registered\r
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Username: \r"
|
||||
send -- "foo\r"
|
||||
expect "Password: \r"
|
||||
send -- "baz\r"
|
||||
expect "*Incorrect password, please try again.\r
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- "2\r"
|
||||
expect "Username: \r"
|
||||
send -- "foo\r"
|
||||
expect "Password: \r"
|
||||
send -- "bar\r"
|
||||
expect "*
|
||||
\r
|
||||
*Login success!\r
|
||||
\r
|
||||
Currently registered usernames: \\\[\"foo\"\\\]\r
|
||||
\r
|
||||
Enter an option (1 or 2):\r
|
||||
1) Register a user\r
|
||||
2) Login as a user\r
|
||||
\r
|
||||
"
|
||||
sleep .1
|
||||
send -- ""
|
||||
expect eof
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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
|
||||
|
||||
use core::ops::Add;
|
||||
|
||||
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
|
||||
/// * `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
|
||||
/// * `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;
|
||||
/// 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;
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
// Copyright (c) VexaHub and contributors.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
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_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;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub(crate) enum InnerEnvelopeMode {
|
||||
Zero = 0,
|
||||
Internal = 1,
|
||||
}
|
||||
|
||||
impl Zeroize for InnerEnvelopeMode {
|
||||
fn zeroize(&mut self) {
|
||||
*self = Self::Zero
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for InnerEnvelopeMode {
|
||||
type Error = ProtocolError;
|
||||
fn try_from(x: u8) -> Result<Self, Self::Error> {
|
||||
match x {
|
||||
1 => Ok(InnerEnvelopeMode::Internal),
|
||||
_ => Err(ProtocolError::SerializationError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// 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<'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<CS: CipherSuite> {
|
||||
pub(crate) export_key: Output<OprfHash<CS>>,
|
||||
}
|
||||
|
||||
#[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>>,
|
||||
);
|
||||
|
||||
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<OutputSize<OprfHash<CS>>, NonceLen>;
|
||||
|
||||
impl<CS: CipherSuite> Envelope<CS> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn seal<R: Rng + CryptoRng>(
|
||||
rng: &mut R,
|
||||
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 (mode, client_s_pk) = (
|
||||
InnerEnvelopeMode::Internal,
|
||||
build_inner_envelope_internal::<CS>(randomized_pwd_hasher, nonce)?,
|
||||
);
|
||||
|
||||
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 result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
|
||||
Ok((
|
||||
result.0,
|
||||
client_s_pk,
|
||||
result.1,
|
||||
#[cfg(test)]
|
||||
result.2,
|
||||
))
|
||||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
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 {
|
||||
mode,
|
||||
nonce,
|
||||
hmac: hmac_bytes,
|
||||
},
|
||||
export_key,
|
||||
#[cfg(test)]
|
||||
hmac_key,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn open<'a>(
|
||||
&self,
|
||||
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 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 opened = self.open_raw(randomized_pwd_hasher, aad)?;
|
||||
|
||||
Ok(OpenedEnvelope {
|
||||
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<'a>(
|
||||
&self,
|
||||
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();
|
||||
|
||||
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 = 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(),
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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())
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
+134
-91
@@ -1,123 +1,166 @@
|
||||
// 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 thiserror::Error;
|
||||
use displaydoc::Display;
|
||||
|
||||
/// Represents an error in the manipulation of internal cryptographic data
|
||||
#[derive(Debug, Error)]
|
||||
pub enum InternalPakeError {
|
||||
#[error("Invalid length for {name}: expected {len}, but is actually {actual_len}.")]
|
||||
SizeError {
|
||||
name: &'static str,
|
||||
len: usize,
|
||||
actual_len: usize,
|
||||
},
|
||||
#[error("Could not decompress point.")]
|
||||
PointError,
|
||||
#[error("Key belongs to a small subgroup!")]
|
||||
SubGroupError,
|
||||
#[error("hashing to a key failed")]
|
||||
HashingFailure,
|
||||
#[error("Computing HKDF failed while deriving subkeys")]
|
||||
#[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,
|
||||
#[error("Computing HMAC failed while supplying a secret key")]
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// Represents an error in password checking
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PakeError {
|
||||
/// This error results from an internal error during PRF construction
|
||||
///
|
||||
#[error("Internal error during PRF verification: {0}")]
|
||||
CryptoError(InternalPakeError),
|
||||
/// This error occurs when the symmetric encryption fails
|
||||
#[error("Symmetric encryption failed.")]
|
||||
EncryptionError,
|
||||
/// This error occurs when the symmetric decryption fails
|
||||
#[error("Symmetric decryption failed.")]
|
||||
DecryptionError,
|
||||
/// This error occurs when the symmetric decryption's hmac check fails
|
||||
#[error("HMAC check in symmetric decryption failed.")]
|
||||
DecryptionHmacError,
|
||||
/// This error occurs when the server object that is being called finish() on is malformed
|
||||
#[error("Incomplete set of keys passed into finish() function")]
|
||||
IncompleteKeysError,
|
||||
#[error("The provided server public key doesn't match the encrypted one")]
|
||||
IncompatibleServerStaticPublicKeyError,
|
||||
#[error("Error in key exchange protocol when attempting to validate MACs")]
|
||||
KeyExchangeMacValidationError,
|
||||
#[error("Error in validating credentials")]
|
||||
InvalidLoginError,
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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(Debug, Error)]
|
||||
pub enum ProtocolError {
|
||||
/// This error results from an error during password verification
|
||||
///
|
||||
#[error("Internal error during password verification: {0}")]
|
||||
VerificationError(PakeError),
|
||||
/// This error occurs when the server answer cannot be handled
|
||||
#[error("Server response cannot be handled.")]
|
||||
ServerError,
|
||||
/// This error occurs when the client request cannot be handled
|
||||
#[error("Client request cannot be handled.")]
|
||||
ClientError,
|
||||
#[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,
|
||||
/// length
|
||||
len: usize,
|
||||
/// actual
|
||||
actual_len: usize,
|
||||
},
|
||||
/** 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),
|
||||
}
|
||||
|
||||
// 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: 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
-160
@@ -1,160 +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;
|
||||
|
||||
use curve25519_dalek::{
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
use generic_array::{
|
||||
typenum::{U32, U64},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
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;
|
||||
/// The byte length necessary to represent scalars
|
||||
type ScalarLen: ArrayLength<u8>;
|
||||
/// Return a scalat 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_bytes(&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>;
|
||||
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> 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 {
|
||||
Scalar::random(rng)
|
||||
}
|
||||
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
|
||||
GenericArray::from_slice(scalar.as_bytes())
|
||||
}
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
||||
scalar.invert()
|
||||
}
|
||||
|
||||
// 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_else(|| InternalPakeError::PointError)
|
||||
}
|
||||
// serialization of a group element
|
||||
fn to_bytes(&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 {
|
||||
let mut bits = [0u8; 64];
|
||||
bits.copy_from_slice(uniform_bytes);
|
||||
// This could really be a from_uniform_bytes!
|
||||
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
|
||||
}
|
||||
}
|
||||
|
||||
/// The implementation of such a subgroup for points on the large Curve25519-subgroup
|
||||
impl Group for EdwardsPoint {
|
||||
type Scalar = Scalar;
|
||||
type ScalarLen = U32;
|
||||
fn from_scalar_slice(
|
||||
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
|
||||
) -> Result<Self::Scalar, InternalPakeError> {
|
||||
let mut bits = [0u8; 32];
|
||||
bits.copy_from_slice(scalar_bits);
|
||||
Ok(Scalar::from_bytes_mod_order(bits))
|
||||
}
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
Scalar::random(rng)
|
||||
}
|
||||
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
|
||||
GenericArray::from_slice(scalar.as_bytes())
|
||||
}
|
||||
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
|
||||
scalar.invert()
|
||||
}
|
||||
|
||||
// The byte length necessary to represent group elements
|
||||
type ElemLen = U32;
|
||||
fn from_element_slice(
|
||||
element_bits: &GenericArray<u8, Self::ElemLen>,
|
||||
) -> Result<Self, InternalPakeError> {
|
||||
CompressedEdwardsY::from_slice(element_bits)
|
||||
.decompress()
|
||||
.ok_or_else(|| InternalPakeError::PointError)
|
||||
}
|
||||
// serialization of a group element
|
||||
fn to_bytes(&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 {
|
||||
let mut result = [0u8; 32];
|
||||
let mut counter = 0;
|
||||
let mut wrapped_point: Option<EdwardsPoint> = None;
|
||||
|
||||
while wrapped_point.is_none() {
|
||||
result.copy_from_slice(
|
||||
&Sha256::new()
|
||||
.chain(&uniform_bytes[..32])
|
||||
.chain(&[counter])
|
||||
.result()[..32],
|
||||
);
|
||||
wrapped_point = CompressedEdwardsY::from_slice(&result).decompress();
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
wrapped_point
|
||||
.expect("guarded by loop exit condition")
|
||||
.mul_by_cofactor()
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// 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::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};
|
||||
|
||||
pub(crate) type OutputSize<H> = <<H as CoreProxy>::Core as OutputSizeUser>::OutputSize;
|
||||
|
||||
/// 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
|
||||
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,
|
||||
> 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,
|
||||
{
|
||||
}
|
||||
@@ -1,406 +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::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
|
||||
keypair::{Key, KeyPair, SizedBytes},
|
||||
};
|
||||
use generic_array::GenericArray;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// This module is a somewhat minimalistic implementation of a key Exchange
|
||||
/// protocol based on 3DH. It assumes a pre-exchange has allowed client and
|
||||
/// server to learn each other's static public key.
|
||||
///
|
||||
/// This private module may undergo significant changes in the near term.
|
||||
|
||||
const KEY_LEN: usize = 32;
|
||||
pub(crate) const NONCE_LEN: usize = 32;
|
||||
pub(crate) const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
|
||||
pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
|
||||
|
||||
static STR_3DH: &[u8] = b"3DH keys";
|
||||
|
||||
pub(crate) struct KE1State {
|
||||
client_e_sk: Key,
|
||||
client_nonce: Vec<u8>,
|
||||
hashed_l1: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct KE1Message {
|
||||
pub(crate) client_nonce: Vec<u8>,
|
||||
pub(crate) client_e_pk: Key,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE1State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
|
||||
|
||||
Ok(Self {
|
||||
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
|
||||
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
|
||||
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KE1State {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.client_e_sk.to_arr(),
|
||||
&self.client_nonce[..],
|
||||
&self.hashed_l1[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl KE1Message {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE1Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes =
|
||||
check_slice_size(ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
|
||||
|
||||
Ok(Self {
|
||||
client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
|
||||
client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
l1_component: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(KE1State, KE1Message), ProtocolError> {
|
||||
let client_e_kp = KeyFormat::generate_random(rng)?;
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
|
||||
let ke1_message = KE1Message {
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&l1_data);
|
||||
let hashed_l1 = hasher.result();
|
||||
|
||||
Ok((
|
||||
KE1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
hashed_l1: hashed_l1.to_vec(),
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) struct KE2State {
|
||||
km3: Vec<u8>,
|
||||
hashed_transcript: Vec<u8>,
|
||||
shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct KE2Message {
|
||||
server_nonce: Vec<u8>,
|
||||
server_e_pk: Key,
|
||||
mac: Vec<u8>,
|
||||
}
|
||||
|
||||
impl KE2State {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.shared_secret[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE2State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
|
||||
|
||||
Ok(Self {
|
||||
km3: checked_bytes[..KEY_LEN].to_vec(),
|
||||
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
|
||||
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KE2Message {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.server_nonce[..],
|
||||
&self.server_e_pk.to_arr(),
|
||||
&self.mac[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE2Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(ke1_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
|
||||
|
||||
Ok(Self {
|
||||
server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
|
||||
server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
|
||||
mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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 shared secret, followed by two mac keys
|
||||
type TripleDHDerivationResult = (
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
);
|
||||
|
||||
// Internal function which takes the public and private components of the client and server keypairs, along
|
||||
// with some auxiliary metadata, to produce the shared secret and two MAC keys
|
||||
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>>(
|
||||
dh: TripleDHComponents,
|
||||
client_nonce: &[u8],
|
||||
server_nonce: &[u8],
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
) -> Result<TripleDHDerivationResult, ProtocolError> {
|
||||
let ikm: Vec<u8> = [
|
||||
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
|
||||
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
|
||||
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let info: Vec<u8> = [
|
||||
STR_3DH,
|
||||
&client_nonce,
|
||||
&server_nonce,
|
||||
&client_s_pk.to_arr(),
|
||||
&server_s_pk.to_arr(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
const OUTPUT_SIZE: usize = 32;
|
||||
let mut okm = [0u8; 3 * OUTPUT_SIZE];
|
||||
let h = Hkdf::<Sha256>::new(None, &ikm);
|
||||
h.expand(&info, &mut okm)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
Ok((
|
||||
*GenericArray::from_slice(&okm[..OUTPUT_SIZE]),
|
||||
*GenericArray::from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
|
||||
*GenericArray::from_slice(&okm[2 * OUTPUT_SIZE..]),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
rng: &mut R,
|
||||
l1_bytes: Vec<u8>,
|
||||
l2_bytes: Vec<u8>,
|
||||
client_e_pk: KeyFormat::Repr,
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_sk: KeyFormat::Repr,
|
||||
client_nonce: Vec<u8>,
|
||||
) -> Result<(KE2State, KE2Message), ProtocolError> {
|
||||
let server_e_kp = KeyFormat::generate_random(rng)?;
|
||||
let mut server_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
|
||||
TripleDHComponents {
|
||||
pk1: client_e_pk.clone(),
|
||||
sk1: server_e_kp.private().clone(),
|
||||
pk2: client_e_pk,
|
||||
sk2: server_s_sk.clone(),
|
||||
pk3: client_s_pk.clone(),
|
||||
sk3: server_e_kp.private().clone(),
|
||||
},
|
||||
&client_nonce,
|
||||
&server_nonce,
|
||||
client_s_pk,
|
||||
KeyFormat::public_from_private(&server_s_sk),
|
||||
)?;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&l1_bytes);
|
||||
let hashed_l1 = hasher.result();
|
||||
|
||||
let transcript2: Vec<u8> = [
|
||||
&hashed_l1[..],
|
||||
&l2_bytes[..],
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public().to_arr(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.input(&transcript2);
|
||||
let hashed_transcript = hasher2.result();
|
||||
|
||||
let mut mac = Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.input(&hashed_transcript);
|
||||
|
||||
Ok((
|
||||
KE2State {
|
||||
km3: km3.to_vec(),
|
||||
hashed_transcript: hashed_transcript.to_vec(),
|
||||
shared_secret: shared_secret.to_vec(),
|
||||
},
|
||||
KE2Message {
|
||||
server_nonce: server_nonce.to_vec(),
|
||||
server_e_pk: server_e_kp.public().clone(),
|
||||
mac: mac.result().code().to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) struct KE3State {
|
||||
pub(crate) shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct KE3Message {
|
||||
mac: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE3State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_state")?;
|
||||
|
||||
Ok(Self {
|
||||
shared_secret: checked_bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KE3Message {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.mac.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE3Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
|
||||
|
||||
Ok(Self {
|
||||
mac: checked_bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
|
||||
l2_component: Vec<u8>,
|
||||
ke2_message: KE2Message,
|
||||
ke1_state: &KE1State,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
client_s_sk: KeyFormat::Repr,
|
||||
) -> Result<(KE3State, KE3Message), ProtocolError> {
|
||||
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
|
||||
TripleDHComponents {
|
||||
pk1: ke2_message.server_e_pk.clone(),
|
||||
sk1: ke1_state.client_e_sk.clone(),
|
||||
pk2: server_s_pk.clone(),
|
||||
sk2: ke1_state.client_e_sk.clone(),
|
||||
pk3: ke2_message.server_e_pk.clone(),
|
||||
sk3: client_s_sk.clone(),
|
||||
},
|
||||
&ke1_state.client_nonce,
|
||||
&ke2_message.server_nonce,
|
||||
KeyFormat::public_from_private(&client_s_sk),
|
||||
server_s_pk,
|
||||
)?;
|
||||
|
||||
let transcript: Vec<u8> = [
|
||||
&ke1_state.hashed_l1[..],
|
||||
&l2_component[..],
|
||||
&ke2_message.server_nonce[..],
|
||||
&ke2_message.server_e_pk[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&transcript);
|
||||
let hashed_transcript = hasher.result();
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
server_mac.input(&hashed_transcript);
|
||||
|
||||
if ke2_message.mac != server_mac.result().code().to_vec() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<Sha256>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.input(&hashed_transcript);
|
||||
|
||||
Ok((
|
||||
KE3State {
|
||||
shared_secret: shared_secret.to_vec(),
|
||||
},
|
||||
KE3Message {
|
||||
mac: client_mac.result().code().to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// Outputs a shared secret
|
||||
pub(crate) fn finish_ke(
|
||||
ke3_message: KE3Message,
|
||||
ke2_state: &KE2State,
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let mut client_mac =
|
||||
Hmac::<Sha256>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.input(&ke2_state.hashed_transcript);
|
||||
|
||||
if ke3_message.mac != client_mac.result().code().to_vec() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ke2_state.shared_secret.to_vec())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
// 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
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
// 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 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 zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
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::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};
|
||||
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
// ====================== //
|
||||
////////////////////////////
|
||||
|
||||
/// The Triple Diffie-Hellman key exchange implementation
|
||||
///
|
||||
/// # 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)>);
|
||||
|
||||
/// The server state produced after the second key exchange message
|
||||
#[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)]
|
||||
#[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,
|
||||
client_e_pk: PublicKey<G>,
|
||||
server_e_pk: PublicKey<G>,
|
||||
shared_secret_1: GenericArray<u8, G::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, G::PkLen>,
|
||||
}
|
||||
|
||||
/// The second 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 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>,
|
||||
}
|
||||
|
||||
/// The third key exchange message
|
||||
#[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>,
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// 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 {
|
||||
session_key: input.take_array("session key")?.into_ha0_4(),
|
||||
expected_mac: input.take_array("expected mac")?.into_ha0_4(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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>>;
|
||||
|
||||
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();
|
||||
|
||||
sk.cat(mac)
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: implement via derive after `Hash` gets `Zeroize` support.
|
||||
impl<G: Group, H: Hash> Drop for Ke2Builder<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 drop(&mut self) {
|
||||
let Self {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk: _,
|
||||
server_e_pk: _,
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
} = self;
|
||||
|
||||
server_nonce.zeroize();
|
||||
digest::Reset::reset(transcript_hasher);
|
||||
shared_secret_1.zeroize();
|
||||
shared_secret_3.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group, H: Hash> ZeroizeOnDrop for Ke2Builder<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,
|
||||
{
|
||||
}
|
||||
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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>>;
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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,743 @@
|
||||
// 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;
|
||||
#[allow(deprecated)]
|
||||
use ml_kem::ExpandedKeyEncoding;
|
||||
use ml_kem::kem::{
|
||||
Ciphertext as MlKemCiphertext, Decapsulate, Encapsulate, Kem as MlKemTrait, KeyExport,
|
||||
KeySizeUser, TryKeyInit,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
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 + 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;
|
||||
#[allow(deprecated)]
|
||||
type RcDecapsulationKeyLen<K> =
|
||||
<<K as MlKemTrait>::DecapsulationKey as ExpandedKeyEncoding>::EncodedSize;
|
||||
type RcCiphertextLen<K> = <K as MlKemTrait>::CiphertextSize;
|
||||
type RcSharedSecretLen<K> = <K as MlKemTrait>::SharedKeySize;
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<K> KemCoreWrapper for K
|
||||
where
|
||||
K: MlKemTrait,
|
||||
K::EncapsulationKey: Encapsulate<Kem = K> + KeyExport + TryKeyInit + Clone,
|
||||
K::DecapsulationKey: Decapsulate<Kem = K> + ExpandedKeyEncoding + Clone + 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_expanded_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 key = ml_kem::array::Array::try_from(bytes.as_slice())
|
||||
.map_err(|_| ProtocolError::SerializationError)?;
|
||||
K::DecapsulationKey::from_expanded_bytes(&key)
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
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)]
|
||||
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,
|
||||
client_e_pk: PublicKey<G>,
|
||||
server_e_pk: PublicKey<G>,
|
||||
shared_secret_1: GenericArray<u8, G::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, G::PkLen>,
|
||||
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> Drop for KemKe2Builder<G, H, K>
|
||||
where
|
||||
G: Group,
|
||||
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,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.server_nonce.zeroize();
|
||||
digest::Digest::reset(&mut self.transcript_hasher);
|
||||
self.shared_secret_1.zeroize();
|
||||
self.shared_secret_3.zeroize();
|
||||
self.kem_shared_secret.zeroize();
|
||||
self.kem_ciphertext.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<G, H, K> ZeroizeOnDrop for KemKe2Builder<G, H, K>
|
||||
where
|
||||
G: Group,
|
||||
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,
|
||||
{
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
+464
-231
@@ -1,283 +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::{utils::check_slice_size, InternalPakeError};
|
||||
use generic_array::{
|
||||
sequence::Concat,
|
||||
typenum::{Sum, Unsigned, U32},
|
||||
ArrayLength, GenericArray,
|
||||
};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use derive_where::derive_where;
|
||||
use digest::{Output, OutputSizeUser};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
use std::ops::{Add, Deref};
|
||||
|
||||
/// A trait for sized key material that can be represented within a fixed byte
|
||||
/// array size, used to represent our DH key types
|
||||
pub trait SizedBytes: Sized + PartialEq {
|
||||
/// The typed representation of the byte length
|
||||
type Len: ArrayLength<u8>;
|
||||
|
||||
/// Converts this sized key material to a `GenericArray` of the same
|
||||
/// size. One can convert this to a `&[u8]` with `GenericArray::as_slice()`
|
||||
/// but the size information is then lost from the type.
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::Len>;
|
||||
|
||||
/// How to parse such sized material from a byte slice.
|
||||
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError>;
|
||||
}
|
||||
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
|
||||
pub trait KeyPair: Sized {
|
||||
/// The single key representation must have a specific byte size itself
|
||||
type Repr: SizedBytes + Clone;
|
||||
#[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, 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
|
||||
fn public(&self) -> &Self::Repr;
|
||||
pub fn public(&self) -> &PublicKey<G> {
|
||||
&self.pk
|
||||
}
|
||||
|
||||
/// The private key component
|
||||
fn private(&self) -> &Self::Repr;
|
||||
pub fn private(&self) -> &SK {
|
||||
&self.sk
|
||||
}
|
||||
}
|
||||
|
||||
/// A constructor that receives public and private key independently as
|
||||
/// bytes
|
||||
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError>;
|
||||
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
|
||||
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError>;
|
||||
|
||||
/// Obtaining a public key from secret bytes. At all times, we should have
|
||||
/// &public_from_private(self.private()) == self.public()
|
||||
fn public_from_private(secret: &Self::Repr) -> Self::Repr;
|
||||
|
||||
/// Check whether a public key is valid. This is meant to be applied on
|
||||
/// material provided through the network which fits the key
|
||||
/// representation (i.e. can be mapped to a curve point), but presents
|
||||
/// some risk - e.g. small subgroup check
|
||||
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError>;
|
||||
|
||||
/// Computes the diffie hellman function on a public key and private key
|
||||
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8>;
|
||||
pub(crate) fn 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
|
||||
/// with any length of keys. This encodes that we serialize the public key
|
||||
/// first, followed by the private key in binary formats (and expect it in this
|
||||
/// order upon decoding).
|
||||
impl<T, KP> SizedBytes for KP
|
||||
/// 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
|
||||
T: SizedBytes + Clone,
|
||||
KP: KeyPair<Repr = T> + PartialEq,
|
||||
T::Len: Add<T::Len>,
|
||||
Sum<T::Len, T::Len>: ArrayLength<u8>,
|
||||
G::Sk: DiffieHellman<G>,
|
||||
{
|
||||
type Len = Sum<T::Len, T::Len>;
|
||||
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
|
||||
let private = self.private().to_arr();
|
||||
let public = self.public().to_arr();
|
||||
public.concat(private)
|
||||
}
|
||||
|
||||
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes =
|
||||
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
|
||||
let single_key_len = <<KP::Repr as SizedBytes>::Len as Unsigned>::to_usize();
|
||||
let public = <T as SizedBytes>::from_bytes(&checked_bytes[..single_key_len])?;
|
||||
let private = <T as SizedBytes>::from_bytes(&checked_bytes[single_key_len..])?;
|
||||
KP::new(public, private)
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimalist key type built around [u8;32]
|
||||
#[derive(PartialEq, Eq, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct Key(Vec<u8>);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Key {
|
||||
type Target = Vec<u8>;
|
||||
/// 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;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
/// 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 TryFrom<Vec<u8>> for Key {
|
||||
type Error = InternalPakeError;
|
||||
|
||||
fn try_from(key_bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
Key::from_bytes(&key_bytes[..])
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedBytes for Key {
|
||||
type Len = U32;
|
||||
/// 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>);
|
||||
|
||||
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
|
||||
GenericArray::clone_from_slice(&self.0[..])
|
||||
/// 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 from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes =
|
||||
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
|
||||
Ok(Key(checked_bytes.to_vec()))
|
||||
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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of an X25519 keypair according to RFC7748
|
||||
#[derive(PartialEq)]
|
||||
pub struct X25519KeyPair {
|
||||
pk: Key,
|
||||
sk: Key,
|
||||
}
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
impl X25519KeyPair {
|
||||
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
|
||||
let sk = StaticSecret::new(rng);
|
||||
let pk = PublicKey::from(&sk);
|
||||
(pk.as_bytes().to_vec(), sk.to_bytes().to_vec())
|
||||
#[cfg(test)]
|
||||
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
|
||||
/// [`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::derive_random(&mut rng))
|
||||
})
|
||||
.no_shrink()
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair for X25519KeyPair {
|
||||
type Repr = Key;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
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;
|
||||
|
||||
fn public(&self) -> &Self::Repr {
|
||||
&self.pk
|
||||
macro_rules! test {
|
||||
($mod:ident, $point:ty) => {
|
||||
mod $mod {
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dh(kp1 in KeyPair::<$point>::uniform_keypair_strategy(),
|
||||
kp2 in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
|
||||
let dh1 = kp2.private().ke_diffie_hellman(&kp1.public());
|
||||
let dh2 = kp1.private().ke_diffie_hellman(kp2.public());
|
||||
|
||||
prop_assert_eq!(dh1, dh2);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn private(&self) -> &Self::Repr {
|
||||
&self.sk
|
||||
#[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;
|
||||
}
|
||||
|
||||
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
|
||||
Ok(X25519KeyPair {
|
||||
pk: public,
|
||||
sk: private,
|
||||
})
|
||||
#[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();
|
||||
}
|
||||
|
||||
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
|
||||
let (public, private) = X25519KeyPair::gen(rng);
|
||||
Ok(X25519KeyPair {
|
||||
pk: Key(public),
|
||||
sk: Key(private),
|
||||
})
|
||||
}
|
||||
#[test]
|
||||
fn remote_seed() {
|
||||
let mut oprf_seed = RemoteSeed::<OprfHash<Default>>(GenericArray::default().into_ha0_4());
|
||||
UnwrapErr(SysRng).fill_bytes(&mut oprf_seed.0);
|
||||
|
||||
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
|
||||
let mut secret_data = [0u8; 32];
|
||||
secret_data.copy_from_slice(&secret.0[..]);
|
||||
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
|
||||
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
|
||||
}
|
||||
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);
|
||||
|
||||
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
|
||||
let mut key_bytes = [0u8; 32];
|
||||
key_bytes.copy_from_slice(&key);
|
||||
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
|
||||
.to_edwards(1)
|
||||
.ok_or(InternalPakeError::PointError)?;
|
||||
if !point.is_torsion_free() {
|
||||
Err(InternalPakeError::SubGroupError)
|
||||
} else {
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
let server_setup = ServerSetup::<Default, _, _>::new_with_key_pair_and_seed(
|
||||
&mut UnwrapErr(SysRng),
|
||||
keypair,
|
||||
oprf_seed,
|
||||
);
|
||||
|
||||
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
|
||||
let mut pk_data = [0; 32];
|
||||
pk_data.copy_from_slice(&pk.0[..]);
|
||||
let mut sk_data = [0; 32];
|
||||
sk_data.copy_from_slice(&sk.0[..]);
|
||||
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom, minimalistic Key pair struct built on Key, aimed at reproducing the behavior of libsignal's keypairs
|
||||
#[derive(PartialEq)]
|
||||
pub struct SignalKeyPair {
|
||||
pk: Key,
|
||||
sk: Key,
|
||||
}
|
||||
|
||||
impl SignalKeyPair {
|
||||
fn clamp_scalar(mut scalar: [u8; 32]) -> ::curve25519_dalek::scalar::Scalar {
|
||||
scalar[0] &= 248;
|
||||
scalar[31] &= 127;
|
||||
scalar[31] |= 64;
|
||||
|
||||
::curve25519_dalek::scalar::Scalar::from_bits(scalar)
|
||||
}
|
||||
|
||||
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
|
||||
let mut bits = [0u8; 32];
|
||||
rng.fill_bytes(&mut bits);
|
||||
|
||||
// It's proper to sanitize the scalar here, and reproduces x25519::StaticSecret::new
|
||||
let sk = SignalKeyPair::clamp_scalar(bits);
|
||||
let pk = ::curve25519_dalek::constants::X25519_BASEPOINT * sk;
|
||||
|
||||
(pk.as_bytes().to_vec(), sk.as_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair for SignalKeyPair {
|
||||
type Repr = Key;
|
||||
|
||||
fn public(&self) -> &Self::Repr {
|
||||
&self.pk
|
||||
}
|
||||
|
||||
fn private(&self) -> &Self::Repr {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
|
||||
Ok(SignalKeyPair {
|
||||
pk: public,
|
||||
sk: private,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
|
||||
let (public, private) = SignalKeyPair::gen(rng);
|
||||
Ok(SignalKeyPair {
|
||||
pk: Key(public),
|
||||
sk: Key(private),
|
||||
})
|
||||
}
|
||||
|
||||
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
|
||||
let mut secret_data = [0u8; 32];
|
||||
secret_data.copy_from_slice(&secret.0[..]);
|
||||
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
|
||||
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
|
||||
}
|
||||
|
||||
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
|
||||
let mut key_bytes = [0u8; 32];
|
||||
key_bytes.copy_from_slice(&key);
|
||||
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
|
||||
.to_edwards(1)
|
||||
.ok_or(InternalPakeError::PointError)?;
|
||||
if !point.is_torsion_free() {
|
||||
Err(InternalPakeError::SubGroupError)
|
||||
} else {
|
||||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
|
||||
let mut pk_data = [0; 32];
|
||||
pk_data.copy_from_slice(&pk.0[..]);
|
||||
let mut sk_data = [0; 32];
|
||||
sk_data.copy_from_slice(&sk.0[..]);
|
||||
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
|
||||
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)
|
||||
}
|
||||
}
|
||||
+1364
-265
File diff suppressed because it is too large
Load Diff
+478
@@ -0,0 +1,478 @@
|
||||
// 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 core::ops::Add;
|
||||
|
||||
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
|
||||
#[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) blinded_element: BlindedElement<CS::OprfCs>,
|
||||
}
|
||||
|
||||
/// The answer sent by the server to the user, upon reception of the
|
||||
/// registration attempt
|
||||
#[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) evaluation_element: EvaluationElement<CS::OprfCs>,
|
||||
/// Server's static public key
|
||||
pub(crate) server_s_pk: PublicKey<KeGroup<CS>>,
|
||||
}
|
||||
|
||||
/// The final message from the client, containing sealed cryptographic
|
||||
/// identifiers
|
||||
#[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
|
||||
#[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> {
|
||||
pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
|
||||
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message,
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) 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,
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
Ok(Self {
|
||||
blinded_element: BlindedElement::deserialize(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> RegistrationResponse<CS> {
|
||||
/// Serialization into 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(mut input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let evaluation_element = EvaluationElement::deserialize(input)?;
|
||||
input = &input[EvaluationElementLen::<CS::OprfCs>::USIZE..];
|
||||
|
||||
Ok(Self {
|
||||
evaluation_element,
|
||||
server_s_pk: PublicKey::deserialize_take(&mut input)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[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 blinded_element = BlindedElement::deserialize(input)?;
|
||||
*input = &input[BlindedElementLen::<CS::OprfCs>::USIZE..];
|
||||
|
||||
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 evaluation_element = EvaluationElement::deserialize(input)?;
|
||||
input = &input[EvaluationElementLen::<CS::OprfCs>::USIZE..];
|
||||
|
||||
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,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
+1086
-841
File diff suppressed because it is too large
Load Diff
-133
@@ -1,133 +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};
|
||||
use generic_array::{typenum::U64, GenericArray};
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
// Low-level API
|
||||
// =============
|
||||
// This file contains an implementation of an oblivious pseudorandom function (OPRF), as well as password hashing and encryption functions.
|
||||
|
||||
pub(crate) struct OprfClientBytes<Grp: Group> {
|
||||
pub(crate) alpha: Grp,
|
||||
pub(crate) blinding_factor: Grp::Scalar,
|
||||
}
|
||||
|
||||
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
|
||||
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
|
||||
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
|
||||
/// an HKDF computation.
|
||||
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: Group<UniformBytesLen = U64>>(
|
||||
input: &[u8],
|
||||
pepper: Option<&[u8]>,
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<OprfClientBytes<G>, InternalPakeError> {
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, &input);
|
||||
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
|
||||
let blinding_factor = G::random_scalar(blinding_factor_rng);
|
||||
let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor;
|
||||
Ok(OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
})
|
||||
}
|
||||
|
||||
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
|
||||
/// message is sent from the server (who holds the OPRF key) to the client.
|
||||
pub(crate) fn generate_oprf2<G: Group>(
|
||||
point: G,
|
||||
oprf_key: &G::Scalar,
|
||||
) -> Result<G, InternalPakeError> {
|
||||
Ok(point * oprf_key)
|
||||
}
|
||||
|
||||
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
|
||||
/// the client unblinds the server's message.
|
||||
pub(crate) fn generate_oprf3<G: Group>(
|
||||
input: &[u8],
|
||||
point: G,
|
||||
blinding_factor: &G::Scalar,
|
||||
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, InternalPakeError> {
|
||||
let unblinded = point * &G::scalar_invert(&blinding_factor);
|
||||
let ikm: Vec<u8> = [&unblinded.to_bytes(), input].concat();
|
||||
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
|
||||
Ok(prk)
|
||||
}
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::group::Group;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::{arr, arr_impl, GenericArray};
|
||||
use hkdf::Hkdf;
|
||||
use rand_core::OsRng;
|
||||
|
||||
fn prf(
|
||||
input: &[u8],
|
||||
oprf_key: &[u8; 32],
|
||||
) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
|
||||
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
|
||||
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&curve_input));
|
||||
let scalar =
|
||||
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
|
||||
let res = point * scalar;
|
||||
let ikm: Vec<u8> = [res.to_bytes().as_slice(), &input].concat();
|
||||
|
||||
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
|
||||
prk
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oprf_retrieval() -> Result<(), InternalPakeError> {
|
||||
let input = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
} = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
|
||||
let salt_bytes = arr![
|
||||
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
];
|
||||
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes)?;
|
||||
let beta = generate_oprf2::<RistrettoPoint>(alpha, &salt)?;
|
||||
let res = generate_oprf3::<RistrettoPoint>(input, beta, &blinding_factor)?;
|
||||
let res2 = prf(&input[..], &salt.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 OprfClientBytes {
|
||||
alpha,
|
||||
blinding_factor,
|
||||
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
|
||||
let res = generate_oprf3::<RistrettoPoint>(&input, alpha, &blinding_factor).unwrap();
|
||||
|
||||
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
|
||||
let mut curve_input: Vec<u8> = Vec::new();
|
||||
curve_input.extend_from_slice(&hashed_input);
|
||||
curve_input.extend_from_slice(&[0u8; 32]);
|
||||
let point = RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&curve_input);
|
||||
let mut ikm: Vec<u8> = Vec::new();
|
||||
ikm.extend_from_slice(&point.to_bytes());
|
||||
ikm.extend_from_slice(&input);
|
||||
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
|
||||
|
||||
assert_eq!(res, prk);
|
||||
}
|
||||
}
|
||||
@@ -1,202 +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::{utils::check_slice_size, InternalPakeError, PakeError};
|
||||
use aead::{Aead, NewAead};
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use hmac::{Hmac, Mac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// This trait encapsulates an encryption scheme that satisfies random-key robustness (RKR), which is implemented
|
||||
/// through encrypt-then-HMAC -- see Section 3.1.1 of
|
||||
/// https://www.ietf.org/id/draft-krawczyk-cfrg-opaque-03.txt
|
||||
/// We require an Aead implementation with a 32-bit key size, since we
|
||||
/// will derive the symmetric key from pw using Sha256
|
||||
pub trait RKRCipher: Sized {
|
||||
/// The requirement of KeySize = U32 is so that we can use a 32-bit hash
|
||||
/// for key derivation form the user's password
|
||||
type AEAD: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead;
|
||||
|
||||
// Required members
|
||||
fn new(
|
||||
aead_output: Vec<u8>,
|
||||
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
|
||||
) -> Self;
|
||||
|
||||
fn aead_output(&self) -> &Vec<u8>;
|
||||
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize>;
|
||||
fn nonce(&self) -> &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
|
||||
// Provided members for enc / dec
|
||||
fn key_len() -> usize {
|
||||
<Self::AEAD as NewAead>::KeySize::to_usize()
|
||||
}
|
||||
|
||||
fn nonce_size() -> usize {
|
||||
<Self::AEAD as Aead>::NonceSize::to_usize()
|
||||
}
|
||||
|
||||
fn hmac_size() -> usize {
|
||||
<Sha256 as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
/// This estimates the size of the ciphertext once we encode —very specifically—
|
||||
/// the payload we have planned for the protocol's env_u
|
||||
fn ciphertest_size() -> usize {
|
||||
Self::key_len() + <Self::AEAD as Aead>::TagSize::to_usize() + Self::hmac_size()
|
||||
}
|
||||
|
||||
fn rkr_with_nonce_size() -> usize {
|
||||
Self::ciphertest_size() + Self::nonce_size()
|
||||
}
|
||||
|
||||
/// The format of the output ciphertext here is:
|
||||
/// encryption_output | tag | hmac | nonce
|
||||
/// variable length | AEAD_TAG_SIZE bytes | HMAC_SIZE bytes | NONCE_SIZE bytes
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes = check_slice_size(&bytes[..], Self::rkr_with_nonce_size(), "bytes")?;
|
||||
let nonce_start = bytes.len() - Self::nonce_size();
|
||||
let hmac_start = nonce_start - Self::hmac_size();
|
||||
|
||||
Ok(<Self as RKRCipher>::new(
|
||||
bytes[..hmac_start].to_vec(),
|
||||
GenericArray::from_slice(&checked_bytes[hmac_start..nonce_start]),
|
||||
GenericArray::from_slice(&checked_bytes[nonce_start..]),
|
||||
))
|
||||
}
|
||||
|
||||
/// Encrypt with AEAD. Note that this encryption scheme needs to satisfy "random-key robustness" (RKR).
|
||||
fn encrypt<R: RngCore + CryptoRng>(
|
||||
encryption_key: &[u8],
|
||||
hmac_key: &[u8],
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<Self, PakeError> {
|
||||
let mut nonce = vec![0u8; Self::nonce_size()];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let gen_nonce = GenericArray::from_slice(&nonce[..]);
|
||||
|
||||
let ciphertext = <Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key))
|
||||
.encrypt(
|
||||
GenericArray::from_slice(&nonce),
|
||||
aead::Payload {
|
||||
msg: &plaintext,
|
||||
aad: &aad,
|
||||
},
|
||||
)
|
||||
.map_err(|_| PakeError::EncryptionError)?;
|
||||
|
||||
let mut mac =
|
||||
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.input(&ciphertext);
|
||||
|
||||
Ok(<Self as RKRCipher>::new(
|
||||
ciphertext,
|
||||
&mac.result().code(),
|
||||
gen_nonce,
|
||||
))
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
encryption_key: &[u8],
|
||||
hmac_key: &[u8],
|
||||
aad: &[u8],
|
||||
) -> Result<Vec<u8>, PakeError> {
|
||||
let mut mac =
|
||||
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.input(self.aead_output());
|
||||
if mac.verify(self.hmac()).is_err() {
|
||||
return Err(PakeError::DecryptionHmacError);
|
||||
}
|
||||
|
||||
Aead::decrypt(
|
||||
&<Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key)),
|
||||
self.nonce(),
|
||||
aead::Payload {
|
||||
msg: self.aead_output(),
|
||||
aad: &aad,
|
||||
},
|
||||
)
|
||||
.map_err(|_| PakeError::DecryptionError)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is a straightforward instantiation of the trait separating the
|
||||
/// three components in Vecs
|
||||
pub struct RKRCiphertext<T> {
|
||||
aead_choice: std::marker::PhantomData<T>,
|
||||
aead_output: Vec<u8>,
|
||||
hmac: Vec<u8>,
|
||||
nonce: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<T: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead> RKRCipher for RKRCiphertext<T> {
|
||||
type AEAD = T;
|
||||
|
||||
fn new(
|
||||
aead_output: Vec<u8>,
|
||||
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
aead_choice: std::marker::PhantomData,
|
||||
aead_output,
|
||||
hmac: hmac.to_vec(),
|
||||
nonce: nonce.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn aead_output(&self) -> &Vec<u8> {
|
||||
&self.aead_output
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.aead_output[..], &self.hmac[..], &self.nonce[..]].concat()
|
||||
}
|
||||
|
||||
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize> {
|
||||
GenericArray::from_slice(&self.hmac[..])
|
||||
}
|
||||
|
||||
fn nonce(&self) -> &GenericArray<u8, <T as Aead>::NonceSize> {
|
||||
GenericArray::from_slice(&self.nonce[..])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chacha20poly1305::ChaCha20Poly1305;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[test]
|
||||
fn encrypt_and_decrypt() {
|
||||
let mut rng = OsRng;
|
||||
let mut encryption_key = [0u8; 32];
|
||||
rng.fill_bytes(&mut encryption_key);
|
||||
let mut hmac_key = [0u8; 32];
|
||||
rng.fill_bytes(&mut hmac_key);
|
||||
|
||||
let mut msg = [0u8; 100];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
|
||||
&encryption_key,
|
||||
&hmac_key,
|
||||
&msg,
|
||||
b"",
|
||||
&mut rng,
|
||||
)
|
||||
.unwrap();
|
||||
let decrypted = ciphertext.decrypt(&encryption_key, &hmac_key, b"").unwrap();
|
||||
assert_eq!(&msg.to_vec(), &decrypted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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 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<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 = 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
|
||||
#[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; size_of::<usize>()];
|
||||
output_array[size_of::<usize>() - input.len()..].copy_from_slice(input);
|
||||
Ok(usize::from_be_bytes(output_array))
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self {
|
||||
let mut self_ = self;
|
||||
|
||||
for bytes in iter {
|
||||
self_ = self_.chain(bytes);
|
||||
}
|
||||
|
||||
self_
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait 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());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+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 {}
|
||||
|
||||
+17
-6
@@ -1,8 +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_ke_test;
|
||||
mod serialization;
|
||||
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,577 +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::*,
|
||||
group::Group,
|
||||
key_exchange::NONCE_LEN,
|
||||
keypair::{Key, KeyPair, SignalKeyPair},
|
||||
opaque::*,
|
||||
tests::mock_rng::CycleRng,
|
||||
};
|
||||
use aes_gcm::Aes256Gcm;
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
|
||||
pub struct TestVectorParameters {
|
||||
pub client_s_pk: Vec<u8>,
|
||||
pub client_s_sk: Vec<u8>,
|
||||
pub client_e_pk: Vec<u8>,
|
||||
pub client_e_sk: Vec<u8>,
|
||||
pub server_s_pk: Vec<u8>,
|
||||
pub server_s_sk: Vec<u8>,
|
||||
pub server_e_pk: Vec<u8>,
|
||||
pub server_e_sk: Vec<u8>,
|
||||
pub password: Vec<u8>,
|
||||
pub blinding_factor_raw: Vec<u8>,
|
||||
pub blinding_factor: Vec<u8>,
|
||||
pub pepper: Vec<u8>,
|
||||
pub oprf_key: Vec<u8>,
|
||||
pub envelope_nonce: Vec<u8>,
|
||||
pub client_nonce: Vec<u8>,
|
||||
pub server_nonce: Vec<u8>,
|
||||
pub r1: Vec<u8>,
|
||||
pub r2: Vec<u8>,
|
||||
pub r3: Vec<u8>,
|
||||
pub l1: Vec<u8>,
|
||||
pub l2: Vec<u8>,
|
||||
pub l3: Vec<u8>,
|
||||
client_registration_state: Vec<u8>,
|
||||
server_registration_state: Vec<u8>,
|
||||
client_login_state: Vec<u8>,
|
||||
server_login_state: Vec<u8>,
|
||||
pub password_file: Vec<u8>,
|
||||
pub opaque_key: Vec<u8>,
|
||||
pub shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
static TEST_VECTOR: &str = r#"
|
||||
{
|
||||
"client_s_pk": "f7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
|
||||
"client_s_sk": "601ed276a42ec5795b3471f1a64e312f192e17ff252ce6053c8ecaf210138273",
|
||||
"client_e_pk": "57260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
|
||||
"client_e_sk": "e89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea68",
|
||||
"server_s_pk": "a2b4e12d0621ebfb2631e00f5c872ab749e1a33915f16fb11203658b2189cc5e",
|
||||
"server_s_sk": "90b6ca2ea8a37306060c7cd0998d4cdae59e972af7760312f7cf77099e78f940",
|
||||
"server_e_pk": "64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073",
|
||||
"server_e_sk": "883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f",
|
||||
"password": "70617373776f7264",
|
||||
"blinding_factor_raw": "b85e0df2ad0495771edf09a04b1073045e6472e2f86a41e9bab3143ebfb8eb08a3462503eb3750bf006dc82c93b37e07cdf3768018c22b431cf5146a9caeda1c",
|
||||
"blinding_factor": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f",
|
||||
"pepper": "706570706572",
|
||||
"oprf_key": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
|
||||
"envelope_nonce": "c87e44792a9dfd8858db676e",
|
||||
"client_nonce": "1f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23",
|
||||
"server_nonce": "d448cb1f58c38605fc29069ac688ec9c667c99d0316b38cd1b2609c1bc14aa90",
|
||||
"r1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd897",
|
||||
"r2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb",
|
||||
"r3": "374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676ef7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
|
||||
"l1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd8971f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a2357260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
|
||||
"l2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073d81a1104fbd599ef56228bdbe9bf7be4a38ae907a8717ca0883b9d69b2efc529",
|
||||
"l3": "a01332643e8aa7113f6f160205a9b3bd0705f3b33d8e4ea8eab9eae6685a6adb",
|
||||
"client_registration_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f70617373776f7264",
|
||||
"client_login_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60fe89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea681f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23dd1a7c2b4e9f9be94bd36f3b6c7f23aa9f1e6b3fda9030412a918d1288b4af1970617373776f7264",
|
||||
"server_registration_state": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
|
||||
"server_login_state": "809f95143f8f7fc1d0b42f578a83f714f58cfd96d9499aacee730ad296b37b19c18c903396e85da607d02542d4d07456e5357ff2e2eade3aaa42e532d4e9364f66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d",
|
||||
"password_file": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0bf7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e",
|
||||
"opaque_key": "682f2868a3e1460fed5a16767bd8778c33b4aecac6607270f848aa61c95a1a68",
|
||||
"shared_secret": "66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d"
|
||||
}
|
||||
"#;
|
||||
|
||||
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
|
||||
values[key]
|
||||
.as_str()
|
||||
.and_then(|s| hex::decode(&s.to_string()).ok())
|
||||
}
|
||||
|
||||
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
|
||||
TestVectorParameters {
|
||||
client_s_pk: decode(&values, "client_s_pk").unwrap(),
|
||||
client_s_sk: decode(&values, "client_s_sk").unwrap(),
|
||||
client_e_pk: decode(&values, "client_e_pk").unwrap(),
|
||||
client_e_sk: decode(&values, "client_e_sk").unwrap(),
|
||||
server_s_pk: decode(&values, "server_s_pk").unwrap(),
|
||||
server_s_sk: decode(&values, "server_s_sk").unwrap(),
|
||||
server_e_pk: decode(&values, "server_e_pk").unwrap(),
|
||||
server_e_sk: decode(&values, "server_e_sk").unwrap(),
|
||||
password: decode(&values, "password").unwrap(),
|
||||
blinding_factor_raw: decode(&values, "blinding_factor_raw").unwrap(),
|
||||
blinding_factor: decode(&values, "blinding_factor").unwrap(),
|
||||
pepper: decode(&values, "pepper").unwrap(),
|
||||
oprf_key: decode(&values, "oprf_key").unwrap(),
|
||||
envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
|
||||
client_nonce: decode(&values, "client_nonce").unwrap(),
|
||||
server_nonce: decode(&values, "server_nonce").unwrap(),
|
||||
r1: decode(&values, "r1").unwrap(),
|
||||
r2: decode(&values, "r2").unwrap(),
|
||||
r3: decode(&values, "r3").unwrap(),
|
||||
l1: decode(&values, "l1").unwrap(),
|
||||
l2: decode(&values, "l2").unwrap(),
|
||||
l3: decode(&values, "l3").unwrap(),
|
||||
client_registration_state: decode(&values, "client_registration_state").unwrap(),
|
||||
client_login_state: decode(&values, "client_login_state").unwrap(),
|
||||
server_registration_state: decode(&values, "server_registration_state").unwrap(),
|
||||
server_login_state: decode(&values, "server_login_state").unwrap(),
|
||||
password_file: decode(&values, "password_file").unwrap(),
|
||||
opaque_key: decode(&values, "opaque_key").unwrap(),
|
||||
shared_secret: decode(&values, "shared_secret").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stringify_test_vectors(p: &TestVectorParameters) -> String {
|
||||
let mut s = String::new();
|
||||
s.push_str("{\n");
|
||||
s.push_str(format!("\"client_s_pk\": \"{}\",\n", hex::encode(&p.client_s_pk)).as_str());
|
||||
s.push_str(format!("\"client_s_sk\": \"{}\",\n", hex::encode(&p.client_s_sk)).as_str());
|
||||
s.push_str(format!("\"client_e_pk\": \"{}\",\n", hex::encode(&p.client_e_pk)).as_str());
|
||||
s.push_str(format!("\"client_e_sk\": \"{}\",\n", hex::encode(&p.client_e_sk)).as_str());
|
||||
s.push_str(format!("\"server_s_pk\": \"{}\",\n", hex::encode(&p.server_s_pk)).as_str());
|
||||
s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
|
||||
s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
|
||||
s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
|
||||
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"blinding_factor_raw\": \"{}\",\n",
|
||||
hex::encode(&p.blinding_factor_raw)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"blinding_factor\": \"{}\",\n",
|
||||
hex::encode(&p.blinding_factor)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"pepper\": \"{}\",\n", hex::encode(&p.pepper)).as_str());
|
||||
s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"envelope_nonce\": \"{}\",\n",
|
||||
hex::encode(&p.envelope_nonce)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
|
||||
s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
|
||||
s.push_str(format!("\"r1\": \"{}\",\n", hex::encode(&p.r1)).as_str());
|
||||
s.push_str(format!("\"r2\": \"{}\",\n", hex::encode(&p.r2)).as_str());
|
||||
s.push_str(format!("\"r3\": \"{}\",\n", hex::encode(&p.r3)).as_str());
|
||||
s.push_str(format!("\"l1\": \"{}\",\n", hex::encode(&p.l1)).as_str());
|
||||
s.push_str(format!("\"l2\": \"{}\",\n", hex::encode(&p.l2)).as_str());
|
||||
s.push_str(format!("\"l3\": \"{}\",\n", hex::encode(&p.l3)).as_str());
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"client_registration_state\": \"{}\",\n",
|
||||
hex::encode(&p.client_registration_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"client_login_state\": \"{}\",\n",
|
||||
hex::encode(&p.client_login_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"server_registration_state\": \"{}\",\n",
|
||||
hex::encode(&p.server_registration_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"server_login_state\": \"{}\",\n",
|
||||
hex::encode(&p.server_login_state)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(
|
||||
format!(
|
||||
"\"password_file\": \"{}\",\n",
|
||||
hex::encode(&p.password_file)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
s.push_str(format!("\"opaque_key\": \"{}\",\n", hex::encode(&p.opaque_key)).as_str());
|
||||
s.push_str(format!("\"shared_secret\": \"{}\"\n", hex::encode(&p.shared_secret)).as_str());
|
||||
s.push_str("}\n");
|
||||
s
|
||||
}
|
||||
|
||||
fn generate_parameters() -> TestVectorParameters {
|
||||
let mut rng = OsRng;
|
||||
|
||||
// Inputs
|
||||
let server_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
|
||||
let server_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
|
||||
let client_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
|
||||
let client_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
|
||||
let password = b"password";
|
||||
let pepper = b"pepper";
|
||||
let mut blinding_factor_raw = [0u8; 64];
|
||||
rng.fill_bytes(&mut blinding_factor_raw);
|
||||
let mut oprf_key_raw = [0u8; 32];
|
||||
rng.fill_bytes(&mut oprf_key_raw);
|
||||
let mut envelope_nonce = [0u8; 12];
|
||||
rng.fill_bytes(&mut envelope_nonce);
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
let mut server_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
|
||||
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
|
||||
password,
|
||||
Some(pepper),
|
||||
&mut blinding_factor_registration_rng,
|
||||
)
|
||||
.unwrap();
|
||||
let r1_bytes = r1.to_bytes().to_vec();
|
||||
let blinding_factor_bytes = client_registration.blinding_factor.to_bytes();
|
||||
let client_registration_state = client_registration.to_bytes().to_vec();
|
||||
|
||||
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
|
||||
let (r2, server_registration) =
|
||||
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(r1, &mut oprf_key_rng)
|
||||
.unwrap();
|
||||
let r2_bytes = r2.to_bytes().to_vec();
|
||||
let oprf_key = server_registration.oprf_key;
|
||||
let oprf_key_bytes = EdwardsPoint::scalar_as_bytes(&oprf_key);
|
||||
let server_registration_state = server_registration.to_bytes().to_vec();
|
||||
|
||||
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
|
||||
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private());
|
||||
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
|
||||
|
||||
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
|
||||
let (r3, opaque_key_registration) = client_registration
|
||||
.finish::<_, SignalKeyPair>(r2, server_s_kp.public(), &mut finish_registration_rng)
|
||||
.unwrap();
|
||||
let r3_bytes = r3.to_bytes().to_vec();
|
||||
|
||||
let password_file = server_registration.finish(r3).unwrap();
|
||||
let password_file_bytes = password_file.to_bytes();
|
||||
|
||||
let mut client_login_start: Vec<u8> = Vec::new();
|
||||
client_login_start.extend_from_slice(&blinding_factor_raw);
|
||||
client_login_start.extend_from_slice(&client_e_kp.private());
|
||||
client_login_start.extend_from_slice(&client_nonce);
|
||||
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
|
||||
password,
|
||||
Some(pepper),
|
||||
&mut client_login_start_rng,
|
||||
)
|
||||
.unwrap();
|
||||
let l1_bytes = l1.to_bytes().to_vec();
|
||||
let client_login_state = client_login.to_bytes().to_vec();
|
||||
|
||||
let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_vec());
|
||||
let (l2, server_login) = ServerLogin::start(
|
||||
password_file,
|
||||
server_s_kp.private(),
|
||||
l1,
|
||||
&mut server_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
let l2_bytes = l2.to_bytes().to_vec();
|
||||
let server_login_state = server_login.to_bytes().to_vec();
|
||||
|
||||
let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_vec());
|
||||
let (l3, client_shared_secret, _opaque_key_login) = client_login
|
||||
.finish(l2, server_s_kp.public(), &mut client_e_sk_rng)
|
||||
.unwrap();
|
||||
let l3_bytes = l3.to_bytes().to_vec();
|
||||
|
||||
TestVectorParameters {
|
||||
client_s_pk: client_s_kp.public().to_vec(),
|
||||
client_s_sk: client_s_kp.private().to_vec(),
|
||||
client_e_pk: client_e_kp.public().to_vec(),
|
||||
client_e_sk: client_e_kp.private().to_vec(),
|
||||
server_s_pk: server_s_kp.public().to_vec(),
|
||||
server_s_sk: server_s_kp.private().to_vec(),
|
||||
server_e_pk: server_e_kp.public().to_vec(),
|
||||
server_e_sk: server_e_kp.private().to_vec(),
|
||||
password: password.to_vec(),
|
||||
blinding_factor_raw: blinding_factor_raw.to_vec(),
|
||||
blinding_factor: blinding_factor_bytes.to_vec(),
|
||||
pepper: pepper.to_vec(),
|
||||
oprf_key: oprf_key_bytes.to_vec(),
|
||||
envelope_nonce: envelope_nonce.to_vec(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
server_nonce: server_nonce.to_vec(),
|
||||
r1: r1_bytes,
|
||||
r2: r2_bytes,
|
||||
r3: r3_bytes,
|
||||
l1: l1_bytes,
|
||||
l2: l2_bytes,
|
||||
l3: l3_bytes,
|
||||
password_file: password_file_bytes,
|
||||
client_registration_state,
|
||||
server_registration_state,
|
||||
client_login_state,
|
||||
server_login_state,
|
||||
shared_secret: client_shared_secret,
|
||||
opaque_key: opaque_key_registration.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_test_vectors() {
|
||||
let parameters = generate_parameters();
|
||||
println!("{}", stringify_test_vectors(¶meters));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r1() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
let mut blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
|
||||
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
|
||||
¶meters.password,
|
||||
Some(¶meters.pepper),
|
||||
&mut blinding_factor_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(¶meters.r1), hex::encode(r1.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_registration_state),
|
||||
hex::encode(client_registration.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r2() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
|
||||
let (r2, server_registration) =
|
||||
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
|
||||
RegisterFirstMessage::try_from(¶meters.r1[..]).unwrap(),
|
||||
&mut oprf_key_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_registration_state),
|
||||
hex::encode(server_registration.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_r3() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_s_sk_and_nonce: Vec<u8> =
|
||||
[parameters.client_s_sk, parameters.envelope_nonce].concat();
|
||||
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
|
||||
let (r3, opaque_key_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::try_from(
|
||||
¶meters.client_registration_state[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish::<CycleRng, SignalKeyPair>(
|
||||
RegisterSecondMessage::try_from(¶meters.r2[..]).unwrap(),
|
||||
&Key::try_from(parameters.server_s_pk).unwrap(),
|
||||
&mut finish_registration_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hex::encode(parameters.r3), hex::encode(r3.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(parameters.opaque_key),
|
||||
hex::encode(opaque_key_registration.to_vec())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_file() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let server_registration =
|
||||
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
|
||||
¶meters.server_registration_state[..],
|
||||
)
|
||||
.unwrap();
|
||||
let password_file = server_registration
|
||||
.finish(RegisterThirdMessage::try_from(¶meters.r3[..]).unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.password_file),
|
||||
hex::encode(password_file.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l1() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let client_login_start = [
|
||||
parameters.blinding_factor_raw,
|
||||
parameters.client_e_sk,
|
||||
parameters.client_nonce,
|
||||
]
|
||||
.concat();
|
||||
let mut client_login_start_rng = CycleRng::new(client_login_start);
|
||||
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
|
||||
¶meters.password,
|
||||
Some(¶meters.pepper),
|
||||
&mut client_login_start_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(¶meters.l1), hex::encode(l1.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_login_state),
|
||||
hex::encode(client_login.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l2() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
|
||||
let (l2, server_login) = ServerLogin::start::<_, Aes256Gcm, _, SignalKeyPair>(
|
||||
ServerRegistration::try_from(¶meters.password_file[..]).unwrap(),
|
||||
&Key::try_from(parameters.server_s_sk).unwrap(),
|
||||
LoginFirstMessage::<EdwardsPoint>::try_from(¶meters.l1[..]).unwrap(),
|
||||
&mut server_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hex::encode(¶meters.l2), hex::encode(l2.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_login_state),
|
||||
hex::encode(server_login.to_bytes())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_l3() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
|
||||
let (l3, shared_secret, opaque_key_login) =
|
||||
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
|
||||
¶meters.client_login_state[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish(
|
||||
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(¶meters.l2[..]).unwrap(),
|
||||
&Key::try_from(parameters.server_s_pk)?,
|
||||
&mut client_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.shared_secret),
|
||||
hex::encode(&shared_secret)
|
||||
);
|
||||
assert_eq!(hex::encode(¶meters.l3), hex::encode(l3.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.opaque_key),
|
||||
hex::encode(opaque_key_login)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let shared_secret = ServerLogin::try_from(¶meters.server_login_state[..])
|
||||
.unwrap()
|
||||
.finish(LoginThirdMessage::try_from(¶meters.l3[..])?)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.shared_secret),
|
||||
hex::encode(shared_secret)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_complete_flow(
|
||||
registration_password: &[u8],
|
||||
login_password: &[u8],
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = SignalKeyPair::generate_random(&mut server_rng)?;
|
||||
let (register_m1, client_state) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
|
||||
registration_password,
|
||||
None,
|
||||
&mut client_rng,
|
||||
)?;
|
||||
let (register_m2, server_state) =
|
||||
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
|
||||
register_m1,
|
||||
&mut server_rng,
|
||||
)?;
|
||||
let (register_m3, registration_opaque_key) =
|
||||
client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
|
||||
let p_file = server_state.finish(register_m3)?;
|
||||
let (login_m1, client_login_state) =
|
||||
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
|
||||
login_password,
|
||||
None,
|
||||
&mut client_rng,
|
||||
)?;
|
||||
let (login_m2, server_login_state) =
|
||||
ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
|
||||
|
||||
let client_login_result =
|
||||
client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng);
|
||||
|
||||
if hex::encode(registration_password) == hex::encode(login_password) {
|
||||
let (login_m3, client_shared_secret, login_opaque_key) = client_login_result?;
|
||||
let server_shared_secret = server_login_state.finish(login_m3)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(server_shared_secret),
|
||||
hex::encode(client_shared_secret)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(registration_opaque_key),
|
||||
hex::encode(login_opaque_key)
|
||||
);
|
||||
} else {
|
||||
let res = match client_login_result {
|
||||
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
|
||||
_ => false,
|
||||
};
|
||||
assert!(res);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_flow_success() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"good password")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_flow_fail() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"bad password")
|
||||
}
|
||||
@@ -0,0 +1,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
|
||||
"#;
|
||||
@@ -1,124 +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::{
|
||||
group::Group,
|
||||
keypair::{KeyPair, SignalKeyPair, SizedBytes},
|
||||
opaque::*,
|
||||
rkr_encryption::{RKRCipher as _, RKRCiphertext},
|
||||
};
|
||||
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
|
||||
use chacha20poly1305::ChaCha20Poly1305;
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
fn random_ristretto_point() -> RistrettoPoint {
|
||||
let mut rng = OsRng;
|
||||
let mut bits = [0u8; 64];
|
||||
rng.fill_bytes(&mut bits);
|
||||
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_registration_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
// serialization order: scalar, password
|
||||
let mut bytes: Vec<u8> = vec![];
|
||||
bytes.extend_from_slice(sc.as_bytes());
|
||||
bytes.extend_from_slice(pw);
|
||||
let reg = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::try_from(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_registration_roundtrip() {
|
||||
// If we don't have envelope and client_pk, the server registration just
|
||||
// contains the prf key
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
let mut oprf_bytes: Vec<u8> = vec![];
|
||||
oprf_bytes.extend_from_slice(sc.as_bytes());
|
||||
let reg = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(
|
||||
&oprf_bytes[..],
|
||||
)
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, oprf_bytes);
|
||||
// If we do have envelope and client pk, the server registration contains
|
||||
// the whole kit
|
||||
let rkr_size = RKRCiphertext::<ChaCha20Poly1305>::rkr_with_nonce_size();
|
||||
let mut mock_rkr_bytes = vec![0u8; rkr_size];
|
||||
rng.fill_bytes(&mut mock_rkr_bytes);
|
||||
println!("{}", mock_rkr_bytes.len());
|
||||
let mock_client_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
|
||||
// serialization order: scalar, public key, envelope
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
bytes.extend_from_slice(sc.as_bytes());
|
||||
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
|
||||
bytes.extend_from_slice(&mock_rkr_bytes);
|
||||
let reg =
|
||||
ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(&bytes[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_first_message_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let pt_bytes = pt.to_bytes();
|
||||
let r1 = RegisterFirstMessage::<RistrettoPoint>::try_from(pt_bytes.as_slice()).unwrap();
|
||||
let r1_bytes = r1.to_bytes();
|
||||
assert_eq!(pt_bytes, r1_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_second_message_roundtrip() {
|
||||
let pt = random_ristretto_point();
|
||||
let pt_bytes = pt.to_bytes();
|
||||
|
||||
let message = pt_bytes.to_vec();
|
||||
let r2 = RegisterSecondMessage::<RistrettoPoint>::try_from(&message[..]).unwrap();
|
||||
let r2_bytes = r2.to_bytes();
|
||||
assert_eq!(message, r2_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_third_message_roundtrip() {
|
||||
let mut rng = OsRng;
|
||||
let skp = SignalKeyPair::generate_random(&mut rng).unwrap();
|
||||
let pubkey_bytes = skp.public().to_arr();
|
||||
|
||||
let mut encryption_key = [0u8; 32];
|
||||
rng.fill_bytes(&mut encryption_key);
|
||||
let mut hmac_key = [0u8; 32];
|
||||
rng.fill_bytes(&mut hmac_key);
|
||||
|
||||
let mut msg = [0u8; 32];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
|
||||
&encryption_key,
|
||||
&hmac_key,
|
||||
&msg,
|
||||
&pubkey_bytes,
|
||||
&mut rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut message = Vec::new();
|
||||
message.extend_from_slice(&ciphertext.to_bytes());
|
||||
message.extend_from_slice(&pubkey_bytes);
|
||||
|
||||
let r3 =
|
||||
RegisterThirdMessage::<ChaCha20Poly1305, SignalKeyPair>::try_from(&message[..]).unwrap();
|
||||
let r3_bytes = r3.to_bytes();
|
||||
assert_eq!(message, r3_bytes);
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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