1 Commits
Author SHA1 Message Date
daxpeddaandGitHub 1012439d2f Backport zeroize fix (#266)
* Backport `zeroize` fix

* Update version

* Fix CI

* Remove bench for CI MSRV

* Downgrade rustyline for MSRV

* Downgrade proptest for MSRV

* Downgrade zeroize for MSRV
2022-01-30 16:05:19 -08:00
65 changed files with 6174 additions and 18358 deletions
-3
View File
@@ -1,3 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
-156
View File
@@ -1,156 +0,0 @@
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
-26
View File
@@ -1,26 +0,0 @@
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 }}
+186
View File
@@ -0,0 +1,186 @@
name: Rust CI
on:
push:
branches:
- master
pull_request:
types: [opened, repoened, synchronize]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
toolchain:
- nightly
- 1.41.0
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ matrix.backend_feature }}
cross-test:
name: Test on ${{ matrix.target }} (using cross)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
# 32-bit x86
- i686-unknown-linux-gnu
backend_feature:
- u64_backend
- u32_backend
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: cargo install cross
# Note: just use `cross` as you would `cargo`, but always
# pass the `--target=${{ matrix.target }}` arg. (Yes, really).
- run: cross test --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.backend_feature }}
slow-hash-test:
name: Test on ${{ matrix.target }} with slow hash
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: cargo test --verbose --features slow-hash --no-default-features --features ${{ matrix.backend_feature }}
serde-test:
name: Test on ${{ matrix.target }} with serde support
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: cargo test --verbose --features serialize --no-default-features --features ${{ matrix.backend_feature }}
simple-login-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- nightly
- 1.41.0
name: test simple_login command-line example
steps:
- name: install expect
run: sudo apt-get install expect
- name: Checkout sources
uses: actions/checkout@v2
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/simple_login.exp
digital-locker-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- nightly
- 1.41.0
name: test digital_locker command-line example
steps:
- name: install expect
run: sudo apt-get install expect
- name: Checkout sources
uses: actions/checkout@v2
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/digital_locker.exp
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
format:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
deny-check:
name: cargo-deny check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: EmbarkStudios/cargo-deny-action@v1
+29
View File
@@ -0,0 +1,29 @@
name: Publish
on:
release:
types: [published] # Only publish to crates.io when we formally publish a release
# For more on how to formally release on Github, read https://help.github.com/en/articles/creating-releases
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
- name: Login to crates.io
run: cargo login $CRATES_IO_TOKEN
env:
CRATES_IO_TOKEN: ${{ secrets.crates_io_token }} # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets
- name: Dry run publish opaque-ke
run: cargo publish --dry-run --manifest-path Cargo.toml
- name: Publish opaque-ke
run: cargo publish --manifest-path Cargo.toml
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
+2 -152
View File
@@ -1,152 +1,8 @@
# Changelog
## 1.0.0-pre.0 (June 29, 2026)
## 0.6.1 (January 25, 2022)
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
* Fix `zeroize` implementing `Drop` on `enum`s now
## 0.6.0 (June 30, 2021)
@@ -161,12 +17,6 @@ Forked from [facebook/opaque-ke](https://github.com/facebook/opaque-ke/) at `4.1
* 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
+76
View File
@@ -0,0 +1,76 @@
# 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
+20 -2
View File
@@ -2,11 +2,29 @@
We want to make contributing to this project as easy and transparent as
possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to opaque-ke, you agree that your contributions will be
licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root
directory of this source tree.
licensed under the LICENSE file in the root directory of this source tree.
+37 -121
View File
@@ -1,130 +1,46 @@
[package]
authors = [
"VexaHub Developers",
"Kevin Lewi <[email protected]>",
"François Garillot <[email protected]>",
]
categories = ["no-std", "cryptography"]
name = "opaque-ke"
version = "0.6.1"
repository = "https://github.com/novifinancial/opaque-ke"
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
edition = "2024"
exclude = ["/src/tests/"]
keywords = ["cryptography", "opaque", "passwords", "authentication", "pake"]
license = "Apache-2.0 OR MIT"
name = "opaque-vx"
authors = ["Kevin Lewi <[email protected]>", "François Garillot <[email protected]>"]
license = "MIT"
edition = "2018"
readme = "README.md"
repository = "https://github.com/vexahub/opaque-vx"
rust-version = "1.90"
version = "1.0.0-pre.0"
[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", "dep:rand_core"]
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"]
default = ["u64_backend", "serialize"]
slow-hash = ["argon2"]
bench = []
u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"]
serialize = ["serde", "base64", "generic-array/serde", "curve25519-dalek/serde"]
[dependencies]
argon2 = { version = "0.6.0-rc", default-features = false, features = [
"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 = [
"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 = "0.13"
ml-kem = { version = "0.3", default-features = false, features = [
"zeroize",
], optional = true }
rand = { version = "0.10", default-features = false }
rand_core = { version = "0.10", default-features = false, optional = true }
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 }
argon2 = { version = "0.2", optional = true }
base64 = { version = "0.13", optional = true }
curve25519-dalek = { version = "3.1.0", default-features = false, features = ["std"] }
digest = "0.9.0"
displaydoc = "0.1.7"
generic-array = "0.14.4"
generic-bytes = { version = "0.1.0" }
hkdf = "0.11.0"
hmac = "0.11.0"
rand = "0.8"
serde = { version = "1", features = ["derive"], optional = true }
subtle = { version = "2.3.0", default-features = false }
thiserror = "1.0.22"
zeroize = { version = "~1.1", features = ["zeroize_derive"] }
[dev-dependencies]
anyhow = "1"
bincode-next = { version = "3", features = ["serde", "alloc"] }
chacha20poly1305 = "0.11"
criterion = "0.8"
cryptoki = "0.12"
elliptic-curve = { version = "0.14", features = ["alloc", "pkcs8"] }
rand_core = { version = "0.10", default-features = false }
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 }
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)'] }
anyhow = "1.0.35"
base64 = "0.13.0"
bincode = "1"
chacha20poly1305 = "0.7.1"
hex = "0.4.2"
lazy_static = "1.4.0"
serde_json = "1.0.60"
sha2 = "0.9.2"
proptest = "0.3"
rustyline = "1"
+21
View File
@@ -0,0 +1,21 @@
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
View File
@@ -1,176 +0,0 @@
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
View File
@@ -1,23 +0,0 @@
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.
+16 -50
View File
@@ -1,28 +1,20 @@
## The OPAQUE key exchange protocol
## The OPAQUE key exchange protocol ![Build Status](https://github.com/novifinancial/opaque-ke/workflows/Rust%20CI/badge.svg)
[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.
[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.
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**.
This implementation is based on the [Internet Draft for OPAQUE](https://github.com/cfrg/draft-irtf-cfrg-opaque).
Background
----------
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.
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.
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-vx/) along with an example for usage. More examples can be found
in
the [examples](./examples) directory.
The API can be found [here](https://docs.rs/opaque-ke/) along with an example for usage. More examples can be found in the [examples](./examples) directory.
Installation
------------
@@ -30,57 +22,31 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
opaque-ke = { package = "opaque-ke-vx", version = "1.0.0-pre.0" }
opaque-ke = "0.6.1"
```
### Minimum Supported Rust Version
Rust **1.89** 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
- [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`
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
- [draft-irtf-cfrg-opaque-05](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-05.html), containing a detailed (byte-level) specification for OPAQUE
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
- [opaque-wasm](https://github.com/marucjmar/opaque-wasm), a WebAssembly package for this library
Contributors
------------
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)).
The authors of this code are Kevin Lewi
([@kevinlewi](https://github.com/kevinlewi)) and François Garillot ([@huitseeker](https://github.com/huitseeker)).
To learn more about contributing to this project, [see this document](./CONTRIBUTING.md).
#### Acknowledgments
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for
improving
this implementation. Additional credit goes to @daxpedda for adding no_std support, p256 support, and making other
general
improvements to the library.
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving
this implementation.
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.
This project is [MIT licensed](./LICENSE).
-334
View File
@@ -1,334 +0,0 @@
// 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::rngs::SysRng;
use rand_core::UnwrapErr;
#[cfg(feature = "ristretto255")]
static SUFFIX: &str = "ristretto255";
#[cfg(not(feature = "ristretto255"))]
static SUFFIX: &str = "p256";
struct Default;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Default {
type OprfCs = Ristretto255;
type KeyExchange = TripleDh<Ristretto255, sha2::Sha512>;
type Ksf = ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for Default {
type OprfCs = p256::NistP256;
type KeyExchange = TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = ksf::Identity;
}
fn server_setup(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
c.bench_function(&format!("server setup ({SUFFIX})"), move |b| {
b.iter(|| {
ServerSetup::<Default>::new(&mut rng);
})
});
}
fn client_registration_start(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let password = b"password";
c.bench_function(&format!("client registration start ({SUFFIX})"), move |b| {
b.iter(|| {
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
})
});
}
fn server_registration_start(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
c.bench_function(&format!("server registration start ({SUFFIX})"), move |b| {
b.iter(|| {
ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
})
});
}
fn client_registration_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
c.bench_function(
&format!("client registration finish ({SUFFIX})"),
move |b| {
b.iter(|| {
client_registration_start_result
.clone()
.state
.finish(
&mut rng,
password,
server_registration_start_result.message.clone(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
})
},
);
}
fn server_registration_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
c.bench_function(
&format!("server registration finish ({SUFFIX})"),
move |b| {
b.iter(|| {
ServerRegistration::finish(client_registration_finish_result.clone().message);
})
},
);
}
fn client_login_start(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let password = b"password";
c.bench_function(&format!("client login start ({SUFFIX})"), move |b| {
b.iter(|| {
ClientLogin::<Default>::start(&mut rng, password).unwrap();
})
});
}
fn server_login_start_real(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
c.bench_function(&format!("server login start (real) ({SUFFIX})"), move |b| {
b.iter(|| {
ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file.clone()),
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
})
});
}
fn server_login_start_fake(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
c.bench_function(&format!("server login start (fake) ({SUFFIX})"), move |b| {
b.iter(|| {
ServerLogin::start(
&mut rng,
&server_setup,
None,
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
})
});
}
fn client_login_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
let server_login_start = ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file),
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
c.bench_function(&format!("client login finish ({SUFFIX})"), move |b| {
b.iter(|| {
client_login_start_result
.clone()
.state
.finish(
&mut rng,
password,
server_login_start.clone().message,
ClientLoginFinishParameters::default(),
)
.unwrap();
})
});
}
fn server_login_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
let server_login_start_result = ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file),
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
let client_login_finish_result = client_login_start_result
.state
.finish(
&mut rng,
password,
server_login_start_result.clone().message,
ClientLoginFinishParameters::default(),
)
.unwrap();
c.bench_function(&format!("server login finish ({SUFFIX})"), move |b| {
b.iter(|| {
server_login_start_result
.clone()
.state
.finish(
client_login_finish_result.clone().message,
ServerLoginParameters::default(),
)
.unwrap();
})
});
}
criterion_group!(
opaque_benches,
server_setup,
client_registration_start,
server_registration_start,
client_registration_finish,
server_registration_finish,
client_login_start,
server_login_start_real,
server_login_start_fake,
client_login_finish,
server_login_finish,
);
criterion_main!(opaque_benches);
-1
View File
@@ -1 +0,0 @@
doc-valid-idents = ["HashEdDSA", "PureEdDSA", ".."]
+54 -30
View File
@@ -9,7 +9,6 @@
# 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
@@ -18,15 +17,14 @@
# 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`
@@ -37,15 +35,20 @@ targets = [
db-path = "~/.cargo/advisory-db"
# The url of the advisory database to use
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
unmaintained = "warn"
# 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 = "deny"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
# dev-dependency
"RUSTSEC-2024-0436",
# bincode is unmaintained but only used as dev-dependency for tests
"RUSTSEC-2025-0141",
#"RUSTSEC-0000-0000",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
@@ -61,26 +64,47 @@ 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",
"BSD-3-Clause",
"Apache-2.0",
#"Apache-2.0 WITH LLVM-exception",
#"MIT",
#"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.95
confidence-threshold = 0.8
# 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 = ["Unicode-3.0"], name = "unicode-ident", version = "*" },
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
@@ -99,8 +123,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]
@@ -111,7 +135,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`.
@@ -128,24 +152,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`.
+78 -90
View File
@@ -1,62 +1,53 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Demonstrates an implementation of a server-side secured digital locker using
//! the client's OPAQUE export key, over a command-line interface
//!
//! A client can password-protect a secret message to be stored in a digital
//! locker, controlled by the server. The locker's contents are only revealed to
//! the holder of the password when attempting to open the locker.
//! A client can password-protect a secret message to be stored in a digital locker,
//! controlled by the server. The locker's contents are only revealed to the holder
//! of the password when attempting to open the locker.
//!
//! The client-server interactions are executed in a three-step protocol within
//! the account_registration (for password registration) and account_login (for
//! password login) functions. These steps must be performed in the specific
//! sequence outlined in each of these functions.
//! The client-server interactions are executed in a three-step protocol
//! within the account_registration (for password registration) and
//! account_login (for password login) functions. These steps
//! must be performed in the specific sequence outlined in each of these
//! functions.
//!
//! The CipherSuite trait allows the application to configure the primitives
//! used by OPAQUE, but must be kept consistent across the steps of the
//! protocol.
//! The CipherSuite trait allows the application to configure the
//! primitives used by OPAQUE, but must be kept consistent across the steps
//! of the protocol.
//!
//! In a more realistic client-server interaction, the client must send messages
//! over "the wire" to the server. These bytes are serialized and explicitly
//! annotated in the below functions.
//! In a more realistic client-server interaction, the client must send
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use rustyline::error::ReadlineError;
use rustyline::Editor;
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::{
use opaque_ke::{
ciphersuite::CipherSuite,
rand::{rngs::OsRng, RngCore},
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginParameters, ServerRegistration, ServerSetup,
ServerLoginStartParameters, ServerRegistration, ServerSetup,
};
use rand_core::UnwrapErr;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
// The ciphersuite trait allows to specify the underlying primitives that will
// be used in the OPAQUE protocol
// The ciphersuite trait allows to specify the underlying primitives
// that will be used in the OPAQUE protocol
#[allow(dead_code)]
struct 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 Default;
impl CipherSuite for Default {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha512;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
struct Locker {
@@ -66,47 +57,44 @@ struct Locker {
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
let mut rng = UnwrapErr(SysRng);
let mut rng = OsRng;
let mut nonce_bytes = [0u8; 12];
rng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::try_from(&nonce_bytes[..]).unwrap();
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(&nonce, plaintext.as_ref()).unwrap();
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
[nonce_bytes.to_vec(), ciphertext].concat()
}
// Decrypt using a key and a ciphertext (nonce included) to recover the original
// plaintext
// Decrypt using a key and a ciphertext (nonce included) to recover the original plaintext
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
cipher
.decrypt(
&Nonce::try_from(&ciphertext[..12]).unwrap(),
Nonce::from_slice(&ciphertext[..12]),
ciphertext[12..].as_ref(),
)
.unwrap()
}
// Password-based registration and encryption of client secret message between a
// client and server
// Password-based registration and encryption of client secret message between a client and server
fn register_locker(
server_setup: &ServerSetup<DefaultCipherSuite>,
server_setup: &ServerSetup<Default>,
locker_id: usize,
password: String,
secret_message: String,
) -> Locker {
let mut client_rng = UnwrapErr(SysRng);
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes())
.unwrap();
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(&registration_request_bytes).unwrap(),
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
&locker_id.to_be_bytes(),
)
.unwrap();
@@ -118,8 +106,7 @@ fn register_locker(
.state
.finish(
&mut client_rng,
password.as_bytes(),
RegistrationResponse::deserialize(&registration_response_bytes).unwrap(),
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
@@ -134,39 +121,39 @@ fn register_locker(
// Client sends message_bytes to server
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
);
Locker {
contents: ciphertext,
password_file: password_file.serialize().to_vec(),
password_file: password_file.serialize(),
}
}
// Open the contents of a locker with a password between a client and server
fn open_locker(
server_setup: &ServerSetup<DefaultCipherSuite>,
server_setup: &ServerSetup<Default>,
locker_id: usize,
password: String,
locker: &Locker,
) -> Result<String, String> {
let mut client_rng = UnwrapErr(SysRng);
let mut client_rng = OsRng;
let client_login_start_result =
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<DefaultCipherSuite>::deserialize(&locker.password_file).unwrap();
let mut server_rng = UnwrapErr(SysRng);
ServerRegistration::<Default>::deserialize(&locker.password_file[..]).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
server_setup,
&server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginParameters::default(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
@@ -174,9 +161,7 @@ fn open_locker(
// 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(),
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
ClientLoginFinishParameters::default(),
);
@@ -191,17 +176,14 @@ fn open_locker(
let server_login_finish_result = server_login_start_result
.state
.finish(
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
ServerLoginParameters::default(),
)
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.unwrap();
// Server sends locker contents, encrypted under the session key, to the client
let encrypted_locker_contents =
encrypt(&server_login_finish_result.session_key, &locker.contents);
// Client decrypts contents of locker, first under the session key, and then
// Client decrypts contents of locker, first under the session key, and then under the export key
let plaintext = decrypt(
&client_login_finish_result.export_key,
&decrypt(
@@ -213,10 +195,10 @@ fn open_locker(
}
fn main() {
let mut rng = UnwrapErr(SysRng);
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
let mut rng = OsRng;
let server_setup = ServerSetup::<Default>::new(&mut rng);
let mut rl = Editor::<(), _>::new().unwrap();
let mut rl = Editor::<()>::new();
let mut registered_lockers: Vec<Locker> = vec![];
loop {
display_lockers(&registered_lockers);
@@ -275,10 +257,13 @@ fn main() {
&registered_lockers[locker_index],
) {
Ok(contents) => {
println!("\n\nSuccess! Contents: {contents}\n\n");
println!("\n\nSuccess! Contents: {}\n\n", contents);
}
Err(err) => {
println!("\n\nError encountered, could not open locker: {err}\n\n");
println!(
"\n\nError encountered, could not open locker: {}\n\n",
err
);
}
}
}
@@ -295,13 +280,16 @@ fn main() {
// Helper functions
fn display_lockers(lockers: &[Locker]) {
fn display_lockers(lockers: &Vec<Locker>) {
let mut locker_numbers = vec![];
for (i, _) in lockers.iter().enumerate() {
locker_numbers.push(i);
}
println!("\nCurrently registered locker numbers: {locker_numbers:?}\n");
println!(
"\nCurrently registered locker numbers: {:?}\n",
locker_numbers
);
}
// Handle readline errors
@@ -314,7 +302,7 @@ fn handle_error(err: ReadlineError) {
println!("CTRL-D");
}
err => {
println!("Error: {err:?}");
println!("Error: {:?}", err);
}
}
}
@@ -323,11 +311,11 @@ fn handle_error(err: ReadlineError) {
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<(), DefaultHistory>,
rl: &mut Editor<()>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{query}: "));
let readline = rl.readline(&format!("{}: ", query));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
+65 -88
View File
@@ -1,78 +1,64 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Demonstrates a simple client-server password-based login protocol using
//! OPAQUE, over a command-line interface
//! Demonstrates a simple client-server password-based login protocol
//! using OPAQUE, over a command-line interface
//!
//! The client-server interactions are executed in a three-step protocol within
//! the account_registration (for password registration) and account_login (for
//! password login) functions. These steps must be performed in the specific
//! sequence outlined in each of these functions.
//! The client-server interactions are executed in a three-step protocol
//! within the account_registration (for password registration) and
//! account_login (for password login) functions. These steps
//! must be performed in the specific sequence outlined in each of these
//! functions.
//!
//! The CipherSuite trait allows the application to configure the primitives
//! used by OPAQUE, but must be kept consistent across the steps of the
//! protocol.
//! The CipherSuite trait allows the application to configure the
//! primitives used by OPAQUE, but must be kept consistent across the steps
//! of the protocol.
//!
//! In a more realistic client-server interaction, the client must send messages
//! over "the wire" to the server. These bytes are serialized and explicitly
//! annotated in the below functions.
//! In a more realistic client-server interaction, the client must send
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
use 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_core::UnwrapErr;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
use rustyline::Editor;
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
use opaque_ke::{
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization,
CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration, ServerSetup,
};
// 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>;
struct Default;
impl CipherSuite for Default {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha512;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
// Password-based registration between a client and server
fn account_registration(
server_setup: &ServerSetup<DefaultCipherSuite>,
server_setup: &ServerSetup<Default>,
username: String,
password: String,
) -> Array<u8, ServerRegistrationLen<DefaultCipherSuite>> {
let mut client_rng = UnwrapErr(SysRng);
) -> Vec<u8> {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes())
.unwrap();
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(&registration_request_bytes).unwrap(),
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
username.as_bytes(),
)
.unwrap();
@@ -84,8 +70,7 @@ fn account_registration(
.state
.finish(
&mut client_rng,
password.as_bytes(),
RegistrationResponse::deserialize(&registration_response_bytes).unwrap(),
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
@@ -94,35 +79,34 @@ fn account_registration(
// Client sends message_bytes to server
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
);
password_file.serialize().into_ha0_4()
password_file.serialize()
}
// Password-based login between a client and server
fn account_login(
server_setup: &ServerSetup<DefaultCipherSuite>,
server_setup: &ServerSetup<Default>,
username: String,
password: String,
password_file_bytes: &[u8],
) -> bool {
let mut client_rng = UnwrapErr(SysRng);
let mut client_rng = OsRng;
let client_login_start_result =
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<DefaultCipherSuite>::deserialize(password_file_bytes).unwrap();
let mut server_rng = UnwrapErr(SysRng);
let password_file = ServerRegistration::<Default>::deserialize(password_file_bytes).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
server_setup,
&server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
username.as_bytes(),
ServerLoginParameters::default(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
@@ -130,9 +114,7 @@ fn account_login(
// 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(),
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
ClientLoginFinishParameters::default(),
);
@@ -147,22 +129,18 @@ fn account_login(
let server_login_finish_result = server_login_start_result
.state
.finish(
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
ServerLoginParameters::default(),
)
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.unwrap();
client_login_finish_result.session_key == server_login_finish_result.session_key
}
fn main() {
let mut rng = UnwrapErr(SysRng);
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
let mut rng = OsRng;
let server_setup = ServerSetup::<Default>::new(&mut rng);
let mut rl = Editor::<(), _>::new().unwrap();
let mut registered_users =
HashMap::<String, Array<u8, ServerRegistrationLen<DefaultCipherSuite>>>::new();
let mut rl = Editor::<()>::new();
let mut registered_users = HashMap::<String, Vec<u8>>::new();
loop {
println!(
"\nCurrently registered usernames: {:?}\n",
@@ -194,12 +172,11 @@ fn main() {
{
println!("\nLogin success!");
} else {
// Note that at this point, the client knows whether or not the
// login succeeded. In this example, we simply rely on
// client-reported result of login, but in a real client-server
// implementation, the server may not know the outcome of login yet,
// and extra care must be taken to ensure that the server can learn
// the outcome as well.
// Note that at this point, the client knows whether or not the login
// succeeded. In this example, we simply rely on client-reported result
// of login, but in a real client-server implementation, the server may not
// know the outcome of login yet, and extra care must be taken to ensure
// that the server can learn the outcome as well.
println!("\nIncorrect password, please try again.");
}
}
@@ -228,7 +205,7 @@ fn handle_error(err: ReadlineError) {
println!("CTRL-D");
}
err => {
println!("Error: {err:?}");
println!("Error: {:?}", err);
}
}
}
@@ -237,11 +214,11 @@ fn handle_error(err: ReadlineError) {
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<(), DefaultHistory>,
rl: &mut Editor<()>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{query}: "));
let readline = rl.readline(&format!("{}: ", query));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
-30
View File
@@ -1,30 +0,0 @@
{
"$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
}
-1
View File
@@ -1 +0,0 @@
newline_style = "Unix"
+4 -4
View File
@@ -1,8 +1,8 @@
#!/bin/expect -f
# SPDX-License-Identifier: MIT OR Apache-2.0
# Copyright (c) VexaHub and contributors.
# Copyright (c) Meta Platforms, Inc. and affiliates.
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set timeout 1
spawn cargo run --example digital_locker
Regular → Executable
+5 -5
View File
@@ -1,11 +1,11 @@
#!/bin/expect -f
# SPDX-License-Identifier: MIT OR Apache-2.0
# Copyright (c) VexaHub and contributors.
# Copyright (c) Meta Platforms, Inc. and affiliates.
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set timeout 1
spawn cargo run --example simple_login --features argon2
spawn cargo run --example simple_login
match_max 100000
sleep 1
expect "*
+26 -47
View File
@@ -1,55 +1,34 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the [`CipherSuite`] trait to specify the underlying primitives for
//! OPAQUE
//! 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;
use crate::{
hash::Hash, key_exchange::traits::KeyExchange, map_to_curve::GroupWithMapToCurve,
slow_hash::SlowHash,
};
use digest::Digest;
/// Configures the underlying primitives used in OPAQUE
/// * `OprfCs`: A VOPRF ciphersuite, see [`voprf::CipherSuite`].
/// * `KeGroup`: A `Group` used for the `KeyExchange`.
/// * `Group`: a finite cyclic group along with a point representation, along
/// with an extension trait PasswordToCurve that allows some customization on
/// how to hash a password to a curve point. See `group::Group` and
/// `map_to_curve::GroupWithMapToCurve`.
/// * `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;
/// * `SlowHash`: A slow hashing function, typically used for password hashing
pub trait CipherSuite {
/// A finite cyclic group along with a point representation along with
/// an extension trait PasswordToCurve that allows some customization on
/// how to hash a password to a curve point. See `group::Group` and
/// `map_to_curve::GroupWithMapToCurve`.
type Group: GroupWithMapToCurve<UniformBytesLen = <Self::Hash as Digest>::OutputSize>;
/// A key exchange protocol
type KeyExchange: KeyExchange;
/// A key stretching function, typically used for password hashing
type Ksf: Ksf;
type KeyExchange: KeyExchange<Self::Hash, Self::Group>;
/// The main hash function use (for HKDF computations and hashing transcripts)
type Hash: Hash;
/// A slow hashing function, typically used for password hashing
type SlowHash: SlowHash<Self::Hash>;
}
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;
+271 -251
View File
@@ -1,310 +1,330 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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 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 crate::{
ciphersuite::CipherSuite,
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
group::Group,
hash::Hash,
keypair::{KeyPair, PrivateKey, PublicKey},
map_to_curve::GroupWithMapToCurve,
opaque::{bytestrings_from_identifiers, Identifiers},
};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
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;
const STR_AUTH_KEY: &[u8] = b"AuthKey";
const STR_EXPORT_KEY: &[u8] = b"ExportKey";
const STR_PRIVATE_KEY: &[u8] = b"PrivateKey";
const STR_OPAQUE_HASH_TO_SCALAR: &[u8] = b"OPAQUE-HashToScalar";
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
const NONCE_LEN: usize = 32;
fn build_inner_envelope_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<PublicKey, InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalPakeError::HkdfError)?;
let client_static_keypair =
KeyPair::<CS::Group>::from_private_key_slice(CS::Group::scalar_as_bytes(
&CS::Group::hash_to_scalar::<CS::Hash>(&keypair_seed[..], STR_OPAQUE_HASH_TO_SCALAR)?,
))?;
Ok(client_static_keypair.public().clone())
}
fn recover_keys_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<KeyPair<CS::Group>, InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalPakeError::HkdfError)?;
let client_static_keypair =
KeyPair::<CS::Group>::from_private_key_slice(CS::Group::scalar_as_bytes(
&CS::Group::hash_to_scalar::<CS::Hash>(&keypair_seed[..], STR_OPAQUE_HASH_TO_SCALAR)?,
))?;
Ok(client_static_keypair)
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
#[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode {
Zero = 0,
Internal = 1,
}
impl Zeroize for InnerEnvelopeMode {
fn zeroize(&mut self) {
*self = Self::Zero
}
}
impl TryFrom<u8> for InnerEnvelopeMode {
type Error = ProtocolError;
type Error = PakeError;
fn try_from(x: u8) -> Result<Self, Self::Error> {
match x {
1 => Ok(InnerEnvelopeMode::Internal),
_ => Err(ProtocolError::SerializationError),
_ => Err(PakeError::SerializationError),
}
}
}
/// This struct is an instantiation of the envelope.
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
///
/// Note that earlier versions of this specification described an implementation
/// of this envelope using an encryption scheme that satisfied random-key
/// robustness.
/// 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)]
/// Note that earlier versions of this specification described an
/// implementation of this envelope using an encryption scheme that
/// satisfied random-key robustness
/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4).
/// The specification update has simplified this assumption by taking
/// an XOR-based approach without compromising on security, and to avoid
/// the confusion around the implementation of an RKR-secure encryption.
pub(crate) struct Envelope<CS: CipherSuite> {
pub(crate) mode: InnerEnvelopeMode,
nonce: GenericArray<u8, NonceLen>,
hmac: Output<OprfHash<CS>>,
mode: InnerEnvelopeMode,
nonce: Vec<u8>,
hmac: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
// Note that this struct represents an envelope that has been "opened" with the
// asssociated key. This key is also used to derive the export_key parameter,
// which is technically unrelated to the envelope's encrypted and authenticated
// contents.
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
pub(crate) client_static_keypair: KeyPair<KeGroup<CS>>,
pub(crate) export_key: Output<OprfHash<CS>>,
pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for Envelope<CS> {
fn clone(&self) -> Self {
Self {
mode: self.mode.clone(),
nonce: self.nonce.clone(),
hmac: self.hmac.clone(),
}
}
}
pub(crate) struct OpenedInnerEnvelope<CS: CipherSuite> {
pub(crate) export_key: Output<OprfHash<CS>>,
impl_debug_eq_hash_for!(struct Envelope<CS: CipherSuite>, [mode, nonce, hmac]);
// Note that this struct represents an envelope that has been "opened" with the asssociated
// key. This key is also used to derive the export_key parameter, which is technically
// unrelated to the envelope's encrypted and authenticated contents.
pub(crate) struct OpenedEnvelope<CS: CipherSuite> {
pub(crate) client_static_keypair: KeyPair<CS::Group>,
pub(crate) export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
pub(crate) id_u: Vec<u8>,
pub(crate) id_s: Vec<u8>,
}
#[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>;
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
}
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,
))
fn hmac_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::to_usize()
}
/// 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,
))
fn export_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::to_usize()
}
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 {
pub(crate) fn len() -> usize {
<CS::Hash as Digest>::OutputSize::to_usize() + NONCE_LEN
}
pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.nonce[..], &self.hmac[..]].concat()
}
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
if bytes.len() < NONCE_LEN {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let nonce = bytes[..NONCE_LEN].to_vec();
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into());
}
InnerEnvelopeMode::Internal => {
recover_keys_internal::<CS>(randomized_pwd_hasher, self.nonce)?
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(),
};
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 hmac_key_size = Self::hmac_key_size();
let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
Ok(OpenedEnvelope {
client_static_keypair,
export_key: opened.export_key,
identifiers,
Ok(Self {
mode,
nonce,
hmac: GenericArray::clone_from_slice(hmac),
})
}
/// 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(),
nonce: vec![0u8; NONCE_LEN],
hmac: GenericArray::clone_from_slice(&vec![
0u8;
<CS::Hash as Digest>::OutputSize::to_usize()
]),
}
}
#[cfg(test)]
pub(crate) fn len() -> usize {
use generic_array::typenum::Unsigned;
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R,
key: &[u8],
server_s_pk: &[u8],
optional_ids: Option<Identifiers>,
) -> Result<
(
Self,
PublicKey,
GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
),
InternalPakeError,
> {
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
let (mode, client_s_pk) = (
InnerEnvelopeMode::Internal,
build_inner_envelope_internal::<CS>(key, &nonce)?,
);
let (id_u, id_s) =
bytestrings_from_identifiers(&optional_ids, &client_s_pk.to_arr(), server_s_pk);
let aad = construct_aad(&id_u, &id_s, server_s_pk);
let (envelope, export_key) = Self::seal_raw(key, &nonce, &aad, mode)?;
Ok((envelope, client_s_pk, export_key))
}
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>> {
self.nonce
.concat_ext(&GenericArray::from_ha0_4(self.hmac.clone()))
/// 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(
key: &[u8],
nonce: &[u8],
aad: &[u8],
mode: InnerEnvelopeMode,
) -> Result<(Self, GenericArray<u8, <CS::Hash as Digest>::OutputSize>), InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, key);
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
h.expand(&[nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(&[nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let mut hmac = Hmac::<CS::Hash>::new_from_slice(&hmac_key)
.map_err(|_| InternalPakeError::HmacError)?;
hmac.update(nonce);
hmac.update(aad);
let hmac_bytes = hmac.finalize().into_bytes();
Ok((
Self {
mode,
nonce: nonce.to_vec(),
hmac: hmac_bytes,
},
GenericArray::clone_from_slice(&export_key),
))
}
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(),
pub(crate) fn open(
&self,
key: &[u8],
server_s_pk: &[u8],
optional_ids: &Option<Identifiers>,
) -> Result<OpenedEnvelope<CS>, InternalPakeError> {
let client_static_keypair = match self.mode {
InnerEnvelopeMode::Zero => {
return Err(InternalPakeError::IncompatibleEnvelopeModeError)
}
InnerEnvelopeMode::Internal => recover_keys_internal::<CS>(key, &self.nonce)?,
};
let (id_u, id_s) = bytestrings_from_identifiers(
optional_ids,
&client_static_keypair.public().to_arr(),
server_s_pk,
);
let aad = construct_aad(&id_u, &id_s, server_s_pk);
let opened = self.open_raw(key, &aad)?;
Ok(OpenedEnvelope {
client_static_keypair,
export_key: opened.export_key,
id_u,
id_s,
})
}
/// 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(
&self,
key: &[u8],
aad: &[u8],
) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalPakeError> {
let h = Hkdf::<CS::Hash>::new(None, key);
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
h.expand(&[&self.nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(&[&self.nonce, STR_EXPORT_KEY].concat(), &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let mut hmac = Hmac::<CS::Hash>::new_from_slice(&hmac_key)
.map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.nonce);
hmac.update(aad);
if hmac.verify(&self.hmac).is_err() {
return Err(InternalPakeError::SealOpenHmacError);
}
Ok(OpenedInnerEnvelope {
export_key: GenericArray::<u8, <CS::Hash as Digest>::OutputSize>::clone_from_slice(
&export_key,
),
})
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![(self.hmac.as_ptr(), self.hmac.len())]
}
}
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for Envelope<CS> {
fn zeroize(&mut self) {
self.mode.zeroize();
self.nonce.zeroize();
self.hmac.zeroize();
}
}
impl<CS: CipherSuite> Drop for Envelope<CS> {
fn drop(&mut self) {
self.zeroize();
}
}
// Helper functions
fn build_inner_envelope_internal<CS: CipherSuite>(
randomized_pwd_hasher: &Hkdf<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)
fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> {
[server_s_pk, id_s, id_u].concat()
}
+153 -136
View File
@@ -1,90 +1,18 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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.
//! A list of error types which are produced during an execution of the protocol
use core::convert::Infallible;
use core::error::Error;
use core::fmt::Debug;
use displaydoc::Display;
use thiserror::Error;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum InternalError {
/// Size of input is empty or longer then [`u16::MAX`].
HashToScalar,
/// Computing HKDF failed while deriving subkeys
HkdfError,
/// Computing HMAC failed while supplying a secret key
HmacError,
/// Computing the key stretching function failed
KsfError,
/// Error while performing a KEM operation
KemError,
/** This error occurs when the envelope seal open hmac check fails
HMAC check in seal open failed. */
SealOpenHmacError,
/** This error occurs when attempting to open an envelope of the wrong
type (base mode, custom identifier) */
IncompatibleEnvelopeModeError,
/// Error from the OPRF evaluation
OprfError(voprf::Error),
/// Error from the OPRF evaluation
OprfInternalError(voprf::InternalError),
}
impl Debug for InternalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::HashToScalar => f.debug_tuple("HashToScalar").finish(),
Self::HkdfError => f.debug_tuple("HkdfError").finish(),
Self::HmacError => f.debug_tuple("HmacError").finish(),
Self::KsfError => f.debug_tuple("KsfError").finish(),
Self::KemError => f.debug_tuple("KemError").finish(),
Self::SealOpenHmacError => f.debug_tuple("SealOpenHmacError").finish(),
Self::IncompatibleEnvelopeModeError => {
f.debug_tuple("IncompatibleEnvelopeModeError").finish()
}
Self::OprfError(error) => f.debug_tuple("OprfError").field(error).finish(),
Self::OprfInternalError(error) => {
f.debug_tuple("OprfInternalError").field(error).finish()
}
}
}
}
impl Error for InternalError {}
impl From<voprf::Error> for InternalError {
fn from(voprf_error: voprf::Error) -> Self {
Self::OprfError(voprf_error)
}
}
impl From<voprf::Error> for ProtocolError {
fn from(voprf_error: voprf::Error) -> Self {
Self::LibraryError(InternalError::OprfError(voprf_error))
}
}
impl From<voprf::InternalError> for ProtocolError {
fn from(voprf_error: voprf::InternalError) -> Self {
Self::LibraryError(InternalError::OprfInternalError(voprf_error))
}
}
/// Represents an error in protocol handling
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ProtocolError<T = Infallible> {
/// Internal error encountered
LibraryError(InternalError),
/// Error in validating credentials
InvalidLoginError,
/// Error with serializing / deserializing protocol messages
SerializationError,
/** Invalid length for `{name}`: expected {len}, actual {actual_len} */
#[allow(clippy::doc_markdown, unused_assignments)]
#[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum InternalPakeError {
/// Deserializing from a byte sequence failed
InvalidByteSequence,
/// Invalid length for {name}: expected {len}, but is actually {actual_len}.
SizeError {
/// name
name: &'static str,
@@ -93,74 +21,163 @@ pub enum ProtocolError<T = Infallible> {
/// 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),
/// Could not decompress point.
PointError,
/// Key belongs to a small subgroup!
SubGroupError,
/// hashing to a key failed
HashingFailure,
/// Computing the hash-to-curve function failed
HashToCurveError,
/// Computing HKDF failed while deriving subkeys
HkdfError,
/// Computing HMAC failed while supplying a secret key
HmacError,
/// Computing the slow hashing function failed
SlowHashError,
/// This error occurs when the envelope seal fails
/// Constructing the envelope seal failed.
SealError,
/// This error occurs when the envelope seal open fails
/// Opening the envelope seal failed.
SealOpenError,
/// This error occurs when the envelope seal open hmac check fails
/// HMAC check in seal open failed.
SealOpenHmacError,
/// This error occurs when the envelope cannot be constructed properly
/// based on the credentials that were specified to be required.
InvalidEnvelopeStructureError,
/// This error occurs when attempting to open an envelope of the wrong
/// type (base mode, custom identifier)
IncompatibleEnvelopeModeError,
/// This error occurs when the envelope is opened and deserialization
/// fails
UnexpectedEnvelopeContentsError,
}
impl<T: Debug> Debug for ProtocolError<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LibraryError(pake_error) => {
f.debug_tuple("LibraryError").field(pake_error).finish()
}
Self::InvalidLoginError => f.debug_tuple("InvalidLoginError").finish(),
Self::SerializationError => f.debug_tuple("SerializationError").finish(),
Self::SizeError {
name,
len,
actual_len,
} => f
.debug_struct("SizeError")
.field("name", name)
.field("len", len)
.field("actual_len", actual_len)
.finish(),
Self::ReflectedValueError => f.debug_tuple("ReflectedValueError").finish(),
Self::Custom(custom) => f.debug_tuple("Custom").field(custom).finish(),
}
/// Represents an error in password checking
#[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum PakeError {
/// This error results from an internal error during PRF construction
///
/// Internal error during PRF verification: {0}
CryptoError(InternalPakeError),
/// This error occurs when the server object that is being called finish() on is malformed
/// Incomplete set of keys passed into finish() function
IncompleteKeysError,
/// The provided server public key doesn't match the sealed one
IncompatibleServerStaticPublicKeyError,
/// Error in key exchange protocol when attempting to validate MACs
KeyExchangeMacValidationError,
/// Error in validating credentials
InvalidLoginError,
/// Error with serializing / deserializing protocol messages
SerializationError,
/// Identity group element was encountered during deserialization, which is invalid
IdentityGroupElementError,
}
// This is meant to express future(ly) non-trivial ways of converting the
// internal error into a PakeError
impl From<InternalPakeError> for PakeError {
fn from(e: InternalPakeError) -> PakeError {
PakeError::CryptoError(e)
}
}
impl<T: Error> Error for ProtocolError<T> {}
/// Represents an error in protocol handling
#[derive(Clone, Debug, Display, Error, Eq, Hash, PartialEq)]
pub enum ProtocolError {
/// This error results from an error during password verification
///
/// Internal error during password verification: {0}
VerificationError(PakeError),
/// This error occurs when the inner envelope is malformed
InvalidInnerEnvelopeError,
/// This error occurs when the server answer cannot be handled
/// Server response cannot be handled.
ServerError,
/// This error occurs when the server specifies an envelope credentials
/// format that is invalid
ServerInvalidEnvelopeCredentialsFormatError,
/// This error occurs when the client request cannot be handled
/// Client request cannot be handled.
ClientError,
}
// This is meant to express future(ly) non-trivial ways of converting the
// Pake error into a ProtocolError
impl From<PakeError> for ProtocolError {
fn from(e: PakeError) -> ProtocolError {
ProtocolError::VerificationError(e)
}
}
// This is meant to express future(ly) non-trivial ways of converting the
// internal error into a ProtocolError
impl<T> From<InternalError> for ProtocolError<T> {
fn from(e: InternalError) -> ProtocolError<T> {
Self::LibraryError(e)
impl From<InternalPakeError> for ProtocolError {
fn from(e: InternalPakeError) -> ProtocolError {
ProtocolError::VerificationError(e.into())
}
}
// 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 {
// 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 {
unreachable!()
}
}
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 {},
}
impl From<generic_bytes::TryFromSizedBytesError> for InternalPakeError {
fn from(_: generic_bytes::TryFromSizedBytesError) -> Self {
InternalPakeError::InvalidByteSequence
}
}
impl From<generic_bytes::TryFromSizedBytesError> for PakeError {
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
PakeError::CryptoError(e.into())
}
}
impl From<generic_bytes::TryFromSizedBytesError> for ProtocolError {
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
PakeError::CryptoError(e.into()).into()
}
}
pub(crate) mod utils {
use super::*;
pub fn check_slice_size<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], InternalPakeError> {
if slice.len() != expected_len {
return Err(InternalPakeError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
pub fn check_slice_size_atleast<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], InternalPakeError> {
if slice.len() < expected_len {
return Err(InternalPakeError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the Group trait to specify the underlying prime order group used in
//! OPAQUE's OPRF
use crate::errors::InternalPakeError;
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use generic_array::{
typenum::{U32, U64},
ArrayLength, GenericArray,
};
use std::convert::TryInto;
use rand::{CryptoRng, RngCore};
use std::ops::Mul;
use zeroize::Zeroize;
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
/// subgroup is noted additively — as in the draft RFC — in this trait.
pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
/// The type of base field scalars
type Scalar: Zeroize + Clone;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8>;
/// Return a scalar from its fixed-length bytes representation
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError>;
/// picks a scalar at random
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
/// The byte length necessary to represent group elements
type ElemLen: ArrayLength<u8>;
/// Return an element from its fixed-length bytes representation
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError>;
/// Serializes the `self` group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
/// Hashes points presumed to be uniformly random to the curve. The
/// impl is allowed to perform additional hashes if it needs to, but this
/// may not be necessary as this function is going to be called with the
/// output of a kdf.
type UniformBytesLen: ArrayLength<u8>;
/// Hashes a slice of pseudo-random bytes of the correct length to a curve point
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self;
/// Get the base point for the group
fn base_point() -> Self;
/// Multiply the point by a scalar, represented as a slice
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool;
}
/// 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_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop {
let scalar = {
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
};
if scalar != Scalar::zero() {
break scalar;
}
}
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert()
}
// The byte length necessary to represent group elements
type ElemLen = U32;
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> {
CompressedRistretto::from_slice(element_bits)
.decompress()
.ok_or(InternalPakeError::PointError)
}
// serialization of a group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
// https://caniuse.rs/features/array_gt_32_impls
let bits: [u8; 64] = {
let mut bytes = [0u8; 64];
bytes.copy_from_slice(uniform_bytes);
bytes
};
RistrettoPoint::from_uniform_bytes(&bits)
}
fn base_point() -> Self {
RISTRETTO_BASEPOINT_POINT
}
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self {
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
self * Scalar::from_bits(arr)
}
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self == &Self::identity()
}
}
+10 -70
View File
@@ -1,75 +1,15 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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.
//! 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};
use digest::{BlockInput, FixedOutput, Reset, Update};
pub(crate) type OutputSize<H> = <<H as CoreProxy>::Core as OutputSizeUser>::OutputSize;
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
// Associated types could be simplified when they are made as defaults:
// https://github.com/rust-lang/rust/issues/29661
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
/// 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,
{
}
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
+102
View File
@@ -0,0 +1,102 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
macro_rules! impl_debug_eq_hash_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? std::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: std::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("$name")
.field("$field1", &self.$field1)
$(.field("$field2", &self.$field2))*
.finish()
}
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
}
impl$(<$($gen$(: $bound)?),+>)? std::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: std::hash::Hash,)+)?
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::hash::Hash::hash(&self.$field1, state);
$(std::hash::Hash::hash(&self.$field2, state);)*
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? std::fmt::Debug for $name$(<$($gen),+>)?
$(where $($type: std::fmt::Debug,)+)?
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("$name")
.field(&self.$field1)
$(.field(&self.$field2))*
.finish()
}
}
impl$(<$($gen$(: $bound)?),+>)? Eq for $name$(<$($gen),+>)?
$(where $($type: Eq,)+)?
{}
impl$(<$($gen$(: $bound)?),+>)? PartialEq for $name$(<$($gen),+>)?
$(where $($type: PartialEq,)+)?
{
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.$field1, &other.$field1)
$(&& PartialEq::eq(&self.$field2, &other.$field2))*
}
}
impl$(<$($gen$(: $bound)?),+>)? std::hash::Hash for $name$(<$($gen),+>)?
$(where $($type: std::hash::Hash,)+)?
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::hash::Hash::hash(&self.$field1, state);
$(std::hash::Hash::hash(&self.$field2, state);)*
}
}
};
}
macro_rules! impl_clone_for {
(struct $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:ident$(, $field2:ident)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self {
$field1: self.$field1.clone(),
$($field2: self.$field2.clone(),)*
}
}
}
};
(tuple $name:ident$(<$($gen:ident$(: $bound:tt)?),+$(,)?>)?, [$field1:tt$(, $field2:tt)*$(,)?]$(, )?$([$($type:ty),+$(,)?]$(,)?)?) => {
impl$(<$($gen$(: $bound)?),+>)? Clone for $name$(<$($gen),+>)?
$(where $($type: Clone,)+)?
{
fn clone(&self) -> Self {
Self(
self.$field1.clone(),
$(self.$field2.clone(),)*
)
}
}
};
}
-150
View File
@@ -1,150 +0,0 @@
// 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());
}
-504
View File
@@ -1,504 +0,0 @@
// 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::rngs::SysRng;
use rand_core::UnwrapErr;
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();
}
}
-111
View File
@@ -1,111 +0,0 @@
// 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()
}
}
-59
View File
@@ -1,59 +0,0 @@
// 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>;
}
-232
View File
@@ -1,232 +0,0 @@
// 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)
}
}
+7 -409
View File
@@ -1,412 +1,10 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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.
//! Includes instantiations of key exchange protocols used in the login step for
//! OPAQUE
//! Includes instantiations of key exchange protocols used in the
//! login step for OPAQUE
pub mod group;
pub(crate) mod shared;
pub mod sigma_i;
pub(crate) mod traits;
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;
-488
View File
@@ -1,488 +0,0 @@
// 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())
}
}
-143
View File
@@ -1,143 +0,0 @@
// 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::rngs::SysRng;
use rand_core::UnwrapErr;
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();
}
-84
View File
@@ -1,84 +0,0 @@
// 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>;
}
}
-307
View File
@@ -1,307 +0,0 @@
// 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())
}
}
-597
View File
@@ -1,597 +0,0 @@
// 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())
}
}
-85
View File
@@ -1,85 +0,0 @@
// 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>;
}
}
-41
View File
@@ -1,41 +0,0 @@
// 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()
}
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::{PakeError, ProtocolError},
group::Group,
hash::Hash,
keypair::{PrivateKey, PublicKey},
};
use rand::{CryptoRng, RngCore};
use zeroize::Zeroize;
pub trait KeyExchange<D: Hash, G: Group> {
type KE1State: FromBytes + ToBytesWithPointers + Zeroize + Clone;
type KE2State: FromBytes + ToBytesWithPointers + Zeroize + Clone;
type KE1Message: FromBytes + ToBytes + Clone;
type KE2Message: FromBytes + ToBytes + Clone;
type KE3Message: FromBytes + ToBytes + Clone;
fn generate_ke1<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: PublicKey,
server_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: PublicKey,
client_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError>;
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError>;
fn ke2_message_size() -> usize;
}
pub trait FromBytes: Sized {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError>;
}
pub trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
}
pub trait ToBytesWithPointers {
fn to_bytes(&self) -> Vec<u8>;
// Only used for tests to grab raw pointers to data
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)>;
}
+494 -414
View File
@@ -1,451 +1,531 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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.
//! 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,
errors::{
utils::{check_slice_size, check_slice_size_atleast},
InternalPakeError, PakeError, ProtocolError,
},
group::Group,
hash::Hash,
key_exchange::traits::{FromBytes, KeyExchange, ToBytes, ToBytesWithPointers},
keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
serialization::serialize,
};
use crate::ciphersuite::{CipherSuite, KeGroup};
use crate::errors::ProtocolError;
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::{self, NonceLen};
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
use crate::keypair::{PrivateKey, PublicKey};
use crate::opaque::Identifiers;
use crate::serialization::{ConcatExt, SliceExt};
use digest::{Digest, FixedOutput};
use generic_array::{
typenum::{Unsigned, U32},
ArrayLength, GenericArray,
};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use zeroize::Zeroize;
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
const KEY_LEN: usize = 32;
pub(crate) type NonceLen = U32;
static STR_RFC: &[u8] = b"RFCXXXX";
static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
static STR_SERVER_MAC: &[u8] = b"ServerMAC";
static STR_SESSION_KEY: &[u8] = b"SessionKey";
static STR_OPAQUE: &[u8] = b"OPAQUE-";
#[allow(clippy::upper_case_acronyms)]
/// The Triple Diffie-Hellman key exchange implementation
///
/// # 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)>);
pub struct TripleDH;
/// 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>,
impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
type KE1State = Ke1State;
type KE2State = Ke2State<<D as FixedOutput>::OutputSize>;
type KE1Message = Ke1Message;
type KE2Message = Ke2Message<<D as FixedOutput>::OutputSize>;
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyPair::<G>::generate_random(rng);
let client_nonce = generate_nonce::<R>(rng);
let ke1_message = Ke1Message {
client_nonce,
client_e_pk: client_e_kp.public().clone(),
};
Ok((
Ke1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce,
},
ke1_message,
))
}
#[allow(clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
serialized_credential_request: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: PublicKey,
server_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyPair::<G>::generate_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let mut transcript_hasher = D::new()
.chain(STR_RFC)
.chain(&serialize(&context, 2))
.chain(&id_u)
.chain(&serialized_credential_request[..])
.chain(&id_s)
.chain(&l2_bytes[..])
.chain(&server_nonce[..])
.chain(&server_e_kp.public().to_arr());
let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke1_message.client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: ke1_message.client_e_pk,
sk2: server_s_sk,
pk3: client_s_pk,
sk3: server_e_kp.private().clone(),
},
&transcript_hasher.clone().finalize(),
)?;
let mut mac_hasher =
Hmac::<D>::new_from_slice(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac_hasher.update(&transcript_hasher.clone().finalize());
let mac = mac_hasher.finalize().into_bytes();
transcript_hasher.update(&mac);
Ok((
Ke2State {
km3,
hashed_transcript: transcript_hasher.finalize(),
session_key,
},
Ke2Message {
server_nonce,
server_e_pk: server_e_kp.public().clone(),
mac,
},
))
}
#[allow(clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: PublicKey,
client_s_sk: PrivateKey,
id_u: Vec<u8>,
id_s: Vec<u8>,
context: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
let mut transcript_hasher = D::new()
.chain(STR_RFC)
.chain(&serialize(&context, 2))
.chain(&id_u)
.chain(&serialized_credential_request)
.chain(&id_s)
.chain(&l2_component[..])
.chain(&ke2_message.to_bytes_without_info_or_mac());
let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk,
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk,
},
&transcript_hasher.clone().finalize(),
)?;
let mut server_mac =
Hmac::<D>::new_from_slice(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&transcript_hasher.clone().finalize());
if server_mac.verify(&ke2_message.mac).is_err() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
transcript_hasher.update(ke2_message.mac);
let mut client_mac =
Hmac::<D>::new_from_slice(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&transcript_hasher.finalize());
Ok((
session_key.to_vec(),
Ke3Message {
mac: client_mac.finalize().into_bytes(),
},
))
}
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<D>::new_from_slice(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript);
if client_mac.verify(&ke3_message.mac).is_err() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.session_key.to_vec())
}
fn ke2_message_size() -> usize {
NonceLen::to_usize() + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
}
}
/// 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 client state produced after the first key exchange message
#[derive(PartialEq, Eq, Debug, Hash, Zeroize, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[zeroize(drop)]
pub struct Ke1State {
client_e_sk: PrivateKey,
client_nonce: GenericArray<u8, NonceLen>,
}
/// The first key exchange message
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Ke1Message {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) client_e_pk: PublicKey,
}
impl FromBytes for Ke1State {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
let nonce_len = NonceLen::to_usize();
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
Ok(Self {
client_e_sk: PrivateKey::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
),
})
}
}
impl ToBytesWithPointers for Ke1State {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(
self.client_e_sk.as_ptr(),
<PrivateKey as SizedBytes>::Len::to_usize(),
),
(self.client_nonce.as_ptr(), NonceLen::to_usize()),
]
}
}
impl ToBytes for Ke1Message {
fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl FromBytes for Ke1Message {
fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, PakeError> {
let nonce_len = NonceLen::to_usize();
let checked_nonce =
check_slice_size(ke1_message_bytes, nonce_len + KEY_LEN, "ke1_message nonce")?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
})
}
}
/// The server state produced after the second key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>,
}
// This can't be derived because of the use of a phantom parameter
impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
fn zeroize(&mut self) {
self.km3.zeroize();
self.hashed_transcript.zeroize();
self.session_key.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat()
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.km3.as_ptr(), HashLen::to_usize()),
(self.hashed_transcript.as_ptr(), HashLen::to_usize()),
(self.session_key.as_ptr(), HashLen::to_usize()),
]
}
}
/// 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>,
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: PublicKey,
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError> {
let hash_len = HashLen::to_usize();
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
hashed_transcript: GenericArray::clone_from_slice(
&checked_bytes[hash_len..2 * hash_len],
),
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
})
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
}
}
impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
[&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
}
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, PakeError> {
let nonce_len = NonceLen::to_usize();
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
let unchecked_server_e_pk = check_slice_size_atleast(
&checked_nonce[nonce_len..],
KEY_LEN,
"ke2_message server_e_pk",
)?;
let checked_mac = check_slice_size(
&unchecked_server_e_pk[KEY_LEN..],
HashLen::to_usize(),
"ke1_message mac",
)?;
// Check the public key bytes
let server_e_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&unchecked_server_e_pk[..KEY_LEN],
)?)?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
mac: GenericArray::clone_from_slice(checked_mac),
})
}
}
#[allow(clippy::upper_case_acronyms)]
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: PublicKey,
sk1: PrivateKey,
pk2: PublicKey,
sk2: PrivateKey,
pk3: PublicKey,
sk3: PrivateKey,
}
#[allow(clippy::upper_case_acronyms)]
// Consists of a session key, followed by two mac keys: (session_key, km2, km3)
type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
);
/// 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>,
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>,
}
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
impl<G: Group + 'static, H: Hash> KeyExchange for TripleDh<G, H>
where
G::Sk: DiffieHellman<G>,
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
type Group = G;
type Hash = H;
type KE1State = Ke1State<G>;
type KE2State<CS: CipherSuite> = Ke2State<H>;
type KE1Message = Ke1Message<G>;
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<G, H>;
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a PublicKey<G>;
type KE2BuilderInput<CS: CipherSuite> = GenericArray<u8, G::PkLen>;
type KE2Message = Ke2Message<G, H>;
type KE3Message = Ke3Message<H>;
fn generate_ke1<R: Rng + CryptoRng>(
rng: &mut R,
) -> Result<GenerateKe1Result<Self>, ProtocolError> {
shared::generate_ke1(rng)
}
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
client_s_pk: PublicKey<G>,
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
context: SerializedContext<'a>,
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
let shared::Ke2BuilderCommon {
server_nonce,
transcript_hasher,
client_e_pk,
server_e_pk,
shared_secret_1,
shared_secret_3,
} = shared::ke2_builder_common::<G, H, CS, R>(
rng,
credential_request,
ke1_message,
credential_response,
client_s_pk,
identifiers,
context,
)?;
Ok(Ke2Builder {
server_nonce,
transcript_hasher,
client_e_pk,
server_e_pk,
shared_secret_1,
shared_secret_3,
})
}
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
builder: &'a Self::KE2Builder<'_, CS>,
) -> Self::KE2BuilderData<'a, CS> {
&builder.client_e_pk
}
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
builder: &Self::KE2Builder<'_, CS>,
_: &mut R,
server_s_sk: &PrivateKey<G>,
) -> Self::KE2BuilderInput<CS> {
server_s_sk.ke_diffie_hellman(&builder.client_e_pk)
}
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
mut builder: Self::KE2Builder<'_, CS>,
shared_secret_2: Self::KE2BuilderInput<CS>,
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
let transcript_digest = builder.transcript_hasher.clone().finalize();
let derived_keys = shared::derive_keys::<H>(
[
builder.shared_secret_1.as_slice(),
&shared_secret_2,
&builder.shared_secret_3,
]
.into_iter(),
&transcript_digest,
)?;
let (mac, expected_mac) = shared::compute_ke2_macs(
&mut builder.transcript_hasher,
&derived_keys,
&transcript_digest,
)?;
Ok(GenerateKe2Result {
state: Ke2State {
session_key: derived_keys.session_key,
expected_mac,
},
message: Ke2Message {
server_nonce: builder.server_nonce,
server_e_pk: builder.server_e_pk.clone(),
mac,
},
#[cfg(test)]
handshake_secret: derived_keys.handshake_secret,
#[cfg(test)]
km2: derived_keys.km2,
})
}
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
_: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
ke1_state: &Self::KE1State,
ke2_message: Self::KE2Message,
server_s_pk: PublicKey<G>,
client_s_sk: PrivateKey<G>,
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
context: SerializedContext<'_>,
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
let mut transcript_hasher = shared::transcript(
&context,
&identifiers,
&credential_request,
&ke1_message.to_iter(),
&credential_response,
ke2_message.server_nonce,
&ke2_message.server_e_pk.serialize(),
);
let shared_secret_1 = ke1_state
.client_e_sk
.ke_diffie_hellman(&ke2_message.server_e_pk);
let shared_secret_2 = ke1_state.client_e_sk.ke_diffie_hellman(&server_s_pk);
let shared_secret_3 = client_s_sk.ke_diffie_hellman(&ke2_message.server_e_pk);
let (derived_keys, client_mac) = shared::finalize_ke3_transcript(
&mut transcript_hasher,
[
shared_secret_1.as_slice(),
shared_secret_2.as_slice(),
shared_secret_3.as_slice(),
]
.into_iter(),
&ke2_message.mac,
)?;
Ok(GenerateKe3Result {
session_key: derived_keys.session_key,
message: Ke3Message { mac: client_mac },
#[cfg(test)]
handshake_secret: derived_keys.handshake_secret,
#[cfg(test)]
km3: derived_keys.km3,
})
}
fn finish_ke<CS: CipherSuite>(
ke2_state: &Self::KE2State<CS>,
ke3_message: Self::KE3Message,
_: Identifiers<'_>,
_: SerializedContext<'_>,
) -> Result<Output<H>, ProtocolError> {
CtOption::new(
ke2_state.session_key.clone(),
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
)
.into_option()
.ok_or(ProtocolError::InvalidLoginError)
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
}
}
////////////////////////////////////////////////
// Trait Implementations //
// ========================================== //
////////////////////////////////////////////////
impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, PakeError> {
let checked_bytes = check_slice_size(bytes, HashLen::to_usize(), "ke3_message")?;
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(),
mac: GenericArray::clone_from_slice(checked_bytes),
})
}
}
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>>;
// Helper functions
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();
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the session key and two MAC keys
fn derive_3dh_keys<D: Hash, G: Group>(
dh: TripleDHComponents,
hashed_derivation_transcript: &[u8],
) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
let ikm: Vec<u8> = [
&KeyPair::<G>::diffie_hellman(dh.pk1, dh.sk1)?[..],
&KeyPair::<G>::diffie_hellman(dh.pk2, dh.sk2)?[..],
&KeyPair::<G>::diffie_hellman(dh.pk3, dh.sk3)?[..],
]
.concat();
sk.cat(mac)
}
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>(
&extracted_ikm,
STR_HANDSHAKE_SECRET,
hashed_derivation_transcript,
)?;
let session_key = derive_secrets::<D>(
&extracted_ikm,
STR_SESSION_KEY,
hashed_derivation_transcript,
)?;
let km2 = hkdf_expand_label::<D>(
&handshake_secret,
STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let km3 = hkdf_expand_label::<D>(
&handshake_secret,
STR_CLIENT_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
Ok((
GenericArray::clone_from_slice(&session_key),
GenericArray::clone_from_slice(&km2),
GenericArray::clone_from_slice(&km3),
))
}
/// 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();
}
fn hkdf_expand_label<D: Hash>(
secret: &[u8],
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalPakeError::HkdfError)?;
hkdf_expand_label_extracted(&h, label, context, length)
}
impl<G: Group, H: Hash> 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,
{
fn hkdf_expand_label_extracted<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let mut okm = vec![0u8; length];
let mut hkdf_label: Vec<u8> = Vec::new();
let length_u16: u16 = u16::try_from(length).map_err(|_| PakeError::SerializationError)?;
hkdf_label.extend_from_slice(&length_u16.to_be_bytes());
let mut opaque_label: Vec<u8> = Vec::new();
opaque_label.extend_from_slice(STR_OPAQUE);
opaque_label.extend_from_slice(label);
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1));
hkdf_label.extend_from_slice(&serialize(context, 1));
hkdf.expand(&hkdf_label, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok(okm)
}
impl<G: Group, H: Hash> Deserialize for Ke2Message<G, H>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
server_nonce: input.take_array("server nonce")?,
server_e_pk: PublicKey::deserialize_take(input)?,
mac: input.take_array("mac")?.into_ha0_4(),
})
}
fn derive_secrets<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
hashed_derivation_transcript: &[u8],
) -> Result<Vec<u8>, ProtocolError> {
hkdf_expand_label_extracted::<D>(
hkdf,
label,
hashed_derivation_transcript,
<D as Digest>::OutputSize::to_usize(),
)
}
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()
}
// Generate a random nonce up to NonceLen::to_usize() bytes.
fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
let mut nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut nonce_bytes);
GenericArray::clone_from_slice(&nonce_bytes)
}
-743
View File
@@ -1,743 +0,0 @@
// 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_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_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 clients 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())
}
}
+258 -458
View File
@@ -1,516 +1,316 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Contains the keypair types that must be supplied for the OPAQUE API
#![allow(unsafe_code)]
use derive_where::derive_where;
use digest::{Output, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, Rng};
use crate::errors::InternalPakeError;
use crate::group::Group;
#[cfg(test)]
use generic_array::typenum::Unsigned;
use generic_array::{typenum::U32, GenericArray};
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
#[cfg(test)]
use proptest::prelude::*;
#[cfg(test)]
use rand::{rngs::StdRng, SeedableRng};
use rand::{CryptoRng, RngCore};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::ops::Deref;
use zeroize::Zeroize;
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
#[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,
/// Convenience extension trait of SizedBytes
pub trait SizedBytesExt: SizedBytes {
/// Convert from bytes
fn from_bytes(bytes: &[u8]) -> Result<Self, TryFromSizedBytesError> {
<Self as SizedBytes>::from_arr(GenericArray::from_slice(bytes))
}
}
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 }
}
// blanket implementation
impl<T> SizedBytesExt for T where T: SizedBytes {}
/// The public key component
pub fn public(&self) -> &PublicKey<G> {
&self.pk
}
/// A Keypair trait with public-private verification
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct KeyPair<G> {
pk: PublicKey,
sk: PrivateKey,
_g: PhantomData<G>,
}
/// The private key component
pub fn private(&self) -> &SK {
&self.sk
impl_clone_for!(
struct KeyPair<G>,
[pk, sk, _g],
);
impl_debug_eq_hash_for!(
struct KeyPair<G>,
[pk, sk, _g],
);
// This can't be derived because of the use of a phantom parameter
impl<G> Zeroize for KeyPair<G> {
fn zeroize(&mut self) {
self.pk.zeroize();
self.sk.zeroize();
}
}
impl<G> Drop for KeyPair<G> {
fn drop(&mut self) {
self.zeroize();
}
}
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),
}
/// The public key component
pub fn public(&self) -> &PublicKey {
&self.pk
}
/// The private key component
pub fn private(&self) -> &PrivateKey {
&self.sk
}
/// Generating a random key pair given a cryptographic rng
pub(crate) fn derive_random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
let mut scalar_bytes = GenericArray::<_, <G as Group>::SkLen>::default();
rng.fill_bytes(&mut scalar_bytes);
let sk = G::derive_scalar(scalar_bytes).unwrap();
let pk = G::public_key(&sk);
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_nonzero_scalar(rng);
let sk_bytes = G::scalar_as_bytes(&sk);
let pk = G::base_point().mult_by_slice(sk_bytes);
Self {
pk: PublicKey(pk),
sk: PrivateKey(sk),
pk: PublicKey(Key(pk.to_arr().to_vec())),
sk: PrivateKey(Key(sk_bytes.to_vec())),
_g: PhantomData,
}
}
}
/// 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)
/// Obtaining a public key from secret bytes. At all times, we should have
/// &public_from_private(self.private()) == self.public()
pub(crate) fn public_from_private(bytes: &PrivateKey) -> PublicKey {
let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]);
PublicKey(Key(G::base_point()
.mult_by_slice(bytes_data)
.to_arr()
.to_vec()))
}
/// Returns public key from private key
pub fn public_key(&self) -> PublicKey<G> {
PublicKey(G::public_key(&self.0))
/// Check whether a public key is valid. This is meant to be applied on
/// material provided through the network which fits the key
/// representation (i.e. can be mapped to a curve point), but presents
/// some risk - e.g. small subgroup check
pub(crate) fn check_public_key(key: PublicKey) -> Result<PublicKey, InternalPakeError> {
G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key)
}
/// Serializes this private key to a fixed-length byte array.
pub fn serialize(&self) -> GenericArray<u8, G::SkLen> {
G::serialize_sk(&self.0)
/// Computes the diffie hellman function on a public key and private key
pub(crate) fn diffie_hellman(
pk: PublicKey,
sk: PrivateKey,
) -> Result<Vec<u8>, InternalPakeError> {
let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]);
let point = G::from_element_slice(pk_data)?;
let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]);
Ok(G::mult_by_slice(&point, secret_data).to_arr().to_vec())
}
/// Creates a [`PrivateKey`] from the given bytes.
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut input)
/// Obtains a KeyPair from a slice representing the private key
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
let sk = PrivateKey(Key::from_arr(GenericArray::from_slice(input))?);
let pk = Self::public_from_private(&sk);
Ok(Self {
pk,
sk,
_g: PhantomData,
})
}
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
G::deserialize_take_sk(input).map(Self)
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.pk.as_ptr(), KeyLen::to_usize()),
(self.sk.as_ptr(), KeyLen::to_usize()),
]
}
}
impl<G: Group> PrivateKey<G>
where
G::Sk: DiffieHellman<G>,
{
/// Diffie-Hellman key exchange implementation
pub(crate) fn ke_diffie_hellman(&self, pk: &PublicKey<G>) -> GenericArray<u8, G::PkLen> {
self.0.diffie_hellman(&pk.0)
}
}
impl<G: Group> PrivateKey<G> {
/// Private-key signing implementation
pub(crate) fn sign<
R: CryptoRng + Rng,
CS: CipherSuite,
SIG: SignatureProtocol<Group = G>,
KE: Group,
>(
&self,
rng: &mut R,
message: &Message<CS, KE>,
) -> (SIG::Signature, SIG::VerifyState<CS, KE>) {
SIG::sign(&self.0, rng, message)
}
}
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
pub trait PrivateKeySerialization<G: Group>: Clone {
/// Custom error type that can be passed down to `ProtocolError::Custom`
type Error;
/// Serialization size in bytes.
type Len: ArrayLength;
/// Serialization into bytes
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take_key_pair(
bytes: &mut &[u8],
) -> Result<KeyPair<G, Self>, ProtocolError<Self::Error>>;
}
impl<G: Group> PrivateKeySerialization<G> for PrivateKey<G> {
type Error = core::convert::Infallible;
type Len = G::SkLen;
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len> {
key_pair.private().serialize()
}
fn deserialize_take_key_pair(input: &mut &[u8]) -> Result<KeyPair<G, Self>, ProtocolError> {
let sk = PrivateKey::deserialize_take(input)?;
let pk = sk.public_key();
Ok(KeyPair::new(sk, pk))
}
}
/// Wrapper around a Key to enforce that it's a public one.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Pk: serde::Deserialize<'de>",
serialize = "G::Pk: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
pub struct PublicKey<G: Group + ?Sized>(G::Pk);
impl<G: Group> PublicKey<G> {
/// Convert from bytes
pub fn deserialize(mut key_bytes: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut key_bytes)
}
pub(crate) fn deserialize_take(key_bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
G::deserialize_take_pk(key_bytes).map(Self)
}
/// Convert to bytes
pub fn serialize(&self) -> GenericArray<u8, G::PkLen> {
G::serialize_pk(&self.0)
}
/// Returns the inner [`Group::Pk`].
pub fn to_group_type(&self) -> &G::Pk {
&self.0
}
}
impl<G: Group> PublicKey<G> {
/// Public-key verifying implementation
pub(crate) fn verify<CS: CipherSuite, SIG: SignatureProtocol<Group = G>, KE: Group>(
&self,
message_builder: MessageBuilder<'_, CS>,
state: SIG::VerifyState<CS, KE>,
signature: &SIG::Signature,
) -> Result<(), ProtocolError> {
SIG::verify(&self.0, message_builder, state, signature)
}
}
/// Default OPRF seed container.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct OprfSeed<H: OutputSizeUser>(pub(crate) Output<H>);
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
///
/// Will be called with `E` being [`PrivateKeySerialization::Error`].
pub trait OprfSeedSerialization<H, E>: Sized {
/// Serialization size in bytes.
type Len: ArrayLength;
/// Serialization into bytes
fn serialize(&self) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError<E>>;
}
impl<H: OutputSizeUser, E> OprfSeedSerialization<H, E> for OprfSeed<H>
where
H::OutputSize: ArrayLength,
{
type Len = H::OutputSize;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
GenericArray::from_slice(self.0.as_slice()).clone()
}
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError<E>> {
Ok(Self(
input
.take_array("OPRF seed")
.map_err(ProtocolError::into_custom)?
.into_ha0_4(),
))
}
}
//////////////////////////
// Test Implementations //
//===================== //
//////////////////////////
#[cfg(test)]
impl<G: Group> KeyPair<G>
where
G::Pk: core::fmt::Debug,
G::Sk: core::fmt::Debug,
{
impl<G: Group + Debug> KeyPair<G> {
/// 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| {
/// generate_random
fn uniform_keypair_strategy() -> BoxedStrategy<Self> {
// The no_shrink is because keypairs should be fixed -- shrinking would cause a different
// keypair to be generated, which appears to not be very useful.
prop::array::uniform32(0_u8..)
.prop_map(|seed| {
let mut rng = StdRng::from_seed(seed);
Some(Self::derive_random(&mut rng))
Self::generate_random(&mut rng)
})
.no_shrink()
.boxed()
}
}
type KeyLen = U32;
/// A minimalist key type built around a \[u8; 32\]
#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)]
pub struct Key(Vec<u8>);
impl Deref for Key {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// Don't make it implement SizedBytes so that it's not constructible outside of this module.
impl Key {
fn to_arr(&self) -> GenericArray<u8, KeyLen> {
GenericArray::clone_from_slice(&self.0[..])
}
#[allow(clippy::unnecessary_wraps)]
fn from_arr(key_bytes: &GenericArray<u8, KeyLen>) -> Result<Self, TryFromSizedBytesError> {
Ok(Key(key_bytes.to_vec()))
}
}
/// Wrapper around a Key to enforce that it's a private one.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)]
pub struct PrivateKey(Key);
impl Deref for PrivateKey {
type Target = Key;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl SizedBytes for PrivateKey {
type Len = KeyLen;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
self.0.to_arr()
}
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
Ok(PrivateKey(Key::from_arr(key_bytes)?))
}
}
/// Wrapper around a Key to enforce that it's a public one.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Zeroize)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)]
pub struct PublicKey(Key);
impl Deref for PublicKey {
type Target = Key;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl SizedBytes for PublicKey {
type Len = KeyLen;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
self.0.to_arr()
}
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
Ok(PublicKey(Key::from_arr(key_bytes)?))
}
}
#[cfg(test)]
mod tests {
use super::*;
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::rngs::SysRng;
use rand_core::UnwrapErr;
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);
}
}
}
};
}
#[cfg(feature = "ristretto255")]
test!(ristretto, crate::Ristretto255);
test!(p256, ::p256::NistP256);
test!(p384, ::p384::NistP384);
test!(p521, ::p521::NistP521);
struct Default;
impl CipherSuite for Default {
#[cfg(feature = "ristretto255")]
type OprfCs = crate::Ristretto255;
#[cfg(not(feature = "ristretto255"))]
type OprfCs = ::p256::NistP256;
#[cfg(feature = "ristretto255")]
type KeyExchange = crate::TripleDh<crate::Ristretto255, sha2::Sha512>;
#[cfg(not(feature = "ristretto255"))]
type KeyExchange = crate::TripleDh<::p256::NistP256, sha2::Sha256>;
type Ksf = crate::ksf::Identity;
}
#[derive(Clone)]
struct RemoteSeed<H: OutputSizeUser>(Output<H>);
#[derive(Clone)]
struct RemoteKey(PrivateKey<KeGroup<Default>>);
const PASSWORD: &str = "password";
use crate::errors::*;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use rand::rngs::OsRng;
use std::slice::from_raw_parts;
#[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);
fn test_zeroize_key() -> Result<(), ProtocolError> {
let key_len = KeyLen::to_usize();
let mut key = Key(vec![1u8; key_len]);
let ptr = key.as_ptr();
let server_setup =
ServerSetup::<Default, RemoteKey>::new_with_key_pair(&mut UnwrapErr(SysRng), keypair);
key.zeroize();
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 bytes = unsafe { from_raw_parts(ptr, key_len) };
assert!(bytes.iter().all(|&x| x == 0));
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();
Ok(())
}
#[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 test_zeroize_keypair() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut keypair = KeyPair::<RistrettoPoint>::generate_random(&mut rng);
let ptrs = keypair.as_byte_ptrs();
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);
keypair.zeroize();
let server_setup = ServerSetup::<Default, _, _>::new_with_key_pair_and_seed(
&mut UnwrapErr(SysRng),
keypair,
oprf_seed,
);
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
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);
Ok(())
}
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();
proptest! {
#[test]
fn test_ristretto_check(ref kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
}
#[test]
fn test_ristretto_pub_from_priv(ref kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&KeyPair::<RistrettoPoint>::public_from_private(sk), pk);
}
#[test]
fn test_ristretto_dh(ref kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
ref kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
prop_assert_eq!(dh1, dh2);
}
#[test]
fn test_private_key_slice(ref kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
let kp2_private_bytes = kp2.private().to_vec();
prop_assert_eq!(sk_bytes, kp2_private_bytes);
}
}
}
-44
View File
@@ -1,44 +0,0 @@
// 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)
}
}
+379 -1006
View File
File diff suppressed because it is too large Load Diff
+241
View File
@@ -0,0 +1,241 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
//! curve point
use crate::errors::InternalPakeError;
use crate::group::Group;
use crate::hash::Hash;
use crate::serialization::i2osp;
use curve25519_dalek::ristretto::RistrettoPoint;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// A subtrait of Group specifying how to hash a password into a point
pub trait GroupWithMapToCurve: Group {
/// The ciphersuite identifier as dictated by
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
/// Hashes a slice of pseudo-random bytes to a scalar
fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8])
-> Result<Self::Scalar, InternalPakeError>;
/// Generates the contextString parameter as defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
fn get_context_string(mode: u8) -> Vec<u8> {
[i2osp(mode as usize, 1), i2osp(Self::SUITE_ID, 2)].concat()
}
}
impl GroupWithMapToCurve for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
// Implements the hash_to_ristretto255() function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError> {
let uniform_bytes =
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
Ok(<Self as Group>::hash_to_curve(
&GenericArray::clone_from_slice(&uniform_bytes[..]),
))
}
fn hash_to_scalar<H: Hash>(
input: &[u8],
dst: &[u8],
) -> Result<Self::Scalar, InternalPakeError> {
const LEN_IN_BYTES: usize = 64;
let uniform_bytes = expand_message_xmd::<H>(input, dst, LEN_IN_BYTES)?;
let mut bits = [0u8; LEN_IN_BYTES];
bits.copy_from_slice(&uniform_bytes[..]);
Ok(Self::Scalar::from_bytes_mod_order_wide(&bits))
}
}
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
let additive = (x % y != 0) as usize;
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalPakeError> {
if x.len() != y.len() {
return Err(InternalPakeError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
}
// Corresponds to the expand_message_xmd() function defined in
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
pub(crate) fn expand_message_xmd<H: Hash>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, InternalPakeError> {
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
let ell = div_ceil(len_in_bytes, b_in_bytes);
if ell > 255 {
return Err(InternalPakeError::HashToCurveError);
}
let dst_prime = [dst, &i2osp(dst.len(), 1)].concat();
let z_pad = i2osp(0, r_in_bytes);
let l_i_b_str = i2osp(len_in_bytes, 2);
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1), &dst_prime].concat();
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
}
#[cfg(test)]
mod tests {
struct Params {
msg: &'static str,
len_in_bytes: usize,
uniform_bytes: &'static str,
}
#[test]
fn test_expand_message_xmd() {
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
let test_vectors: Vec<Params> = vec![
Params {
msg: "",
len_in_bytes: 0x20,
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
92181df928fca88",
},
Params {
msg: "abc",
len_in_bytes: 0x20,
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
5a1151775fe4da7",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x20,
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x20,
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
1716b1b964e1c642",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x20,
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
350db46f429b771b",
},
Params {
msg: "",
len_in_bytes: 0x80,
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
},
Params {
msg: "abc",
len_in_bytes: 0x80,
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x80,
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x80,
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x80,
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
}
}
+304 -396
View File
@@ -1,478 +1,386 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Contains the messages used for OPAQUE
use 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::{
ciphersuite::CipherSuite,
envelope::Envelope,
errors::{
utils::{check_slice_size, check_slice_size_atleast},
PakeError, ProtocolError,
},
group::Group,
key_exchange::traits::{FromBytes, KeyExchange, ToBytes},
keypair::{KeyPair, PublicKey, SizedBytesExt},
opaque::ServerSetup,
};
use crate::keypair::PublicKey;
use crate::opaque::{
MaskedResponse, MaskedResponseLen, ServerLogin, ServerLoginStartResult, ServerSetup,
};
use crate::serialization::{ConcatExt, SliceExt};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use rand::{CryptoRng, RngCore};
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
// Messages
// =========
/// 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>,
pub(crate) alpha: CS::Group,
}
/// 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()
pub fn get_alpha_for_testing(&self) -> CS::Group {
self.alpha
}
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
fn clone(&self) -> Self {
Self { alpha: self.alpha }
}
}
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [alpha], [CS::Group]);
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Array<u8, RegistrationRequestLen<CS>> {
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.blinded_element.value())
pub fn serialize(&self) -> Vec<u8> {
self.alpha.to_arr().to_vec()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
blinded_element: BlindedElement::deserialize(input)?,
})
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = check_slice_size(input, elem_len, "first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(checked_slice);
let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
Ok(Self { alpha })
}
}
/// 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_serialize_and_deserialize_for!(RegistrationRequest);
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(),
));
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) beta: CS::Group,
/// Server's static public key
pub(crate) server_s_pk: PublicKey,
}
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 {
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationResponse<CS> {
fn clone(&self) -> Self {
Self {
evaluation_element: EvaluationElement::from_value_unchecked(beta),
beta: self.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_debug_eq_hash_for!(
struct RegistrationResponse<CS: CipherSuite>,
[beta, server_s_pk],
[CS::Group],
);
impl<CS: CipherSuite> RegistrationUpload<CS> {
impl<CS: CipherSuite> RegistrationResponse<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())
pub fn serialize(&self) -> Vec<u8> {
[self.beta.to_arr().to_vec(), self.server_s_pk.to_vec()].concat()
}
/// Deserialization from bytes
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size(input, elem_len + key_len, "registration_response_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let beta = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
// Ensure that public key is valid
let server_s_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[elem_len..],
)?)?;
Ok(Self { server_s_pk, beta })
}
}
impl_serialize_and_deserialize_for!(RegistrationResponse);
/// The final message from the client, containing sealed cryptographic
/// identifiers
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
pub(crate) envelope: Envelope<CS>,
/// The masking key used to mask the envelope
pub(crate) masking_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The user's public key
pub(crate) client_s_pk: PublicKey,
}
impl_clone_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
self.client_s_pk.to_arr().to_vec(),
self.masking_key.to_vec(),
self.envelope.serialize(),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let hash_len = <CS::Hash as Digest>::OutputSize::to_usize();
let checked_slice =
check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
Ok(Self {
client_s_pk: PublicKey::deserialize_take(&mut input)?,
masking_key: input.take_array("masking key")?.into_ha0_4(),
envelope: Envelope::deserialize_take(&mut input)?,
envelope,
masking_key: GenericArray::clone_from_slice(
&checked_slice[key_len..key_len + hash_len],
),
client_s_pk: KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[..key_len],
)?)?,
})
}
// Creates a dummy instance used for faking a [CredentialResponse]
pub(crate) fn dummy<R: Rng + CryptoRng, SK: Clone, OS: Clone>(
pub(crate) fn dummy<R: RngCore + CryptoRng>(
rng: &mut R,
server_setup: &ServerSetup<CS, SK, OS>,
server_setup: &ServerSetup<CS>,
) -> Self {
let mut masking_key = Output::<OprfHash<CS>>::default();
let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
rng.fill_bytes(&mut masking_key);
Self {
envelope: Envelope::<CS>::dummy(),
masking_key,
client_s_pk: server_setup.dummy_pk.clone(),
masking_key: GenericArray::clone_from_slice(&masking_key),
client_s_pk: server_setup.fake_keypair.public().clone(),
}
}
}
/// Length of [`CredentialRequest`] in bytes for serialization.
pub type CredentialRequestLen<CS: CipherSuite> =
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, Ke1MessageLen<CS>>;
impl_serialize_and_deserialize_for!(RegistrationUpload);
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(),
));
/// The message sent by the user to the server, to initiate registration
pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::Group,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message,
}
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()
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialRequest<CS> {
fn clone(&self) -> Self {
Self {
alpha: self.alpha,
ke1_message: self.ke1_message.clone(),
}
}
}
/// Length of [`CredentialResponse`] in bytes for serialization.
pub type CredentialResponseLen<CS: CipherSuite> =
Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
impl_debug_eq_hash_for!(
struct CredentialRequest<CS: CipherSuite>,
[alpha, ke1_message],
[
CS::Group,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message
],
);
pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
impl<CS: CipherSuite> CredentialResponse<CS> {
impl<CS: CipherSuite> CredentialRequest<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())
pub fn serialize(&self) -> Vec<u8> {
[self.alpha.to_arr().to_vec(), self.ke1_message.to_bytes()].concat()
}
/// 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..];
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_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,
)?,
})
let checked_slice = check_slice_size_atleast(input, elem_len, "login_first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::from_bytes::<CS>(
&checked_slice[elem_len..],
)?;
Ok(Self { alpha, ke1_message })
}
}
pub(crate) fn to_parts(&self) -> SerializedCredentialResponse<CS> {
SerializedCredentialResponse::new(
&self.evaluation_element,
self.masking_nonce,
self.masked_response.clone(),
)
}
impl_serialize_and_deserialize_for!(CredentialRequest);
#[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 {
/// The answer sent by the server to the user, upon reception of the
/// login attempt
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) beta: CS::Group,
pub(crate) masking_nonce: Vec<u8>,
pub(crate) masked_response: Vec<u8>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialResponse<CS> {
fn clone(&self) -> Self {
Self {
evaluation_element: EvaluationElement::from_value_unchecked(beta),
masking_nonce: self.masking_nonce,
beta: self.beta,
masking_nonce: self.masking_nonce.clone(),
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_debug_eq_hash_for!(
struct CredentialResponse<CS: CipherSuite>,
[beta, masking_nonce, masked_response, ke2_message],
[
CS::Group,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
],
);
impl<CS: CipherSuite> CredentialFinalization<CS> {
impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
{
self.ke3_message.serialize()
pub fn serialize(&self) -> Vec<u8> {
[
Self::serialize_without_ke(&self.beta, &self.masking_nonce, &self.masked_response),
self.ke2_message.to_bytes(),
]
.concat()
}
pub(crate) fn serialize_without_ke(
beta: &CS::Group,
masking_nonce: &[u8],
masked_response: &[u8],
) -> Vec<u8> {
[&beta.to_arr(), masking_nonce, masked_response].concat()
}
/// Deserialization from bytes
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
{
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let nonce_len: usize = 32;
let envelope_len = Envelope::<CS>::len();
let masked_response_len = key_len + envelope_len;
let ke2_message_len = CS::KeyExchange::ke2_message_size();
let checked_slice = check_slice_size_atleast(
input,
elem_len + nonce_len + masked_response_len + ke2_message_len,
"credential_response_bytes",
)?;
// Check that the message is actually containing an element of the
// correct subgroup
let beta_bytes = &checked_slice[..elem_len];
let arr = GenericArray::from_slice(beta_bytes);
let beta = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let masking_nonce = checked_slice[elem_len..elem_len + nonce_len].to_vec();
let masked_response = checked_slice
[elem_len + nonce_len..elem_len + nonce_len + masked_response_len]
.to_vec();
let ke2_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::from_bytes::<CS>(
&checked_slice[elem_len + nonce_len + masked_response_len..],
)?;
Ok(Self {
ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(
&mut input,
)?,
beta,
masking_nonce,
masked_response,
ke2_message,
})
}
}
impl_serialize_and_deserialize_for!(CredentialResponse);
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message,
}
impl_clone_for!(struct CredentialFinalization<CS: CipherSuite>, [ke3_message]);
impl_debug_eq_hash_for!(
struct CredentialFinalization<CS: CipherSuite>,
[ke3_message],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message],
);
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.ke3_message.to_bytes()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let ke3_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::from_bytes::<CS>(
input,
)?;
Ok(Self { ke3_message })
}
}
impl_serialize_and_deserialize_for!(CredentialFinalization);
+810 -992
View File
File diff suppressed because it is too large Load Diff
+167
View File
@@ -0,0 +1,167 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
serialization::serialize,
};
use digest::Digest;
use generic_array::GenericArray;
use rand::{CryptoRng, RngCore};
/// Used to store the OPRF input and blinding factor
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Token<Grp: Group> {
pub(crate) data: Vec<u8>,
pub(crate) blind: Grp::Scalar,
}
impl_clone_for!(struct Token<Grp: Group>, [data, blind]);
impl_debug_eq_hash_for!(struct Token<Grp: Group>, [data, blind], [Grp::Scalar]);
static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-";
static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-";
static MODE_BASE: u8 = 0x00;
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
/// an HKDF computation.
pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), InternalPakeError> {
// Choose a random scalar that must be non-zero
let blind = G::random_nonzero_scalar(blinding_factor_rng);
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
let mapped_point = G::map_to_curve::<H>(input, &dst)?;
let blind_token = mapped_point * &blind;
Ok((
Token {
data: input.to_vec(),
blind,
},
blind_token,
))
}
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the server (who holds the OPRF key) to the client.
pub(crate) fn evaluate<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
point * oprf_key
}
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
/// the client unblinds the server's message.
pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blind: &G::Scalar,
evaluated_element: G,
) -> GenericArray<u8, <H as Digest>::OutputSize> {
let unblinded_element = evaluated_element * &G::scalar_invert(blind);
finalize_after_unblind::<G, H>(input, unblinded_element)
}
fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
unblinded_element: G,
) -> GenericArray<u8, <H as Digest>::OutputSize> {
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
let hash_input = [
serialize(input, 2),
serialize(&unblinded_element.to_arr().to_vec(), 2),
serialize(&finalize_dst, 2),
]
.concat();
<H as Digest>::digest(&hash_input)
}
////////////////////////
// Benchmarking shims //
////////////////////////
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), InternalPakeError> {
blind::<R, G, H>(input, blinding_factor_rng)
}
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
evaluate(point, oprf_key)
}
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn finalize_shim<G: GroupWithMapToCurve, H: Hash>(
token: &Token<G>,
point: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
Ok(finalize::<G, H>(&token.data, &token.blind, point))
}
///////////
// Tests //
// ===== //
///////////
#[cfg(test)]
mod tests {
use super::*;
use crate::group::Group;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::{arr, GenericArray};
use rand::rngs::OsRng;
use sha2::Sha512;
fn prf(input: &[u8], oprf_key: &[u8; 32]) -> GenericArray<u8, <Sha512 as Digest>::OutputSize> {
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(input, &dst).unwrap();
let scalar =
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar;
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res)
}
#[test]
fn oprf_retrieval() -> Result<(), InternalPakeError> {
let input = b"hunter2";
let mut rng = OsRng;
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng)?;
let oprf_key_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta);
let res2 = prf(&input[..], &oprf_key.as_bytes());
assert_eq!(res, res2);
Ok(())
}
#[test]
fn oprf_inversion_unsalted() {
let mut rng = OsRng;
let mut input = vec![0u8; 64];
rng.fill_bytes(&mut input);
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha);
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point);
assert_eq!(res, res2);
}
}
+98 -130
View File
@@ -1,156 +1,124 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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 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};
use crate::errors::PakeError;
// 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);
pub(crate) fn i2osp(input: usize, length: usize) -> Vec<u8> {
if length <= std::mem::size_of::<usize>() {
return (&input.to_be_bytes()[std::mem::size_of::<usize>() - length..]).to_vec();
}
let mut output = GenericArray::default();
output[L::USIZE.saturating_sub(SIZEOF_USIZE)..]
.copy_from_slice(&input.to_be_bytes()[SIZEOF_USIZE.saturating_sub(L::USIZE)..]);
Ok(output)
let mut output = vec![0u8; length];
output.splice(
length - std::mem::size_of::<usize>()..length,
input.to_be_bytes().iter().cloned(),
);
output
}
// Corresponds to the OS2IP() function from RFC8017
#[cfg(test)]
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, ProtocolError> {
if input.len() > size_of::<usize>() {
return Err(ProtocolError::SerializationError);
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, PakeError> {
if input.len() > std::mem::size_of::<usize>() {
return Err(PakeError::SerializationError);
}
let mut output_array = [0u8; size_of::<usize>()];
output_array[size_of::<usize>() - input.len()..].copy_from_slice(input);
let mut output_array = [0u8; std::mem::size_of::<usize>()];
output_array[std::mem::size_of::<usize>() - input.len()..].copy_from_slice(input);
Ok(usize::from_be_bytes(output_array))
}
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;
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Vec<u8> {
[&i2osp(input.len(), max_bytes), input].concat()
}
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);
}
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
// (input, remainder)
pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec<u8>, Vec<u8>), PakeError> {
if size_bytes > std::mem::size_of::<usize>() || input.len() < size_bytes {
return Err(PakeError::SerializationError);
}
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self {
let mut self_ = self;
let size = os2ip(&input[..size_bytes])?;
if size_bytes + size > input.len() {
return Err(PakeError::SerializationError);
}
for bytes in iter {
self_ = self_.chain(bytes);
Ok((
input[size_bytes..size_bytes + size].to_vec(),
input[size_bytes + size..].to_vec(),
))
}
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
macro_rules! impl_serialize_and_deserialize_for {
($t:ident) => {
#[cfg(feature = "serialize")]
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
self_
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
$t::<CS>::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
} else {
struct ByteVisitor<CS: CipherSuite> {
marker: std::marker::PhantomData<CS>,
}
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
type Value = $t<CS>;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter.write_str(std::concat!(
"the byte representation of a ",
std::stringify!($t)
))
}
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(),
});
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
$t::<CS>::deserialize(value).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Bytes(value),
&std::concat!(
"invalid byte sequence for ",
std::stringify!($t)
),
)
})
}
}
deserializer.deserialize_bytes(ByteVisitor::<CS> {
marker: std::marker::PhantomData,
})
}
}
}
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());
}
}
+322 -946
View File
File diff suppressed because it is too large Load Diff
+51
View File
@@ -0,0 +1,51 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Trait specifying a slow hashing function
use crate::{errors::InternalPakeError, hash::Hash};
use digest::Digest;
#[cfg(feature = "slow-hash")]
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// Used for the slow hashing function in OPAQUE
pub trait SlowHash<D: Hash> {
/// Computes the slow hashing function
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError>;
}
/// A no-op hash which simply returns its input
pub struct NoOpHash;
impl<D: Hash> SlowHash<D> for NoOpHash {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
Ok(input.to_vec())
}
}
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for argon2::Argon2<'_> {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
let params = argon2::Argon2::default();
let mut output = vec![0u8; <D as Digest>::OutputSize::to_usize()];
params
.hash_password_into(
argon2::Algorithm::Argon2id,
&input,
&[0; argon2::MIN_SALT_LENGTH],
&[],
&mut output,
)
.map_err(|_| InternalPakeError::SlowHashError)?;
Ok(output)
}
}
+715 -1076
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+26 -32
View File
@@ -1,24 +1,24 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
// 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 core::cmp::min;
use std::vec::Vec;
use rand::{CryptoRng, Error, RngCore};
use std::cmp::min;
use core::convert::Infallible;
use rand_core::{TryCryptoRng, TryRng};
/// A simple implementation of `Rng` for testing purposes.
/// A simple implementation of `RngCore` for testing purposes.
///
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
#[derive(Clone, Debug)]
///
///
#[derive(Debug, Clone)]
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,35 +35,29 @@ fn rotate_left<T>(data: &mut [T], steps: usize) {
data.reverse();
}
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))
impl RngCore for CycleRng {
fn next_u32(&mut self) -> u32 {
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 next_u64(&mut self) -> u64 {
unimplemented!()
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let len = min(self.v.len(), dest.len());
dest[..len].copy_from_slice(&self.v[..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);
Ok(())
}
}
// This is meant for testing only
impl TryCryptoRng for CycleRng {}
impl CryptoRng for CycleRng {}
+6 -16
View File
@@ -1,19 +1,9 @@
// 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;
// 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.
mod full_test;
#[rustfmt::skip]
#[allow(dead_code)]
mod full_test_vectors;
pub mod mock_rng;
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())
}
mod opaque_test_vectors;
mod voprf_test_vectors;
+787
View File
@@ -0,0 +1,787 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::PrivateKey,
opaque::*, slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes;
use serde_json::Value;
// Tests
// =====
struct Ristretto255Sha512NoSlowHash;
impl CipherSuite for Ristretto255Sha512NoSlowHash {
type Group = RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
#[derive(PartialEq)]
pub enum EnvelopeMode {
Base,
CustomIdentifier,
}
#[allow(non_snake_case)]
pub struct TestVectorParameters {
pub dummy_private_key: Vec<u8>,
pub dummy_masking_key: Vec<u8>,
pub context: Vec<u8>,
pub envelope_mode: EnvelopeMode,
pub client_private_key: Option<Vec<u8>>,
pub client_keyshare: Vec<u8>,
pub client_private_keyshare: Vec<u8>,
pub server_public_key: Vec<u8>,
pub server_private_key: Vec<u8>,
pub server_keyshare: Vec<u8>,
pub server_private_keyshare: Vec<u8>,
pub client_identity: 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 client_info: Vec<u8>,
pub server_info: Vec<u8>,
pub registration_request: Vec<u8>,
pub registration_response: Vec<u8>,
pub registration_upload: Vec<u8>,
pub KE1: Vec<u8>,
pub blind_login: Vec<u8>,
pub KE2: Vec<u8>,
pub KE3: Vec<u8>,
pub export_key: Vec<u8>,
pub session_key: Vec<u8>,
}
// Pulled from "OPAQUE-3DH Test Vector 1" and "OPAQUE-3DH Test Vector 6"
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
static TEST_VECTORS: &[&str] = &[
r#"
## OPAQUE-3DH Test Vector 1
### Configuration
~~~
OPRF: 0001
Hash: SHA512
MHF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
~~~
### Input Values
~~~
oprf_seed: 5c4f99877d253be5817b4b03f37b6da680b0d5671d1ec5351fa61c5d82
eab28b9de4c4e170f27e433ba377c71c49aa62ad26391ee1cac17011d8a7e9406657c
8
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd
539c4676775
masking_nonce: 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab
86ff39ed7f
server_private_key: 16eb9dc74a3df2033cd738bf2cfb7a3670c569d7749f284b2
b241cb237e7d10f
server_public_key: 18d5035fd0a9c1d6412226df037125901a43f4dff660c0549d
402f672bcc0933
server_nonce: f9c5ec75a8cd571370add249e99cb8a8c43f6ef05610ac6e354642b
f4fedbf69
client_nonce: 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f
2e9784f69
server_keyshare: 6e77d4749eb304c4d74be9457c597546bc22aed699225499910f
c913b3e90712
client_keyshare: f67926bd036c5dc4971816b9376e9f64737f361ef8269c18f69f
1ab555e96d4a
server_private_keyshare: f8e3e31543dd6fc86833296726773d51158291ab9afd
666bb55dce83474c1101
client_private_keyshare: 4230d62ea740b13e178185fc517cf2c313e6908c4cd9
fb42154870ff3490c608
blind_registration: c62937d17dc9aa213c9038f84fe8c5bf3d953356db01c4d48
acb7cae48e6a504
blind_login: b5f458822ea11c900ad776e38e29d7be361f75b4d79b55ad74923299
bf8d6503
oprf_key: 23d431bab39aea4d2737ac391a50076300210730971788e3a6a8c29ad3c
5930e
~~~
### Intermediate Values
~~~
client_public_key: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a33
439911e66c3c27
auth_key: 27972f9b1cf2ce524d50a7afa40a2ee6957904e2bef29976bdbda452a84
fcf01023f3ddd8182e64ea5287f99765dd39b83fa89fe189db227212a144134684783
randomized_pwd: 750ef06299c2fb102242fd84e59613616338f83e69c09c1dc3f91
c57ac0642876ccbe785e94aa094262efdc6aed08b3faff7c1bddfa14c434c5a908ad6
c5f9d5
envelope: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c46
76775455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e
43d4c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
handshake_secret: 02fb23a668b7138b029c95d21f1e0eec9e10377be933bdbf3e5
33ea39073d3ce9d1ef16b55a8a8464f3bf6a991cc645d14c1fa3d9d6cfe36c6c0dcc2
691d7109
server_mac_key: e75ce46beeebd26f22540d7988de9809a69cf34fec6c050750708
e91232297fdbb51e875cd37167d5ce661ebccf0004dbbf96311daf64ddec7faae04c4
8bbd89
client_mac_key: 4bce132daa031fff2a6e5ac29287c4641e3b9dc2560394b8c73f3
b748f1e51e577b932a960b236981217b33bee220b0bce2696638cfb7791f427ade292
d60f55
~~~
### Output Values
~~~
registration_request: 80576bce33c6ce89f9e1a06d8595cd9d09d9aef46b20dad
d57a845dc50e7c074
registration_response: 1a80fdb4f4eb1985587b5b95661d2cff1ef2493cdcdd88
b5699f39048f0d6c2618d5035fd0a9c1d6412226df037125901a43f4dff660c0549d4
02f672bcc0933
registration_upload: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a
33439911e66c3c2795014d8fc0c710bd763c981c5b9329c95e149c6717af91bad2cec
daf87f2c3c9c11914cb6d44aaee5679e3e61e1b65241fda74902cca908a065495c0b2
8b799e71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c467677
5455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e43d4
c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
KE1: 60d71c9f5d2a14568807b869e2c251a8e5f7ad8951cd8386c7e32c0634b26b16
804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f2e9784f69f6792
6bd036c5dc4971816b9376e9f64737f361ef8269c18f69f1ab555e96d4a
KE2: 78a428204f552d3532bad040c961324edb22c738d98f1dd770d65caba0bd8966
54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab86ff39ed7fbcbbb
84a18810b8eb1dc898d9af686f5901a21d0768720b325279fde4931ee52f0d4a0d0d9
cd1cd7c424d4622b1588ba554cd9241352a59ef52bbe85e0f865021404b115ba954f5
540cf2d811a6566a93876cac1239b1f75f39b070250af5a84a819e08b13e9e437a80f
c25cc130f8475dde43efe6d900c664e9bac300298bb0f9c5ec75a8cd571370add249e
99cb8a8c43f6ef05610ac6e354642bf4fedbf696e77d4749eb304c4d74be9457c5975
46bc22aed699225499910fc913b3e907120485942e3e077f71c1dd2d87053b39f0d31
bfe5d5f90df0e85ad9ce771e4f4d1ab697a10a02002cd73916051b887da9554465d58
68811fd8b22b8f457ed5a4b0
KE3: b4f8aece9fb4f6b7b5ffe1c98747a91f4ec7bf5481fe5719ba4baad668e3fd4e
8aba4fa227bd4c688ed9e17f6c6d28ab5e5617a883207d80979dc4797ca89304
export_key: 045f61f4baa0a945c2e85dfb7a85fe4df8a49e6c31344920e863c286b
c8a17fe25fc16c84836335b4b5ecc9743c5d3a221101ab004aa99ce65026b6953ad6c
c0
session_key: 91187690e5ea0da3110a1dd7d5ffd7c4c3111950c587d9fcf3b9f34b
f73b86dbeafed42a05024fa875a32415c6143d20c39cd732eb0e31db5e60ea3fb2551
cf7
~~~
"#,
r#"
## OPAQUE-3DH Test Vector 2
### Configuration
~~~
OPRF: 0001
Hash: SHA512
MHF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
~~~
### Input Values
~~~
client_identity: 616c696365
server_identity: 626f62
oprf_seed: db5c1c16e264b8933d5da56439e7cfed23ab7287b474fe3cdcd58df089
a365a426ea849258d9f4bc13573601f2e727c90ecc19d448cf3145a662e0065f157ba
5
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf
2747829b2d2
masking_nonce: 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b
5def228c85
server_private_key: eeb2fcc794f98501b16139771720a0713a2750b9e528adfd3
662ad56a7e19b04
server_public_key: 8aa90cb321a38759fc253c444f317782962ca18d33101eab2c
8cda04405a181f
server_nonce: 3fa57f7ef652185f89114109f5a61cc8c9216fdd7398246bb7a0c20
e2fbca2d8
client_nonce: a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e608
5d8c1187a
server_keyshare: ae070cdffe5bb4b1c373e71be8e7d8f356ee5de37881533f1039
7bcd84d35445
client_keyshare: 642e7eecf19b804a62817486663d6c6c239396f709b663a4350c
da67d025687a
server_private_keyshare: 0974010a8528b813f5b33ae0d791df88516c8839c152
b030697637878b2d8b0a
client_private_keyshare: 03b52f066898929f4aca48014b2b97365205ce691ee3
444b0a7cecec3c7efb01
blind_registration: a66ffb41ccf1194a8d7dda900f8b6b0652e4c7fac4610066f
e0489a804d3bb05
blind_login: e6f161ac189e6873a19a54efca4baa0719e801e336d929d35ca28b5b
4f60560e
oprf_key: 1e0550d2dbb9ce5dd9bdbb5f808afbb724c573dc03306dcfc7217796465
ce607
~~~
### Intermediate Values
~~~
client_public_key: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7e1
c9004f20d5db71
auth_key: 5142ae6f6bd80686039656fd7a03cdd7e39cc6e869aa637220d4b5fb64f
afee2f284a1581fff95ad3a5261b413c5e5b91115f78a3c35486fa56023c300d1726b
randomized_pwd: cea240b632b9c1d704034920cc3dc3c664ed8cd82cf5c0339af76
4d6350d2ee9ba1f675ce8df7b6cf8692d1efb158bafa3c2695ac03a2d92346c19810c
1a698b
envelope: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf274782
9b2d26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c
1517b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
handshake_secret: 7925c12d7bf3050e62fe5c8caaece3c85737754c5df79bc59a6
0fa87929ab1f4a4730f903b87be8b7d89ded8ec97aaec97bc8e7d53a555fd4ad74c4f
33b9bc83
server_mac_key: 27d6036335c5654132fb08cc81d95b3067ef7fe795f017531231a
e3fa03cd3ab72f1f5e81473318f9c01f990263d885dfce4b6ac8630fdc8ee8abc6a36
7c2339
client_mac_key: ebb3693bac6310075a89922c7a40599d14d03d9104b7a331106e8
a578a32a4944751f9d3c230a6690a5747137388a86159cf587969d13dadc0a3830218
dfbca5
~~~
### Output Values
~~~
registration_request: f841cbb85844967568c7405f3831a58c4f5f37ccddb0baa
4972ea912c960ae66
registration_response: 0256257cc6e2b04444edc076b9ad44d8b31593e050bea8
06485707a818f8a93f8aa90cb321a38759fc253c444f317782962ca18d33101eab2c8
cda04405a181f
registration_upload: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7
e1c9004f20d5db71146e42585d25fa19913876edce4b5ee99b638eb37b1d8a8a76607
efaa12299e828641ba4fbf1c46fc2c3776e0a0c9791f88a15b9ddfb5495d63ce92d8f
58823bd0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf2747829b2d
26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c1517
b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
KE1: 14cc586d982b6db9846c78e0b3c543591e95fbf2fc877fa0e5eff89897dd3050
a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e6085d8c1187a642e7
eecf19b804a62817486663d6c6c239396f709b663a4350cda67d025687a
KE2: 8ab71c17547f376ae787741c367142790087090cdde6327dabb2581197bffa59
30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b5def228c85dd973
a1ac59244f674da4a1c057961886661bd29e0c1346f0fcf75bf1c78d4781815c2f9f6
f2f9fe0e370b256f6e82fb2e14c7ffc374d42caf26abf13dca169a6faafd5cff8baa9
717090bc1fc5e1ba56acb93492d1a8b789f33ff29b6004c4be9a755ff590d7d00d6e8
893e7e54e639aebf69d18f2182a9bb0f2e1c27c81ba73fa57f7ef652185f89114109f
5a61cc8c9216fdd7398246bb7a0c20e2fbca2d8ae070cdffe5bb4b1c373e71be8e7d8
f356ee5de37881533f10397bcd84d35445401c619d464ab3a134c71da4d9874f2f736
189b8bbb659c28f8db25a58b9f089272132e3091efa87d6b07d10321ba464047be011
3e91514aba299fd1553bcebb
KE3: c4a0d5b8148f3ac0f8611b38de38bda085d4eb00d561397ae59676f36dc705be
1c939e7bfdd7301103af5eb164bdfb70298aab889bd2ac797e419a82bfb442e6
export_key: 6b50ae4dba956930c0465b4a26c3cee58e05afcab623c1c254ae34acc
38babf954530a53475672ff46a1cf7fd53ef9e808f85b08793d021bb5c6d2a1bb9204
f6
session_key: c9bc2b7e2237f6fbeccd92dc6ec6d51faeb886492f8d23f21743a967
597025215df02a4afb75349acbafeef9dfd4f19e6d38da8bea4912f7b691b70849b0d
78e
~~~
"#,
];
static FAKE_TEST_VECTORS: &[&str] = &[r#"
### OPAQUE-3DH Fake Test Vector 1
#### Configuration
~~~
OPRF: 0001
Hash: SHA512
MHF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
EnvelopeMode: 01
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
~~~
#### Input Values
~~~
client_identity: 616c696365
server_identity: 626f62
oprf_seed: d3cb00535339fe4063c7ba5506a990c243a2b5c77b06848a0be9a0568c
252fb0d7425382babd267deeed669e56d1d5654c036211f49b42f4489f96f37100779
f
credential_identifier: 31323334
masking_nonce: 3058799f42516228746821dc8c8530d0e8273ebde81941591d69ca
5aea773090
client_private_key: 83c9bcc31a9da0ffa4489900d3d1f85bb65c27f26e9ae4e3b
66f6e02e098c503
client_public_key: 56717b74a5e1770edb14c65f22cee0487046bd96e122ba97da
ffed06c4bf4052
server_private_key: 8d3a9355f9757e7071b3f836e3fb1461a6436e92971625b17
cd7e580dd27c009
server_public_key: 7a464761cb19c8b6e832fdfcfd18779b0edc246fe808f5de6c
e7bdb54df41b67
server_nonce: 4e2a8098173efa2968036f1762f2e5df41ab976fb1bfb91dae29950
f8526de4c
server_keyshare: 0e247410004d83d7cbe3af89c62ff03f942127aec4b0084c9eb5
88e74ce6dd06
server_private_keyshare: 326345820acc8aacf4948fce775a1fd265e4e93fd579
cec8177d6389ee379b0a
masking_key: e968bfe56ad934c3e1088115bcbf1af8b405fd0de94cdf301f9192cc
2781de00617e568b14b7235cc1189265811ea354031ea39b62e31a104f181c01d3dae
4b8
KE1: 480b6c0066c9320c50dce20f8b6b63e4ded7681defd9da3f70ecdc15770f9e68
05603c1acb64ea417c0dabaab858a5f9da046d4a0cdbf092034c00451ccdc6e1ee835
5c91d5ed7aa5ea75b8a730ba8dc45f6b41ae9713e6aa7126211346e8754
~~~
#### Output Values
~~~
KE2: 04013bca360b4b9ba95b2f494927375e0f234dac23053822e466a9738f781522
3058799f42516228746821dc8c8530d0e8273ebde81941591d69ca5aea77309078577
13efdc95f69166737cd7a80ead60e1a1f805c1da9cccbc0d29120f34be291518798c7
00793f232374e66182495b76b388d9e11f479580cc2297da02fecee88a99cea6bc411
b9467e8bfa9a4006aba7f21b74b4ce3bccd686785878b0ec9b3fc4200228014d5d073
69d42d1d1b1669ecd2ad8905734ca0a641d8f16667ca4e2a8098173efa2968036f176
2f2e5df41ab976fb1bfb91dae29950f8526de4c0e247410004d83d7cbe3af89c62ff0
3f942127aec4b0084c9eb588e74ce6dd06fb1a0fd81da51bc1d87c740c186d881ed79
71fdba5ad1d5cfc94ffe6a731241c78ea7ea5dae503e987edc37355b7348883dc65cd
b57aec04e64593007f98a405
~~~
"#];
macro_rules! parse {
( $v:ident, $s:expr ) => {
parse_default!($v, $s, vec![])
};
}
macro_rules! parse_default {
( $v:ident, $s:expr, $d:expr ) => {
match decode(&$v, $s) {
Some(x) => x,
None => $d,
}
};
}
macro_rules! rfc_to_params {
( $v:ident ) => {
$v.iter()
.map(|x| populate_test_vectors(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap()))
.collect::<Vec<TestVectorParameters>>()
};
}
fn rfc_to_json(input: &str) -> String {
let mut json = vec![];
for line in input.lines() {
// If line contains colon, then
if line.contains(':') {
if !json.is_empty() {
// Adding closing quote for previous line, comma, and newline
json.push("\",\n".to_string());
}
let mut iter = line.split(':');
let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap();
json.push(format!(" \"{}\": \"{}", key, val));
} else {
let s = line.trim().to_string();
if s.contains("~") || s.contains("#") {
// Ignore comment lines
continue;
}
if s.len() > 0 {
json.push(s);
}
}
}
format!("{{\n{}\"\n}}", json.join(""))
}
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
TestVectorParameters {
dummy_private_key: parse_default!(
values,
"client_private_key",
vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()]
),
dummy_masking_key: parse_default!(values, "masking_key", vec![0u8; 64]),
context: parse!(values, "Context"),
envelope_mode: match values["EnvelopeMode"].as_str() {
Some("01") => EnvelopeMode::Base,
Some("02") => EnvelopeMode::CustomIdentifier,
_ => panic!("Could not match envelope mode"),
},
client_private_key: decode(values, "client_private_key"),
client_keyshare: parse!(values, "client_keyshare"),
client_private_keyshare: parse!(values, "client_private_keyshare"),
server_public_key: parse!(values, "server_public_key"),
server_private_key: parse!(values, "server_private_key"),
server_keyshare: parse!(values, "server_keyshare"),
server_private_keyshare: parse!(values, "server_private_keyshare"),
client_identity: 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"),
client_info: parse!(values, "client_info"),
server_info: parse!(values, "server_info"),
registration_request: parse!(values, "registration_request"),
registration_response: parse!(values, "registration_response"),
registration_upload: parse!(values, "registration_upload"),
KE1: parse!(values, "KE1"),
KE2: parse!(values, "KE2"),
KE3: parse!(values, "KE3"),
blind_login: parse!(values, "blind_login"),
export_key: parse!(values, "export_key"),
session_key: parse!(values, "session_key"),
}
}
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
let password_file = ServerRegistration::<Ristretto255Sha512NoSlowHash>::finish(
RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap(),
);
Ok(password_file.serialize())
}
fn parse_identifiers(
client_identity: Option<Vec<u8>>,
server_identity: Option<Vec<u8>>,
) -> Option<Identifiers> {
match (client_identity, server_identity) {
(None, None) => None,
(Some(x), None) => Some(Identifiers::ClientIdentifier(x)),
(None, Some(y)) => Some(Identifiers::ServerIdentifier(y)),
(Some(x), Some(y)) => Some(Identifiers::ClientAndServerIdentifiers(x, y)),
}
}
#[test]
fn test_registration_request() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut rng,
&parameters.password,
)?;
assert_eq!(
hex::encode(&parameters.registration_request),
hex::encode(client_registration_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_registration_response() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&server_setup,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&parameters.credential_identifier,
)?;
assert_eq!(
hex::encode(parameters.registration_response),
hex::encode(server_registration_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_registration_upload() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut rng,
&parameters.password,
)?;
let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce);
let result = client_registration_start_result.state.finish(
&mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ClientRegistrationFinishParameters::Default,
Some(ids) => ClientRegistrationFinishParameters::WithIdentifiers(ids),
},
)?;
assert_eq!(
hex::encode(parameters.registration_upload),
hex::encode(result.message.serialize())
);
assert_eq!(
hex::encode(parameters.export_key),
hex::encode(result.export_key.to_vec())
);
}
Ok(())
}
#[test]
fn test_ke1() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let client_login_start = [
parameters.blind_login,
parameters.client_private_keyshare,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
)?;
assert_eq!(
hex::encode(&parameters.KE1),
hex::encode(client_login_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_ke2() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
&get_password_file_bytes(&parameters)?[..],
)?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
parameters.masking_nonce,
parameters.server_private_keyshare,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
Some(record),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
&parameters.credential_identifier,
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.context.to_vec(),
ids,
),
},
)?;
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_ke3() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let client_login_start = [
parameters.blind_login,
parameters.client_private_keyshare,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
)?;
let client_login_finish_result = client_login_start_result.state.finish(
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?,
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ClientLoginFinishParameters::WithContext(parameters.context),
Some(ids) => {
ClientLoginFinishParameters::WithContextAndIdentifiers(parameters.context, ids)
}
},
)?;
assert_eq!(
hex::encode(&parameters.session_key),
hex::encode(&client_login_finish_result.session_key)
);
assert_eq!(
hex::encode(&parameters.KE3),
hex::encode(client_login_finish_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.export_key),
hex::encode(client_login_finish_result.export_key)
);
}
Ok(())
}
#[test]
fn test_server_login_finish() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
&get_password_file_bytes(&parameters)?[..],
)?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
parameters.masking_nonce,
parameters.server_private_keyshare,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
Some(record),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
&parameters.credential_identifier,
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.context.to_vec(),
ids,
),
},
)?;
let server_login_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&parameters.KE3[..])?)?;
assert_eq!(
hex::encode(parameters.session_key),
hex::encode(&server_login_result.session_key)
);
}
Ok(())
}
#[test]
fn test_fake_vectors() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(FAKE_TEST_VECTORS) {
let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
&[
&parameters.oprf_seed[..],
&parameters.server_private_key[..],
&parameters.dummy_private_key[..],
]
.concat(),
)?;
let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
[
parameters.dummy_masking_key,
parameters.masking_nonce,
parameters.server_private_keyshare,
parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
&server_setup,
None,
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
&parameters.credential_identifier,
match parse_identifiers(parameters.client_identity, parameters.server_identity) {
None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
parameters.context.to_vec(),
ids,
),
},
)?;
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize())
);
}
Ok(())
}
-102
View File
@@ -1,102 +0,0 @@
// 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;
}
}
}
}
}
}
-806
View File
@@ -1,806 +0,0 @@
// 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
"#;
-688
View File
@@ -1,688 +0,0 @@
// 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::rngs::SysRng;
use rand_core::UnwrapErr;
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(&parameters.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, &parameters.password)?;
assert_eq!(
hex::encode(&parameters.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(),
&parameters.server_private_key,
&parameters.dummy_public_key,
]
.concat(),
)?;
let server_registration_start_result = ServerRegistration::<CS>::start(
&server_setup,
RegistrationRequest::deserialize(&parameters.registration_request).unwrap(),
&parameters.credential_identifier,
)?;
assert_eq!(
hex::encode(&parameters.server_public_key),
hex::encode(server_setup.keypair().public().serialize()),
);
assert_eq!(
hex::encode(&parameters.oprf_key),
hex::encode(server_registration_start_result.oprf_key)
);
assert_eq!(
hex::encode(&parameters.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, &parameters.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,
&parameters.password,
RegistrationResponse::deserialize(&parameters.registration_response).unwrap(),
ClientRegistrationFinishParameters::new(
Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
None,
),
)?;
assert_eq!(
hex::encode(&parameters.auth_key),
hex::encode(result.auth_key)
);
assert_eq!(
hex::encode(&parameters.randomized_pwd),
hex::encode(result.randomized_pwd)
);
assert_eq!(
hex::encode(&parameters.registration_upload),
hex::encode(result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.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(),
&parameters.client_keyshare_seed,
&parameters.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, &parameters.password)?;
assert_eq!(
hex::encode(&parameters.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(),
&parameters.server_private_key,
&parameters.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(),
&parameters.masking_nonce,
&parameters.server_keyshare_seed,
&parameters.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(&parameters.KE1).unwrap(),
&parameters.credential_identifier,
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
assert_eq!(
hex::encode(&parameters.handshake_secret),
hex::encode(server_login_start_result.handshake_secret)
);
assert_eq!(
hex::encode(&parameters.server_mac_key),
hex::encode(server_login_start_result.server_mac_key)
);
assert_eq!(
hex::encode(&parameters.oprf_key),
hex::encode(server_login_start_result.oprf_key)
);
assert_eq!(
hex::encode(&parameters.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(),
&parameters.client_keyshare_seed,
&parameters.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, &parameters.password)?;
let client_login_finish_result = client_login_start_result.state.finish(
&mut UnwrapErr(SysRng),
&parameters.password,
CredentialResponse::<CS>::deserialize(&parameters.KE2)?,
ClientLoginFinishParameters::new(
Some(&parameters.context.clone()),
Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
None,
),
)?;
assert_eq!(
hex::encode(&parameters.session_key),
hex::encode(&client_login_finish_result.session_key)
);
assert_eq!(
hex::encode(&parameters.handshake_secret),
hex::encode(&client_login_finish_result.handshake_secret)
);
assert_eq!(
hex::encode(&parameters.client_mac_key),
hex::encode(&client_login_finish_result.client_mac_key)
);
assert_eq!(
hex::encode(&parameters.KE3),
hex::encode(client_login_finish_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.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(),
&parameters.server_private_key,
&parameters.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(),
&parameters.masking_nonce,
&parameters.server_keyshare_seed,
&parameters.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(&parameters.KE1).unwrap(),
&parameters.credential_identifier,
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
let server_login_result = server_login_start_result.state.finish(
CredentialFinalization::deserialize(&parameters.KE3)?,
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
assert_eq!(
hex::encode(&parameters.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(),
&parameters.server_private_key,
&parameters.dummy_public_key,
]
.concat(),
)?;
let mut server_keyshare_seed_and_nonce_rng = CycleRng::new(
[
parameters.dummy_masking_key.as_slice(),
&parameters.masking_nonce,
&parameters.server_keyshare_seed,
&parameters.server_nonce,
]
.concat(),
);
let server_login_start_result = ServerLogin::<CS>::start(
&mut server_keyshare_seed_and_nonce_rng,
&server_setup,
None,
CredentialRequest::<CS>::deserialize(&parameters.KE1).unwrap(),
&parameters.credential_identifier,
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize())
);
}
Ok(())
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::tests::mock_rng::CycleRng;
use crate::{errors::*, group::Group, oprf};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::GenericArray;
use serde_json::Value;
use sha2::Sha512;
struct VOPRFTestVectorParameters {
sksm: Vec<u8>,
input: Vec<u8>,
blind: Vec<u8>,
blinded_element: Vec<u8>,
evaluation_element: Vec<u8>,
output: Vec<u8>,
}
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
// in base mode
static OPRF_RISTRETTO255_SHA512: &[&str] = &[
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "00",
"blind": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"blinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
"evaluation_element": "fc6c2b854553bf1ed6674072ed0bde1a9911e02b4bd64aa02cfb428f30251e77",
"output": "d8ed12382086c74564ae19b7a2b5ed9bdc52656d1fc151faaae51aaba86291e8df0b2143a92f24d44d5efd0892e2e26721d27d88745343493634a66d3a925e3a"
}
"#,
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
"blinded_element": "28a5e797b710f76d20a52507145fbf320a574ec2c8ab0e33e65dd2c277d0ee56",
"evaluation_element": "345e140b707257ae83d4911f7ead3177891e7a62c54097732802c4c7a98ab25a",
"output": "4d5f4221b5ebfd4d1a9dd54830e1ed0bce5a8f30a792723a6fddfe6cfe9f86bb1d95a3725818aeb725eb0b1b52e01ee9a72f47042372ef66c307770054d674fc"
}
"#,
];
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
sksm: decode(&values, "sksm").unwrap(),
input: decode(&values, "input").unwrap(),
blind: decode(&values, "blind").unwrap(),
blinded_element: decode(&values, "blinded_element").unwrap(),
evaluation_element: decode(&values, "evaluation_element").unwrap(),
output: decode(&values, "output").unwrap(),
}
}
// Tests input -> blind, blinded_element
#[test]
fn test_blind() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let mut rng = CycleRng::new(parameters.blind.to_vec());
let (token, blinded_element) =
oprf::blind::<_, RistrettoPoint, Sha512>(&parameters.input, &mut rng)?;
assert_eq!(
&parameters.blind,
&RistrettoPoint::scalar_as_bytes(&token.blind).to_vec()
);
assert_eq!(
&parameters.blinded_element,
&blinded_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests sksm, blinded_element -> evaluation_element
#[test]
fn test_evaluate() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let evaluation_element = oprf::evaluate::<RistrettoPoint>(
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.blinded_element,
))
.unwrap(),
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&parameters.sksm)).unwrap(),
);
assert_eq!(
&parameters.evaluation_element,
&evaluation_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests input, blind, evaluation_element -> output
#[test]
fn test_finalize() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let output = oprf::finalize::<RistrettoPoint, Sha512>(
&parameters.input,
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&parameters.blind))?,
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.evaluation_element,
))?,
);
assert_eq!(&parameters.output, &output.to_vec());
}
Ok(())
}
-3
View File
@@ -1,3 +0,0 @@
[formatting]
allowed_blank_lines = 1
reorder_keys = true
-653
View File
@@ -1,653 +0,0 @@
// 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())
}