182 Commits
Author SHA1 Message Date
breakingbread 2e1bcfe4c2 chore: fix README package name, enable zeroize on crates, rand_core removed for reexport (#4)
Rust CI / cargo fmt (push) Successful in 15s
Rust CI / test (1.90.0 / no backend / --features argon2) (push) Successful in 2m35s
Rust CI / cargo clippy (push) Successful in 1m28s
Rust CI / test (1.90.0 / no backend / no frontend) (push) Successful in 2m40s
Rust CI / test (stable / no backend / no frontend) (push) Successful in 2m28s
Rust CI / test (stable / no backend / --features serde) (push) Successful in 2m54s
Rust CI / test (stable / no backend / --features argon2) (push) Successful in 2m35s
Rust CI / test (1.90.0 / no backend / --features serde) (push) Successful in 2m54s
Rust CI / test (stable / --features curve25519 / no frontend) (push) Successful in 2m35s
Rust CI / test (1.90.0 / --features curve25519 / no frontend) (push) Successful in 2m29s
Rust CI / test (stable / --features curve25519 / --features argon2) (push) Successful in 2m39s
Rust CI / test (1.90.0 / --features curve25519 / --features argon2) (push) Successful in 2m38s
Rust CI / test (stable / --features curve25519 / --features serde) (push) Successful in 3m8s
Rust CI / test (1.90.0 / --features curve25519 / --features serde) (push) Successful in 2m57s
Rust CI / test (1.90.0 / --features ecdsa / no frontend) (push) Successful in 2m56s
Rust CI / test (stable / --features ecdsa / no frontend) (push) Successful in 2m52s
Rust CI / test (stable / --features ecdsa / --features argon2) (push) Successful in 3m0s
Publish / publish (release) Successful in 1m5s
Rust CI / test (1.90.0 / --features ecdsa / --features argon2) (push) Successful in 2m56s
Rust CI / test (1.90.0 / --features ed25519 / no frontend) (push) Successful in 2m51s
Rust CI / test (1.90.0 / --features ecdsa / --features serde) (push) Successful in 3m20s
Rust CI / test (stable / --features ecdsa / --features serde) (push) Successful in 3m19s
Rust CI / test (stable / --features ed25519 / no frontend) (push) Successful in 2m53s
Rust CI / test (stable / --features ed25519 / --features argon2) (push) Successful in 3m1s
Rust CI / test (1.90.0 / --features ed25519 / --features argon2) (push) Successful in 3m0s
Rust CI / test (1.90.0 / --features ed25519 / --features serde) (push) Successful in 3m21s
Rust CI / test (stable / --features ed25519 / --features serde) (push) Successful in 3m20s
Rust CI / test (stable / --features ristretto255 / no frontend) (push) Successful in 3m7s
Rust CI / test (1.90.0 / --features ristretto255 / no frontend) (push) Successful in 3m3s
Rust CI / test (1.90.0 / --features ristretto255 / --features argon2) (push) Successful in 3m13s
Rust CI / test (stable / --features ristretto255 / --features argon2) (push) Successful in 3m10s
Rust CI / test (1.90.0 / --features ristretto255 / --features serde) (push) Successful in 3m31s
Rust CI / test (stable / --features ristretto255 / --features serde) (push) Successful in 3m29s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m20s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m8s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m28s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m17s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m56s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m47s
Rust CI / test (1.90.0 / --features ristretto255,kem / no frontend) (push) Successful in 4m0s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features argon2) (push) Successful in 4m21s
Rust CI / test (stable / --features ristretto255,kem / no frontend) (push) Successful in 3m58s
Rust CI / test (stable / --features ristretto255,kem / --features argon2) (push) Successful in 4m3s
Rust CI / test (stable / --features ristretto255,kem / --features serde) (push) Successful in 4m34s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features serde) (push) Successful in 4m28s
Rust CI / test simple_login example (push) Successful in 19s
Rust CI / test digital_locker example (push) Successful in 17s
Rust CI / cargo bench compilation () (push) Successful in 1m45s
Rust CI / cargo bench compilation (--features ristretto255) (push) Successful in 1m53s
Rust CI / cargo bench compilation (--features ristretto255,kem) (push) Successful in 2m33s
Rust CI / cargo audit (push) Successful in 10s
Rust CI / no-std (thumbv6m-none-eabi / ecdsa) (push) Successful in 29s
Rust CI / no-std (wasm32-unknown-unknown / ecdsa) (push) Successful in 29s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 31s
Rust CI / no-std (thumbv6m-none-eabi / no backend) (push) Successful in 29s
Rust CI / no-std (wasm32-unknown-unknown / no backend) (push) Successful in 27s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255) (push) Successful in 31s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 21s
Rust CI / no-std (thumbv6m-none-eabi / curve25519) (push) Successful in 27s
Rust CI / no-std (wasm32-unknown-unknown / curve25519) (push) Successful in 41s
Rust CI / no-std (wasm32-unknown-unknown / ed25519) (push) Successful in 20s
Rust CI / no-std (thumbv6m-none-eabi / ed25519) (push) Successful in 27s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255) (push) Successful in 29s
Reviewed-on: #4
Co-authored-by: UneBaguette <[email protected]>
Co-committed-by: UneBaguette <[email protected]>
2026-07-02 08:19:22 +02:00
breakingbread efa98962b7 chore: update copyright headers to SPDX format and code cleanup (#3)
Publish / publish (release) Successful in 1m2s
Rust CI / cargo fmt (push) Successful in 3s
Rust CI / test (stable / no backend / no frontend) (push) Successful in 2m31s
Rust CI / cargo clippy (push) Successful in 1m24s
Rust CI / test (1.90.0 / no backend / no frontend) (push) Successful in 2m28s
Rust CI / test (1.90.0 / no backend / --features argon2) (push) Successful in 2m35s
Rust CI / test (stable / no backend / --features argon2) (push) Successful in 2m33s
Rust CI / test (1.90.0 / no backend / --features serde) (push) Successful in 2m53s
Rust CI / test (1.90.0 / --features curve25519 / --features argon2) (push) Successful in 2m36s
Rust CI / test (stable / no backend / --features serde) (push) Successful in 2m51s
Rust CI / test (1.90.0 / --features curve25519 / no frontend) (push) Successful in 2m29s
Rust CI / test (stable / --features curve25519 / no frontend) (push) Successful in 2m29s
Rust CI / test (stable / --features curve25519 / --features serde) (push) Successful in 2m55s
Rust CI / test (stable / --features curve25519 / --features argon2) (push) Successful in 2m35s
Rust CI / test (1.90.0 / --features curve25519 / --features serde) (push) Successful in 2m56s
Rust CI / test (stable / --features ecdsa / no frontend) (push) Successful in 2m53s
Rust CI / test (1.90.0 / --features ecdsa / no frontend) (push) Successful in 2m51s
Rust CI / test (1.90.0 / --features ecdsa / --features argon2) (push) Successful in 2m57s
Rust CI / test (1.90.0 / --features ecdsa / --features serde) (push) Successful in 3m20s
Rust CI / test (stable / --features ed25519 / no frontend) (push) Successful in 2m54s
Rust CI / test (stable / --features ecdsa / --features argon2) (push) Successful in 3m0s
Rust CI / test (stable / --features ecdsa / --features serde) (push) Successful in 3m19s
Rust CI / test (1.90.0 / --features ed25519 / no frontend) (push) Successful in 2m52s
Rust CI / test (1.90.0 / --features ed25519 / --features argon2) (push) Successful in 2m57s
Rust CI / test (1.90.0 / --features ristretto255 / no frontend) (push) Successful in 3m2s
Rust CI / test (stable / --features ed25519 / --features argon2) (push) Successful in 3m1s
Rust CI / test (1.90.0 / --features ed25519 / --features serde) (push) Successful in 3m20s
Rust CI / test (stable / --features ed25519 / --features serde) (push) Successful in 3m21s
Rust CI / test (stable / --features ristretto255 / no frontend) (push) Successful in 3m4s
Rust CI / test (1.90.0 / --features ristretto255 / --features argon2) (push) Successful in 3m8s
Rust CI / test (1.90.0 / --features ristretto255 / --features serde) (push) Successful in 3m28s
Rust CI / test (stable / --features ristretto255 / --features argon2) (push) Successful in 3m9s
Rust CI / test (stable / --features ristretto255 / --features serde) (push) Successful in 3m32s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m21s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m11s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m27s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m14s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m45s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m58s
Rust CI / test (1.90.0 / --features ristretto255,kem / no frontend) (push) Successful in 3m59s
Rust CI / test (stable / --features ristretto255,kem / no frontend) (push) Successful in 4m0s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features argon2) (push) Successful in 4m12s
Rust CI / test (stable / --features ristretto255,kem / --features argon2) (push) Successful in 4m4s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features serde) (push) Successful in 4m34s
Rust CI / test (stable / --features ristretto255,kem / --features serde) (push) Successful in 4m27s
Rust CI / test simple_login example (push) Successful in 18s
Rust CI / test digital_locker example (push) Successful in 19s
Rust CI / cargo bench compilation () (push) Successful in 1m46s
Rust CI / cargo bench compilation (--features ristretto255) (push) Successful in 1m54s
Rust CI / cargo audit (push) Successful in 6s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 29s
Rust CI / cargo bench compilation (--features ristretto255,kem) (push) Successful in 2m32s
Rust CI / no-std (wasm32-unknown-unknown / no backend) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / curve25519) (push) Successful in 18s
Rust CI / no-std (thumbv6m-none-eabi / no backend) (push) Successful in 28s
Rust CI / no-std (thumbv6m-none-eabi / curve25519) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / ecdsa) (push) Successful in 18s
Rust CI / no-std (thumbv6m-none-eabi / ecdsa) (push) Successful in 29s
Rust CI / no-std (thumbv6m-none-eabi / ed25519) (push) Successful in 20s
Rust CI / no-std (wasm32-unknown-unknown / ed25519) (push) Successful in 20s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255) (push) Successful in 27s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 18s
- Added VexaHub copyright alongside original Meta copyright
- Replaced verbose license blocks with SPDX-License-Identifier
- Minor code improvements

Reviewed-on: #3
Co-authored-by: UneBaguette <[email protected]>
Co-committed-by: UneBaguette <[email protected]>
2026-07-01 20:48:44 +02:00
breakingbread 71df1ee49a feat: upgrade crypto ecosystem to latest RustCrypto stack (#1)
Rust CI / cargo audit (push) Successful in 6s
Rust CI / cargo fmt (push) Successful in 4s
Rust CI / test (1.90.0 / no backend / no frontend) (push) Successful in 2m25s
Rust CI / test (stable / no backend / no frontend) (push) Successful in 2m27s
Rust CI / cargo clippy (push) Successful in 1m25s
Rust CI / test (1.90.0 / no backend / --features argon2) (push) Successful in 2m35s
Rust CI / test (stable / no backend / --features argon2) (push) Successful in 2m34s
Rust CI / test (stable / no backend / --features serde) (push) Successful in 2m55s
Rust CI / test (stable / --features curve25519 / no frontend) (push) Successful in 2m31s
Rust CI / test (1.90.0 / no backend / --features serde) (push) Successful in 2m52s
Rust CI / test (1.90.0 / --features curve25519 / no frontend) (push) Successful in 2m29s
Rust CI / test (1.90.0 / --features curve25519 / --features argon2) (push) Successful in 2m37s
Rust CI / test (stable / --features curve25519 / --features argon2) (push) Successful in 2m36s
Rust CI / test (1.90.0 / --features curve25519 / --features serde) (push) Successful in 2m59s
Rust CI / test (stable / --features curve25519 / --features serde) (push) Successful in 3m1s
Rust CI / test (1.90.0 / --features ecdsa / no frontend) (push) Successful in 2m52s
Rust CI / test (stable / --features ecdsa / no frontend) (push) Successful in 2m51s
Rust CI / test (1.90.0 / --features ecdsa / --features argon2) (push) Successful in 2m57s
Rust CI / test (stable / --features ecdsa / --features argon2) (push) Successful in 2m58s
Rust CI / test (1.90.0 / --features ecdsa / --features serde) (push) Successful in 3m19s
Rust CI / test (1.90.0 / --features ristretto255 / no frontend) (push) Successful in 3m2s
Rust CI / test (stable / --features ecdsa / --features serde) (push) Successful in 3m19s
Rust CI / test (1.90.0 / --features ed25519 / no frontend) (push) Successful in 2m53s
Rust CI / test (stable / --features ed25519 / no frontend) (push) Successful in 2m54s
Rust CI / test (1.90.0 / --features ed25519 / --features argon2) (push) Successful in 3m3s
Rust CI / test (stable / --features ed25519 / --features argon2) (push) Successful in 3m0s
Rust CI / test (1.90.0 / --features ed25519 / --features serde) (push) Successful in 3m22s
Rust CI / test (stable / --features ed25519 / --features serde) (push) Successful in 3m22s
Rust CI / test (1.90.0 / --features ristretto255 / --features argon2) (push) Successful in 3m9s
Rust CI / test (stable / --features ristretto255 / no frontend) (push) Successful in 3m4s
Rust CI / test (stable / --features ristretto255 / --features argon2) (push) Successful in 3m11s
Rust CI / test (1.90.0 / --features ristretto255 / --features serde) (push) Successful in 3m28s
Rust CI / test (stable / --features ristretto255 / --features serde) (push) Successful in 3m32s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m26s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / no frontend) (push) Successful in 5m17s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m27s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 5m43s
Rust CI / test (stable / --features ristretto255,curve25519,ecdsa,ed25519 / --features argon2) (push) Successful in 5m20s
Rust CI / test (1.90.0 / --features ristretto255,curve25519,ecdsa,ed25519 / --features serde) (push) Successful in 6m1s
Rust CI / test (stable / --features ristretto255,kem / no frontend) (push) Successful in 4m0s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features argon2) (push) Successful in 4m5s
Rust CI / test (1.90.0 / --features ristretto255,kem / no frontend) (push) Successful in 4m2s
Rust CI / test (stable / --features ristretto255,kem / --features argon2) (push) Successful in 4m3s
Rust CI / test (stable / --features ristretto255,kem / --features serde) (push) Successful in 4m32s
Rust CI / test (1.90.0 / --features ristretto255,kem / --features serde) (push) Successful in 4m31s
Rust CI / test simple_login example (push) Successful in 19s
Rust CI / test digital_locker example (push) Successful in 18s
Rust CI / cargo bench compilation () (push) Successful in 1m47s
Rust CI / cargo bench compilation (--features ristretto255) (push) Successful in 1m55s
Rust CI / cargo bench compilation (--features ristretto255,kem) (push) Successful in 2m35s
Rust CI / no-std (thumbv6m-none-eabi / no backend) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / no backend) (push) Successful in 28s
Rust CI / no-std (thumbv6m-none-eabi / curve25519) (push) Successful in 18s
Rust CI / no-std (wasm32-unknown-unknown / curve25519) (push) Successful in 28s
Rust CI / no-std (thumbv6m-none-eabi / ecdsa) (push) Successful in 29s
Rust CI / no-std (wasm32-unknown-unknown / ecdsa) (push) Successful in 28s
Rust CI / no-std (thumbv6m-none-eabi / ed25519) (push) Successful in 29s
Rust CI / no-std (wasm32-unknown-unknown / ed25519) (push) Successful in 30s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255) (push) Successful in 28s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255) (push) Successful in 28s
Rust CI / no-std (thumbv6m-none-eabi / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 29s
Rust CI / no-std (wasm32-unknown-unknown / ristretto255,curve25519,ecdsa,ed25519) (push) Successful in 19s
Upgrade all core cryptographic dependencies to their latest versions:

Dependencies:
- digest: 0.10 to 0.11
- elliptic-curve: 0.13 to 0.14
- 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
- sha2: 0.10 to 0.11
- getrandom: 0.2 to 0.4 (WASM)
- ml-kem: 0.3.0-rc.0 to 0.3
- ecdsa: 0.16 to 0.17.0-rc.23
- rfc6979: 0.4 to 0.6 (now internal to ecdsa)
- p256/p384/p521: 0.13 to 0.14.0-rc.15
- curve25519-dalek: 4 to 5.0.0-rc
- ed25519-dalek: 2 to 3.0.0-rc
- cryptoki: 0.9 to 0.12
- rustyline: 17 to 18
- scrypt: 0.11 to 0.12
- voprf replaced by voprf-vx 1.0.0-pre.0

Migration changes:
- generic-array 0.14 to 1.4 with hybrid-array 0.4 interop
- ArrayLength<u8> to ArrayLength (generic-array 1.x)
- Added ConcatExt trait to disambiguate from [T]::concat
- Replaced Hmac with SimpleHmac for digest 0.11 compatibility
- Added OutputSize<H>: ArrayLength bounds throughout Hash trait
- Converted hybrid_array::Array between GenericArray at API boundaries
- Updated GroupEncoding Repr bound to hybrid_array::Array
- ECDSA sign now uses ecdsa::hazmat::sign_prehashed_rfc6979
- Removed direct rfc6979 dependency (handled by ecdsa internally)
- Replaced bincode with postcard for no_std serialization
- Re-exported hybrid_array from crate root

Other changes:
- Renamed crate to opaque-vx
- Increased MSRV to 1.89
- Added cryptography to Cargo.toml categories
- Removed Facebook-specific contributions from CONTRIBUTING.md
- Removed v3 to v4 migration test
- Removed unstable rustfmt configurations for stable compatibility

Reviewed-on: #1
Co-authored-by: UneBaguette <[email protected]>
Co-committed-by: UneBaguette <[email protected]>
2026-07-01 11:52:12 +02:00
breakingbread 5e2ba86643 Delete directory '.github' 2026-06-27 05:39:01 +02:00
Kevin LewiandGitHub bc588b149e Upgrade ml-kem from 0.2 to 0.3.0-rc.0 (#421) 2026-03-26 17:17:13 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
b20d78c074 Bump actions/cache from 4 to 5 (#417)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-22 15:59:54 -08:00
Kevin LewiandGitHub 2dee6fbc01 Fixing clippy issues (#418) 2026-01-22 15:37:21 -08:00
Kevin LewiandGitHub a85a7bea8f Publishing v4.1.0-pre.1 (#415) 2025-11-18 10:28:27 -08:00
Kevin LewiandGitHub 22bcbfc821 Adding ml-kem re-export (#414) 2025-11-17 21:01:18 -08:00
Kevin LewiandGitHub 9b5eef4f8f Publishing v4.1.0-pre.0 (#413) 2025-11-12 13:49:39 -08:00
Kevin LewiandGitHub 8afec46637 Adding TripleDhKem key exchange (#404) 2025-11-08 14:21:51 -08:00
Kevin LewiandGitHub 67297dc73a Moving shared functionality out of TripleDh key exchange (#405) 2025-11-08 13:25:57 -08:00
Kevin LewiandGitHub 6d2af37a28 Deleting extraneous file (#412) 2025-11-04 11:38:21 -08:00
Kevin LewiandGitHub 96498709f4 Fixing rand dependency exporting (#411) 2025-11-03 14:31:59 -08:00
Kevin LewiandGitHub 75fe4cdddb Fixing docs build + publishing 4.0.1 (#409) 2025-10-30 17:09:53 -07:00
Kevin LewiandGitHub ed5e7cf116 Publishing v4.0.0 (#407) 2025-10-23 16:28:30 -07:00
Kevin LewiandGitHub 4dae85b3a9 Resolving zeroize_derive old version issue (#406) 2025-10-22 00:27:29 -07:00
Kevin LewiandGitHub af220c6d00 Fixing generic-array dependency (#402) 2025-10-15 15:05:51 -07:00
Kevin LewiandGitHub 79e4eacc40 Making full test vector generation deterministic (#398) 2025-10-13 15:05:15 -07:00
Kevin LewiandGitHub c94f942ed7 Fixing clippy/lint errors (#396) 2025-09-18 13:15:32 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
3f8026d30e Update criterion requirement from 0.5 to 0.7 (#394)
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.5.0...0.7.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-version: 0.7.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-15 20:48:32 -07:00
daxpeddaandGitHub 33464543c1 Add migration guide to changelog (#393) 2025-09-09 00:25:04 -07:00
Kevin LewiandGitHub a1624a92dd Re-exporting argon2 and generic_array (#387) 2025-09-08 23:32:58 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6593833622 Update rustyline requirement from 15 to 17 (#391)
Updates the requirements on [rustyline](https://github.com/kkawakam/rustyline) to permit the latest version.
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v15.0.0...v17.0.1)

---
updated-dependencies:
- dependency-name: rustyline
  dependency-version: 17.0.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 14:50:58 -07:00
226fe0ea7a Updating test vectors to RFC 9807 (#386)
* Updating to RFC 9807

* Update CHANGELOG.md

Co-authored-by: daxpedda <[email protected]>

* Update CHANGELOG.md

Co-authored-by: daxpedda <[email protected]>

* Update CHANGELOG.md

Co-authored-by: daxpedda <[email protected]>

---------

Co-authored-by: daxpedda <[email protected]>
2025-09-08 11:27:47 -07:00
daxpeddaandGitHub 8f9d7ec125 Update to Clippy v1.89 (#385) 2025-09-08 04:08:23 -07:00
daxpeddaandGitHub c04fb97b5c Remove Zeroize Tests (#381)
* Remove Zeroize tests

* Remove `Zeroize` requirements from `Group::Pk`

* Remove `Copy` requirements from `Group::Pk`

* Change to `ZeroizeOnDrop` requirements for `Group::Sk`

* Add note why we don't use `elliptic_curve::PublicKey`
2025-07-17 13:15:30 -07:00
daxpeddaandGitHub 1b6633cdcc Move Serde De/Serialization to Group Implementation (#380)
* Move Serde De/Serialization to `Group` implementation

* Clean up Serde formats

* Fix typo
2025-07-14 19:54:43 -07:00
daxpeddaandGitHub 5b492aedbf Update MSRV to v1.85 and edition to 2024 (#383) 2025-07-10 12:55:41 -07:00
daxpeddaandGitHub 8ee35498ea Publicly Expose trait KeyExchange (#379)
* Clean up types used in `trait KeyExchange`

* Expose `trait KeyExchange` publicly
2025-06-24 15:17:29 -07:00
daxpeddaandGitHub 3777ee680a Only store dummy public key (#374) 2025-05-20 12:49:11 -07:00
daxpeddaandGitHub fdf6a1103b Add missing copyright header (#382) 2025-05-20 12:48:30 -07:00
daxpeddaandGitHub bebd2c605b SIGMA-I Key Exchange (#378)
* Move `KeGroup` to `KeyExchange::Group`

- Introduce `KeyExchange::Hash`, which separates the OPRF hash from the one used in `KeyExchange`.
- Remove `De/Serialize` requirement on key exchange messages and states, which forced a lot of where bounds on downstream users.
- Rename `KeGroup` to `Group`.
- Replace `D` generic for hash with `H`.

* Use `voprf::derive_key()` directly

* Implement SIGMA-I key exchange

* Improve `KeyExchange` for SIGMA-I and Ed25519

* Implement EdDSA

* Un-qualify some method calls

* SIGMA-I: only include client identity in client mac

* SIGMA-I: include server mac in client signature

* Expose key exchange types in `crate` & move modules

* Implement Ed25519ph

* Document `ed25519` crate feature

* Remove `ristretto255-voprf` crate feature

* Adjust CI crate feature testing

* Fix Rustdoc

* Remove unnecessary generic parameters from SIGMA-I

* Properly mark to-do's with TODO

* Assorted fixes

* SIGMA-I: include context in signature

* SIGMA-I: include identifiers in signature

* Merge `ServerLoginStart/FinishParameters`

* Re-export more necessary types

* More carefully expose types

* Add ECDSA test

* SIGMA-I: share context hashing

* De-duplicate client static public key storage

* Hide `KeyExchange` better

* Use the correct hash in the root documentation

* Bump `derive-where`

* Format documentation examples a bit further

* Add remote OPRF seed documentation

* Rename `deserialize_key_pair` to `deserialize_take_key_pair`

* Add more key tests

* Remove `SharedSecret` trait

* SIGMA-I refactor message API

* Share more implementation between 3DH and SIGMA-I

* Remove unnecessary zero scalar check for Curve25519

* Use correct hash in test

* Add some more TODOs

* Exclude `tests` folder from Cargo publishing

* Enable missing dependencies

* Use right crate for testing Ed25519

* Remove unnecessary `Sized` constraints

* Remove unnecessary `ecdsa` crate features

* Move signature de/serialization to trait methods

* Nit: move import to appropriate location

* Add warning to SIGMA-I
2025-05-19 13:56:25 -07:00
daxpeddaandGitHub 58b4d746c0 Enable remote OPRF seed support (#373) 2025-05-05 04:12:54 -07:00
daxpeddaandGitHub b16cf8ce05 Enable curve25519-dalek/serde (#375) 2025-04-28 12:48:06 -07:00
daxpeddaandGitHub 560de5b718 Always create dummy record (#376) 2025-04-28 12:47:39 -07:00
daxpeddaandGitHub 27c6d4658b Add copyright header to remote_key test (#377) 2025-04-28 12:46:58 -07:00
daxpeddaandGitHub d324584d79 Rework SecretKey API to facilitate async (#371)
* Rework `SecretKey` API to facilitate async

* Remove left-over constraints
2025-04-22 00:08:17 -07:00
daxpeddaandGitHub 6b69e93dc9 Bump MSRV to v1.83 (#370)
* Fix Clippy warnings for Rust v1.86

* Bump MSRV to v1.83
2025-04-15 13:31:37 -07:00
Nik GrafandGitHub ff3a8b960d Update Readme link (#368) 2024-10-25 15:06:50 -07:00
Kevin LewiandGitHub df8e61d24b Publishing v3.0.0 (#367) 2024-10-09 22:13:13 -07:00
CynthiaandGitHub 94d9c563a4 chore!: align to draft 16, update dependencies (#365)
* chore!: align to draft 16, update dependencies

* chore: fix toml formatting
2024-09-23 21:28:40 -07:00
Kevin LewiandGitHub 4e525560c7 Publishing v3.0.0-pre.5 (#364) 2024-09-18 14:58:35 -07:00
Luca BrunoandGitHub a21678e969 docs: add details for client login final step (#358)
This tweaks the documentation on the main module, in order to
add some details on the outcome of the client login final step.
In particular, it clarifies the result of `ClientLogin::finish()`
both on success and on errors and it adds some intra-crate links
to the relevant structures and fields.
2024-09-18 13:04:14 -07:00
Kevin LewiandGitHub 066f88f434 Updating dependencies (#360) 2024-09-16 18:01:45 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7eb363f571 Bump actions/cache from 3 to 4 (#354)
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 18:31:49 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1cc30c255e Update rustyline requirement from 12 to 13 (#352)
Updates the requirements on [rustyline](https://github.com/kkawakam/rustyline) to permit the latest version.
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v12.0.0...v13.0.0)

---
updated-dependencies:
- dependency-name: rustyline
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-20 14:06:15 -08:00
Kevin LewiandGitHub 31c65f5d79 Adding copyright header to generated test file (#351) 2023-12-20 10:05:35 -08:00
daxpeddaandGitHub 56cdf9a024 Test P-521 (#349)
* Test P-521

* De-duplicate generic calls

* Simplify full test vectors generation
2023-11-16 11:07:46 -08:00
Kevin LewiandGitHub c728c37f15 Fixing clippy errors (#347) 2023-10-28 22:19:53 -07:00
daxpeddaandGitHub 47b37779d4 Add clippy::doc_markdown (#346) 2023-10-08 12:48:43 -07:00
Kevin LewiandGitHub ead80ad892 Clarifying the persisting of server setup (#344) 2023-10-04 20:53:07 -07:00
Kevin LewiandGitHub 83795f3f1c Increase MSRV to 1.70 and update workflow dependencies (#342) 2023-09-24 17:43:59 -07:00
Nik GrafandGitHub 07c3f74582 update docs: clarify export_key and session_key length (#338) 2023-08-14 17:37:56 -04:00
Kevin LewiandGitHub d13d261cf1 Publishing v3.0.0-pre.4 (#337) 2023-07-25 01:38:33 -07:00
d65cc4d38d add more resources (WebAssembly and React Native) (#335)
* add more resources (WebAssembly and React Native)

* Fixing clippy

---------

Co-authored-by: Kevin Lewi <[email protected]>
2023-07-15 20:44:47 -07:00
daxpeddaandGitHub 4487f11f87 Bump curve25519-dalek to v4.0.0-rc.3 (#330) 2023-06-29 11:51:40 -07:00
Nik GrafandGitHub bca1abfe42 update parameter from sk to private_key (#329) 2023-06-29 11:51:20 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7f0b6a8e66 Update rustyline requirement from 11 to 12 (#332)
Updates the requirements on [rustyline](https://github.com/kkawakam/rustyline) to permit the latest version.
- [Release notes](https://github.com/kkawakam/rustyline/releases)
- [Changelog](https://github.com/kkawakam/rustyline/blob/master/History.md)
- [Commits](https://github.com/kkawakam/rustyline/compare/v11.0.0...v12.0.0)

---
updated-dependencies:
- dependency-name: rustyline
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-26 16:59:13 -07:00
Kevin LewiandGitHub c3498ead45 Publishing v3.0.0-pre.3 (#327) 2023-06-08 01:39:28 -07:00
Kevin LewiandGitHub 4f69de68fd Fixing simple_login test to enable argon2 feature (#328) 2023-06-08 01:18:49 -07:00
Kevin LewiandGitHub deb7ca3dc0 Update keypair generation to use derive_auth_keypair (#326) 2023-05-27 17:39:04 -05:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c73eb963db Update criterion requirement from 0.4 to 0.5 (#325)
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version.
- [Changelog](https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bheisler/criterion.rs/compare/0.4.0...0.5.0)

---
updated-dependencies:
- dependency-name: criterion
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 19:24:34 -07:00
Kevin LewiandGitHub a88373722e Updating dual-license language (#324) 2023-05-22 23:04:26 -07:00
98b42d609f Curve25519 test vectors (#319)
* Curve25519 test vectors

* Adjust `derive_auth_keypair()` for Curve25519

* Update test vectors

* Fix Curve25519 random scalar generation

Co-Authored-By: Kevin Lewi <[email protected]>

* Update test vectors

* Update test vectors

* Update test vectors

---------

Co-authored-by: Kevin Lewi <[email protected]>
2023-05-22 23:04:01 -07:00
daxpeddaandGitHub e6c9870899 Correctly clamp Curve25519 secret keys (#323) 2023-05-21 17:36:01 -07:00
daxpeddaandGitHub 7b205b73eb Bump voprf to v0.5.0-pre.4 (#322) 2023-05-21 02:06:34 -07:00
Kevin LewiandGitHub 8b755bf076 Publishing v3.0.0-pre.2 (#318) 2023-03-14 19:07:10 -07:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f8f5d22721 Update scrypt requirement from 0.10 to 0.11 (#315)
Updates the requirements on [scrypt](https://github.com/RustCrypto/password-hashes) to permit the latest version.
- [Release notes](https://github.com/RustCrypto/password-hashes/releases)
- [Commits](https://github.com/RustCrypto/password-hashes/compare/scrypt-v0.10.0...scrypt-v0.11.0)

---
updated-dependencies:
- dependency-name: scrypt
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-10 10:11:29 -08:00
daxpeddaandGitHub c1c1c40191 Test P-384 (#290) 2023-03-06 11:28:19 -08:00
daxpeddaandGitHub 3096c4d9df Update argon2 to v0.5 (#314) 2023-03-04 20:31:18 -08:00
daxpeddaandGitHub c255364ca0 Update VOPRF to draft 19 (#307) 2023-03-04 20:31:07 -08:00
daxpeddaandGitHub 5400128833 Update rustyline to v0.11 (#313) 2023-03-04 14:49:29 -08:00
Kevin LewiandGitHub b40092db28 Publishing v3.0.0-pre.1 (#309) 2023-02-08 17:46:28 -08:00
daxpeddaandGitHub 09489ddf8b Only use explicit crate features (#306) 2023-02-04 13:25:41 -08:00
daxpeddaandGitHub 1fbe25507e Bump voprf to v0.5.0-pre.2 (#304) 2023-02-03 16:45:52 -08:00
daxpeddaandGitHub 8901b74030 Update curve25519-dalek to 4.0.0-pre.5 (#301)
* Update `curve25519-dalek`

* Improve documentation

* Update `voprf` to 0.5.0-pre.1
2022-12-19 14:04:52 -08:00
daxpeddaandGitHub 2dc2b0e617 Rename X25519 to Curve25519 (#302) 2022-12-19 09:03:11 -08:00
daxpeddaandGitHub 7da97be1db Fix CI (#298) 2022-12-16 14:14:53 -08:00
daxpeddaandGitHub a0cab10663 Update dependencies (#288)
* Fix Clippy

* Update dependencies
2022-12-10 21:30:05 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
765a15d58f Bump actions/cache from 2 to 3 (#292)
Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 21:28:33 -08:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
86915604ee Bump actions/checkout from 2 to 3 (#291)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-10 21:28:24 -08:00
daxpeddaandGitHub cdb5222235 Add Dependabot (#287)
* Fix Clippy

* Add Dependabot
2022-12-10 14:23:53 -08:00
daxpeddaandGitHub d2bdcb391e Fix Clippy (#289) 2022-12-10 14:21:56 -08:00
Kevin LewiandGitHub 556f6c2bd4 Version bump for 2.0.0 (#284) 2022-09-21 21:18:20 -07:00
Kevin LewiandGitHub d2a2139348 Publishing v2.0.0 (#283) 2022-09-21 20:31:31 -07:00
94fd3598d0 Argon2 Example (#282)
* add argon2 example to simple_login.rs

* rename Default to DefaultCipherSuite in examples to avoid conflicts with core::default::Default

* put the custom KSF example in the documentation

* add links in documentation

Co-authored-by: daxpedda <[email protected]>

* remove no_run on argon2 doctest

* hide argon2 cfg attribute in docs

Co-authored-by: daxpedda <[email protected]>

Co-authored-by: daxpedda <[email protected]>
2022-07-24 20:26:48 -07:00
Kevin LewiandGitHub 9ce5d8d191 Fixing voprf dependency pinning (#280) 2022-07-11 19:17:29 -04:00
Kevin LewiandGitHub 22eded4ed8 Updating voprf dependency and test vectors (#278) 2022-07-05 05:21:50 -04:00
e08ee1925c Document responsibility for server-side id. #276 (#277)
* Document responsibility for server-side id. #276

This is a passive warning to the server implementor
to not blindly trust the client to send the same
identifier in both [RegistrationRequest] and [RegistrationUpload].

* Perform suggested copy-editing

* Update src/lib.rs

Add wording "typically supplied by the client"

Co-authored-by: daxpedda <[email protected]>

Co-authored-by: Jonah Beckford <[email protected]>
Co-authored-by: daxpedda <[email protected]>
2022-06-24 16:31:11 -07:00
FalkoandGitHub 5a0aef607c Change argon2 salt length to recommended value (16 bytes) (#275)
This commit changes the `Ksf` implementation for Argon2
and sets the salt length to 16 bytes, the value
recommended in section 3.1 of the Argon2 specification.
2022-06-12 20:19:26 -07:00
Kevin LewiandGitHub 6e90913132 Adding documentation on RegistrationUpload and removing patch voprf in Cargo.toml (#273) 2022-04-22 01:51:16 -07:00
Kevin LewiandGitHub f83773dec9 Fixing taplo bug (#272) 2022-04-17 22:53:41 -07:00
Kevin LewiandGitHub 3fe6bbb80f Updating to version 08 (#271) 2022-04-17 16:23:31 -07:00
daxpeddaandGitHub 384207acbb General Improvements (#268)
* Move `elliptic-curve` implementation to points to allow `Zeroize`

* Simplify `Ristretto255::random_scalar` implementation

* Fix `Ristretto255` deserialization

* Remove unnecessary check in `Ristretto255::random_scalar`

* Base `X25519` implementation on `curve25519-dalek`

* Constrain public and secret key to `Copy`

* Replace manual `ZeroizeOnDrop` implementation with `derive`

* Update dependencies

* Add `warn(unused_crate_dependencies)`

* Sync crate feature naming with `voprf`

* Remove unnecessary dependency crate features

* Never produce a zero scalar

* Rename `OprfGroup` to `OprfCs`

* Rename `TripleDH` to `TripleDh`

* Remove `slow-hash` crate feature

* Rename `NoOpHash` to `Identity`

* Rename `SlowHash` to `Ksf`

* Move `KeyExchange` type definitions down

* Deserialize secret and public keys from slices

* Remove `PrivateKey::from_bytes`

* Rename `From/ToBytes` to `De/Serialize`

* Re-export `serde_` as `serde`

* Custom `De/Serialize` implementation for keys

* Remove custom `De/Serialize` implementation

* Run Taplo v0.6
2022-04-01 16:10:00 -07:00
Kevin LewiandGitHub f952a26e29 Publishing v2.0.0-pre.1 (#270) 2022-03-15 19:51:40 -07:00
daxpeddaandGitHub b2f10858e0 Group revamp (#261)
* Revamp `KeGroup` trait

* Update dependencies

* Fix `hash_to_scalar` using `OprfGroup` instead of `KeGroup`

* Relax constraints on associated types of `KeGroup`

* Improve `KeGroup` implementation on `Curve`

* Improve `KeyExchange` trait

* Fix new Clippy 1.59 warnings
2022-02-24 22:13:22 -08:00
daxpeddaandGitHub 47a26a19c5 Format (#257)
* Add Rustfmt

* Add Taplo
2022-01-05 21:19:02 -08:00
daxpeddaandGitHub 36f0a55518 Voprf update (#255)
* Update to latest voprf

* Upgrade to Rust edition 2021

* Fix Clippy

* Remove all allocations

* Remove unnecessary `allow(type_alias_bounds)`

* Make all serialization infallible

* Remove self-dependency

* Update rustyline
2022-01-05 15:10:57 -08:00
daxpeddaandGitHub 82e4436d39 General improvements (#250)
* Remove unnecessary constraints on hash

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

* Fix de-serialization issue on `Ke1State`

* Fix rustfmt

* Remove allocations in `envelope`

* Run Clippy for tests and rustdoc lints too

* Fix `Debug` implementation

* Fix missing constraints on `ClientRegistration`

* Fix de-serialization

* Pin temporary dependency

* Update dependencies

* Replace macro with derive-where

* Remove unnecessary installation of Rust components

* Improve macro naming

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

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

* Remove unnecessary allocations

* Fix MSRV

* Fix no_std

* Remove unnecessary allocations

* Remove unnecessary allocations

* Not importing items from voprf helps readability

* Fix rustdoc

* Remove unnecessary allocations

* Remove unnecessary allocations

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

* Remove unnecessary allocations

* Remove unnecessary allocations

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

* Fix documentation

* Remove all remaining allocations from `KeyExchange`

* Improve type-safety

* Remove all remaining allocations in `keypair`

* Remove last remaining allocations except `NonVerifiableClient` input

* Remove base64 encoding in Serde implementation

* Remove unnecessary Serde `alloc` feature

* Make curve25519-dalek optional

* Rename `serialize` crate feature to `serde`

* Switch `KeGroup` implementations to higher-level libraries

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

* Fix typo
2022-01-03 15:50:40 -08:00
Kevin LewiandGitHub d59d0b775b Adding audit report reference to README + licenses (#253) 2021-12-13 15:29:06 -08:00
Kevin LewiandGitHub adbd50f523 Updating to dual-license (#252) 2021-12-03 14:38:11 -08:00
Kevin LewiandGitHub 29b2ebef1b Using voprf as a dependency (#248)
* Using voprf as a dependency

* Adding back x25519 and KeGroup

* Addressing comments
2021-10-25 02:54:32 -07:00
Kevin LewiandGitHub f1f4184400 Add better parsing for RFC test vectors (#247) 2021-10-09 18:17:07 -07:00
77fbdb29f8 Constant time cmov (#241)
* Constant time `cmov`

* Adjust byte size

* Move from `to_signed_bytes_le` to `to_bytes_le`

* Add experimental p256 warning

* Remove redundant sign handling in `cmov`

* Remove unnecessary `Sub` implementation

* Improve note on missing constant-time implementations

* Move `inv0()` to `FieldElement`

* Fix Rust 1.51 compilation

* Remove unnecessary reference

Co-authored-by: Kevin Lewi <[email protected]>
2021-10-04 13:50:03 -07:00
daxpeddaandGitHub 328b07f217 P256 improvements (#244)
* Fix p256 `to_bytes`

* Remove unnecessary calls to `mod_floor`

* Port some improvements from VOPRF
2021-10-01 11:11:56 -07:00
Kevin LewiandGitHub 65a0c2f98d General cleanups and reorganizing code (#236) 2021-09-25 16:36:00 -07:00
daxpeddaandGitHub 11a93fe63e Fix is_square and is_zero (#240) 2021-09-25 12:13:06 -07:00
Aidan GilmoreandGitHub 8df79cb2b4 Remove Copy trait implementation from InnerEnvelopeMode (#237)
The Copy trait isn't allowed on types with destructors
2021-09-24 20:04:26 -07:00
d1dfee9a65 Add SlowHash configuration (#234)
* Add `SlowHash` configuration

* Use a reference

* Changing generic argument to be CipherSuite instead of SlowHash

Co-authored-by: Kevin Lewi <[email protected]>
2021-09-02 02:28:21 -07:00
Kevin LewiandGitHub 390db51910 Updating test vectors based on latest spec changes (#233) 2021-08-26 03:07:59 -07:00
Kevin LewiandGitHub aee4b5d50e Simplifying error handling (#232) 2021-08-22 12:28:19 -07:00
Kevin LewiandGitHub a99a934ead Changes related to renaming master branch to main (#231) 2021-08-18 13:57:24 -07:00
daxpeddaandGitHub 6c7840514d Fix no_std support (#229) 2021-08-16 20:11:53 -07:00
8a7bcf9097 no_std support (#225)
* No std implementation

* Run tests with std

* Adding wasm32-unknown-unknown target

Co-authored-by: Kevin Lewi <[email protected]>
2021-08-11 21:25:07 -07:00
daxpeddaandGitHub 88673d8e05 Separate AKE from OPRF take 2 (#222)
* Separate AKE from OPRF

Introduce X25519 implementation

* Rename `AkeGroup` to `KeGroup` and `Group` to `OprfGroup`

* Add documentation to "Overview"
2021-08-04 12:24:46 -07:00
daxpeddaandGitHub 10a38bc58b Merge GroupWithMapToCurve into Group (#219)
* Merge `GroupWithMapToCurve` into `Group`

* Remove `hash_to_curve`

* Ristretto improvements

* P-256 improvements
2021-08-01 17:48:56 -07:00
daxpeddaandGitHub a169d8a1c5 Fix small numbers (#221) 2021-07-31 16:40:59 -07:00
e491c1b533 P256 implementation (#213)
* Initial P256 implementation

* Add P256 VOPRF test vectors

* Fix implementation

* Forgot `Cargo.toml` and CI

* Fix MSRV

* Fix MSRV tests

* Remove accidental leftovers

* Document constants

* Use included constant time checks

* Add P-256 VOPRF test vector checks

* Remove unnecessary bound on `CipherSuite::Group`

* P-256 improvement and fixes

* Add OPAQUE test vectors for P-256

* Remove fixed key size for 3DH

* Hide P-256 test vectors behind feature flag

* Split `Group` into modules

* Fix `hash_to_scalar`

* Point to source

* Move constants to `once_cell`

Co-authored-by: Kevin Lewi <[email protected]>
2021-07-30 16:02:02 -07:00
2373ca8680 Increase MSRV to 1.51 (#217)
* Increase MSRV to 1.51

* Adding MSRV note

Co-authored-by: Kevin Lewi <[email protected]>
2021-07-30 15:36:57 -07:00
dAxpeDDaandKevin Lewi 42e06e0aff Fix MSRV 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi db4e07811e Implement oprf_key test 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi a5b9330b63 Improve types and documentation 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi 3f6b6d4afe Implement client_mac_key test 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi ec528eb4d9 Implement server_mac_key test 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi 4298deadff Implement handshake_secret test 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi 49dc5b9050 Implement auth_key test 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi dcaedc278c Fix randomized_pwd 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi 460e2aef1c Fix rustfmt 2021-07-30 14:45:27 -07:00
dAxpeDDaandKevin Lewi b90163a007 Add randomized_pwd tests 2021-07-30 14:45:27 -07:00
Kevin LewiandKevin Lewi 35886152ca Adding tilde to zeroize dep and minor doc adjustments 2021-07-23 15:23:10 -07:00
dAxpeDDaandKevin Lewi 1ec96e942b Remove unneeded derives 2021-07-23 15:23:10 -07:00
dAxpeDDaandKevin Lewi 68d0dd2988 Run rustfmt 2021-07-23 15:23:10 -07:00
dAxpeDDaandKevin Lewi b27b29e652 Add documentation for remote key 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi 7b1328b9b7 Add missing documentation 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi dedc814416 Add test 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi 77461b640d Add custom ServerSetup construction 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi 4ce61acc6e Implement custom error type 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi 17b9c49473 Remove breaking change 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi 3d01a605df Move PrivateKey from CipherSuite to ServerSetup 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi 124d64c9ca Fix 1.41 compilation 2021-07-23 15:23:10 -07:00
daxpeddaandKevin Lewi d61e2d0b5c Introduce PrivateKey associated type to CipherSuite 2021-07-23 15:23:10 -07:00
Kevin LewiandKevin Lewi 990acf67e3 Updating changelog for v0.5.1 2021-07-16 00:46:49 -07:00
Kevin LewiandKevin Lewi b4882f8810 Adding reflected value check on client side 2021-07-14 18:10:35 -07:00
Kevin LewiandKevin Lewi 8de2f37235 Updating VOPRF dependency 2021-07-12 15:16:08 -07:00
Kevin LewiandKevin Lewi e6b5a5dcf6 Adding i2osp error checking condition 2021-07-08 19:38:30 -07:00
Kevin LewiandKevin Lewi 6062bdb5cd Switch CI to using stable instead of nightly toolchain 2021-07-08 19:30:15 -07:00
daxpeddaandKevin Lewi e5db303dd1 Phantom removed 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi 2955bcf5ac Replace Vec in Key with GenericArray 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi 93d92133f9 Rustfmt 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi b4e59cc60e Change Group::hash_to_curve to return Result 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi c0e1181cac Rustfmt 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi 2d9b1d550e Remove unnecessary clones 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi ff54d32fdd Expose expand_message_xmd 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi 52d68d6f77 Constraint Scalar with Copy 2021-07-08 18:55:53 -07:00
daxpeddaandKevin Lewi dd97a81641 Generic PublicKey and PrivateKey 2021-07-08 18:55:53 -07:00
Kevin LewiandKevin Lewi 349329cdeb Releasing v0.6.0 2021-06-30 12:20:57 -07:00
Kevin LewiandKevin Lewi c8c57785af Fixing minor nits: conversion to u16 and removing keypair constructor 2021-06-28 19:19:11 -07:00
Kevin LewiandKevin Lewi 809337f458 One-liner fix for deserialization test for CredentialResponse 2021-06-28 18:16:30 -07:00
Kevin LewiandKevin Lewi e86fbac0b7 Add copyright header and update server consistency documentation 2021-06-28 13:16:16 -07:00
daxpeddaandKevin Lewi 81b2719587 More missing common traits 2021-06-28 02:06:20 -07:00
daxpeddaandKevin Lewi f05fcf0278 Expose public key in ClientRegistrationFinishResult 2021-06-28 02:06:10 -07:00
daxpeddaandKevin Lewi eb59676a94 Implement common traits 2021-06-22 20:46:34 -07:00
daxpeddaandKevin Lewi 48590056ca Remove Cargo.lock 2021-06-22 14:35:14 -07:00
daxpeddaandKevin Lewi ed086c9528 Update dependencies 2021-06-22 14:35:14 -07:00
daxpeddaandKevin Lewi ca50d92f96 Remove scrypt 2021-06-21 12:26:22 -07:00
daxpeddaandKevin Lewi 535b9b8ee4 Argon2 implementation 2021-06-21 12:26:22 -07:00
Kevin LewiandKevin Lewi 1572ff0104 Adding support for "internal mode" and fake credential response + test vectors (#155)
* Adding support for internal and external mode
2021-06-21 01:29:39 -07:00
Kevin LewiandKevin Lewi f0c13945d1 Adding client enumeration mitigations (#153) 2021-06-21 01:29:39 -07:00
Kevin LewiandKevin Lewi 98f1821897 Adding identity element checks and ensuring non-zero scalar selection 2021-06-15 18:39:33 -07:00
Valentin TolmerandKevin Lewi 210e0e99df Enforce public vs private keys via types 2021-06-15 14:47:35 -07:00
Valentin TolmerandKevin Lewi cd85efc603 Fix some clippy lint warnings 2021-06-15 12:59:36 -07:00
Valentin TolmerandKevin Lewi 2c7fe4e382 Implement Clone for every message type 2021-06-15 10:06:32 -07:00
Kevin LewiandKevin Lewi 30e27a11e2 Ensure that all public keys are being checked when deserialized 2021-06-14 22:39:13 -07:00
Kevin LewiandKevin Lewi 0935bea8ff Adding documentation of slow-hash + other features 2021-06-13 15:45:53 -07:00
Kevin LewiandKevin Lewi 51b14f34e0 Fixing some clippy errors and CI 2021-06-12 23:24:27 -07:00
Marcelin DuprazandKevin Lewi 055e76a115 Implement serde serialization and deserialization to follow Rust's standards. 2021-06-12 23:24:27 -07:00
Kevin LewiandKevin Lewi 940d1dcdb2 Ensuring mac operations are constant-time 2021-06-04 17:07:10 -07:00
Kevin LewiandKevin Lewi 8bc5e7dc02 Add zeroize on drop for remaining intermediate API states and tests 2021-06-04 16:37:54 -07:00
TonyandKevin Lewi 468e0690d7 Zeroize keys on drop (#156) 2021-06-04 16:37:54 -07:00
Kevin LewiandGitHub b15c89f997 Adding reference to wasm package and link to examples in README (#160) 2021-05-10 21:40:53 -07:00
Kevin LewiandGitHub acaf778ee7 Fixing year typo in CHANGELOG.md (#151) 2021-03-02 00:39:53 -08:00
67 changed files with 18620 additions and 6531 deletions
+3
View File
@@ -0,0 +1,3 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
+156
View File
@@ -0,0 +1,156 @@
name: Rust CI
on:
push:
branches:
- master
pull_request:
types: [ opened, reopened, synchronize ]
concurrency:
group: ci-${{ gitea.ref }}
cancel-in-progress: true
jobs:
fmt:
name: cargo fmt
runs-on: linux_amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- name: Run cargo fmt
run: cargo fmt --all -- --check
clippy:
name: cargo clippy
runs-on: linux_amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: cargo-registry-
- name: Run cargo clippy
run: cargo clippy --all-targets --features argon2,std,curve25519,ecdsa,ed25519,kem -- -D warnings
- name: Run cargo doc
run: cargo doc --no-deps --document-private-items --features argon2,std,curve25519,ecdsa,ed25519,kem
env:
RUSTDOCFLAGS: -D warnings
test:
name: test (${{ matrix.toolchain }} / ${{ matrix.backend_feature || 'no backend' }} / ${{ matrix.frontend_feature || 'no frontend' }})
runs-on: linux_amd64
strategy:
fail-fast: false
matrix:
backend_feature:
- ""
- --features ristretto255
- --features ristretto255,kem
- --features curve25519
- --features ecdsa
- --features ed25519
- --features ristretto255,curve25519,ecdsa,ed25519
frontend_feature:
- ""
- --features argon2
- --features serde
toolchain:
- stable
- "1.90.0"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@${{ matrix.toolchain }}
- name: Cache cargo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: cargo-registry-
- name: Run cargo test
run: cargo test --no-default-features ${{ matrix.backend_feature }} ${{ matrix.frontend_feature }}
- name: Run cargo test with std
run: cargo test --no-default-features --features std ${{ matrix.backend_feature }} ${{ matrix.frontend_feature }}
simple-login-test:
name: test simple_login example
runs-on: linux_amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
- name: Run expect
run: expect -f scripts/simple_login.exp
digital-locker-test:
name: test digital_locker example
runs-on: linux_amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
- name: Run expect
run: expect -f scripts/digital_locker.exp
build-no-std:
name: no-std (${{ matrix.target }} / ${{ matrix.backend_feature || 'no backend' }})
runs-on: linux_amd64
strategy:
fail-fast: false
matrix:
target:
- wasm32-unknown-unknown
- thumbv6m-none-eabi
backend_feature:
- ""
- ristretto255
- curve25519
- ecdsa
- ed25519
- ristretto255,curve25519,ecdsa,ed25519
frontend_feature:
- argon2
- serde
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build no-std
run: cargo build --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.frontend_feature }},${{ matrix.backend_feature }}
benches:
name: cargo bench compilation
runs-on: linux_amd64
strategy:
fail-fast: false
matrix:
backend_feature:
- --features ristretto255
- --features ristretto255,kem
- ""
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
- name: Run cargo bench --no-run
run: cargo bench --no-default-features ${{ matrix.backend_feature }} --no-run
audit:
name: cargo audit
runs-on: linux_amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run cargo audit
run: cargo audit -D warnings
+26
View File
@@ -0,0 +1,26 @@
name: Publish
on:
release:
types: [ published ]
jobs:
publish:
runs-on: linux_amd64
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: dtolnay/rust-toolchain@stable
- name: Login to crates.io
run: cargo login $CRATES_IO_TOKEN
env:
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
- name: Dry run publish
run: cargo publish --dry-run --manifest-path Cargo.toml
- name: Publish
run: cargo publish --manifest-path Cargo.toml
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
-192
View File
@@ -1,192 +0,0 @@
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 }}
simple-login-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- nightly
- 1.41.0
name: test simple_login command-line example
steps:
- name: install expect
run: sudo apt-get install expect
- name: Checkout sources
uses: actions/checkout@v2
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/simple_login.exp
digital-locker-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- nightly
- 1.41.0
name: test digital_locker command-line example
steps:
- name: install expect
run: sudo apt-get install expect
- name: Checkout sources
uses: actions/checkout@v2
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/digital_locker.exp
benches:
name: cargo bench compilation
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo bench --no-run
uses: actions-rs/cargo@v1
with:
command: bench
args: --features "bench" --no-run
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
format:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
deny-check:
name: cargo-deny check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: EmbarkStudios/cargo-deny-action@v1
-29
View File
@@ -1,29 +0,0 @@
name: Publish
on:
release:
types: [published] # Only publish to crates.io when we formally publish a release
# For more on how to formally release on Github, read https://help.github.com/en/articles/creating-releases
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
- name: Login to crates.io
run: cargo login $CRATES_IO_TOKEN
env:
CRATES_IO_TOKEN: ${{ secrets.crates_io_token }} # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets
- name: Dry run publish opaque-ke
run: cargo publish --dry-run --manifest-path Cargo.toml
- name: Publish opaque-ke
run: cargo publish --manifest-path Cargo.toml
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
+1
View File
@@ -2,4 +2,5 @@
.vscode/
src/.DS_Store
/target
Cargo.lock
**/*.rs.bk
+177 -4
View File
@@ -1,10 +1,183 @@
# Changelog
## 0.5.0 (March 1, 2020)
## 1.0.0-pre.1 (July 1, 2026)
* Fixed README package name
* Implement `zeroize` feature on `argon2`, `ed25519-dalek`, `hmac`, `chacha20poly1305` and `sha2`
* Removed `rand_core` dependency to use it through `rand` re-export
## 1.0.0-pre.0 (June 29, 2026)
Forked from [facebook/opaque-ke](https://github.com/facebook/opaque-ke/) at `4.1.0-pre.2`.
* Upgraded dependencies:
* `ml-kem`: `0.3.0-rc.0` to `0.3`
* `digest`: `0.10` to `0.11`
* `elliptic-curve`: `0.13` to `0.14`
* `curve25519-dalek`: `4` to `5.0.0-rc`
* `ed25519-dalek`: `2` to `3.0.0-rc`
* `ecdsa`: `0.16` to `0.17.0-rc.23`
* `hkdf`: `0.12` to `0.13`
* `hmac`: `0.12` to `0.13`
* `rand`: `0.8` to `0.10`
* `rand_chacha`: `0.3` to `0.10`
* `rfc6979`: `0.4` to `0.6` (now internal to `ecdsa`)
* `sha2`: `0.10` to `0.11`
* `getrandom`: `0.2` to `0.4` (WASM target)
* `p256`/`p384`/`p521`: `0.13` to `0.14.0-rc.15` (dev-dependency)
* `cryptoki`: `0.9` to `0.12` (dev-dependency)
* `rustyline`: `17` to `18` (dev-dependency)
* `scrypt`: `0.11` to `0.12` (dev-dependency)
* `voprf` replaced by `voprf-vx 1.0.0-pre.0`
* Bump `generic-array 0.14` to `generic-array 1.4` with `hybrid-array 0.4` interop
* Added `hybrid-array 0.4` for interop
* Added `ConcatExt` trait to disambiguate from `[T]::concat`
* Added **`cryptography`** to `categories` in `Cargo.toml`
* Replaced `Hmac` with `SimpleHmac` throughout for `digest 0.11` compatibility
* Replaced `bincode` with `postcard` for `no_std` serialization
* Replaced license appendix in files while keeping original copyright
* Re-exported `hybrid_array` from crate root
* Updated `Hash` trait to remove `BlockSizeUser` bounds incompatible with `digest 0.11`
* Updated `GroupEncoding Repr` bound to `hybrid_array::Array`
* Fixed `MaskedResponse::serialize` field ordering to match deserialization
* Increased **MSRV** to **1.90**
* Renamed crate to `opaque-vx`
* Removed direct `rfc6979` dependency (handled by `ecdsa` internally)
* Removed unstable `rustfmt` configurations for **Rust stable** compatibility
* Removed Facebook-specific contributions (CLA, bounty program) from `CONTRIBUTING.md`
* Removed `v3` to `v4` migration test (no longer relevant for fork)
## 4.1.0-pre.2 (March 26, 2026)
* Upgraded ml-kem from 0.2 to 0.3.0-rc.0
* Increased MSRV to 1.87
## 4.1.0-pre.1 (November 17, 2025)
* Added ml-kem re-export behind the kem feature
## 4.1.0-pre.0 (November 11, 2025)
* Fixed dependency exporting for the rand crate
* Added TripleDhKem key exchange protocol
## 4.0.1 (October 30, 2025)
* Fixing docs building issue
## 4.0.0 (October 23, 2025)
* Increased MSRV to 1.83
* Synced implementation with RFC 9807 (no core protocol changes)
* Added a SIGMA-I key exchange implementation
* Removed KeGroup type from the Ciphersuite trait (now part of KeyExchange type)
* **Breaking: existing Ciphersuite trait definitions need to be updated**
* Ensured that dummy record is always created to avoid timing attack issues
* Modified the dummy registration file to only contain the public key
instead of the keypair
* **Breaking: existing `ServerSetup`s need to be updated**
```rust
// Given `old` is a `ServerSetup` from `opaque-ke` v3.
let old_serialized = old.serialize();
type OldSeedLen = <<<OldCipherSuite as opaque_ke_3::CipherSuite>::OprfCs as voprf::CipherSuite>::Hash as OutputSizeUser>::OutputSize;
type OldSkLen = <<OldCipherSuite as opaque_ke_3::CipherSuite>::KeGroup as opaque_ke_3::key_exchange::group::KeGroup>::SkLen;
let (old_serialied_rest, old_fake_keypair_serialized): (
GenericArray<u8, Sum<OldSeedLen, OldSkLen>>,
_,
) = old_serialized.split();
let old_fake_keypair =
KeyPair::<<OldCipherSuite as opaque_ke_3::CipherSuite>::KeGroup>::from_private_key_slice(
&old_fake_keypair_serialized,
)
.unwrap();
let old_fake_pk_serialized = old_fake_keypair.public().serialize();
let new_serialized = old_serialied_rest.concat(old_fake_pk_serialized);
// Given `NewCipherSuite` is a `CipherSuite` implementation equivalent to `OldCipherSuite`.
ServerSetup::<NewCipherSuite>::deserialize(&new_serialized).unwrap()
```
* Added remote OPRF seed support
* Replace remote private key trait with a state machine, facilitating async support.
* Serde de/serialization formats have been simplified
* **Breaking: existing `ServerRegistration`s may need to be updated**
```rust
// Given `old` is a `ServerRegistration` from `opaque-ke` v3.
let old_serialized = old.serialize();
// Given `NewCipherSuite` is a `CipherSuite` implementation equivalent to the old cipher suite.
ServerRegistration::<NewCipherSuite>::deserialize(&old_serialized).unwrap()
```
## 3.0.0 (October 10, 2024)
* Synced implementation with draft-irtf-cfrg-opaque-16
* **Breaking: protocol context string changed from `RFCXXXX` to `OPAQUEv1-`**
* Dropped unmaintained json crate in favor of serde_json
* Updated dependencies
* Increased MSRV to 1.74
* Adjusted curve25519 support logic
* Adjusted key generation logic to be in line with commit 727b9ac of
https://github.com/cfrg/draft-irtf-cfrg-opaque
* Updated VOPRF to draft 19
* **Breaking: backwards-incompatible changes introduced in OPRF protocol**
* Added P384 testing support
* Renaming of X25519 to Curve25519
## 2.0.0 (September 21, 2022)
* Synced implementation with draft-irtf-cfrg-opaque-10
* Changed argon2 salt length to recommended value (16 bytes)
* Fixed issue from 2.0.0-pre.2 not pinning voprf dependency correctly
* Split out VOPRF implementation into its own crate
* Added support for running the API without performing
allocations
* Revamped the way the Group trait was used, so as to be more
easily extendable to other groups
* Added support for p256 as the group and x25519 as the key exchange group
* Added common traits for each public-facing struct, including serde support
## 1.2.0 (October 7, 2021)
* Added explicit support for the thumbv6m-none-eabi target (no-std)
## 1.1.0 (August 18, 2021)
* Updated dependencies and bumped MSRV to 1.51
* Added no_std support
## 1.0.0 (July 19, 2021)
* Branched from v0.5.0
* Various security improvements: non-zero scalars, zeroizing on drop,
constant-time operations, reflected value check, and adding an
i2osp error condition
## 0.6.0 (June 30, 2021)
* Synced implementation with draft-irtf-cfrg-opaque-05, which changes
the envelope structure and introduces a ServerSetup object to be
maintained by the server
* Various security improvements: non-zero scalars, zeroizing on drop,
constant-time operations
* Adding serde support behind a feature
* Supporting common traits (eb59676)
* Swapping out scrypt for argon2 (535b9b8) for the slow-hash feature
* Adding support for common traits on public structs
* Updated dependencies
## 0.5.1 (July 16, 2021)
* Various security improvements: non-zero scalars, zeroizing on drop,
constant-time operations, reflected value check, and adding an
i2osp error condition
## 0.5.0 (March 1, 2021)
* Removed dependency on generic-bytes-derive package
## 0.4.0 (February 26, 2020)
## 0.4.0 (February 26, 2021)
* Adherence to protocol format described in
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
@@ -13,13 +186,13 @@
* Conformed all message type parameters to be parameterized in the
Ciphersuite object
## 0.3.1 (February 11, 2020)
## 0.3.1 (February 11, 2021)
* Re-exporting the rand library (and including it as a dependency instead of
just rand_core)
* Exposing a convenience function for converting from byte array to Key type
## 0.3.0 (February 8, 2020)
## 0.3.0 (February 8, 2021)
* General API and documentation improvements, including the support of custom
identifiers, optional result parameters, and the use of the export key
-76
View File
@@ -1,76 +0,0 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <opensource-conduct@fb.com>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+2 -20
View File
@@ -2,29 +2,11 @@
We want to make contributing to this project as easy and transparent as
possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to opaque-ke, you agree that your contributions will be
licensed under the LICENSE file in the root directory of this source tree.
licensed under both the LICENSE-MIT and LICENSE-APACHE files in the root
directory of this source tree.
Generated
-1250
View File
File diff suppressed because it is too large Load Diff
+118 -36
View File
@@ -1,48 +1,130 @@
[package]
name = "opaque-ke"
version = "0.5.0"
repository = "https://github.com/novifinancial/opaque-ke"
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
authors = [
"VexaHub Developers",
"Kevin Lewi <[email protected]>",
"François Garillot <[email protected]>",
]
categories = ["no-std", "cryptography"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
authors = ["Kevin Lewi <[email protected]>", "François Garillot <[email protected]>"]
license = "MIT"
edition = "2018"
edition = "2024"
exclude = ["/src/tests/"]
keywords = ["cryptography", "opaque", "passwords", "authentication", "pake"]
license = "Apache-2.0 OR MIT"
name = "opaque-vx"
readme = "README.md"
repository = "https://github.com/vexahub/opaque-vx"
rust-version = "1.90"
version = "1.0.0-pre.1"
[features]
default = ["u64_backend"]
slow-hash = ["scrypt"]
bench = []
u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"]
argon2 = ["dep:argon2"]
curve25519 = ["dep:curve25519-dalek"]
default = ["ristretto255", "serde"]
ecdsa = ["dep:ecdsa"]
ed25519 = ["dep:curve25519-dalek", "dep:ed25519-dalek"]
kem = ["dep:ml-kem"]
ristretto255 = ["dep:curve25519-dalek", "voprf/ristretto255-ciphersuite"]
serde = [
"dep:serde",
"curve25519-dalek?/serde",
"ecdsa?/serde",
"ed25519-dalek?/serde",
"elliptic-curve/serde",
"generic-array/serde",
"hybrid-array/serde",
"voprf/serde",
"zeroize/serde",
]
std = ["dep:getrandom", "rand/std"]
[dependencies]
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] }
digest = "0.9.0"
displaydoc = "0.1.7"
generic-array = "0.14.4"
generic-bytes = { version = "0.1.0" }
hkdf = "0.10.0"
hmac = "0.10.1"
rand = "0.8"
scrypt = { version = "0.5.0", optional = true }
subtle = { version = "2.3.0", default-features = false }
thiserror = "1.0.22"
zeroize = "1.1.1"
argon2 = { version = "0.6.0-rc", default-features = false, features = [
"zeroize",
"alloc",
], optional = true }
curve25519-dalek = { version = "5.0.0-rc", default-features = false, features = [
"zeroize",
], optional = true }
derive-where = { version = "1.6", features = ["zeroize-on-drop"] }
digest = { version = "0.11", features = ["zeroize"] }
displaydoc = { version = "0.2", default-features = false }
ecdsa = { version = "0.17.0-rc.23", default-features = false, features = [
"algorithm",
], optional = true }
ed25519-dalek = { version = "3.0.0-rc", default-features = false, features = [
"zeroize",
"digest",
"hazmat",
], optional = true }
elliptic-curve = { version = "0.14", features = ["sec1"] }
generic-array = { version = "1.4", features = ["hybrid-array-0_4", "zeroize"] }
hybrid-array = { version = "0.4", features = ["extra-sizes", "zeroize"] }
hkdf = "0.13"
hmac = { version = "0.13", features = ["zeroize"] }
ml-kem = { version = "0.3", default-features = false, features = [
"zeroize",
], optional = true }
rand = { version = "0.10", default-features = false }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
subtle = { version = "2.6", default-features = false }
voprf = { package = "voprf-vx", version = "1.0.0-pre.1", default-features = false, features = [
"danger",
] }
zeroize = { version = "1.9", features = ["zeroize_derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.4", features = ["wasm_js"], optional = true }
[dev-dependencies]
anyhow = "1.0.35"
base64 = "0.13.0"
chacha20poly1305 = "0.7.1"
criterion = "0.3.3"
hex = "0.4.2"
lazy_static = "1.4.0"
serde_json = "1.0.60"
sha2 = "0.9.2"
proptest = "0.10.1"
rustyline = "6.3.0"
anyhow = "1"
bincode-next = { version = "3", features = ["serde", "alloc"] }
chacha20poly1305 = { version = "0.11", features = ["zeroize"] }
criterion = "0.8"
cryptoki = "0.12"
elliptic-curve = { version = "0.14", features = ["alloc", "pkcs8"] }
hex = "0.4"
p256 = { version = "0.14.0-rc.15", default-features = false, features = [
"ecdsa",
"hash2curve",
"pkcs8",
"oprf",
] }
p384 = { version = "0.14.0-rc.15", default-features = false, features = [
"hash2curve",
"pkcs8",
"oprf",
] }
p521 = { version = "0.14.0-rc.15", default-features = false, features = [
"hash2curve",
"pkcs8",
"oprf",
] }
pastey = "0.2"
proptest = "1"
rand = "0.10"
rand_chacha = "0.10"
regex = "1"
sha2 = { version = "0.11", default-features = false, features = ["zeroize"] }
thiserror = "2"
# MSRV
rustyline = "18"
scrypt = "0.12"
serde_json = "1"
[[bench]]
name = "oprf"
harness = false
required-features = ["bench"]
name = "opaque"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
targets = []
[[example]]
name = "simple_login"
required-features = ["argon2"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test_hsm)'] }
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+50 -15
View File
@@ -1,20 +1,28 @@
## The OPAQUE key exchange protocol ![Build Status](https://github.com/novifinancial/opaque-ke/workflows/Rust%20CI/badge.svg)
## The OPAQUE key exchange protocol
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an asymmetric password-authenticated key exchange protocol. It allows a client to authenticate to a server using a password, without ever having to expose the plaintext password to the server.
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an augmented password-authenticated key exchange protocol. It allows a
client to authenticate to a server using a password, without ever having to expose the plaintext password to the server.
This implementation is based on the [Internet Draft for OPAQUE](https://github.com/cfrg/draft-irtf-cfrg-opaque).
This implementation is based on [RFC 9807](https://datatracker.ietf.org/doc/rfc9807/).
This is a fork of [facebook/opaque-ke](https://github.com/facebook/opaque-ke) maintained
by [VexaHub](https://github.com/vexahub), targeting the latest **RustCrypto ecosystem**.
Background
----------
Asymmetric Password Authenticated Key Exchange (aPAKE) protocols are designed to provide password authentication and mutually authenticated key exchange without relying on PKI (except during user/password registration) and without disclosing passwords to servers or other entities other than the client machine.
Augmented Password Authenticated Key Exchange (aPAKE) protocols are designed to provide password authentication and
mutually authenticated key exchange without relying on PKI (except during user/password registration) and without
disclosing passwords to servers or other entities other than the client machine.
OPAQUE is a PKI-free aPAKE that is secure against pre-computation attacks and capable of using a secret salt.
Documentation
-------------
The API can be found [here](https://docs.rs/opaque-ke/) along with an example for usage.
The API can be found [here](https://docs.rs/opaque-ke-vx/) along with an example for usage. More examples can be found
in
the [examples](./examples) directory.
Installation
------------
@@ -22,30 +30,57 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
opaque-ke = "0.5.0"
opaque-ke = { package = "opaque-vx", version = "1.0.0-pre.0" }
```
### Minimum Supported Rust Version
Rust **1.90** or higher.
Audit
-----
This library was audited by NCC Group in June of 2021. The audit was sponsored by WhatsApp for its use
in [enabling end-to-end encrypted backups](https://engineering.fb.com/2021/09/10/security/whatsapp-e2ee-backups/).
The audit found issues in release `v0.5.0`, and the fixes were subsequently incorporated into release `v1.2.0`. See
the [full audit report here](https://research.nccgroup.com/2021/12/13/public-report-whatsapp-opaque-ke-cryptographic-implementation-review/).
Resources
---------
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
- [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html), containing a detailed (byte-level) specification for OPAQUE
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of
security
- [RFC 9807](https://datatracker.ietf.org/doc/rfc9807/), containing a detailed (byte-level) specification for OPAQUE
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory
blog post written by Matthew Green that covers OPAQUE
- [@serenity-kit/opaque](https://github.com/serenity-kit/opaque), a WebAssembly package for this library
- [opaque-wasm](https://github.com/marucjmar/opaque-wasm), a WebAssembly package for this library. A comparison between
`@serenity-kit/opaque` and `opaque-wasm` can be
found [here](https://opaque-documentation.netlify.app/docs/faq#how-does-it-compare-to-opaque-wasm)
- [react-native-opaque](https://github.com/serenity-kit/react-native-opaque), a React Native package for this library
matching the API of `@serenity-kit/opaque`
Contributors
------------
The authors of this code are Kevin Lewi
([@kevinlewi](https://github.com/kevinlewi)) and François Garillot ([@huitseeker](https://github.com/huitseeker)).
This fork is maintained by [VexaHub](https://github.com/vexahub).
The original authors are Kevin Lewi ([@kevinlewi](https://github.com/kevinlewi)) and François
Garillot ([@huitseeker](https://github.com/huitseeker)).
To learn more about contributing to this project, [see this document](./CONTRIBUTING.md).
#### Acknowledgments
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving
this implementation.
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for
improving
this implementation. Additional credit goes to @daxpedda for adding no_std support, p256 support, and making other
general
improvements to the library.
License
-------
This project is [MIT licensed](./LICENSE).
This project is dual-licensed under either the [MIT license](./LICENSE-MIT)
or the [Apache License, Version 2.0](./LICENSE-APACHE).
You may select, at your option, one of the above-listed licenses.
+334
View File
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
#[macro_use]
extern crate criterion;
use criterion::Criterion;
use opaque_vx::*;
use rand::rand_core::UnwrapErr;
use rand::rngs::SysRng;
#[cfg(feature = "ristretto255")]
static SUFFIX: &str = "ristretto255";
#[cfg(not(feature = "ristretto255"))]
static SUFFIX: &str = "p256";
struct Default;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Default {
type OprfCs = Ristretto255;
type KeyExchange = TripleDh<Ristretto255, sha2::Sha512>;
type Ksf = ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for Default {
type OprfCs = p256::NistP256;
type KeyExchange = TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = ksf::Identity;
}
fn server_setup(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
c.bench_function(&format!("server setup ({SUFFIX})"), move |b| {
b.iter(|| {
ServerSetup::<Default>::new(&mut rng);
})
});
}
fn client_registration_start(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let password = b"password";
c.bench_function(&format!("client registration start ({SUFFIX})"), move |b| {
b.iter(|| {
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
})
});
}
fn server_registration_start(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
c.bench_function(&format!("server registration start ({SUFFIX})"), move |b| {
b.iter(|| {
ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
})
});
}
fn client_registration_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
c.bench_function(
&format!("client registration finish ({SUFFIX})"),
move |b| {
b.iter(|| {
client_registration_start_result
.clone()
.state
.finish(
&mut rng,
password,
server_registration_start_result.message.clone(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
})
},
);
}
fn server_registration_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
c.bench_function(
&format!("server registration finish ({SUFFIX})"),
move |b| {
b.iter(|| {
ServerRegistration::finish(client_registration_finish_result.clone().message);
})
},
);
}
fn client_login_start(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let password = b"password";
c.bench_function(&format!("client login start ({SUFFIX})"), move |b| {
b.iter(|| {
ClientLogin::<Default>::start(&mut rng, password).unwrap();
})
});
}
fn server_login_start_real(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
c.bench_function(&format!("server login start (real) ({SUFFIX})"), move |b| {
b.iter(|| {
ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file.clone()),
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
})
});
}
fn server_login_start_fake(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
c.bench_function(&format!("server login start (fake) ({SUFFIX})"), move |b| {
b.iter(|| {
ServerLogin::start(
&mut rng,
&server_setup,
None,
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
})
});
}
fn client_login_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
let server_login_start = ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file),
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
c.bench_function(&format!("client login finish ({SUFFIX})"), move |b| {
b.iter(|| {
client_login_start_result
.clone()
.state
.finish(
&mut rng,
password,
server_login_start.clone().message,
ClientLoginFinishParameters::default(),
)
.unwrap();
})
});
}
fn server_login_finish(c: &mut Criterion) {
let mut rng = UnwrapErr(SysRng);
let username = b"username";
let password = b"password";
let server_setup = ServerSetup::<Default>::new(&mut rng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut rng, password).unwrap();
let server_registration_start_result = ServerRegistration::<Default>::start(
&server_setup,
client_registration_start_result.message.clone(),
username,
)
.unwrap();
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut rng,
password,
server_registration_start_result.message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let password_file = ServerRegistration::finish(client_registration_finish_result.message);
let client_login_start_result = ClientLogin::<Default>::start(&mut rng, password).unwrap();
let server_login_start_result = ServerLogin::start(
&mut rng,
&server_setup,
Some(password_file),
client_login_start_result.clone().message,
username,
ServerLoginParameters::default(),
)
.unwrap();
let client_login_finish_result = client_login_start_result
.state
.finish(
&mut rng,
password,
server_login_start_result.clone().message,
ClientLoginFinishParameters::default(),
)
.unwrap();
c.bench_function(&format!("server login finish ({SUFFIX})"), move |b| {
b.iter(|| {
server_login_start_result
.clone()
.state
.finish(
client_login_finish_result.clone().message,
ServerLoginParameters::default(),
)
.unwrap();
})
});
}
criterion_group!(
opaque_benches,
server_setup,
client_registration_start,
server_registration_start,
client_registration_finish,
server_registration_finish,
client_login_start,
server_login_start_real,
server_login_start_fake,
client_login_finish,
server_login_finish,
);
criterion_main!(opaque_benches);
-68
View File
@@ -1,68 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#[macro_use]
extern crate criterion;
use criterion::Criterion;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::arr;
use opaque_ke::{
group::Group,
oprf::{blind_shim, evaluate_shim, finalize_shim},
};
use rand::{prelude::ThreadRng, thread_rng};
use sha2::Sha512;
fn oprf1(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
c.bench_function("blind with Ristretto", move |b| {
b.iter(|| {
blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
})
});
}
fn oprf2(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let (_, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
c.bench_function("evaluate with Ristretto", move |b| {
b.iter(|| {
let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
})
});
}
fn oprf3(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let (token, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
c.bench_function("finalize with Ristretto", move |b| {
b.iter(|| {
let _res = finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
})
});
}
criterion_group!(oprf_benches, oprf1, oprf2, oprf3);
criterion_main!(oprf_benches);
+1
View File
@@ -0,0 +1 @@
doc-valid-idents = ["HashEdDSA", "PureEdDSA", ".."]
+30 -54
View File
@@ -9,6 +9,7 @@
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
[graph]
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
@@ -17,14 +18,15 @@
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
all-features = true
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#{ triple = "x86_64-unknown-linux-musl" },
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# This section is considered when running `cargo deny check advisories`
@@ -35,20 +37,15 @@ 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 = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
# dev-dependency
"RUSTSEC-2024-0436",
# bincode is unmaintained but only used as dev-dependency for tests
"RUSTSEC-2025-0141",
]
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
@@ -64,47 +61,26 @@ ignore = [
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# The lint level for crates which do not have a detectable license
unlicensed = "deny"
# List of explictly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
allow = [
#"MIT",
#"Apache-2.0",
#"Apache-2.0 WITH LLVM-exception",
"MIT",
"BSD-3-Clause",
"Apache-2.0",
#"Apache-2.0 WITH LLVM-exception",
]
# List of explictly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.7 short identifier (+ optional exception)].
deny = [
#"Nokia",
]
# Lint level for licenses considered copyleft
copyleft = "warn"
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
# * both - The license will be approved if it is both OSI-approved *AND* FSF
# * either - The license will be approved if it is either OSI-approved *OR* FSF
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
# * neither - This predicate is ignored and the default lint level is used
allow-osi-fsf-free = "neither"
# Lint level used when no other predicates are matched
# 1. License isn't in the allow or deny lists
# 2. License isn't copyleft
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
default = "allow"
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
confidence-threshold = 0.95
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], name = "adler32", version = "*" },
# Each entry is the crate and version constraint, and its specific allow
# list
{ allow = ["Unicode-3.0"], name = "unicode-ident", version = "*" },
]
# Some crates don't have (easily) machine readable licensing information,
@@ -123,8 +99,8 @@ exceptions = [
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
@@ -135,7 +111,7 @@ ignore = false
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
@@ -152,24 +128,24 @@ multiple-versions = "warn"
highlight = "all"
# List of crates that are allowed. Use with care!
allow = [
#{ name = "ansi_term", version = "=0.11.0" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# List of crates to deny
deny = [
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#{ name = "ansi_term", version = "=0.11.0" },
]
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#{ name = "ansi_term", version = "=0.11.0" },
#{ name = "ansi_term", version = "=0.11.0" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite
skip-tree = [
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
+108 -93
View File
@@ -1,53 +1,62 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Demonstrates an implementation of a server-side secured digital locker using
//! the client's OPAQUE export key, over a command-line interface
//!
//! A client can password-protect a secret message to be stored in a digital locker,
//! controlled by the server. The locker's contents are only revealed to the holder
//! of the password when attempting to open the locker.
//! A client can password-protect a secret message to be stored in a digital
//! locker, controlled by the server. The locker's contents are only revealed to
//! the holder of the password when attempting to open the locker.
//!
//! The client-server interactions are executed in a three-step protocol
//! within the account_registration (for password registration) and
//! account_login (for password login) functions. These steps
//! must be performed in the specific sequence outlined in each of these
//! functions.
//! The client-server interactions are executed in a three-step protocol within
//! the account_registration (for password registration) and account_login (for
//! password login) functions. These steps must be performed in the specific
//! sequence outlined in each of these functions.
//!
//! The CipherSuite trait allows the application to configure the
//! primitives used by OPAQUE, but must be kept consistent across the steps
//! of the protocol.
//! The CipherSuite trait allows the application to configure the primitives
//! used by OPAQUE, but must be kept consistent across the steps of the
//! protocol.
//!
//! In a more realistic client-server interaction, the client must send
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
//! In a more realistic client-server interaction, the client must send messages
//! over "the wire" to the server. These bytes are serialized and explicitly
//! annotated in the below functions.
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::process::exit;
use opaque_ke::{
ciphersuite::CipherSuite,
rand::{rngs::OsRng, RngCore},
ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistration,
use chacha20poly1305::aead::{Aead, KeyInit};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use opaque_vx::ciphersuite::CipherSuite;
use opaque_vx::rand::Rng;
use opaque_vx::rand::rngs::SysRng;
use opaque_vx::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration,
ServerLoginParameters, ServerRegistration, ServerSetup,
};
use rand::rand_core::UnwrapErr;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
// The ciphersuite trait allows to specify the underlying primitives
// that will be used in the OPAQUE protocol
// The ciphersuite trait allows to specify the underlying primitives that will
// be used in the OPAQUE protocol
#[allow(dead_code)]
struct Default;
impl CipherSuite for Default {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha512;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
struct DefaultCipherSuite;
#[cfg(feature = "ristretto255")]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_vx::Ristretto255;
type KeyExchange = opaque_vx::TripleDh<opaque_vx::Ristretto255, sha2::Sha512>;
type Ksf = opaque_vx::ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = p256::NistP256;
type KeyExchange = opaque_vx::TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = opaque_vx::ksf::Identity;
}
struct Locker {
@@ -57,46 +66,48 @@ struct Locker {
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
let mut rng = OsRng;
let mut rng = UnwrapErr(SysRng);
let mut nonce_bytes = [0u8; 12];
rng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let nonce = Nonce::try_from(&nonce_bytes[..]).unwrap();
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
let ciphertext = cipher.encrypt(&nonce, plaintext.as_ref()).unwrap();
[nonce_bytes.to_vec(), ciphertext].concat()
}
// Decrypt using a key and a ciphertext (nonce included) to recover the original plaintext
// Decrypt using a key and a ciphertext (nonce included) to recover the original
// plaintext
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
let cipher = ChaCha20Poly1305::new(&Key::try_from(&key[..32]).unwrap());
cipher
.decrypt(
Nonce::from_slice(&ciphertext[..12]),
&Nonce::try_from(&ciphertext[..12]).unwrap(),
ciphertext[12..].as_ref(),
)
.unwrap()
}
// Password-based registration and encryption of client secret message between a client and server
// Password-based registration and encryption of client secret message between a
// client and server
fn register_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<DefaultCipherSuite>,
locker_id: usize,
password: String,
secret_message: String,
) -> Locker {
let mut client_rng = OsRng;
let mut client_rng = UnwrapErr(SysRng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
ClientRegistration::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes())
.unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(),
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(&registration_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -107,7 +118,8 @@ fn register_locker(
.state
.finish(
&mut client_rng,
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
password.as_bytes(),
RegistrationResponse::deserialize(&registration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
@@ -121,43 +133,40 @@ fn register_locker(
// Client sends message_bytes to server
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
.unwrap();
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
);
Locker {
contents: ciphertext,
password_file: password_file.serialize(),
password_file: password_file.serialize().to_vec(),
}
}
// Open the contents of a locker with a password between a client and server
fn open_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<DefaultCipherSuite>,
locker_id: usize,
password: String,
locker: &Locker,
) -> Result<String, String> {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
&mut client_rng,
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let mut client_rng = UnwrapErr(SysRng);
let client_login_start_result =
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<Default>::deserialize(&locker.password_file[..]).unwrap();
let mut server_rng = OsRng;
ServerRegistration::<DefaultCipherSuite>::deserialize(&locker.password_file).unwrap();
let mut server_rng = UnwrapErr(SysRng);
let server_login_start_result = ServerLogin::start(
&mut server_rng,
password_file,
&server_kp.private(),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
ServerLoginStartParameters::default(),
server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
@@ -165,7 +174,9 @@ fn open_locker(
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
&mut client_rng,
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
@@ -180,14 +191,17 @@ fn open_locker(
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.finish(
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
ServerLoginParameters::default(),
)
.unwrap();
// Server sends locker contents, encrypted under the session key, to the client
let encrypted_locker_contents =
encrypt(&server_login_finish_result.session_key, &locker.contents);
// Client decrypts contents of locker, first under the session key, and then under the export key
// Client decrypts contents of locker, first under the session key, and then
let plaintext = decrypt(
&client_login_finish_result.export_key,
&decrypt(
@@ -199,10 +213,10 @@ fn open_locker(
}
fn main() {
let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut rng);
let mut rng = UnwrapErr(SysRng);
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
let mut rl = Editor::<()>::new();
let mut rl = Editor::<(), _>::new().unwrap();
let mut registered_lockers: Vec<Locker> = vec![];
loop {
display_lockers(&registered_lockers);
@@ -225,8 +239,10 @@ fn main() {
&mut rl,
None,
);
let locker_id = registered_lockers.len();
registered_lockers.push(register_locker(
&server_kp,
&server_setup,
locker_id,
password,
secret_message,
));
@@ -252,15 +268,17 @@ fn main() {
continue;
}
match open_locker(&server_kp, password, &registered_lockers[locker_index]) {
match open_locker(
&server_setup,
locker_index,
password,
&registered_lockers[locker_index],
) {
Ok(contents) => {
println!("\n\nSuccess! Contents: {}\n\n", contents);
println!("\n\nSuccess! Contents: {contents}\n\n");
}
Err(err) => {
println!(
"\n\nError encountered, could not open locker: {}\n\n",
err
);
println!("\n\nError encountered, could not open locker: {err}\n\n");
}
}
}
@@ -277,16 +295,13 @@ fn main() {
// Helper functions
fn display_lockers(lockers: &Vec<Locker>) {
fn display_lockers(lockers: &[Locker]) {
let mut locker_numbers = vec![];
for (i, _) in lockers.iter().enumerate() {
locker_numbers.push(i);
}
println!(
"\nCurrently registered locker numbers: {:?}\n",
locker_numbers
);
println!("\nCurrently registered locker numbers: {locker_numbers:?}\n");
}
// Handle readline errors
@@ -299,7 +314,7 @@ fn handle_error(err: ReadlineError) {
println!("CTRL-D");
}
err => {
println!("Error: {:?}", err);
println!("Error: {err:?}");
}
}
}
@@ -308,11 +323,11 @@ fn handle_error(err: ReadlineError) {
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<()>,
rl: &mut Editor<(), DefaultHistory>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{}: ", query));
let readline = rl.readline(&format!("{query}: "));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
+102 -80
View File
@@ -1,66 +1,79 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Demonstrates a simple client-server password-based login protocol
//! using OPAQUE, over a command-line interface
//! Demonstrates a simple client-server password-based login protocol using
//! OPAQUE, over a command-line interface
//!
//! The client-server interactions are executed in a three-step protocol
//! within the account_registration (for password registration) and
//! account_login (for password login) functions. These steps
//! must be performed in the specific sequence outlined in each of these
//! functions.
//! The client-server interactions are executed in a three-step protocol within
//! the account_registration (for password registration) and account_login (for
//! password login) functions. These steps must be performed in the specific
//! sequence outlined in each of these functions.
//!
//! The CipherSuite trait allows the application to configure the
//! primitives used by OPAQUE, but must be kept consistent across the steps
//! of the protocol.
//! The CipherSuite trait allows the application to configure the primitives
//! used by OPAQUE, but must be kept consistent across the steps of the
//! protocol.
//!
//! In a more realistic client-server interaction, the client must send
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
//! In a more realistic client-server interaction, the client must send messages
//! over "the wire" to the server. These bytes are serialized and explicitly
//! annotated in the below functions.
use rustyline::error::ReadlineError;
use opaque_vx::argon2::Argon2;
use opaque_vx::ciphersuite::CipherSuite;
use opaque_vx::hybrid_array::Array;
use opaque_vx::rand::rngs::SysRng;
use opaque_vx::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
};
use rand::rand_core::UnwrapErr;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
use std::collections::HashMap;
use std::process::exit;
use opaque_ke::{
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters,
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload, ServerLogin, ServerLoginStartParameters,
ServerRegistration,
};
// The ciphersuite trait allows to specify the underlying primitives
// that will be used in the OPAQUE protocol
// The ciphersuite trait allows to specify the underlying primitives that will
// be used in the OPAQUE protocol
#[allow(dead_code)]
struct Default;
impl CipherSuite for Default {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha512;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
struct DefaultCipherSuite;
#[cfg(feature = "ristretto255")]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_vx::Ristretto255;
type KeyExchange = opaque_vx::TripleDh<opaque_vx::Ristretto255, sha2::Sha512>;
type Ksf = Argon2<'static>;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = p256::NistP256;
type KeyExchange = opaque_vx::TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = Argon2<'static>;
}
// Password-based registration between a client and server
fn account_registration(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<DefaultCipherSuite>,
username: String,
password: String,
) -> Vec<u8> {
let mut client_rng = OsRng;
) -> Array<u8, ServerRegistrationLen<DefaultCipherSuite>> {
let mut client_rng = UnwrapErr(SysRng);
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
ClientRegistration::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes())
.unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(),
let server_registration_start_result = ServerRegistration::<DefaultCipherSuite>::start(
server_setup,
RegistrationRequest::deserialize(&registration_request_bytes).unwrap(),
username.as_bytes(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -71,7 +84,8 @@ fn account_registration(
.state
.finish(
&mut client_rng,
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
password.as_bytes(),
RegistrationResponse::deserialize(&registration_response_bytes).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
@@ -79,38 +93,36 @@ fn account_registration(
// Client sends message_bytes to server
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
.unwrap();
password_file.serialize()
let password_file = ServerRegistration::finish(
RegistrationUpload::<DefaultCipherSuite>::deserialize(&message_bytes).unwrap(),
);
password_file.serialize().into_ha0_4()
}
// Password-based login between a client and server
fn account_login(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
server_setup: &ServerSetup<DefaultCipherSuite>,
username: String,
password: String,
password_file_bytes: &[u8],
) -> bool {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
&mut client_rng,
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let mut client_rng = UnwrapErr(SysRng);
let client_login_start_result =
ClientLogin::<DefaultCipherSuite>::start(&mut client_rng, password.as_bytes()).unwrap();
let credential_request_bytes = client_login_start_result.message.serialize();
// Client sends credential_request_bytes to server
let password_file = ServerRegistration::<Default>::deserialize(password_file_bytes).unwrap();
let mut server_rng = OsRng;
let password_file =
ServerRegistration::<DefaultCipherSuite>::deserialize(password_file_bytes).unwrap();
let mut server_rng = UnwrapErr(SysRng);
let server_login_start_result = ServerLogin::start(
&mut server_rng,
password_file,
&server_kp.private(),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
ServerLoginStartParameters::default(),
server_setup,
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
username.as_bytes(),
ServerLoginParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
@@ -118,7 +130,9 @@ fn account_login(
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
&mut client_rng,
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
);
@@ -133,18 +147,22 @@ fn account_login(
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.finish(
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
ServerLoginParameters::default(),
)
.unwrap();
client_login_finish_result.session_key == server_login_finish_result.session_key
}
fn main() {
let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut rng);
let mut rng = UnwrapErr(SysRng);
let server_setup = ServerSetup::<DefaultCipherSuite>::new(&mut rng);
let mut rl = Editor::<()>::new();
let mut registered_users = HashMap::<String, Vec<u8>>::new();
let mut rl = Editor::<(), _>::new().unwrap();
let mut registered_users =
HashMap::<String, Array<u8, ServerRegistrationLen<DefaultCipherSuite>>>::new();
loop {
println!(
"\nCurrently registered usernames: {:?}\n",
@@ -164,20 +182,24 @@ fn main() {
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
match line.as_ref() {
"1" => {
registered_users
.insert(username, account_registration(&server_kp, password));
registered_users.insert(
username.clone(),
account_registration(&server_setup, username, password),
);
continue;
}
"2" => match registered_users.get(&username) {
Some(password_file_bytes) => {
if account_login(&server_kp, password, password_file_bytes) {
if account_login(&server_setup, username, password, password_file_bytes)
{
println!("\nLogin success!");
} else {
// Note that at this point, the client knows whether or not the login
// succeeded. In this example, we simply rely on client-reported result
// of login, but in a real client-server implementation, the server may not
// know the outcome of login yet, and extra care must be taken to ensure
// that the server can learn the outcome as well.
// Note that at this point, the client knows whether or not the
// login succeeded. In this example, we simply rely on
// client-reported result of login, but in a real client-server
// implementation, the server may not know the outcome of login yet,
// and extra care must be taken to ensure that the server can learn
// the outcome as well.
println!("\nIncorrect password, please try again.");
}
}
@@ -206,7 +228,7 @@ fn handle_error(err: ReadlineError) {
println!("CTRL-D");
}
err => {
println!("Error: {:?}", err);
println!("Error: {err:?}");
}
}
}
@@ -215,11 +237,11 @@ fn handle_error(err: ReadlineError) {
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<()>,
rl: &mut Editor<(), DefaultHistory>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{}: ", query));
let readline = rl.readline(&format!("{query}: "));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
+30
View File
@@ -0,0 +1,30 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"dependencyDashboard": true,
"osvVulnerabilityAlerts": true,
"rangeStrategy": "auto",
"packageRules": [
{
"matchManagers": [
"cargo"
],
"groupName": "rust deps"
},
{
"matchManagers": [
"cargo"
],
"matchUpdateTypes": [
"major"
],
"automerge": false
}
],
"lockFileMaintenance": {
"enabled": true
},
"configMigration": true
}
+1
View File
@@ -0,0 +1 @@
newline_style = "Unix"
+4 -4
View File
@@ -1,8 +1,8 @@
#!/bin/expect -f
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# SPDX-License-Identifier: MIT OR Apache-2.0
# Copyright (c) VexaHub and contributors.
# Copyright (c) Meta Platforms, Inc. and affiliates.
set timeout 1
spawn cargo run --example digital_locker
Executable → Regular
+5 -5
View File
@@ -1,11 +1,11 @@
#!/bin/expect -f
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
# SPDX-License-Identifier: MIT OR Apache-2.0
# Copyright (c) VexaHub and contributors.
# Copyright (c) Meta Platforms, Inc. and affiliates.
set timeout 1
spawn cargo run --example simple_login
spawn cargo run --example simple_login --features argon2
match_max 100000
sleep 1
expect "*
+46 -32
View File
@@ -1,41 +1,55 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE
//! Defines the [`CipherSuite`] trait to specify the underlying primitives for
//! OPAQUE
use crate::{
hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
};
use digest::Digest;
use core::ops::Add;
use rand::{CryptoRng, RngCore};
use digest::block_api::{CoreProxy, EagerHash, SmallBlockSizeUser};
use generic_array::ArrayLength;
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
use crate::envelope::NonceLen;
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::KeyExchange;
use crate::key_exchange::group::Group;
use crate::ksf::Ksf;
use crate::opaque::MaskedResponseLen;
/// Configures the underlying primitives used in OPAQUE
/// * `Group`: a finite cyclic group along with a point representation, along
/// with an extension trait PasswordToCurve that allows some customization on
/// how to hash a password to a curve point. See `group::Group` and
/// `map_to_curve::GroupWithMapToCurve`.
/// * `OprfCs`: A VOPRF ciphersuite, see [`voprf::CipherSuite`].
/// * `KeGroup`: A `Group` used for the `KeyExchange`.
/// * `KeyExchange`: The key exchange protocol to use in the login step
/// * `Hash`: The main hashing function to use
/// * `SlowHash`: A slow hashing function, typically used for password hashing
pub trait CipherSuite {
/// A finite cyclic group along with a point representation along with
/// an extension trait PasswordToCurve that allows some customization on
/// how to hash a password to a curve point. See `group::Group` and
/// `map_to_curve::GroupWithMapToCurve`.
type Group: GroupWithMapToCurve<UniformBytesLen = <Self::Hash as Digest>::OutputSize>;
/// * `Ksf`: A key stretching function, typically used for password hashing
pub trait CipherSuite
where
OprfHash<Self>: Hash + EagerHash,
<OprfHash<Self> as CoreProxy>::Core: ProxyHash,
<<OprfHash<Self> as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<OprfHash<Self> as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
// Envelope: Nonce + Hash
// MaskedResponse: (Nonce + Hash) + KePk
// TODO: migrate fully to after hybrid-array v0.5 releases
// https://github.com/RustCrypto/hybrid-array/issues/66
OutputSize<OprfHash<Self>>: Add<NonceLen> + ArrayLength,
Sum<OutputSize<OprfHash<Self>>, NonceLen>: ArrayLength + Add<<KeGroup<Self> as Group>::PkLen>,
MaskedResponseLen<Self>: ArrayLength,
// hybrid-array interop bounds
<OprfGroup<Self> as voprf::Group>::ScalarLen: ArrayLength,
<OprfGroup<Self> as voprf::Group>::ElemLen: ArrayLength,
{
/// A VOPRF ciphersuite, see [`voprf::CipherSuite`].
type OprfCs: voprf::CipherSuite;
/// A key exchange protocol
type KeyExchange: KeyExchange<Self::Hash, Self::Group>;
/// The main hash function use (for HKDF computations and hashing transcripts)
type Hash: Hash;
/// A slow hashing function, typically used for password hashing
type SlowHash: SlowHash<Self::Hash>;
/// Generating a random key pair given a cryptographic rng
fn generate_random_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> KeyPair<Self::Group> {
KeyPair::<Self::Group>::generate_random(rng)
}
type KeyExchange: KeyExchange;
/// A key stretching function, typically used for password hashing
type Ksf: Ksf;
}
pub(crate) type OprfGroup<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Group;
pub(crate) type OprfHash<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Hash;
pub(crate) type KeGroup<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Group;
pub(crate) type KeHash<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Hash;
+245 -267
View File
@@ -1,332 +1,310 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use crate::{
errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
hash::Hash,
keypair::Key,
serialization::serialize,
};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use core::convert::TryFrom;
use derive_where::derive_where;
use digest::Output;
use generic_array::GenericArray;
use generic_array::typenum::{Sum, U32};
use hkdf::SimpleHkdf as Hkdf;
use hmac::{KeyInit, Mac, SimpleHmac};
use rand::{CryptoRng, Rng};
use zeroize::Zeroize;
use crate::ciphersuite::{CipherSuite, KeGroup, OprfHash};
use crate::errors::{InternalError, ProtocolError};
use crate::hash::OutputSize;
use crate::key_exchange::SerializedIdentifiers;
use crate::key_exchange::group::Group;
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
use crate::opaque::Identifiers;
use crate::serialization::{GenericArrayExt, SliceExt, UpdateExt};
// Constant string used as salt for HKDF computation
const STR_PAD: &[u8] = b"Pad";
const STR_AUTH_KEY: &[u8] = b"AuthKey";
const STR_EXPORT_KEY: &[u8] = b"ExportKey";
const STR_AUTH_KEY: [u8; 7] = *b"AuthKey";
const STR_EXPORT_KEY: [u8; 9] = *b"ExportKey";
const STR_PRIVATE_KEY: [u8; 10] = *b"PrivateKey";
pub(crate) type NonceLen = U32;
const NONCE_LEN: usize = 32;
#[derive(Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) enum InnerEnvelopeMode {
Base = 1,
CustomIdentifier = 2,
Zero = 0,
Internal = 1,
}
impl Zeroize for InnerEnvelopeMode {
fn zeroize(&mut self) {
*self = Self::Zero
}
}
impl TryFrom<u8> for InnerEnvelopeMode {
type Error = PakeError;
type Error = ProtocolError;
fn try_from(x: u8) -> Result<Self, Self::Error> {
match x {
1 => Ok(InnerEnvelopeMode::Base),
2 => Ok(InnerEnvelopeMode::CustomIdentifier),
_ => Err(PakeError::SerializationError),
1 => Ok(InnerEnvelopeMode::Internal),
_ => Err(ProtocolError::SerializationError),
}
}
}
pub(crate) struct InnerEnvelope {
mode: InnerEnvelopeMode,
nonce: Vec<u8>,
ciphertext: Vec<u8>,
}
impl InnerEnvelope {
pub(crate) fn serialize(&self) -> Vec<u8> {
[&[self.mode as u8], &self.nonce[..], &self.ciphertext[..]].concat()
}
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
if input.is_empty() {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let mode = InnerEnvelopeMode::try_from(input[0])?;
let key_len = <Key as SizedBytes>::Len::to_usize();
let bytes = &input[1..];
if bytes.len() < NONCE_LEN + key_len {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
Ok((
Self {
mode,
nonce: bytes[..NONCE_LEN].to_vec(),
ciphertext: bytes[NONCE_LEN..NONCE_LEN + key_len].to_vec(),
},
bytes[NONCE_LEN + key_len..].to_vec(),
))
}
}
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
/// This struct is an instantiation of the envelope.
///
/// Note that earlier versions of this specification described an
/// implementation of this envelope using an encryption scheme that
/// satisfied random-key robustness
/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4).
/// The specification update has simplified this assumption by taking
/// an XOR-based approach without compromising on security, and to avoid
/// the confusion around the implementation of an RKR-secure encryption.
pub(crate) struct Envelope<D: Hash> {
inner_envelope: InnerEnvelope,
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
/// Note that earlier versions of this specification described an implementation
/// of this envelope using an encryption scheme that satisfied random-key
/// robustness.
/// The specification update has simplified this assumption by taking an
/// XOR-based approach without compromising on security, and to avoid the
/// confusion around the implementation of an RKR-secure encryption.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
pub(crate) struct Envelope<CS: CipherSuite> {
pub(crate) mode: InnerEnvelopeMode,
nonce: GenericArray<u8, NonceLen>,
hmac: Output<OprfHash<CS>>,
}
// Note that this struct represents an envelope that has been "opened" with the asssociated
// key. This key is also used to derive the export_key parameter, which is technically
// unrelated to the envelope's encrypted and authenticated contents.
pub(crate) struct OpenedEnvelope<D: Hash> {
pub(crate) client_s_sk: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
// Note that this struct represents an envelope that has been "opened" with the
// asssociated key. This key is also used to derive the export_key parameter,
// which is technically unrelated to the envelope's encrypted and authenticated
// contents.
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
pub(crate) client_static_keypair: KeyPair<KeGroup<CS>>,
pub(crate) export_key: Output<OprfHash<CS>>,
pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
}
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
pub(crate) plaintext: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
pub(crate) struct OpenedInnerEnvelope<CS: CipherSuite> {
pub(crate) export_key: Output<OprfHash<CS>>,
}
impl<D: Hash> Envelope<D> {
fn hmac_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
#[cfg(not(test))]
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>);
#[cfg(test)]
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>, Output<OprfHash<CS>>);
#[cfg(not(test))]
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<KeGroup<CS>>, Output<OprfHash<CS>>);
#[cfg(test)]
type SealResult<CS: CipherSuite> = (
Envelope<CS>,
PublicKey<KeGroup<CS>>,
Output<OprfHash<CS>>,
Output<OprfHash<CS>>,
);
fn export_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<OutputSize<OprfHash<CS>>, NonceLen>;
pub(crate) fn get_mode(&self) -> InnerEnvelopeMode {
self.inner_envelope.mode
}
/// The format of the output is:
/// mode | nonce | ciphertext | hmac
/// u8 | nonce_size bytes | variable length | hmac_size bytes
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let (result, remainder) = Self::deserialize(bytes)
.map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?;
if !remainder.is_empty() {
return Err(InternalPakeError::InvalidEnvelopeStructureError);
}
Ok(result)
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
self.serialize()
}
pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.inner_envelope.serialize(), &self.hmac[..]].concat()
}
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?;
let hmac_key_size = Self::hmac_key_size();
let hmac_and_remainder =
check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
Ok((
Self {
inner_envelope,
hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]),
},
hmac_and_remainder[hmac_key_size..].to_vec(),
))
}
pub(crate) fn seal<R: RngCore + CryptoRng>(
impl<CS: CipherSuite> Envelope<CS> {
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: Rng + CryptoRng>(
rng: &mut R,
key: &[u8],
client_s_sk: &[u8],
server_s_pk: &[u8],
optional_ids: Option<(Vec<u8>, Vec<u8>)>,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
let aad = construct_aad(server_s_pk, &optional_ids);
Self::seal_raw(rng, key, &client_s_sk, &aad, mode_from_ids(&optional_ids))
}
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
/// Note that a new nonce is sampled for each call to seal.
pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
rng: &mut R,
key: &[u8],
plaintext: &[u8],
aad: &[u8],
mode: InnerEnvelopeMode,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
let mut nonce = vec![0u8; NONCE_LEN];
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
server_s_pk: &PublicKey<KeGroup<CS>>,
ids: Identifiers,
) -> Result<SealResult<CS>, ProtocolError> {
let mut nonce = GenericArray::default();
rng.fill_bytes(&mut nonce);
let h = Hkdf::<D>::new(Some(&nonce), &key);
let mut xor_key = vec![0u8; plaintext.len()];
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
let (mode, client_s_pk) = (
InnerEnvelopeMode::Internal,
build_inner_envelope_internal::<CS>(randomized_pwd_hasher, nonce)?,
);
h.expand(STR_PAD, &mut xor_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_AUTH_KEY, &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let server_s_pk_bytes = server_s_pk.serialize();
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
ids,
client_s_pk.serialize(),
server_s_pk_bytes.clone(),
)?;
let aad = construct_aad(
identifiers.client.iter(),
identifiers.server.iter(),
&server_s_pk_bytes,
);
let ciphertext: Vec<u8> = xor_key
.iter()
.zip(plaintext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
Ok((
result.0,
client_s_pk,
result.1,
#[cfg(test)]
result.2,
))
}
let inner_envelope = InnerEnvelope {
mode,
nonce,
ciphertext,
};
/// Uses a key to convert the plaintext into an envelope, authenticated by
/// the aad field. Note that a new nonce is sampled for each call to seal.
#[allow(clippy::type_complexity)]
pub(crate) fn seal_raw<'a>(
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
nonce: GenericArray<u8, NonceLen>,
aad: impl Iterator<Item = &'a [u8]>,
mode: InnerEnvelopeMode,
) -> Result<SealRawResult<CS>, InternalError> {
let mut hmac_key = Output::<OprfHash<CS>>::default();
let mut export_key = Output::<OprfHash<CS>>::default();
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&inner_envelope.serialize());
hmac.update(&aad);
randomized_pwd_hasher
.expand_multi_info(&[&nonce, &STR_AUTH_KEY], &mut hmac_key)
.map_err(|_| InternalError::HkdfError)?;
randomized_pwd_hasher
.expand_multi_info(&[&nonce, &STR_EXPORT_KEY], &mut export_key)
.map_err(|_| InternalError::HkdfError)?;
let mut hmac = SimpleHmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
.map_err(|_| InternalError::HmacError)?;
hmac.update(&nonce);
hmac.update_iter(aad);
let hmac_bytes = hmac.finalize().into_bytes();
Ok((
Self {
inner_envelope,
mode,
nonce,
hmac: hmac_bytes,
},
GenericArray::clone_from_slice(&export_key),
export_key,
#[cfg(test)]
hmac_key,
))
}
pub(crate) fn open(
pub(crate) fn open<'a>(
&self,
key: &[u8],
server_s_pk: &[u8],
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
) -> Result<OpenedEnvelope<D>, InternalPakeError> {
// First, check that mode matches
if self.inner_envelope.mode != mode_from_ids(optional_ids) {
return Err(InternalPakeError::IncompatibleEnvelopeModeError);
}
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
server_s_pk: PublicKey<KeGroup<CS>>,
optional_ids: Identifiers<'a>,
) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
let client_static_keypair = match self.mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into());
}
InnerEnvelopeMode::Internal => {
recover_keys_internal::<CS>(randomized_pwd_hasher, self.nonce)?
}
};
let aad = construct_aad(server_s_pk, optional_ids);
let opened = self.open_raw(key, &aad)?;
let server_s_pk_bytes = server_s_pk.serialize();
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
optional_ids,
client_static_keypair.public().serialize(),
server_s_pk_bytes.clone(),
)?;
let aad = construct_aad(
identifiers.client.iter(),
identifiers.server.iter(),
&server_s_pk_bytes,
);
if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
// Plaintext should consist of a single key
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
}
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
Ok(OpenedEnvelope {
client_s_sk: opened.plaintext,
client_static_keypair,
export_key: opened.export_key,
identifiers,
})
}
/// Attempts to decrypt the envelope using a key, which is successful only if the key and
/// aad used to construct the envelope are the same.
pub(crate) fn open_raw(
/// Attempts to decrypt the envelope using a key, which is successful only
/// if the key and aad used to construct the envelope are the same.
pub(crate) fn open_raw<'a>(
&self,
key: &[u8],
aad: &[u8],
) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> {
let h = Hkdf::<D>::new(Some(&self.inner_envelope.nonce), &key);
let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
aad: impl Iterator<Item = &'a [u8]>,
) -> Result<OpenedInnerEnvelope<CS>, InternalError> {
let mut hmac_key = Output::<OprfHash<CS>>::default();
let mut export_key = Output::<OprfHash<CS>>::default();
h.expand(STR_PAD, &mut xor_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_AUTH_KEY, &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
randomized_pwd_hasher
.expand_multi_info(&[&self.nonce, &STR_AUTH_KEY], &mut hmac_key)
.map_err(|_| InternalError::HkdfError)?;
randomized_pwd_hasher
.expand_multi_info(&[&self.nonce, &STR_EXPORT_KEY], &mut export_key)
.map_err(|_| InternalError::HkdfError)?;
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.inner_envelope.serialize());
hmac.update(aad);
if hmac.verify(&self.hmac).is_err() {
return Err(InternalPakeError::SealOpenHmacError);
let mut hmac = SimpleHmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
.map_err(|_| InternalError::HmacError)?;
hmac.update(&self.nonce);
hmac.update_iter(aad);
hmac.verify(&self.hmac)
.map_err(|_| InternalError::SealOpenHmacError)?;
Ok(OpenedInnerEnvelope { export_key })
}
// Creates a dummy envelope object that serializes to the all-zeros byte string
pub(crate) fn dummy() -> Self {
Self {
mode: InnerEnvelopeMode::Zero,
nonce: GenericArray::default(),
hmac: GenericArray::default().into_ha0_4(),
}
}
let plaintext: Vec<u8> = xor_key
.iter()
.zip(self.inner_envelope.ciphertext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok(OpenedInnerEnvelope {
plaintext,
export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
&export_key,
),
#[cfg(test)]
pub(crate) fn len() -> usize {
use generic_array::typenum::Unsigned;
OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
}
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>> {
self.nonce
.concat_ext(&GenericArray::from_ha0_4(self.hmac.clone()))
}
pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
mode: InnerEnvelopeMode::Internal,
nonce: bytes.take_array("nonce")?,
hmac: bytes
.take_array::<OutputSize<OprfHash<CS>>>("hmac")?
.into_ha0_4(),
})
}
}
// Helper functions
fn construct_aad(server_s_pk: &[u8], optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> {
let ids = optional_ids
.iter()
.flat_map(|(l, r)| [serialize(l, 2), serialize(r, 2)].concat())
.collect();
[server_s_pk.to_vec(), ids].concat()
fn build_inner_envelope_internal<CS: CipherSuite>(
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
nonce: GenericArray<u8, NonceLen>,
) -> Result<PublicKey<KeGroup<CS>>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
randomized_pwd_hasher
.expand_multi_info(&[&nonce, &STR_PRIVATE_KEY], &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
Ok(client_s_sk.public_key())
}
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
match optional_ids {
Some(_) => InnerEnvelopeMode::CustomIdentifier,
None => InnerEnvelopeMode::Base,
}
fn recover_keys_internal<CS: CipherSuite>(
randomized_pwd_hasher: &Hkdf<OprfHash<CS>>,
nonce: GenericArray<u8, NonceLen>,
) -> Result<KeyPair<KeGroup<CS>>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
randomized_pwd_hasher
.expand_multi_info(&[&nonce, &STR_PRIVATE_KEY], &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
let client_s_pk = client_s_sk.public_key();
Ok(KeyPair::new(client_s_sk, client_s_pk))
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn seal_and_open() {
let mut rng = OsRng;
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
&mut rng,
&key,
&msg,
b"aad",
InnerEnvelopeMode::Base,
)
.unwrap();
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
}
fn construct_aad<'a>(
id_u: impl Iterator<Item = &'a [u8]>,
id_s: impl Iterator<Item = &'a [u8]>,
server_s_pk: &'a [u8],
) -> impl Iterator<Item = &'a [u8]> {
[server_s_pk].into_iter().chain(id_s).chain(id_u)
}
+135 -148
View File
@@ -1,18 +1,90 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! A list of error types which are produced during an execution of the protocol
use core::convert::Infallible;
use core::error::Error;
use core::fmt::Debug;
use displaydoc::Display;
use thiserror::Error;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Debug, Display, Error)]
pub enum InternalPakeError {
/// Deserializing from a byte sequence failed
InvalidByteSequence,
/// Invalid length for {name}: expected {len}, but is actually {actual_len}.
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum InternalError {
/// Size of input is empty or longer then [`u16::MAX`].
HashToScalar,
/// Computing HKDF failed while deriving subkeys
HkdfError,
/// Computing HMAC failed while supplying a secret key
HmacError,
/// Computing the key stretching function failed
KsfError,
/// Error while performing a KEM operation
KemError,
/** This error occurs when the envelope seal open hmac check fails
HMAC check in seal open failed. */
SealOpenHmacError,
/** This error occurs when attempting to open an envelope of the wrong
type (base mode, custom identifier) */
IncompatibleEnvelopeModeError,
/// Error from the OPRF evaluation
OprfError(voprf::Error),
/// Error from the OPRF evaluation
OprfInternalError(voprf::InternalError),
}
impl Debug for InternalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::HashToScalar => f.debug_tuple("HashToScalar").finish(),
Self::HkdfError => f.debug_tuple("HkdfError").finish(),
Self::HmacError => f.debug_tuple("HmacError").finish(),
Self::KsfError => f.debug_tuple("KsfError").finish(),
Self::KemError => f.debug_tuple("KemError").finish(),
Self::SealOpenHmacError => f.debug_tuple("SealOpenHmacError").finish(),
Self::IncompatibleEnvelopeModeError => {
f.debug_tuple("IncompatibleEnvelopeModeError").finish()
}
Self::OprfError(error) => f.debug_tuple("OprfError").field(error).finish(),
Self::OprfInternalError(error) => {
f.debug_tuple("OprfInternalError").field(error).finish()
}
}
}
}
impl Error for InternalError {}
impl From<voprf::Error> for InternalError {
fn from(voprf_error: voprf::Error) -> Self {
Self::OprfError(voprf_error)
}
}
impl From<voprf::Error> for ProtocolError {
fn from(voprf_error: voprf::Error) -> Self {
Self::LibraryError(InternalError::OprfError(voprf_error))
}
}
impl From<voprf::InternalError> for ProtocolError {
fn from(voprf_error: voprf::InternalError) -> Self {
Self::LibraryError(InternalError::OprfInternalError(voprf_error))
}
}
/// Represents an error in protocol handling
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ProtocolError<T = Infallible> {
/// Internal error encountered
LibraryError(InternalError),
/// Error in validating credentials
InvalidLoginError,
/// Error with serializing / deserializing protocol messages
SerializationError,
/** Invalid length for `{name}`: expected {len}, actual {actual_len} */
#[allow(clippy::doc_markdown, unused_assignments)]
SizeError {
/// name
name: &'static str,
@@ -21,159 +93,74 @@ pub enum InternalPakeError {
/// actual
actual_len: usize,
},
/// Could not decompress point.
PointError,
/// Key belongs to a small subgroup!
SubGroupError,
/// hashing to a key failed
HashingFailure,
/// Computing the hash-to-curve function failed
HashToCurveError,
/// Computing HKDF failed while deriving subkeys
HkdfError,
/// Computing HMAC failed while supplying a secret key
HmacError,
/// Computing the slow hashing function failed
SlowHashError,
/// This error occurs when the envelope seal fails
/// Constructing the envelope seal failed.
SealError,
/// This error occurs when the envelope seal open fails
/// Opening the envelope seal failed.
SealOpenError,
/// This error occurs when the envelope seal open hmac check fails
/// HMAC check in seal open failed.
SealOpenHmacError,
/// This error occurs when the envelope cannot be constructed properly
/// based on the credentials that were specified to be required.
InvalidEnvelopeStructureError,
/// This error occurs when attempting to open an envelope of the wrong
/// type (base mode, custom identifier)
IncompatibleEnvelopeModeError,
/// This error occurs when the envelope is opened and deserialization
/// fails
UnexpectedEnvelopeContentsError,
/** This error occurs when the client detects that the server has
reflected the OPRF value (beta == alpha) */
ReflectedValueError,
/// Custom [`SecretKey`](crate::keypair::PrivateKeySerialization) error type
Custom(T),
}
/// Represents an error in password checking
#[derive(Debug, Display, Error)]
pub enum PakeError {
/// This error results from an internal error during PRF construction
///
/// Internal error during PRF verification: {0}
CryptoError(InternalPakeError),
/// This error occurs when the server object that is being called finish() on is malformed
/// Incomplete set of keys passed into finish() function
IncompleteKeysError,
/// The provided server public key doesn't match the sealed one
IncompatibleServerStaticPublicKeyError,
/// Error in key exchange protocol when attempting to validate MACs
KeyExchangeMacValidationError,
/// Error in validating credentials
InvalidLoginError,
/// Error with serializing / deserializing protocol messages
SerializationError,
}
// This is meant to express future(ly) non-trivial ways of converting the
// internal error into a PakeError
impl From<InternalPakeError> for PakeError {
fn from(e: InternalPakeError) -> PakeError {
PakeError::CryptoError(e)
impl<T: Debug> Debug for ProtocolError<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LibraryError(pake_error) => {
f.debug_tuple("LibraryError").field(pake_error).finish()
}
Self::InvalidLoginError => f.debug_tuple("InvalidLoginError").finish(),
Self::SerializationError => f.debug_tuple("SerializationError").finish(),
Self::SizeError {
name,
len,
actual_len,
} => f
.debug_struct("SizeError")
.field("name", name)
.field("len", len)
.field("actual_len", actual_len)
.finish(),
Self::ReflectedValueError => f.debug_tuple("ReflectedValueError").finish(),
Self::Custom(custom) => f.debug_tuple("Custom").field(custom).finish(),
}
}
}
/// Represents an error in protocol handling
#[derive(Debug, Display, Error)]
pub enum ProtocolError {
/// This error results from an error during password verification
///
/// Internal error during password verification: {0}
VerificationError(PakeError),
/// This error occurs when the server answer cannot be handled
/// Server response cannot be handled.
ServerError,
/// This error occurs when the server specifies an envelope credentials
/// format that is invalid
ServerInvalidEnvelopeCredentialsFormatError,
/// This error occurs when the client request cannot be handled
/// Client request cannot be handled.
ClientError,
}
// This is meant to express future(ly) non-trivial ways of converting the
// Pake error into a ProtocolError
impl From<PakeError> for ProtocolError {
fn from(e: PakeError) -> ProtocolError {
ProtocolError::VerificationError(e)
}
}
impl<T: Error> Error for ProtocolError<T> {}
// This is meant to express future(ly) non-trivial ways of converting the
// internal error into a ProtocolError
impl From<InternalPakeError> for ProtocolError {
fn from(e: InternalPakeError) -> ProtocolError {
ProtocolError::VerificationError(e.into())
impl<T> From<InternalError> for ProtocolError<T> {
fn from(e: InternalError) -> ProtocolError<T> {
Self::LibraryError(e)
}
}
// See https://github.com/rust-lang/rust/issues/64715 and remove this when
// merged, and https://github.com/dtolnay/thiserror/issues/62 for why this
// comes up in our doc tests.
impl From<::std::convert::Infallible> for ProtocolError {
fn from(_: ::std::convert::Infallible) -> Self {
// See https://github.com/rust-lang/rust/issues/64715 and remove this when merged,
// and https://github.com/dtolnay/thiserror/issues/62 for why this comes up in our
// doc tests.
impl<T> From<Infallible> for ProtocolError<T> {
fn from(_: Infallible) -> Self {
unreachable!()
}
}
impl From<generic_bytes::TryFromSizedBytesError> for InternalPakeError {
fn from(_: generic_bytes::TryFromSizedBytesError) -> Self {
InternalPakeError::InvalidByteSequence
}
}
impl From<generic_bytes::TryFromSizedBytesError> for PakeError {
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
PakeError::CryptoError(e.into())
}
}
impl From<generic_bytes::TryFromSizedBytesError> for ProtocolError {
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
PakeError::CryptoError(e.into()).into()
}
}
pub(crate) mod utils {
use super::*;
pub fn check_slice_size<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], InternalPakeError> {
if slice.len() != expected_len {
return Err(InternalPakeError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
impl ProtocolError {
/// Convert `ProtocolError<Infallible>` into `ProtocolError<T>`
pub fn into_custom<T>(self) -> ProtocolError<T> {
match self {
Self::LibraryError(internal_error) => ProtocolError::LibraryError(internal_error),
Self::InvalidLoginError => ProtocolError::InvalidLoginError,
Self::SerializationError => ProtocolError::SerializationError,
Self::SizeError {
name,
len,
actual_len,
} => ProtocolError::SizeError {
name,
len,
actual_len,
},
Self::ReflectedValueError => ProtocolError::ReflectedValueError,
Self::Custom(infallible) => match infallible {},
}
Ok(slice)
}
pub fn check_slice_size_atleast<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], InternalPakeError> {
if slice.len() < expected_len {
return Err(InternalPakeError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
}
-137
View File
@@ -1,137 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the Group trait to specify the underlying prime order group used in
//! OPAQUE's OPRF
use crate::errors::InternalPakeError;
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
};
use generic_array::{
typenum::{U32, U64},
ArrayLength, GenericArray,
};
use std::convert::TryInto;
use rand::{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_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
/// The byte length necessary to represent group elements
type ElemLen: ArrayLength<u8>;
/// Return an element from its fixed-length bytes representation
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError>;
/// Serializes the `self` group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen>;
/// Hashes points presumed to be uniformly random to the curve. The
/// impl is allowed to perform additional hashes if it needs to, but this
/// may not be necessary as this function is going to be called with the
/// output of a kdf.
type UniformBytesLen: ArrayLength<u8>;
/// Hashes a slice of pseudo-random bytes of the correct length to a curve point
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self;
/// Get the base point for the group
fn base_point() -> Self;
/// Multiply the point by a scalar, represented as a slice
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
}
/// The implementation of such a subgroup for Ristretto
impl Group for RistrettoPoint {
type Scalar = Scalar;
type ScalarLen = U32;
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError> {
let mut bits = [0u8; 32];
bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits))
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert()
}
// The byte length necessary to represent group elements
type ElemLen = U32;
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> {
CompressedRistretto::from_slice(element_bits)
.decompress()
.ok_or(InternalPakeError::PointError)
}
// serialization of a group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
// https://caniuse.rs/features/array_gt_32_impls
let bits: [u8; 64] = {
let mut bytes = [0u8; 64];
bytes.copy_from_slice(uniform_bytes);
bytes
};
RistrettoPoint::from_uniform_bytes(&bits)
}
fn base_point() -> Self {
RISTRETTO_BASEPOINT_POINT
}
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self {
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
self * Scalar::from_bits(arr)
}
}
+70 -10
View File
@@ -1,15 +1,75 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! A convenience trait for digest bounds used throughout the library
use digest::{BlockInput, FixedOutput, Reset, Update};
use digest::block_api::{
BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, SmallBlockSizeUser,
};
use digest::block_buffer::Eager;
use digest::{Digest, FixedOutputReset, HashMarker, OutputSizeUser};
use generic_array::typenum::{IsLess, Le, NonZero, U256};
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
// Associated types could be simplified when they are made as defaults:
// https://github.com/rust-lang/rust/issues/29661
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
pub(crate) type OutputSize<H> = <<H as CoreProxy>::Core as OutputSizeUser>::OutputSize;
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
/// Trait to simplify requirements for [`Hash`].
pub trait ProxyHash:
HashMarker + FixedOutputCore + BufferKindUser<BufferKind = Eager> + OutputSizeUser + Default + Clone
where
<Self as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<Self as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
{
}
impl<
T: HashMarker
+ FixedOutputCore
+ BufferKindUser<BufferKind = Eager>
+ OutputSizeUser
+ Default
+ Clone,
> ProxyHash for T
where
<T as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<T as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
{
}
/// Trait inheriting the requirements from [`Digest`] for compatibility
/// with HKDF and HMAC Associated types could be simplified when they are made
/// as defaults: <https://github.com/rust-lang/rust/issues/29661>
pub trait Hash:
Default
+ HashMarker
+ Digest
+ OutputSizeUser<OutputSize = OutputSize<Self>>
+ BlockSizeUser
+ FixedOutputReset
+ CoreProxy
+ Clone
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,
{
}
+150
View File
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Key Exchange group implementation for Curve25519
pub use curve25519_dalek;
use curve25519_dalek::montgomery::MontgomeryPoint;
use curve25519_dalek::scalar;
use curve25519_dalek::traits::IsIdentity;
use generic_array::GenericArray;
use generic_array::typenum::U32;
use rand::{CryptoRng, Rng};
use subtle::ConstantTimeEq;
use zeroize::ZeroizeOnDrop;
use super::Group;
use crate::errors::{InternalError, ProtocolError};
use crate::key_exchange::shared::DiffieHellman;
use crate::serialization::SliceExt;
/// Implementation for Curve25519.
pub struct Curve25519;
/// The implementation of such a subgroup for Curve25519
impl Group for Curve25519 {
type Pk = NonIdentity;
type PkLen = U32;
type Sk = Scalar;
type SkLen = U32;
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
pk.0.to_bytes().into()
}
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
bytes
.take_array::<U32>("public key")
.and_then(|bytes| NonIdentity::from_bytes(bytes.into()))
}
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
// Sample 32 random bytes and then clamp, as described in https://cr.yp.to/ecdh.html
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
let scalar = scalar::clamp_integer(scalar_bytes);
Scalar(scalar)
}
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
Ok(Scalar(scalar::clamp_integer(seed.into())))
}
fn public_key(sk: &Self::Sk) -> Self::Pk {
NonIdentity(MontgomeryPoint::mul_base_clamped(sk.0))
}
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
sk.0.into()
}
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
bytes
.take_array::<U32>("secret key")
.and_then(|bytes| Scalar::from_bytes(bytes.into()))
}
}
impl DiffieHellman<Curve25519> for Scalar {
fn diffie_hellman(&self, pk: &NonIdentity) -> GenericArray<u8, U32> {
Curve25519::serialize_pk(&NonIdentity(pk.0.mul_clamped(self.0)))
}
}
/// Non-identity point wrapper for [`MontgomeryPoint`].
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct NonIdentity(
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_pk"))]
MontgomeryPoint,
);
impl NonIdentity {
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
let point = MontgomeryPoint(bytes);
if point.is_identity() {
Err(ProtocolError::SerializationError)
} else {
Ok(NonIdentity(point))
}
}
}
#[cfg(feature = "serde")]
fn serde_deserialize_pk<'de, D>(deserializer: D) -> Result<MontgomeryPoint, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
let point = MontgomeryPoint::deserialize(deserializer)?;
NonIdentity::from_bytes(point.0)
.map(|point| point.0)
.map_err(Error::custom)
}
/// Curve25519 scalar.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct Scalar(
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_sk"))] [u8; 32],
);
impl Scalar {
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
let scalar = scalar::clamp_integer(bytes);
if scalar.ct_eq(&bytes).into() {
Ok(Self(scalar))
} else {
Err(ProtocolError::SerializationError)
}
}
}
#[cfg(feature = "serde")]
fn serde_deserialize_sk<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
Scalar::from_bytes(<[u8; 32]>::deserialize(deserializer)?)
.map(|scalar| scalar.0)
.map_err(D::Error::custom)
}
#[test]
fn non_zero_scalar() {
use std::vec;
use crate::tests::mock_rng::CycleRng;
let mut rng = CycleRng::new(vec![0]);
let sk = Curve25519::random_sk(&mut rng);
assert_ne!(sk.0, curve25519_dalek::Scalar::ZERO.to_bytes());
}
+504
View File
@@ -0,0 +1,504 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Key Exchange group implementation for Ed25519
use core::iter;
use curve25519_dalek::edwards::CompressedEdwardsY;
use curve25519_dalek::traits::IsIdentity;
use curve25519_dalek::{EdwardsPoint, Scalar};
use digest::Digest;
pub use ed25519_dalek;
use ed25519_dalek::hazmat::ExpandedSecretKey;
use ed25519_dalek::{SecretKey, Sha512};
use generic_array::GenericArray;
use generic_array::typenum::{U32, U64};
use rand::{CryptoRng, Rng};
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::Group;
use crate::ciphersuite::CipherSuite;
use crate::errors::{InternalError, ProtocolError};
use crate::key_exchange::sigma_i::hash_eddsa::implementation::HashEddsaImpl;
use crate::key_exchange::sigma_i::pure_eddsa::implementation::PureEddsaImpl;
pub use crate::key_exchange::sigma_i::shared::PreHash;
use crate::key_exchange::sigma_i::{CachedMessage, Message, MessageBuilder};
use crate::serialization::{ConcatExt, SliceExt, UpdateExt};
/// Implementation for Ed25519.
pub struct Ed25519;
impl Group for Ed25519 {
type Pk = VerifyingKey;
type PkLen = U32;
type Sk = SigningKey;
type SkLen = U32;
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
pk.compressed.0.into()
}
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
let bytes = bytes.take_array::<U32>("public key")?;
VerifyingKey::from_bytes(bytes.into())
}
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
let mut sk = <[u8; 32]>::default();
rng.fill_bytes(&mut sk);
SigningKey::from_bytes(sk)
}
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
Ok(SigningKey::from_bytes(seed.into()))
}
fn public_key(sk: &Self::Sk) -> Self::Pk {
sk.verifying_key
}
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
sk.sk.into()
}
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
Ok(SigningKey::from_bytes(
bytes.take_array::<U32>("secret key")?.into(),
))
}
}
impl PureEddsaImpl for Ed25519 {
type Signature = Signature;
type SignatureLen = U64;
fn sign<CS: CipherSuite, KE: Group>(
sk: &Self::Sk,
message: &Message<CS, KE>,
) -> (Self::Signature, CachedMessage<CS, KE>) {
(sign(sk, false, message.sign_message()), message.to_cached())
}
/// Validates that the signature was created by signing the given message
/// with the corresponding private key.
fn verify<CS: CipherSuite, KE: Group>(
pk: &Self::Pk,
message_builder: MessageBuilder<'_, CS>,
state: CachedMessage<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError> {
verify(
pk,
false,
message_builder.build::<KE>(state).verify_message(),
signature,
)
}
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
Signature::deserialize_take(bytes)
}
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
signature.serialize()
}
}
impl HashEddsaImpl for Ed25519 {
type Signature = Signature;
type SignatureLen = U64;
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<Sha512>;
fn sign<CS: CipherSuite, KE: Group>(
sk: &Self::Sk,
message: &Message<CS, KE>,
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
let hash = message.hash::<Sha512>();
(
sign(sk, true, iter::once(hash.sign.finalize().as_slice())),
PreHash(hash.verify.finalize()),
)
}
/// Validates that the signature was created by signing the given message
/// with the corresponding private key.
fn verify<CS: CipherSuite, KE: Group>(
pk: &Self::Pk,
state: Self::VerifyState<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError> {
verify(pk, true, iter::once(state.0.as_slice()), signature)
}
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
Signature::deserialize_take(bytes)
}
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
signature.serialize()
}
}
// This contains a manual implementation of EdDSA because `ed25519-dalek`
// doesn't support message streaming. See
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
fn sign<'a>(
sk: &SigningKey,
pre_hash: bool,
message: impl Clone + Iterator<Item = &'a [u8]>,
) -> Signature {
let mut h = Sha512::new();
if pre_hash {
h.update(b"SigEd25519 no Ed25519 collisions");
h.update([1]); // Ed25519ph
h.update([0]);
}
h.update(sk.hash_prefix);
h.update_iter(message.clone());
let r = Scalar::from_hash(h);
#[allow(non_snake_case)]
let R = EdwardsPoint::mul_base(&r).compress();
h = Sha512::new();
if pre_hash {
h.update(b"SigEd25519 no Ed25519 collisions");
h.update([1]); // Ed25519ph
h.update([0]);
}
h.update(R.as_bytes());
h.update(sk.verifying_key.compressed.0);
h.update_iter(message);
let k = Scalar::from_hash(h);
let s: Scalar = (k * sk.scalar) + r;
Signature { R, s }
}
fn verify<'a>(
pk: &VerifyingKey,
pre_hash: bool,
message: impl Iterator<Item = &'a [u8]>,
signature: &Signature,
) -> Result<(), ProtocolError> {
let mut h = Sha512::new();
if pre_hash {
h.update(b"SigEd25519 no Ed25519 collisions");
h.update([1]); // Ed25519ph
h.update([0]);
}
h.update(signature.R.as_bytes());
h.update(pk.compressed.as_bytes());
h.update_iter(message);
let k = Scalar::from_hash(h);
#[allow(non_snake_case)]
let minus_A: EdwardsPoint = -pk.point;
#[allow(non_snake_case)]
let expected_R =
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s).compress();
if expected_R == signature.R {
Ok(())
} else {
Err(ProtocolError::InvalidLoginError)
}
}
/// Ed25519 verifying key.
// `ed25519_dalek::VerifyingKey` doesn't implement `Zeroize`.
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
// Required for manual implementation of EdDSA.
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Zeroize)]
pub struct VerifyingKey {
point: EdwardsPoint,
compressed: CompressedEdwardsY,
}
impl VerifyingKey {
fn from_bytes(bytes: [u8; 32]) -> Result<Self, ProtocolError> {
let compressed = CompressedEdwardsY(bytes);
if let Some(point) = compressed.decompress().filter(|point| !point.is_identity()) {
Ok(Self { point, compressed })
} else {
Err(ProtocolError::SerializationError)
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for VerifyingKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use core::fmt::{self, Formatter};
use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor};
struct VerifyingKeyVisitor;
impl<'de> Visitor<'de> for VerifyingKeyVisitor {
type Value = VerifyingKey;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
Formatter::write_str(formatter, "tuple struct VerifyingKey")
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let compressed = CompressedEdwardsY::deserialize(deserializer)?;
VerifyingKey::from_bytes(compressed.0).map_err(Error::custom)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let compressed: CompressedEdwardsY = seq.next_element()?.ok_or_else(|| {
Error::invalid_length(0, &"tuple struct VerifyingKey with 1 element")
})?;
VerifyingKey::from_bytes(compressed.0).map_err(Error::custom)
}
}
deserializer.deserialize_newtype_struct("VerifyingKey", VerifyingKeyVisitor)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for VerifyingKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_struct("VerifyingKey", &self.compressed)
}
}
/// Ed25519 signing key.
// We store the `ExpandedSecret` in memory to avoid computing it on demand and then discarding it
// again.
#[derive(Clone, Debug, Eq, PartialEq, ZeroizeOnDrop)]
pub struct SigningKey {
// `ed25519_dalek::SigningKey` doesn't implement `Zeroize`. See
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747
// Required for manual implementation of EdDSA.
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
sk: SecretKey,
verifying_key: VerifyingKey,
// `ed25519_dalek::ExpandedSecret` doesn't implement traits we need. See
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/748 and
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
scalar: Scalar,
hash_prefix: [u8; 32],
}
impl SigningKey {
fn from_bytes(sk: [u8; 32]) -> Self {
let ExpandedSecretKey {
scalar,
hash_prefix,
} = ExpandedSecretKey::from(&sk);
let point = EdwardsPoint::mul_base(&scalar);
let verifying_key = VerifyingKey {
point,
compressed: point.compress(),
};
SigningKey {
sk,
verifying_key,
scalar,
hash_prefix,
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SigningKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use core::fmt::{self, Formatter};
use serde::de::{Deserialize, Deserializer, Error, SeqAccess, Visitor};
struct SigningKeyVisitor;
impl<'de> Visitor<'de> for SigningKeyVisitor {
type Value = SigningKey;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
Formatter::write_str(formatter, "tuple struct SigningKey")
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let sk = Scalar::deserialize(deserializer)?;
Ok(SigningKey::from_bytes(sk.to_bytes()))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let sk: Scalar = seq.next_element()?.ok_or_else(|| {
Error::invalid_length(0, &"tuple struct SigningKey with 1 element")
})?;
Ok(SigningKey::from_bytes(sk.to_bytes()))
}
}
deserializer.deserialize_newtype_struct("SigningKey", SigningKeyVisitor)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SigningKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_newtype_struct("SigningKey", &self.sk)
}
}
/// Ed25519 Signature.
// `ed25519_dalek::Signature` doesn't implement validation with Serde de/serialization.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(non_snake_case)]
pub struct Signature {
R: CompressedEdwardsY,
s: Scalar,
}
impl Signature {
/// Expects the `R` and `s` components of an Ed25519 signature with no added
/// framing.
pub fn from_slice(mut bytes: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut bytes)
}
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
#[allow(non_snake_case)]
let R = CompressedEdwardsY(bytes.take_array::<U32>("signature R")?.into());
let s = Scalar::from_canonical_bytes(bytes.take_array::<U32>("signature s")?.into())
.into_option()
.ok_or(ProtocolError::SerializationError)?;
Ok(Self { R, s })
}
fn serialize(&self) -> GenericArray<u8, U64> {
GenericArray::<u8, U32>::from(self.R.0)
.cat(GenericArray::<u8, U32>::from(self.s.to_bytes()))
}
}
impl Zeroize for Signature {
fn zeroize(&mut self) {
self.R.0 = [0; 32];
self.s = Scalar::default();
}
}
#[cfg(test)]
mod test {
use std::iter;
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
use rand::rand_core::UnwrapErr;
use rand::rngs::SysRng;
use super::*;
#[test]
fn pure_eddsa() {
let mut message = [0; 1024];
UnwrapErr(SysRng).fill_bytes(&mut message);
let mut sk = SecretKey::default();
UnwrapErr(SysRng).fill_bytes(&mut sk);
let signing_key = SigningKey::from_bytes(&sk);
let signature = signing_key.sign(&message);
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
let custom_signature = sign(&custom_sk, false, iter::once(message.as_slice()));
assert_eq!(
signature.to_bytes(),
custom_signature.serialize().as_slice()
);
let verifying_key = VerifyingKey::from(&signing_key);
verifying_key.verify(&message, &signature).unwrap();
let custom_pk = Ed25519::public_key(&custom_sk);
verify(
&custom_pk,
false,
iter::once(message.as_slice()),
&custom_signature,
)
.unwrap();
}
#[test]
fn hash_eddsa() {
let mut message = [0; 1024];
UnwrapErr(SysRng).fill_bytes(&mut message);
let message = Sha512::new_with_prefix(message);
let pre_hash = message.clone().finalize();
let mut sk = SecretKey::default();
UnwrapErr(SysRng).fill_bytes(&mut sk);
let signing_key = SigningKey::from_bytes(&sk);
let signature = signing_key.sign_prehashed(message.clone(), None).unwrap();
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
let custom_signature = sign(&custom_sk, true, iter::once(pre_hash.as_slice()));
assert_eq!(
signature.to_bytes(),
custom_signature.serialize().as_slice()
);
let verifying_key = VerifyingKey::from(&signing_key);
verifying_key
.verify_prehashed(message, None, &signature)
.unwrap();
let custom_pk = Ed25519::public_key(&custom_sk);
verify(
&custom_pk,
true,
iter::once(pre_hash.as_slice()),
&custom_signature,
)
.unwrap();
}
}
+111
View File
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Implementation for EC curves via [`elliptic_curve`] traits.
use core::ops::Mul;
use elliptic_curve::group::GroupEncoding;
use elliptic_curve::point::NonIdentity;
use elliptic_curve::sec1::{ModulusSize, ToSec1Point};
use elliptic_curve::{
CurveArithmetic, FieldBytesSize, Generate, NonZeroScalar, ProjectivePoint, Scalar, SecretKey,
};
use generic_array::typenum::U2;
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, Rng};
use voprf::Mode;
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
use crate::errors::{InternalError, ProtocolError};
use crate::key_exchange::shared::DiffieHellman;
use crate::serialization::SliceExt;
impl<G> Group for G
where
Self: CurveArithmetic + voprf::CipherSuite<Group = Self> + voprf::Group<Scalar = Scalar<Self>>,
FieldBytesSize<Self>: ModulusSize + ArrayLength,
<FieldBytesSize<Self> as ModulusSize>::CompressedPointSize: ArrayLength,
ProjectivePoint<Self>: GroupEncoding<
Repr = hybrid_array::Array<
u8,
<FieldBytesSize<Self> as ModulusSize>::CompressedPointSize,
>,
> + ToSec1Point<Self>,
// Bounds required by voprf::CipherSuite
<Self as voprf::Group>::SecurityLevel: Mul<U2>,
{
// We don't use `elliptic_curve::PublicKey` because it stores its internals in a
// format ideal for serialization and not computation. This is inconsistent with
// our other implementations.
type Pk = NonIdentity<ProjectivePoint<Self>>;
type PkLen = <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize;
type Sk = SecretKey<Self>;
type SkLen = FieldBytesSize<Self>;
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
GenericArray::from_slice(pk.to_sec1_point(true).as_bytes()).clone()
}
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
NonIdentity::<ProjectivePoint<Self>>::from_bytes(
&bytes.take_array("public key")?.into_ha0_4(),
)
.into_option()
.ok_or(ProtocolError::SerializationError)
}
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
SecretKey::<Self>::generate_from_rng(rng)
}
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
.map(|scalar| {
NonZeroScalar::new(scalar).expect("`voprf::derive_key()` returned a zero scalar")
})
.map(SecretKey::from)
.map_err(InternalError::from)
}
fn public_key(sk: &Self::Sk) -> Self::Pk {
NonIdentity::<ProjectivePoint<Self>>::mul_by_generator(&sk.to_nonzero_scalar())
}
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
GenericArray::from(sk.to_bytes())
}
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
SecretKey::<Self>::from_bytes(&bytes.take_array("secret key")?.into_ha0_4())
.map_err(|_| ProtocolError::SerializationError)
}
}
impl<G> DiffieHellman<G> for SecretKey<G>
where
G: CurveArithmetic + voprf::CipherSuite<Group = G> + voprf::Group<Scalar = Scalar<G>>,
FieldBytesSize<G>: ModulusSize + ArrayLength,
<FieldBytesSize<G> as ModulusSize>::CompressedPointSize: ArrayLength,
ProjectivePoint<G>: GroupEncoding<
Repr = hybrid_array::Array<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize>,
> + ToSec1Point<G>,
// Bounds required by voprf::CipherSuite
<G as voprf::Group>::SecurityLevel: Mul<U2>,
{
fn diffie_hellman(
&self,
pk: &NonIdentity<ProjectivePoint<G>>,
) -> GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize> {
GenericArray::from_slice(
(pk * self.to_nonzero_scalar())
.to_sec1_point(true)
.as_bytes(),
)
.clone()
}
}
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Includes the [`Group`] trait and definitions for the key exchange groups
#[cfg(feature = "curve25519")]
pub mod curve25519;
#[cfg(feature = "ed25519")]
pub mod ed25519;
pub mod elliptic_curve;
#[cfg(feature = "ristretto255")]
pub mod ristretto255;
use generic_array::{ArrayLength, GenericArray};
use hybrid_array::ArraySize;
use rand::{CryptoRng, Rng};
use zeroize::ZeroizeOnDrop;
use crate::errors::{InternalError, ProtocolError};
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 33] = *b"OPAQUE-DeriveDiffieHellmanKeyPair";
/// A group representation for use in the key exchange
pub trait Group {
/// Public key
type Pk: Clone;
/// Length of the public key
type PkLen: ArrayLength + ArraySize;
/// Secret key
type Sk: Clone + ZeroizeOnDrop;
/// Length of the secret key
type SkLen: ArrayLength + ArraySize;
/// Serializes `self`
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen>;
/// Return a public key from its fixed-length bytes representation
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError>;
/// Generate a random secret key
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk;
/// Deterministically derive a [`Self::Sk`] from `seed`.
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError>;
/// Return a public key from its secret key
fn public_key(sk: &Self::Sk) -> Self::Pk;
/// Serializes `self`
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen>;
/// Return a public key from its fixed-length bytes representation
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError>;
}
+232
View File
@@ -0,0 +1,232 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Key Exchange group implementation for ristretto255
pub use curve25519_dalek;
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
use curve25519_dalek::scalar::Scalar;
use curve25519_dalek::traits::IsIdentity;
use digest::block_api::BlockSizeUser;
use digest::{FixedOutput, HashMarker};
use generic_array::GenericArray;
use generic_array::typenum::{IsGreaterOrEqual, IsLess, IsLessOrEqual, Prod, True, U2, U32, U256};
use hybrid_array::Array;
use rand::{CryptoRng, Rng, TryCryptoRng, TryRng};
use voprf::Mode;
use zeroize::ZeroizeOnDrop;
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
use crate::errors::{InternalError, ProtocolError};
use crate::key_exchange::shared::DiffieHellman;
use crate::serialization::SliceExt;
/// Implementation for Ristretto255.
// This is necessary because Rust lacks specialization, otherwise we could
// implement `KeGroup` for `voprf::Ristretto255`.
pub struct Ristretto255;
impl Group for Ristretto255 {
type Pk = NonIdentity;
type PkLen = U32;
type Sk = NonZeroScalar;
type SkLen = U32;
fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
pk.0.compress().to_bytes().into()
}
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
CompressedRistretto(bytes.take_array::<U32>("public key")?.into())
.decompress()
.ok_or(ProtocolError::SerializationError)
.and_then(NonIdentity::from_point)
}
fn random_sk<R: Rng + CryptoRng>(rng: &mut R) -> Self::Sk {
loop {
let mut bytes = [0u8; 64];
rng.fill_bytes(&mut bytes);
let scalar = Scalar::from_bytes_mod_order_wide(&bytes);
if scalar != Scalar::ZERO {
break NonZeroScalar(scalar);
}
}
}
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
.map(NonZeroScalar)
.map_err(InternalError::from)
}
fn public_key(sk: &Self::Sk) -> Self::Pk {
NonIdentity(RISTRETTO_BASEPOINT_POINT * sk.0)
}
fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
sk.0.to_bytes().into()
}
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
Scalar::from_canonical_bytes(bytes.take_array::<U32>("secret key")?.into())
.into_option()
.ok_or(ProtocolError::SerializationError)
.and_then(NonZeroScalar::from_scalar)
}
}
impl DiffieHellman<Ristretto255> for NonZeroScalar {
fn diffie_hellman(&self, pk: &NonIdentity) -> GenericArray<u8, U32> {
Ristretto255::serialize_pk(&NonIdentity(pk.0 * self.0))
}
}
/// Non-identity point wrapper for [`RistrettoPoint`].
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NonIdentity(
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_pk"))] RistrettoPoint,
);
impl NonIdentity {
fn from_point(point: RistrettoPoint) -> Result<Self, ProtocolError> {
if point.is_identity() {
Err(ProtocolError::SerializationError)
} else {
Ok(NonIdentity(point))
}
}
}
#[cfg(feature = "serde")]
fn serde_deserialize_pk<'de, D>(deserializer: D) -> Result<RistrettoPoint, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
let point = RistrettoPoint::deserialize(deserializer)?;
NonIdentity::from_point(point)
.map(|point| point.0)
.map_err(Error::custom)
}
/// Non-zero scalar wrapper for [`Scalar`]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct NonZeroScalar(
#[cfg_attr(feature = "serde", serde(deserialize_with = "serde_deserialize_sk"))] Scalar,
);
impl NonZeroScalar {
fn from_scalar(scalar: Scalar) -> Result<Self, ProtocolError> {
if scalar == Scalar::ZERO {
Err(ProtocolError::SerializationError)
} else {
Ok(Self(scalar))
}
}
}
#[cfg(feature = "serde")]
fn serde_deserialize_sk<'de, D>(deserializer: D) -> Result<Scalar, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Deserialize, Error};
let scalar = Scalar::deserialize(deserializer)?;
NonZeroScalar::from_scalar(scalar)
.map(|scalar| scalar.0)
.map_err(Error::custom)
}
impl voprf::CipherSuite for Ristretto255 {
const ID: &'static [u8] = voprf::Ristretto255::ID;
type Group = <voprf::Ristretto255 as voprf::CipherSuite>::Group;
type Hash = <voprf::Ristretto255 as voprf::CipherSuite>::Hash;
}
impl voprf::Group for Ristretto255 {
type Elem = <voprf::Ristretto255 as voprf::Group>::Elem;
type ElemLen = <voprf::Ristretto255 as voprf::Group>::ElemLen;
type Scalar = <voprf::Ristretto255 as voprf::Group>::Scalar;
type ScalarLen = <voprf::Ristretto255 as voprf::Group>::ScalarLen;
type SecurityLevel = <voprf::Ristretto255 as voprf::Group>::SecurityLevel;
fn hash_to_curve<H>(
input: &[&[u8]],
dst: &[&[u8]],
) -> voprf::Result<Self::Elem, voprf::InternalError>
where
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256>
+ IsLessOrEqual<H::BlockSize, Output = True>
+ IsGreaterOrEqual<Prod<<Self as voprf::Group>::SecurityLevel, U2>, Output = True>,
{
<voprf::Ristretto255 as voprf::Group>::hash_to_curve::<H>(input, dst)
}
fn hash_to_scalar<H>(
input: &[&[u8]],
dst: &[&[u8]],
) -> voprf::Result<Self::Scalar, voprf::InternalError>
where
H: BlockSizeUser + Default + FixedOutput + HashMarker,
H::OutputSize: IsLess<U256>
+ IsLessOrEqual<H::BlockSize, Output = True>
+ IsGreaterOrEqual<Prod<<Self as voprf::Group>::SecurityLevel, U2>, Output = True>,
{
<voprf::Ristretto255 as voprf::Group>::hash_to_scalar::<H>(input, dst)
}
fn base_elem() -> Self::Elem {
<voprf::Ristretto255 as voprf::Group>::base_elem()
}
fn identity_elem() -> Self::Elem {
<voprf::Ristretto255 as voprf::Group>::identity_elem()
}
fn serialize_elem(elem: Self::Elem) -> Array<u8, Self::ElemLen> {
<voprf::Ristretto255 as voprf::Group>::serialize_elem(elem)
}
fn deserialize_elem(element_bits: &[u8]) -> voprf::Result<Self::Elem> {
<voprf::Ristretto255 as voprf::Group>::deserialize_elem(element_bits)
}
fn random_scalar<R: TryRng + TryCryptoRng>(rng: &mut R) -> voprf::Result<Self::Scalar> {
<voprf::Ristretto255 as voprf::Group>::random_scalar(rng)
}
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
<voprf::Ristretto255 as voprf::Group>::invert_scalar(scalar)
}
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
<voprf::Ristretto255 as voprf::Group>::is_zero_scalar(scalar)
}
fn serialize_scalar(scalar: Self::Scalar) -> Array<u8, Self::ScalarLen> {
<voprf::Ristretto255 as voprf::Group>::serialize_scalar(scalar)
}
fn deserialize_scalar(scalar_bits: &[u8]) -> voprf::Result<Self::Scalar> {
<voprf::Ristretto255 as voprf::Group>::deserialize_scalar(scalar_bits)
}
}
+409 -7
View File
@@ -1,10 +1,412 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Includes instantiations of key exchange protocols used in the
//! login step for OPAQUE
//! Includes instantiations of key exchange protocols used in the login step for
//! OPAQUE
pub(crate) mod traits;
pub mod group;
pub(crate) mod shared;
pub mod sigma_i;
pub mod tripledh;
#[cfg(feature = "kem")]
pub mod tripledh_kem;
use core::iter;
use core::ops::Add;
use derive_where::derive_where;
use digest::Output;
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U2, U256};
use generic_array::{ArrayLength, GenericArray};
use hybrid_array::Array;
use rand::{CryptoRng, Rng};
use voprf::{BlindedElement, EvaluationElement};
use zeroize::{Zeroize, ZeroizeOnDrop};
#[cfg(test)]
use crate::ciphersuite::KeHash;
use crate::ciphersuite::{CipherSuite, OprfGroup};
use crate::errors::ProtocolError;
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::{NonceLen, STR_CONTEXT};
use crate::keypair::{PrivateKey, PublicKey};
use crate::opaque::{Identifiers, MaskedResponse, MaskedResponseLen};
use crate::serialization::{ConcatExt, SliceExt, i2osp};
/// The key exchange trait.
pub trait KeyExchange
where
<Self::Hash as CoreProxy>::Core: ProxyHash,
<<Self::Hash as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<Self::Hash as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<Self::Hash>: ArrayLength,
{
/// The group used for the key exchange.
type Group: Group;
/// The hash used for the key exchange.
type Hash: Hash;
/// Client state.
type KE1State: ZeroizeOnDrop + Clone;
/// Server state.
type KE2State<CS: CipherSuite>: ZeroizeOnDrop + Clone;
/// First message sent by the client.
type KE1Message: ZeroizeOnDrop + Clone;
/// Server state builder.
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>>: ZeroizeOnDrop + Clone;
/// Server data for the remote key interaction.
type KE2BuilderData<'a, CS: 'static + CipherSuite>;
/// Server remote key input.
type KE2BuilderInput<CS: CipherSuite>;
/// Message sent by the server.
type KE2Message: ZeroizeOnDrop + Clone;
/// Second message sent by the client.
type KE3Message: ZeroizeOnDrop + Clone;
/// Client generates [`KE1Message`](Self::KE1Message) and
/// [`KE1State`](Self::KE1State).
fn generate_ke1<R: Rng + CryptoRng>(
rng: &mut R,
) -> Result<GenerateKe1Result<Self>, ProtocolError>;
/// Server generates [`KE2Builder`](Self::KE2Builder).
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
client_s_pk: PublicKey<Self::Group>,
identifiers: SerializedIdentifiers<'a, Self::Group>,
context: SerializedContext<'a>,
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError>;
/// Server returns the data for the remote key interaction.
fn ke2_builder_data<'a, CS: CipherSuite<KeyExchange = Self>>(
builder: &'a Self::KE2Builder<'_, CS>,
) -> Self::KE2BuilderData<'a, CS>;
/// Server generates the input without a remote key.
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
builder: &Self::KE2Builder<'_, CS>,
rng: &mut R,
server_s_sk: &PrivateKey<Self::Group>,
) -> Self::KE2BuilderInput<CS>;
/// Server generates [`KE2Message`](Self::KE2Message) and
/// [`KE2State`](Self::KE2State).
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
builder: Self::KE2Builder<'_, CS>,
input: Self::KE2BuilderInput<CS>,
) -> Result<GenerateKe2Result<CS>, ProtocolError>;
/// Client generates [`KE3Message`](Self::KE3Message) and the session key.
#[allow(clippy::too_many_arguments)]
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
ke1_state: &Self::KE1State,
ke2_message: Self::KE2Message,
server_s_pk: PublicKey<Self::Group>,
client_s_sk: PrivateKey<Self::Group>,
identifiers: SerializedIdentifiers<'_, Self::Group>,
context: SerializedContext<'_>,
) -> Result<GenerateKe3Result<Self>, ProtocolError>;
/// Server generates the session key.
fn finish_ke<CS: CipherSuite<KeyExchange = Self>>(
ke2_state: &Self::KE2State<CS>,
ke3_message: Self::KE3Message,
identifiers: Identifiers<'_>,
context: SerializedContext<'_>,
) -> Result<Output<Self::Hash>, ProtocolError>;
}
/// Serialized form of [`CredentialRequest`](crate::CredentialRequest).
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
pub struct SerializedCredentialRequest<CS: CipherSuite>(
Array<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>,
);
impl<CS: CipherSuite> SerializedCredentialRequest<CS> {
pub(crate) fn new(blinded_element: &BlindedElement<CS::OprfCs>) -> Self {
Self(blinded_element.serialize())
}
/// Returns the serialized form of
/// [`CredentialRequest`](crate::CredentialRequest) in multiple byte slices.
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
iter::once(self.0.as_slice())
}
/// Returns a [`SerializedCredentialRequest`] deserialized from the given
/// `bytes`.
pub fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self(bytes.take_array("blinded element")?.into_ha0_4()))
}
}
type SerializedCredentialRequestLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
impl<CS: CipherSuite> Serialize for SerializedCredentialRequest<CS>
where
<OprfGroup<CS> as voprf::Group>::ElemLen: ArrayLength,
{
type Len = SerializedCredentialRequestLen<CS>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
GenericArray::from_slice(self.0.as_slice()).clone()
}
}
/// Serialized form of [`CredentialResponse`](crate::CredentialResponse).
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
pub struct SerializedCredentialResponse<CS: CipherSuite> {
evaluation_element: Array<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>,
masking_nonce: GenericArray<u8, NonceLen>,
masked_response: MaskedResponse<CS>,
}
impl<CS: CipherSuite> SerializedCredentialResponse<CS> {
pub(crate) fn new(
evaluation_element: &EvaluationElement<CS::OprfCs>,
masking_nonce: GenericArray<u8, NonceLen>,
masked_response: MaskedResponse<CS>,
) -> Self {
Self {
evaluation_element: evaluation_element.serialize(),
masking_nonce,
masked_response,
}
}
/// Returns the serialized form of
/// [`CredentialResponse`](crate::CredentialResponse) in multiple byte
/// slices.
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
[self.evaluation_element.as_slice(), &self.masking_nonce]
.into_iter()
.chain(self.masked_response.iter())
}
/// Returns a [`SerializedCredentialRequest`] deserialized from the given
/// `bytes`.
pub fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
evaluation_element: input.take_array("evaluation element")?.into_ha0_4(),
masking_nonce: input.take_array("masking nonce")?,
masked_response: MaskedResponse::deserialize_take(input)?,
})
}
}
type SerializedCredentialResponseLen<CS: CipherSuite> =
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
impl<CS: CipherSuite> Serialize for SerializedCredentialResponse<CS>
where
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength + Add<MaskedResponseLen<CS>>,
SerializedCredentialResponseLen<CS>: ArrayLength,
{
type Len = SerializedCredentialResponseLen<CS>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
let elem = GenericArray::<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>::from_slice(
self.evaluation_element.as_slice(),
)
.clone();
elem.cat(self.masking_nonce)
.cat(self.masked_response.serialize())
}
}
/// Serialized form of a `context` given in
/// [`ClientLoginFinishParameters`](crate::ClientLoginFinishParameters) or
/// [`ServerLoginParameters`](crate::ServerLoginParameters).
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
#[allow(unused_assignments)]
pub struct SerializedContext<'a> {
length: GenericArray<u8, U2>,
#[zeroize(skip)]
context: &'a [u8],
}
impl<'a> SerializedContext<'a> {
pub(crate) fn from(context: Option<&'a [u8]>) -> Result<Self, ProtocolError> {
let context = context.unwrap_or(&[]);
Ok(Self {
length: i2osp::<U2>(context.len())?,
context,
})
}
/// Returns the serialized form of `context` in multiple byte slices.
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
iter::once(STR_CONTEXT).chain([self.length.as_slice(), self.context])
}
}
/// Serialized form of [`Identifiers`].
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(deserialize = "'de: 'a", serialize = ""))
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
pub struct SerializedIdentifiers<'a, G: Group> {
/// Client identifiers.
pub client: SerializedIdentifier<'a, G>,
/// Server identifiers.
pub server: SerializedIdentifier<'a, G>,
}
/// Serialized form of a single identifier from [`Identifiers`].
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(deserialize = "'de: 'a", serialize = ""))
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
pub struct SerializedIdentifier<'a, G: Group> {
length: GenericArray<u8, U2>,
identifier: Identifier<'a, G>,
}
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
enum Identifier<'a, G: Group> {
Owned(GenericArray<u8, G::PkLen>),
#[derive_where(skip_inner(Zeroize))]
Borrowed(&'a [u8]),
}
impl<'a, G: Group> SerializedIdentifiers<'a, G> {
pub(crate) fn from_identifiers(
ids: Identifiers<'a>,
client_s_pk: GenericArray<u8, G::PkLen>,
server_s_pk: GenericArray<u8, G::PkLen>,
) -> Result<Self, ProtocolError> {
let client = SerializedIdentifier::from_identifier(ids.client, client_s_pk)?;
let server = SerializedIdentifier::from_identifier(ids.server, server_s_pk)?;
Ok(Self { client, server })
}
}
impl<'a, G: Group> SerializedIdentifier<'a, G> {
/// Creates a [`SerializedIdentifier`] an identifier or the corresponding
/// static public key.
pub fn from_identifier(
id: Option<&'a [u8]>,
s_pk: GenericArray<u8, G::PkLen>,
) -> Result<Self, ProtocolError> {
if let Some(id) = id {
Ok(SerializedIdentifier {
length: i2osp::<U2>(id.len())?,
identifier: Identifier::Borrowed(id),
})
} else {
Ok(SerializedIdentifier {
length: i2osp::<U2>(s_pk.len())?,
identifier: Identifier::Owned(s_pk),
})
}
}
/// Returns the serialized form of an identifier in multiple byte slices.
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
[self.length.as_slice()]
.into_iter()
.chain(match &self.identifier {
Identifier::Owned(bytes) => [bytes.as_slice()],
Identifier::Borrowed(bytes) => [*bytes],
})
}
}
/// Deserialization trait for key exchange types.
pub trait Deserialize: Sized {
/// Deserialize [`Self`] from the given `bytes`.
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError>;
}
/// Serialization trait for key exchange types.
pub trait Serialize {
/// The length of the serialized types.
type Len: ArrayLength;
/// Serialize [`Self`] to a fixed-length byte array.
fn serialize(&self) -> GenericArray<u8, Self::Len>;
}
/// Result type of [`KeyExchange::generate_ke1()`].
pub struct GenerateKe1Result<KE: KeyExchange + ?Sized> {
/// The client state.
pub state: KE::KE1State,
/// The first client message.
pub message: KE::KE1Message,
}
/// Result type of [`KeyExchange::build_ke2()`].
pub struct GenerateKe2Result<CS: CipherSuite> {
/// The server state.
pub state: <CS::KeyExchange as KeyExchange>::KE2State<CS>,
/// The server message.
pub message: <CS::KeyExchange as KeyExchange>::KE2Message,
#[cfg(test)]
pub(crate) handshake_secret: Output<KeHash<CS>>,
#[cfg(test)]
pub(crate) km2: Output<KeHash<CS>>,
}
/// Result type of [`KeyExchange::generate_ke3()`].
pub struct GenerateKe3Result<KE: KeyExchange + ?Sized> {
/// The session key.
pub session_key: Output<KE::Hash>,
/// The second client message.
pub message: KE::KE3Message,
#[cfg(test)]
pub(crate) handshake_secret: Output<KE::Hash>,
#[cfg(test)]
pub(crate) km3: Output<KE::Hash>,
}
pub(crate) type Ke1StateLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange>::KE1State as Serialize>::Len;
pub(crate) type Ke1MessageLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange>::KE1Message as Serialize>::Len;
pub(crate) type Ke2StateLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange>::KE2State<CS> as Serialize>::Len;
pub(crate) type Ke2MessageLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange>::KE2Message as Serialize>::Len;
pub(crate) type Ke3MessageLen<CS: CipherSuite> =
<<CS::KeyExchange as KeyExchange>::KE3Message as Serialize>::Len;
+488
View File
@@ -0,0 +1,488 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use core::ops::Add;
use derive_where::derive_where;
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
use digest::{Digest, Mac, Output, OutputSizeUser, Update};
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U1, U2, U32, U256, Unsigned};
use generic_array::{ArrayLength, GenericArray};
use hkdf::SimpleHkdf as Hkdf;
use hkdf::SimpleHkdfExtract as HkdfExtract;
use hmac::{KeyInit, SimpleHmac};
use rand::{CryptoRng, Rng};
use super::{
Deserialize, GenerateKe1Result, KeyExchange, Serialize, SerializedContext,
SerializedCredentialRequest, SerializedCredentialResponse, SerializedIdentifiers,
};
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash};
use crate::errors::{InternalError, ProtocolError};
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
use crate::serialization::{ConcatExt, SliceExt, UpdateExt, i2osp};
///////////////
// Constants //
// ========= //
///////////////
pub(crate) type NonceLen = U32;
pub(super) static STR_CONTEXT: &[u8] = b"OPAQUEv1-";
static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
static STR_SERVER_MAC: &[u8] = b"ServerMAC";
static STR_SESSION_KEY: &[u8] = b"SessionKey";
static STR_OPAQUE: &[u8] = b"OPAQUE-";
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
/// Trait required by [`Group::Sk`] to be compatible with
/// [`TripleDh`](crate::TripleDh) and [`SigmaI`](crate::SigmaI).
pub trait DiffieHellman<G: Group> {
/// Diffie-Hellman key exchange.
fn diffie_hellman(&self, pk: &G::Pk) -> GenericArray<u8, G::PkLen>;
}
/// The client state produced after the first key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Sk: serde::Deserialize<'de>",
serialize = "G::Sk: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)]
pub struct Ke1State<G: Group> {
pub(super) client_e_sk: PrivateKey<G>,
pub(super) client_nonce: GenericArray<u8, NonceLen>,
}
/// The first key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Pk: serde::Deserialize<'de>",
serialize = "G::Pk: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
pub struct Ke1Message<G: Group> {
pub(super) client_nonce: GenericArray<u8, NonceLen>,
#[derive_where(skip(Zeroize))]
pub(super) client_e_pk: PublicKey<G>,
}
/////////////////////////
// Convenience Structs //
//==================== //
/////////////////////////
// Consists of a session key, followed by two mac keys: (session_key, km2, km3)
pub(super) struct DerivedKeys<H: OutputSizeUser> {
pub(super) session_key: Output<H>,
pub(super) km2: Output<H>,
pub(super) km3: Output<H>,
#[cfg(test)]
pub(super) handshake_secret: Output<H>,
}
/// Helper bundle containing the common `TripleDH` server state that both
/// `TripleDh` and `TripleDhKem` builders need.
pub(super) struct Ke2BuilderCommon<G: Group, H: Hash>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
G::Sk: DiffieHellman<G>,
{
pub(super) server_nonce: GenericArray<u8, NonceLen>,
pub(super) transcript_hasher: H,
pub(super) client_e_pk: PublicKey<G>,
pub(super) server_e_pk: PublicKey<G>,
pub(super) shared_secret_1: GenericArray<u8, G::PkLen>,
pub(super) shared_secret_3: GenericArray<u8, G::PkLen>,
}
////////////////////////////////////////////////
// Helper functions and Trait Implementations //
// ========================================== //
////////////////////////////////////////////////
// Helper functions
pub(super) fn generate_ke1<
R: Rng + CryptoRng,
KE: KeyExchange<KE1State = Ke1State<G>, KE1Message = Ke1Message<G>>,
G: Group,
>(
rng: &mut R,
) -> Result<GenerateKe1Result<KE>, ProtocolError> {
let client_e_kp = KeyPair::<G>::derive_random(rng);
let client_nonce = generate_nonce::<R>(rng);
let ke1_message = Ke1Message {
client_nonce,
client_e_pk: client_e_kp.public().clone(),
};
Ok(GenerateKe1Result {
state: Ke1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce,
},
message: ke1_message,
})
}
// Generate a random nonce up to NonceLen::USIZE bytes.
pub(super) fn generate_nonce<R: Rng + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
let mut nonce_bytes = GenericArray::default();
rng.fill_bytes(&mut nonce_bytes);
nonce_bytes
}
pub(super) fn transcript<CS: CipherSuite, KE: Group>(
context: &SerializedContext<'_>,
identifiers: &SerializedIdentifiers<'_, KeGroup<CS>>,
credential_request: &SerializedCredentialRequest<CS>,
ke1_message: &Ke1MessageIter<KE>,
credential_response: &SerializedCredentialResponse<CS>,
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: &GenericArray<u8, KE::PkLen>,
) -> KeHash<CS> {
KeHash::<CS>::new()
.chain_iter(context.iter())
.chain_iter(identifiers.client.iter())
.chain_iter(credential_request.iter())
.chain_iter(ke1_message.iter())
.chain_iter(identifiers.server.iter())
.chain_iter(credential_response.iter())
.chain(server_nonce)
.chain(server_e_pk)
}
/// Generates the server-side `TripleDH` transcript state shared by multiple
/// key-exchange variants.
pub(super) fn ke2_builder_common<'a, G, H, CS, R>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Ke1Message<G>,
credential_response: SerializedCredentialResponse<CS>,
client_s_pk: PublicKey<G>,
identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
context: SerializedContext<'a>,
) -> Result<Ke2BuilderCommon<G, H>, ProtocolError>
where
G: Group,
H: Hash,
R: Rng + CryptoRng,
CS: CipherSuite,
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
G::Sk: DiffieHellman<G>,
CS::KeyExchange: KeyExchange<Group = G, Hash = H>,
{
let server_ephemeral = KeyPair::<G>::derive_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let server_e_pk_bytes = server_ephemeral.public().serialize();
let ke1_iter = ke1_message.to_iter();
let client_e_pk = ke1_message.client_e_pk.clone();
let transcript_hasher = transcript(
&context,
&identifiers,
&credential_request,
&ke1_iter,
&credential_response,
server_nonce,
&server_e_pk_bytes,
);
let shared_secret_1 = server_ephemeral
.private()
.ke_diffie_hellman(&ke1_message.client_e_pk);
let shared_secret_3 = server_ephemeral.private().ke_diffie_hellman(&client_s_pk);
Ok(Ke2BuilderCommon {
server_nonce,
transcript_hasher,
client_e_pk,
server_e_pk: server_ephemeral.public().clone(),
shared_secret_1,
shared_secret_3,
})
}
// Internal function which takes computed shared secrets, along with some
// auxiliary metadata, to produce the session key and two MAC keys
pub(super) fn derive_keys<'a, H: Hash>(
ikms: impl Iterator<Item = &'a [u8]>,
hashed_derivation_transcript: &[u8],
) -> Result<DerivedKeys<H>, ProtocolError>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
let mut hkdf = HkdfExtract::<H>::new(None);
for ikm in ikms {
hkdf.input_ikm(ikm);
}
let (_, extracted_ikm) = hkdf.finalize();
let handshake_secret = derive_secrets::<H>(
&extracted_ikm,
STR_HANDSHAKE_SECRET,
hashed_derivation_transcript,
)?;
let session_key = derive_secrets::<H>(
&extracted_ikm,
STR_SESSION_KEY,
hashed_derivation_transcript,
)?;
let km2 = hkdf_expand_label::<H>(&handshake_secret, STR_SERVER_MAC, b"")?;
let km3 = hkdf_expand_label::<H>(&handshake_secret, STR_CLIENT_MAC, b"")?;
Ok(DerivedKeys {
session_key,
km2,
km3,
#[cfg(test)]
handshake_secret,
})
}
/// Helper function for shared functionality in KE2 MAC computation
/// for both `TripleDH` and TripleDH-KEM
pub(super) fn compute_ke2_macs<H: Hash>(
transcript_hasher: &mut H,
derived_keys: &DerivedKeys<H>,
transcript_digest: &[u8],
) -> Result<(Output<H>, Output<H>), ProtocolError>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
let mut mac_hasher =
SimpleHmac::<H>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
Mac::update(&mut mac_hasher, transcript_digest);
let mac = mac_hasher.finalize().into_bytes();
Update::update(transcript_hasher, &mac);
let finalized_transcript = transcript_hasher.clone().finalize();
let mut expected_mac_hasher =
SimpleHmac::<H>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
Mac::update(&mut expected_mac_hasher, &finalized_transcript);
let expected_mac = expected_mac_hasher.finalize().into_bytes();
Ok((mac, expected_mac))
}
/// Finalizes the KE3 transcript by deriving session material from the provided
/// shared secrets and verifying the server's MAC, returning both the derived
/// keys and the client's MAC response. Callers are expected to supply any
/// protocol-specific shared secrets (e.g. classic Diffie-Hellman results or
/// KEM outputs) as byte slices.
pub(super) fn finalize_ke3_transcript<'a, H: Hash>(
transcript_hasher: &mut H,
shared_secrets: impl Iterator<Item = &'a [u8]>,
server_mac: &Output<H>,
) -> Result<(DerivedKeys<H>, Output<H>), ProtocolError>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
let transcript_digest = transcript_hasher.clone().finalize();
let derived_keys = derive_keys::<H>(shared_secrets, &transcript_digest)?;
let mut server_mac_hasher =
SimpleHmac::<H>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
Mac::update(&mut server_mac_hasher, &transcript_digest);
server_mac_hasher
.verify(server_mac)
.map_err(|_| ProtocolError::InvalidLoginError)?;
Update::update(transcript_hasher, server_mac);
let finalized_transcript = transcript_hasher.clone().finalize();
let mut client_mac_hasher =
SimpleHmac::<H>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
Mac::update(&mut client_mac_hasher, &finalized_transcript);
let client_mac = client_mac_hasher.finalize().into_bytes();
Ok((derived_keys, client_mac))
}
fn hkdf_expand_label<H: Hash>(
secret: &[u8],
label: &[u8],
context: &[u8],
) -> Result<Output<H>, ProtocolError>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
let h = Hkdf::<H>::from_prk(secret).map_err(|_| InternalError::HkdfError)?;
hkdf_expand_label_extracted(&h, label, context)
}
fn hkdf_expand_label_extracted<H: Hash>(
hkdf: &Hkdf<H>,
label: &[u8],
context: &[u8],
) -> Result<Output<H>, ProtocolError>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
let mut okm = GenericArray::default().into_ha0_4();
let length = i2osp::<U2>(OutputSize::<H>::USIZE)?;
let label_length = i2osp::<U1>(STR_OPAQUE.len() + label.len())?;
let context_len = i2osp::<U1>(context.len())?;
let hkdf_label = [
length.as_slice(),
&label_length,
STR_OPAQUE,
label,
&context_len,
context,
];
hkdf.expand_multi_info(&hkdf_label, &mut okm)
.map_err(|_| InternalError::HkdfError)?;
Ok(okm)
}
fn derive_secrets<H: Hash>(
hkdf: &Hkdf<H>,
label: &[u8],
hashed_derivation_transcript: &[u8],
) -> Result<Output<H>, ProtocolError>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
hkdf_expand_label_extracted::<H>(hkdf, label, hashed_derivation_transcript)
}
// Serialization and deserialization implementations
impl<G: Group> Deserialize for Ke1State<G> {
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
client_e_sk: PrivateKey::deserialize_take(bytes)?,
client_nonce: bytes.take_array("client nonce")?,
})
}
}
impl<G: Group> Serialize for Ke1State<G>
where
// Ke1State: KeSk + Nonce
G::SkLen: Add<NonceLen>,
Sum<G::SkLen, NonceLen>: ArrayLength,
{
type Len = Sum<G::SkLen, NonceLen>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.client_e_sk.serialize().cat(self.client_nonce)
}
}
impl<G: Group> Deserialize for Ke1Message<G> {
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
client_nonce: input.take_array("client nonce")?,
client_e_pk: PublicKey::deserialize_take(input)?,
})
}
}
impl<G: Group> Serialize for Ke1Message<G>
where
// Ke1Message: Nonce + KePk
NonceLen: Add<G::PkLen>,
Sum<NonceLen, G::PkLen>: ArrayLength,
{
type Len = Sum<NonceLen, G::PkLen>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.client_nonce.cat(self.client_e_pk.serialize())
}
}
impl<G: Group> Ke1Message<G> {
pub(crate) fn to_iter(&self) -> Ke1MessageIter<G> {
Ke1MessageIter {
client_nonce: self.client_nonce,
client_e_pk: self.client_e_pk.serialize(),
}
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
pub(crate) struct Ke1MessageIter<G: Group> {
client_nonce: GenericArray<u8, NonceLen>,
client_e_pk: GenericArray<u8, G::PkLen>,
}
pub(crate) type Ke1MessageIterLen<G: Group> = Sum<NonceLen, G::PkLen>;
impl<G: Group> Ke1MessageIter<G> {
pub(crate) fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
[self.client_nonce.as_slice(), self.client_e_pk.as_slice()].into_iter()
}
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Ke1MessageIter {
client_nonce: input.take_array("client nonce")?,
client_e_pk: input.take_array("client ephemeral public key")?,
})
}
}
impl<G: Group> Ke1MessageIter<G>
where
NonceLen: Add<G::PkLen>,
Ke1MessageIterLen<G>: ArrayLength,
{
pub(crate) fn serialize(&self) -> GenericArray<u8, Ke1MessageIterLen<G>> {
self.client_nonce.cat(self.client_e_pk.clone())
}
}
+143
View File
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! ECDSA implementation for [`elliptic_curve`] [`Group`] implementations to
//! support [`SigmaI`](crate::SigmaI).
use core::marker::PhantomData;
use digest::block_api::{BlockSizeUser, EagerHash};
use digest::{Digest, FixedOutputReset, HashMarker};
use ecdsa::{EcdsaCurve, SignatureSize};
use elliptic_curve::point::NonIdentity;
use elliptic_curve::{CurveArithmetic, FieldBytes, ProjectivePoint, SecretKey};
use generic_array::{ArrayLength, GenericArray};
use hybrid_array::ArraySize;
use rand::{CryptoRng, Rng};
use super::{Message, MessageBuilder, SignatureProtocol};
use crate::ciphersuite::CipherSuite;
use crate::errors::ProtocolError;
use crate::key_exchange::group::Group;
pub use crate::key_exchange::sigma_i::shared::PreHash;
use crate::serialization::SliceExt;
/// ECDSA for [`SigmaI`](crate::SigmaI).
///
/// The ["verification state"](Self::VerifyState) is the pre-hash for the
/// message to be verified.
pub struct Ecdsa<G, H>(PhantomData<(G, H)>);
impl<G, H> SignatureProtocol for Ecdsa<G, H>
where
G: CurveArithmetic
+ Group<Sk = SecretKey<G>, Pk = NonIdentity<ProjectivePoint<G>>>
+ EcdsaCurve,
SignatureSize<G>: ArrayLength + ArraySize,
H: EagerHash + FixedOutputReset + BlockSizeUser + HashMarker + Digest + Clone + Default,
{
type Group = G;
type Signature = ecdsa::Signature<G>;
type SignatureLen = SignatureSize<G>;
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<H>;
// We use a manual implementation of `RandomizedPrehashSigner` to use the same
// hash for the message as for generating `k`. See
// https://github.com/RustCrypto/signatures/issues/949.
fn sign<'a, R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
sk: &<Self::Group as Group>::Sk,
rng: &mut R,
message: &Message<CS, KE>,
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
let hash = message.hash::<H>();
(
sign::<_, G, H>(sk, rng, &hash.sign.finalize_fixed()),
PreHash(hash.verify.finalize_fixed()),
)
}
fn verify<CS: CipherSuite, KE: Group>(
pk: &<Self::Group as Group>::Pk,
_: MessageBuilder<'_, CS>,
state: Self::VerifyState<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError> {
verify(pk, &state.0, signature)
}
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
GenericArray::from_slice(signature.to_bytes().as_slice()).clone()
}
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
ecdsa::Signature::from_bytes(&bytes.take_array("signature")?.into_ha0_4())
.map_err(|_| ProtocolError::SerializationError)
}
}
fn sign<R, C, H>(sk: &SecretKey<C>, rng: &mut R, pre_hash: &[u8]) -> ecdsa::Signature<C>
where
R: CryptoRng + Rng,
C: CurveArithmetic + EcdsaCurve,
SignatureSize<C>: ArraySize,
H: Digest + BlockSizeUser + FixedOutputReset,
{
let mut ad = FieldBytes::<C>::default();
rng.fill_bytes(&mut ad);
ecdsa::hazmat::sign_prehashed_rfc6979::<C, H>(&sk.to_nonzero_scalar(), pre_hash, &ad).0
}
fn verify<C>(
pk: &NonIdentity<ProjectivePoint<C>>,
pre_hash: &[u8],
signature: &ecdsa::Signature<C>,
) -> Result<(), ProtocolError>
where
C: CurveArithmetic + EcdsaCurve,
SignatureSize<C>: ArraySize,
{
ecdsa::hazmat::verify_prehashed(&pk.to_point(), pre_hash, signature)
.map_err(|_| ProtocolError::InvalidLoginError)
}
#[test]
fn ecdsa() {
use std::vec;
use digest::Digest;
use ecdsa::signature::hazmat::PrehashVerifier;
use p256::ecdsa::signature::RandomizedDigestSigner;
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
use p256::{NistP256, PublicKey};
use rand::rand_core::UnwrapErr;
use rand::rngs::SysRng;
use sha2::Sha256;
use crate::tests::mock_rng::CycleRng;
let mut rng = CycleRng::new(vec![1; 32]);
let mut message = [0; 1024];
UnwrapErr(SysRng).fill_bytes(&mut message);
let hash = Sha256::new_with_prefix(message);
let sk = NistP256::random_sk(&mut UnwrapErr(SysRng));
let signing_key = SigningKey::from(sk.clone());
let signature: Signature = signing_key.sign_digest_with_rng(&mut rng, |d: &mut Sha256| {
d.update(message);
});
let custom_signature = sign::<_, _, Sha256>(&sk, &mut rng, &hash.clone().finalize());
assert_eq!(signature, custom_signature);
let pk = NistP256::public_key(&sk);
let verifying_key = VerifyingKey::from(PublicKey::from(&pk));
verifying_key
.verify_prehash(&hash.clone().finalize(), &signature)
.unwrap();
verify(&pk, &hash.finalize(), &custom_signature).unwrap();
}
+84
View File
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! HashEdDSA implementation for [`SigmaI`](crate::SigmaI). Currently only
//! supports [`Ed25519`](crate::Ed25519).
use core::marker::PhantomData;
use generic_array::GenericArray;
use rand::{CryptoRng, Rng};
use zeroize::Zeroize;
use self::implementation::HashEddsaImpl;
use super::{Message, MessageBuilder, SignatureProtocol};
use crate::ciphersuite::CipherSuite;
use crate::errors::ProtocolError;
use crate::key_exchange::group::Group;
/// HashEdDSA for [`SigmaI`](crate::SigmaI).
///
/// The ["verification state"](Self::VerifyState) is the pre-hash for the
/// message to be verified.
pub struct HashEddsa<G>(PhantomData<G>);
impl<G: HashEddsaImpl> SignatureProtocol for HashEddsa<G> {
type Group = G;
type Signature = G::Signature;
type SignatureLen = G::SignatureLen;
type VerifyState<CS: CipherSuite, KE: Group> = G::VerifyState<CS, KE>;
fn sign<'a, R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
sk: &<Self::Group as Group>::Sk,
_: &mut R,
message: &Message<CS, KE>,
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
G::sign(sk, message)
}
fn verify<CS: CipherSuite, KE: Group>(
pk: &<Self::Group as Group>::Pk,
_: MessageBuilder<'_, CS>,
state: Self::VerifyState<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError> {
G::verify(pk, state, signature)
}
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
G::serialize_signature(signature)
}
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
G::deserialize_take_signature(bytes)
}
}
pub(in super::super) mod implementation {
use generic_array::ArrayLength;
use super::*;
pub trait HashEddsaImpl: Group {
type Signature: Clone + Zeroize;
type SignatureLen: ArrayLength;
type VerifyState<CS: CipherSuite, KE: Group>: Clone + Zeroize;
fn sign<CS: CipherSuite, KE: Group>(
sk: &Self::Sk,
message: &Message<CS, KE>,
) -> (Self::Signature, Self::VerifyState<CS, KE>);
fn verify<CS: CipherSuite, KE: Group>(
pk: &Self::Pk,
state: Self::VerifyState<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError>;
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
fn serialize_signature(signature: &Self::Signature)
-> GenericArray<u8, Self::SignatureLen>;
}
}
+307
View File
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use core::ops::Add;
use derive_where::derive_where;
use digest::{FixedOutput, Output, Update};
use generic_array::typenum::Sum;
use generic_array::{ArrayLength, GenericArray};
use zeroize::Zeroize;
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash, OprfGroup};
use crate::errors::ProtocolError;
use crate::hash::OutputSize;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::{Ke1MessageIter, Ke1MessageIterLen, NonceLen};
use crate::key_exchange::{
Deserialize, Serialize, SerializedContext, SerializedCredentialRequest,
SerializedCredentialRequestLen, SerializedCredentialResponse, SerializedCredentialResponseLen,
SerializedIdentifier, SerializedIdentifiers,
};
use crate::opaque::MaskedResponseLen;
use crate::serialization::{ConcatExt, SliceExt, UpdateExt};
/// This holds the message to be signed and the message to be verified.
///
/// If your signature protocol requires pre-hashes, you can call [`hash()`].
///
/// If you require the actual message, call [`sign_message()`]. To get the
/// message to verify, call [`to_cached()`] to create a [`CachedMessage`] and
/// save it in [`SignatureProtocol::VerifyState`], which you can then use in
/// [`SignatureProtocol::verify()`] with [`MessageBuilder`] to create
/// [`VerifyMessage`].
///
/// [`hash()`]: super::Message::hash
/// [`sign_message()`]: super::Message::sign_message
/// [`to_cached()`]: super::Message::to_cached
/// [`SignatureProtocol::sign()`]: super::SignatureProtocol::sign
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(deserialize = "'de: 'a", serialize = ""))
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct Message<'a, CS: CipherSuite, KE: Group> {
pub(super) role: Role,
pub(super) context: SerializedContext<'a>,
pub(super) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
pub(super) cache: CachedMessage<CS, KE>,
}
/// This holds the message to be verified.
///
/// Create it by using [`MessageBuilder::build()`] with [`CachedMessage`].
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(deserialize = "'de: 'a", serialize = ""))
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct VerifyMessage<'a, CS: CipherSuite, KE: Group> {
role: Role,
context: SerializedContext<'a>,
identifier: SerializedIdentifier<'a, KeGroup<CS>>,
pub(super) cache: CachedMessage<CS, KE>,
}
/// Used to build [`VerifyMessage`]. It is only available in
/// [`SignatureProtocol::verify()`].
///
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
#[derive_where(Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct MessageBuilder<'a, CS: CipherSuite> {
pub(super) role: Role,
pub(super) context: SerializedContext<'a>,
pub(super) identifier: SerializedIdentifier<'a, KeGroup<CS>>,
}
/// Created by [`Message::to_cached()`]. This is used to save the message to be
/// verified in [`SignatureProtocol::VerifyState`].
///
/// Use [`MessageBuilder::build()`] to create [`VerifyMessage`] in
/// [`SignatureProtocol::verify()`].
///
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize, ZeroizeOnDrop)]
pub struct CachedMessage<CS: CipherSuite, KE: Group> {
pub(super) credential_request: SerializedCredentialRequest<CS>,
pub(super) ke1_message: Ke1MessageIter<KE>,
pub(super) credential_response: SerializedCredentialResponse<CS>,
pub(super) server_nonce: GenericArray<u8, NonceLen>,
pub(super) server_e_pk: GenericArray<u8, KE::PkLen>,
pub(super) server_mac: Output<KeHash<CS>>,
}
impl<CS: CipherSuite, KE: Group> Message<'_, CS, KE> {
/// Returns the message to be signed.
pub fn sign_message(&self) -> impl Clone + Iterator<Item = &[u8]> {
self.context.iter().chain(self.post_message(Stage::Sign))
}
/// Returns the hash of both messages.
pub fn hash<KEH: Default + Clone + FixedOutput + Update>(&self) -> HashOutput<KEH> {
let mut context = KEH::default();
context.update_iter(self.context.iter());
let sign = context.clone().chain_iter(self.post_message(Stage::Sign));
let verify = context.chain_iter(self.post_message(Stage::Verify));
HashOutput { sign, verify }
}
fn post_message(&self, stage: Stage) -> impl Clone + Iterator<Item = &[u8]> {
let transcript = match (self.role, stage) {
(Role::Server, Stage::Sign) => Role::Server,
(Role::Server, Stage::Verify) => Role::Client,
(Role::Client, Stage::Sign) => Role::Client,
(Role::Client, Stage::Verify) => Role::Server,
};
let identifier = match transcript {
Role::Server => &self.identifiers.server,
Role::Client => &self.identifiers.client,
};
self.cache.post_message(transcript, identifier)
}
/// Create a [`CachedMessage`], which can be saved in
/// [`SignatureProtocol::VerifyState`] and create a [`VerifyMessage`] with
/// [`MessageBuilder::build()`].
///
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
pub fn to_cached(&self) -> CachedMessage<CS, KE> {
self.cache.clone()
}
}
impl<CS: CipherSuite, KE: Group> VerifyMessage<'_, CS, KE> {
/// Returns the message to be verified.
pub fn verify_message(&self) -> impl Clone + Iterator<Item = &[u8]> {
let transcript = match self.role {
Role::Server => Role::Client,
Role::Client => Role::Server,
};
self.context
.iter()
.chain(self.cache.post_message(transcript, &self.identifier))
}
}
impl<CS: CipherSuite, KE: Group> CachedMessage<CS, KE> {
fn post_message<'a>(
&'a self,
transcript: Role,
identifier: &'a SerializedIdentifier<'_, KeGroup<CS>>,
) -> impl Clone + Iterator<Item = &'a [u8]> {
Some(identifier.iter())
.filter(|_| matches!(transcript, Role::Client))
.into_iter()
.flatten()
.chain(self.credential_request.iter())
.chain(self.ke1_message.iter())
.chain(
Some(identifier.iter())
.filter(|_| matches!(transcript, Role::Server))
.into_iter()
.flatten(),
)
.chain(self.credential_response.iter())
.chain([self.server_nonce.as_slice(), &self.server_e_pk])
.chain(Some(self.server_mac.as_slice()).filter(|_| matches!(transcript, Role::Client)))
}
}
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(super) enum Role {
Server,
Client,
}
impl Zeroize for Role {
fn zeroize(&mut self) {
*self = Self::Server;
}
}
enum Stage {
Sign,
Verify,
}
/// Returned by [`Message::hash()`] containing the hash of the message to be
/// signed and the message to be verified.
pub struct HashOutput<H> {
/// The hash of the message to be signed.
pub sign: H,
/// The hash of the message to be verified.
pub verify: H,
}
impl<'a, CS: CipherSuite> MessageBuilder<'a, CS> {
/// Creates a [`VerifyMessage`]. [`CachedMessage`] can be created by
/// [`Message::to_cached()`] and stored in
/// [`SignatureProtocol::VerifyState`].
///
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
pub fn build<KE: Group>(self, cache: CachedMessage<CS, KE>) -> VerifyMessage<'a, CS, KE> {
VerifyMessage {
role: self.role,
context: self.context.clone(),
identifier: self.identifier.clone(),
cache,
}
}
}
impl<CS: CipherSuite, KE: Group> Deserialize for CachedMessage<CS, KE> {
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
credential_request: SerializedCredentialRequest::deserialize_take(input)?,
ke1_message: Ke1MessageIter::deserialize_take(input)?,
credential_response: SerializedCredentialResponse::deserialize_take(input)?,
server_nonce: input.take_array("server nonce")?,
server_e_pk: input.take_array("serialized server ephemeral key")?,
server_mac: input.take_array("server mac")?.into_ha0_4(),
})
}
}
/// Length of [`CachedMessage`].
type CachedMessageLen<CS: CipherSuite, KE: Group> = Sum<
Sum<
Sum<
Sum<
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
SerializedCredentialResponseLen<CS>,
>,
NonceLen,
>,
KE::PkLen,
>,
OutputSize<KeHash<CS>>,
>;
impl<CS: CipherSuite, KE: Group> Serialize for CachedMessage<CS, KE>
where
SerializedCredentialRequestLen<CS>: ArrayLength + Add<Ke1MessageIterLen<KE>>,
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>:
ArrayLength + Add<SerializedCredentialResponseLen<CS>>,
Sum<
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
SerializedCredentialResponseLen<CS>,
>: ArrayLength + Add<NonceLen>,
Sum<
Sum<
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
SerializedCredentialResponseLen<CS>,
>,
NonceLen,
>: ArrayLength + Add<KE::PkLen>,
Sum<
Sum<
Sum<
Sum<SerializedCredentialRequestLen<CS>, Ke1MessageIterLen<KE>>,
SerializedCredentialResponseLen<CS>,
>,
NonceLen,
>,
KE::PkLen,
>: ArrayLength + Add<OutputSize<KeHash<CS>>>,
CachedMessageLen<CS, KE>: ArrayLength,
// Ke1MessageIter
NonceLen: Add<KE::PkLen>,
Ke1MessageIterLen<KE>: ArrayLength,
// CredentialResponseParts
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength + Add<MaskedResponseLen<CS>>,
SerializedCredentialResponseLen<CS>: ArrayLength,
{
type Len = CachedMessageLen<CS, KE>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.credential_request
.serialize()
.cat(self.ke1_message.serialize())
.cat(self.credential_response.serialize())
.cat(self.server_nonce)
.cat(self.server_e_pk.clone())
.cat(GenericArray::from_slice(self.server_mac.as_slice()).clone())
}
}
+597
View File
@@ -0,0 +1,597 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! An implementation of the SIGMA-I key exchange protocol
//!
//! ⚠️ **Warning**: This implementation has not been audited. Use at your own
//! risk!
#[cfg(feature = "ecdsa")]
pub mod ecdsa;
pub mod hash_eddsa;
mod message;
pub mod pure_eddsa;
pub(super) mod shared;
use core::iter;
use core::marker::PhantomData;
use core::ops::Add;
use derive_where::derive_where;
use digest::block_api::{BlockSizeUser, CoreProxy, SmallBlockSizeUser};
use digest::{Mac, Output, OutputSizeUser};
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
use generic_array::{ArrayLength, GenericArray};
use hmac::{KeyInit, SimpleHmac};
use rand::{CryptoRng, Rng};
use subtle::{ConstantTimeEq, CtOption};
use zeroize::Zeroize;
use self::message::Role;
pub use self::message::{CachedMessage, HashOutput, Message, MessageBuilder, VerifyMessage};
use super::{
Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
SerializedIdentifier, SerializedIdentifiers,
};
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash};
use crate::envelope::NonceLen;
use crate::errors::{InternalError, ProtocolError};
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
use crate::key_exchange::shared::{derive_keys, generate_ke1, generate_nonce, transcript};
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
use crate::opaque::Identifiers;
use crate::serialization::{ConcatExt, SliceExt, UpdateExt};
/// The SIGMA-I key exchange implementation
///
/// `SIG` determines the algorithm used for the signature. `KE` determines the
/// algorithm used for establishing the shared secret. `KEH` determines the hash
/// used for the key exchange.
///
/// # Remote Key
///
/// [`ServerLoginBuilder::data()`](crate::ServerLoginBuilder::data()) will
/// return [`Message`].
///
/// [`ServerLoginBuilder::build()`](crate::ServerLoginBuilder::build()) expects
/// a signature from signing the [message](Message::sign_message) with the
/// servers private key, and a ["verification
/// state"](SignatureProtocol::VerifyState).
///
/// To understand what kind of "verification state" is expected here exactly,
/// refer to the documentation of your chosen [`SignatureProtocol`] `SIG`. E.g.
/// [`Ecdsa`](ecdsa::Ecdsa), [`PureEddsa`](pure_eddsa::PureEddsa) or
/// [`HashEddsa`](hash_eddsa::HashEddsa).
pub struct SigmaI<SIG, KE, KEH>(PhantomData<(SIG, KE, KEH)>);
/// Trait to implement for `SIG` used in [`SigmaI`].
///
/// The [`sign()`] and [`verify()`] methods do not function independent of each
/// other. [`sign()`] is always called first and receives a [Message] containing
/// the message for both signing and verifying. A ["verification
/// state"](Self::VerifyState) is created by [`sign()`] and then passed onto
/// [`verify()`].
///
/// The most straightforward implementation would simply store the message for
/// verifying in [`VerifyState`](Self::VerifyState). However, protocols that
/// allow for pre-hashing don't need to store the whole message and can
/// preemptively hash the verification message and only store that instead,
/// getting rid of the much larger message.
///
/// [`sign()`]: Self::sign
/// [`verify()`]: Self::verify
pub trait SignatureProtocol {
/// The [`Group`] used to generate and derive keys.
type Group: Group;
/// The signature.
type Signature: Clone + Zeroize;
/// Length of a serialized [`Signature`](Self::Signature).
type SignatureLen: ArrayLength;
/// The state required to run the verification. This is used to cache the
/// pre-hash for curves that support that, otherwise the [`Message`] to
/// verify is stored via [`CachedMessage`].
type VerifyState<CS: CipherSuite, KE: Group>: Clone + Zeroize;
/// Returns a signature from the given message signed by the given private
/// key.
///
/// [`Message`] contains both signature messages for signing and
/// verification. If you need it again during verification, consider
/// using [`CachedMessage`].
///
/// The returned [`VerifyState`](Self::VerifyState) will be passed to
/// [`verify()`](Self::verify) and must contain the necessary
/// information to verify the incoming signature.
fn sign<R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
sk: &<Self::Group as Group>::Sk,
rng: &mut R,
message: &Message<CS, KE>,
) -> (Self::Signature, Self::VerifyState<CS, KE>);
/// Validates that the signature was created by signing the message with the
/// corresponding private key.
///
/// The [`MessageBuilder`] can be used with [`CachedMessage`] to create
/// [`VerifyMessage`] which contains the message of the given `signature`.
///
/// The `state` is created by [`sign()`](Self::sign()).
fn verify<CS: CipherSuite, KE: Group>(
pk: &<Self::Group as Group>::Pk,
message_builder: MessageBuilder<'_, CS>,
state: Self::VerifyState<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError>;
/// Serialize [`Signature`](Self::Signature) into a fixed-sized byte array.
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen>;
/// Deserialize [`Signature`](Self::Signature) from the given `bytes`.
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
}
/// Builder for the second key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "'de: 'a, <KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>, KE::Pk: \
serde::Deserialize<'de>",
serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize, KE::Pk: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, PartialEq; <KeGroup<CS> as Group>::Pk, KE::Pk)]
pub struct Ke2Builder<'a, CS: CipherSuite, KE: Group> {
transcript: Message<'a, CS, KE>,
server_nonce: GenericArray<u8, NonceLen>,
#[derive_where(skip(Zeroize))]
client_s_pk: PublicKey<KeGroup<CS>>,
#[derive_where(skip(Zeroize))]
server_e_pk: PublicKey<KE>,
expected_mac: Output<KeHash<CS>>,
session_key: Output<KeHash<CS>>,
#[cfg(test)]
handshake_secret: Output<KeHash<CS>>,
#[cfg(test)]
km2: Output<KeHash<CS>>,
}
/// The server state produced after the second key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<SIG::Group as Group>::Pk: serde::Deserialize<'de>, SIG::VerifyState<CS, \
KE>: serde::Deserialize<'de>",
serialize = "<SIG::Group as Group>::Pk: serde::Serialize, SIG::VerifyState<CS, KE>: \
serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, PartialEq; <SIG::Group as Group>::Pk, SIG::VerifyState<CS, KE>)]
pub struct Ke2State<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> {
#[derive_where(skip(Zeroize))]
client_s_pk: PublicKey<SIG::Group>,
session_key: Output<KeHash<CS>>,
verify_state: SIG::VerifyState<CS, KE>,
expected_mac: Output<KeHash<CS>>,
}
/// The second key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "KE::Pk: serde::Deserialize<'de>, SIG::Signature: serde::Deserialize<'de>",
serialize = "KE::Pk: serde::Serialize, SIG::Signature: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KE::Pk, SIG::Signature)]
pub struct Ke2Message<SIG: SignatureProtocol, KE: Group, KEH: Hash>
where
KEH::Core: ProxyHash,
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<KEH>: ArrayLength,
{
server_nonce: GenericArray<u8, NonceLen>,
#[derive_where(skip(Zeroize))]
server_e_pk: PublicKey<KE>,
signature: SIG::Signature,
mac: Output<KEH>,
}
/// The third key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "SIG::Signature: serde::Deserialize<'de>",
serialize = "SIG::Signature: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; SIG::Signature)]
pub struct Ke3Message<SIG: SignatureProtocol, KEH: OutputSizeUser>
where
<KEH as OutputSizeUser>::OutputSize: ArrayLength,
{
signature: SIG::Signature,
mac: Output<KEH>,
}
impl<SIG: SignatureProtocol, KE: 'static + Group, KEH: Hash + BlockSizeUser> KeyExchange
for SigmaI<SIG, KE, KEH>
where
KE::Sk: DiffieHellman<KE>,
KEH::Core: ProxyHash,
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<KEH>: ArrayLength,
{
type Group = SIG::Group;
type Hash = KEH;
type KE1State = Ke1State<KE>;
type KE2State<CS: CipherSuite> = Ke2State<CS, SIG, KE>;
type KE1Message = Ke1Message<KE>;
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<'a, CS, KE>;
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a Message<'a, CS, KE>;
type KE2BuilderInput<CS: CipherSuite> = (SIG::Signature, SIG::VerifyState<CS, KE>);
type KE2Message = Ke2Message<SIG, KE, KEH>;
type KE3Message = Ke3Message<SIG, KEH>;
fn generate_ke1<R: Rng + CryptoRng>(
rng: &mut R,
) -> Result<GenerateKe1Result<Self>, ProtocolError> {
generate_ke1(rng)
}
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
client_s_pk: PublicKey<Self::Group>,
identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
context: SerializedContext<'a>,
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
let server_e = KeyPair::<KE>::derive_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let ke1_message_iter = ke1_message.to_iter();
let server_e_pk = server_e.public().serialize();
let transcript_hasher = transcript(
&context,
&identifiers,
&credential_request,
&ke1_message_iter,
&credential_response,
server_nonce,
&server_e_pk,
);
let shared_secret = server_e
.private()
.ke_diffie_hellman(&ke1_message.client_e_pk);
let derived_keys = derive_keys::<KEH>(
iter::once(shared_secret.as_slice()),
&transcript_hasher.finalize(),
)?;
let mut server_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km2)
.map_err(|_| InternalError::HmacError)?;
server_mac.update_iter(identifiers.server.iter());
let server_mac = server_mac.finalize().into_bytes();
let mut client_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km3)
.map_err(|_| InternalError::HmacError)?;
client_mac.update_iter(identifiers.client.iter());
let client_mac = client_mac.finalize().into_bytes();
let message = Message {
role: Role::Server,
context,
identifiers,
cache: CachedMessage {
credential_request,
ke1_message: ke1_message_iter,
credential_response,
server_nonce,
server_e_pk,
server_mac,
},
};
Ok(Ke2Builder {
transcript: message,
server_nonce,
client_s_pk,
server_e_pk: server_e.public().clone(),
expected_mac: client_mac,
session_key: derived_keys.session_key,
#[cfg(test)]
handshake_secret: derived_keys.handshake_secret,
#[cfg(test)]
km2: derived_keys.km2,
})
}
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
builder: &'a Self::KE2Builder<'_, CS>,
) -> Self::KE2BuilderData<'a, CS> {
&builder.transcript
}
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
builder: &Self::KE2Builder<'_, CS>,
rng: &mut R,
server_s_sk: &PrivateKey<Self::Group>,
) -> Self::KE2BuilderInput<CS> {
server_s_sk.sign::<_, CS, SIG, KE>(rng, &builder.transcript)
}
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
builder: Self::KE2Builder<'_, CS>,
input: Self::KE2BuilderInput<CS>,
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
Ok(GenerateKe2Result {
state: Ke2State {
client_s_pk: builder.client_s_pk.clone(),
session_key: builder.session_key.clone(),
verify_state: input.1,
expected_mac: builder.expected_mac.clone(),
},
message: Ke2Message {
server_nonce: builder.server_nonce,
server_e_pk: builder.server_e_pk.clone(),
signature: input.0,
mac: builder.transcript.cache.server_mac.clone(),
},
#[cfg(test)]
handshake_secret: builder.handshake_secret.clone(),
#[cfg(test)]
km2: builder.km2.clone(),
})
}
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
ke1_state: &Self::KE1State,
ke2_message: Self::KE2Message,
server_s_pk: PublicKey<Self::Group>,
client_s_sk: PrivateKey<Self::Group>,
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
context: SerializedContext<'_>,
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
let ke1_message_iter = ke1_message.to_iter();
let server_e_pk = ke2_message.server_e_pk.serialize();
let transcript_hasher = transcript(
&context,
&identifiers,
&credential_request,
&ke1_message_iter,
&credential_response,
ke2_message.server_nonce,
&server_e_pk,
);
let shared_secret = ke1_state
.client_e_sk
.ke_diffie_hellman(&ke2_message.server_e_pk);
let derived_keys = derive_keys::<KEH>(
iter::once(shared_secret.as_slice()),
&transcript_hasher.finalize(),
)?;
let mut server_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km2)
.map_err(|_| InternalError::HmacError)?;
server_mac.update_iter(identifiers.server.iter());
let server_mac = server_mac.finalize().into_bytes();
bool::from(server_mac.ct_eq(&ke2_message.mac))
.then_some(())
.ok_or(ProtocolError::InvalidLoginError)?;
let mut client_mac = SimpleHmac::<KEH>::new_from_slice(&derived_keys.km3)
.map_err(|_| InternalError::HmacError)?;
client_mac.update_iter(identifiers.client.iter());
let client_mac = client_mac.finalize().into_bytes();
let message = Message {
role: Role::Client,
context: context.clone(),
identifiers: identifiers.clone(),
cache: CachedMessage {
credential_request,
ke1_message: ke1_message_iter,
credential_response,
server_nonce: ke2_message.server_nonce,
server_e_pk,
server_mac,
},
};
let (signature, state) = client_s_sk.sign::<_, CS, SIG, KE>(rng, &message);
server_s_pk.verify::<CS, SIG, KE>(
MessageBuilder {
role: Role::Client,
context,
identifier: identifiers.server,
},
state,
&ke2_message.signature,
)?;
Ok(GenerateKe3Result {
session_key: derived_keys.session_key,
message: Ke3Message {
signature,
mac: client_mac,
},
#[cfg(test)]
handshake_secret: derived_keys.handshake_secret,
#[cfg(test)]
km3: derived_keys.km3,
})
}
fn finish_ke<CS: CipherSuite<KeyExchange = Self>>(
ke2_state: &Self::KE2State<CS>,
ke3_message: Self::KE3Message,
identifiers: Identifiers<'_>,
context: SerializedContext<'_>,
) -> Result<Output<KEH>, ProtocolError> {
ke2_state.client_s_pk.verify::<CS, SIG, KE>(
MessageBuilder {
role: Role::Server,
context,
identifier: SerializedIdentifier::from_identifier(
identifiers.client,
ke2_state.client_s_pk.serialize(),
)?,
},
ke2_state.verify_state.clone(),
&ke3_message.signature,
)?;
CtOption::new(
ke2_state.session_key.clone(),
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
)
.into_option()
.ok_or(ProtocolError::InvalidLoginError)
}
}
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> Deserialize for Ke2State<CS, SIG, KE>
where
SIG::VerifyState<CS, KE>: Deserialize,
OutputSize<KeHash<CS>>: ArrayLength,
{
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
client_s_pk: PublicKey::deserialize_take(input)?,
session_key: input.take_array("session key")?.into_ha0_4(),
verify_state: SIG::VerifyState::<CS, KE>::deserialize_take(input)?,
expected_mac: input.take_array("expected mac")?.into_ha0_4(),
})
}
}
type Ke2StateLen<CS, SIG: SignatureProtocol, KE> = Sum<
Sum<Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>, VerifyStateLen<CS, SIG, KE>>,
OutputSize<KeHash<CS>>,
>;
type VerifyStateLen<CS, SIG: SignatureProtocol, KE> = <SIG::VerifyState<CS, KE> as Serialize>::Len;
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> Serialize for Ke2State<CS, SIG, KE>
where
SIG::VerifyState<CS, KE>: Serialize,
OutputSize<KeHash<CS>>: ArrayLength,
// Ke2State: ((SigPk + Hash) + VerifyState) + Hash
<SIG::Group as Group>::PkLen: Add<OutputSize<KeHash<CS>>>,
Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>:
ArrayLength + Add<VerifyStateLen<CS, SIG, KE>>,
Sum<Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>, VerifyStateLen<CS, SIG, KE>>:
ArrayLength + Add<OutputSize<KeHash<CS>>>,
Ke2StateLen<CS, SIG, KE>: ArrayLength,
{
type Len = Ke2StateLen<CS, SIG, KE>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.client_s_pk
.serialize()
.cat(GenericArray::from_slice(self.session_key.as_slice()).clone())
.cat(self.verify_state.serialize())
.cat(GenericArray::from_slice(self.expected_mac.as_slice()).clone())
}
}
impl<SIG: SignatureProtocol, KE: Group, KEH: Hash> Deserialize for Ke2Message<SIG, KE, KEH>
where
KEH::Core: ProxyHash,
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<KEH>: ArrayLength,
{
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
server_nonce: input.take_array("server nonce")?,
server_e_pk: PublicKey::deserialize_take(input)?,
signature: SIG::deserialize_take_signature(input)?,
mac: input.take_array("mac")?.into_ha0_4(),
})
}
}
impl<SIG: SignatureProtocol, KE: Group, KEH: Hash> Serialize for Ke2Message<SIG, KE, KEH>
where
KEH::Core: ProxyHash,
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<KEH>: ArrayLength,
// Ke2Message: ((Nonce + KePk) + Signature) + Hash
NonceLen: Add<KE::PkLen>,
Sum<NonceLen, KE::PkLen>: ArrayLength + Add<SIG::SignatureLen>,
Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>: ArrayLength + Add<OutputSize<KEH>>,
Sum<Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>, OutputSize<KEH>>: ArrayLength,
{
type Len = Sum<Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>, OutputSize<KEH>>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.server_nonce
.cat(self.server_e_pk.serialize())
.cat(SIG::serialize_signature(&self.signature))
.cat(GenericArray::from_slice(self.mac.as_slice()).clone())
}
}
impl<SIG: SignatureProtocol, KEH: Hash> Deserialize for Ke3Message<SIG, KEH>
where
KEH::Core: ProxyHash,
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<KEH>: ArrayLength,
{
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
signature: SIG::deserialize_take_signature(input)?,
mac: input.take_array("mac")?.into_ha0_4(),
})
}
}
impl<SIG: SignatureProtocol, KEH: Hash> Serialize for Ke3Message<SIG, KEH>
where
KEH::Core: ProxyHash,
<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<KEH as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<KEH>: ArrayLength,
// Ke2Message: Signature + Hash
SIG::SignatureLen: Add<OutputSize<KEH>>,
Sum<SIG::SignatureLen, OutputSize<KEH>>: ArrayLength,
{
type Len = Sum<SIG::SignatureLen, OutputSize<KEH>>;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
SIG::serialize_signature(&self.signature)
.cat(GenericArray::from_slice(self.mac.as_slice()).clone())
}
}
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! PureEdDSA implementation for [`SigmaI`](crate::SigmaI). Currently only
//! supports [`Ed25519`](crate::Ed25519).
use core::marker::PhantomData;
use generic_array::GenericArray;
use rand::{CryptoRng, Rng};
use zeroize::Zeroize;
use self::implementation::PureEddsaImpl;
use super::{Message, MessageBuilder, SignatureProtocol};
use crate::ciphersuite::CipherSuite;
use crate::errors::ProtocolError;
use crate::key_exchange::group::Group;
use crate::key_exchange::sigma_i::CachedMessage;
/// PureEdDSA for [`SigmaI`](crate::SigmaI).
///
/// The ["verification state"](Self::VerifyState) is a [`CachedMessage`],
/// created by calling [`Message::to_cached()`].
pub struct PureEddsa<G>(PhantomData<G>);
impl<G: PureEddsaImpl> SignatureProtocol for PureEddsa<G> {
type Group = G;
type Signature = G::Signature;
type SignatureLen = G::SignatureLen;
type VerifyState<CS: CipherSuite, KE: Group> = CachedMessage<CS, KE>;
fn sign<'a, R: CryptoRng + Rng, CS: CipherSuite, KE: Group>(
sk: &G::Sk,
_: &mut R,
message: &Message<CS, KE>,
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
G::sign(sk, message)
}
fn verify<CS: CipherSuite, KE: Group>(
pk: &G::Pk,
message_builder: MessageBuilder<'_, CS>,
state: Self::VerifyState<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError> {
G::verify(pk, message_builder, state, signature)
}
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
G::serialize_signature(signature)
}
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
G::deserialize_take_signature(bytes)
}
}
pub(in super::super) mod implementation {
use generic_array::ArrayLength;
use super::*;
pub trait PureEddsaImpl: Group {
type Signature: Clone + Zeroize;
type SignatureLen: ArrayLength;
fn sign<CS: CipherSuite, KE: Group>(
sk: &Self::Sk,
message: &Message<CS, KE>,
) -> (Self::Signature, CachedMessage<CS, KE>);
fn verify<CS: CipherSuite, KE: Group>(
pk: &Self::Pk,
message_builder: MessageBuilder<'_, CS>,
state: CachedMessage<CS, KE>,
signature: &Self::Signature,
) -> Result<(), ProtocolError>;
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
fn serialize_signature(signature: &Self::Signature)
-> GenericArray<u8, Self::SignatureLen>;
}
}
+41
View File
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use derive_where::derive_where;
use digest::{Output, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use crate::errors::ProtocolError;
use crate::key_exchange::{Deserialize, Serialize};
use crate::serialization::SliceExt;
/// Pre-hash of the message to be verified.
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[allow(dead_code)]
pub struct PreHash<H: OutputSizeUser>(pub Output<H>);
impl<H: OutputSizeUser> Deserialize for PreHash<H>
where
H::OutputSize: ArrayLength,
{
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self(input.take_array("pre-hash")?.into_ha0_4()))
}
}
impl<H: OutputSizeUser> Serialize for PreHash<H>
where
H::OutputSize: ArrayLength,
{
type Len = H::OutputSize;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
GenericArray::from_slice(self.0.as_slice()).clone()
}
}
-64
View File
@@ -1,64 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::{PakeError, ProtocolError},
group::Group,
hash::Hash,
keypair::Key,
};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
pub trait KeyExchange<D: Hash, G: Group> {
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError>;
fn ke2_message_size() -> usize;
}
pub trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
}
+415 -502
View File
@@ -1,538 +1,451 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! An implementation of the Triple Diffie-Hellman key exchange protocol
use crate::{
errors::{
utils::{check_slice_size, check_slice_size_atleast},
InternalPakeError, PakeError, ProtocolError,
},
group::Group,
hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytesExt},
serialization::{serialize, tokenize},
use core::marker::PhantomData;
use core::ops::Add;
use derive_where::derive_where;
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
use digest::{Output, OutputSizeUser};
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, Rng};
use subtle::{ConstantTimeEq, CtOption};
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::{
Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
SerializedIdentifiers,
};
use digest::{Digest, FixedOutput};
use generic_array::{
typenum::{Unsigned, U32},
ArrayLength, GenericArray,
};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use crate::ciphersuite::{CipherSuite, KeGroup};
use crate::errors::ProtocolError;
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::{self, NonceLen};
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
use crate::keypair::{PrivateKey, PublicKey};
use crate::opaque::Identifiers;
use crate::serialization::{ConcatExt, SliceExt};
use std::convert::TryFrom;
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
const KEY_LEN: usize = 32;
pub(crate) type NonceLen = U32;
static STR_3DH: &[u8] = b"3DH";
static STR_CLIENT_MAC: &[u8] = b"client mac";
static STR_HANDSHAKE_SECRET: &[u8] = b"handshake secret";
static STR_SERVER_MAC: &[u8] = b"server mac";
static STR_HANDSHAKE_ENC: &[u8] = b"handshake enc";
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
static STR_SESSION_SECRET: &[u8] = b"session secret";
static STR_OPAQUE: &[u8] = b"OPAQUE ";
#[allow(clippy::upper_case_acronyms)]
/// The Triple Diffie-Hellman key exchange implementation
pub struct TripleDH;
///
/// # Remote Key
///
/// [`ServerLoginBuilder::data()`](crate::ServerLoginBuilder::data()) will
/// return the client's ephemeral public key.
///
/// [`ServerLoginBuilder::build()`](crate::ServerLoginBuilder::build()) expects
/// a shared secret computed through Diffie-Hellman from the servers private key
/// and the given public key.
pub struct TripleDh<G, H>(PhantomData<(G, H)>);
impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
type KE1State = Ke1State;
type KE2State = Ke2State<<D as FixedOutput>::OutputSize>;
type KE1Message = Ke1Message;
type KE2Message = Ke2Message<<D as FixedOutput>::OutputSize>;
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyPair::<G>::generate_random(rng);
let client_nonce = generate_nonce::<R>(rng);
let ke1_message = Ke1Message {
client_nonce,
info,
client_e_pk: client_e_kp.public().clone(),
};
Ok((
Ke1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce,
},
ke1_message,
))
}
#[allow(clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
serialized_credential_request: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyPair::<G>::generate_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let mut transcript_hasher = D::new()
.chain(STR_3DH)
.chain(&serialize(&id_u, 2))
.chain(&serialized_credential_request[..])
.chain(&serialize(&id_s, 2))
.chain(&l2_bytes[..])
.chain(&server_nonce[..])
.chain(&server_e_kp.public().to_arr());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke1_message.client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: ke1_message.client_e_pk,
sk2: server_s_sk,
pk3: client_s_pk,
sk3: server_e_kp.private().clone(),
},
&transcript_hasher.clone().finalize(),
)?;
// Compute encryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = encryption_pad
.iter()
.zip(e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
transcript_hasher.update(&serialize(&ciphertext, 2));
let mut mac_hasher =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac_hasher.update(&transcript_hasher.clone().finalize());
let mac = mac_hasher.finalize().into_bytes();
transcript_hasher.update(&mac);
Ok((
ke1_message.info,
Ke2State {
km3,
hashed_transcript: transcript_hasher.finalize(),
session_key,
},
Ke2Message {
server_nonce,
server_e_pk: server_e_kp.public().clone(),
e_info: ciphertext,
mac,
},
))
}
#[allow(clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
let mut transcript_hasher = D::new()
.chain(STR_3DH)
.chain(&serialize(&id_u, 2))
.chain(&serialized_credential_request)
.chain(&serialize(&id_s, 2))
.chain(&l2_component[..])
.chain(&ke2_message.to_bytes_without_info_or_mac());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk,
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk,
},
&transcript_hasher.clone().finalize(),
)?;
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2));
let mut server_mac =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&transcript_hasher.clone().finalize());
if ke2_message.mac != server_mac.finalize().into_bytes() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
transcript_hasher.update(ke2_message.mac.to_vec());
let mut client_mac =
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&transcript_hasher.finalize());
// Compute decryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let plaintext: Vec<u8> = encryption_pad
.iter()
.zip(ke2_message.e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok((
plaintext,
session_key.to_vec(),
Ke3Message {
mac: client_mac.finalize().into_bytes(),
},
))
}
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript);
if ke3_message.mac != client_mac.finalize().into_bytes() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.session_key.to_vec())
}
fn ke2_message_size() -> usize {
NonceLen::to_usize() + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
}
}
/// The client state produced after the first key exchange message
#[derive(PartialEq, Eq)]
pub struct Ke1State {
client_e_sk: Key,
client_nonce: GenericArray<u8, NonceLen>,
}
/// The first key exchange message
#[derive(PartialEq, Eq)]
pub struct Ke1Message {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) info: Vec<u8>,
pub(crate) client_e_pk: Key,
}
impl TryFrom<&[u8]> for Ke1State {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
),
})
}
}
impl ToBytes for Ke1State {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output
}
}
impl ToBytes for Ke1Message {
fn to_bytes(&self) -> Vec<u8> {
[
&self.client_nonce[..],
&serialize(&self.info, 2),
&self.client_e_pk.to_arr(),
]
.concat()
}
}
impl TryFrom<&[u8]> for Ke1Message {
type Error = PakeError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_nonce =
check_slice_size_atleast(ke1_message_bytes, nonce_len, "ke1_message nonce")?;
let (info, remainder) = tokenize(&checked_nonce[nonce_len..], 2)?;
let checked_client_e_pk = check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
info,
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
})
}
}
/// The server state produced after the second key exchange message
pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>,
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
pub struct Ke2State<H: OutputSizeUser> {
pub(super) session_key: Output<H>,
pub(super) expected_mac: Output<H>,
}
/// Builder for the second key exchange message
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "H: serde::Deserialize<'de>, PublicKey<G>: serde::Deserialize<'de>",
serialize = "H: serde::Serialize, PublicKey<G>: serde::Serialize",
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, PartialEq; H, PublicKey<G>)]
pub struct Ke2Builder<G: Group, H: Hash>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
server_nonce: GenericArray<u8, NonceLen>,
transcript_hasher: H,
client_e_pk: PublicKey<G>,
server_e_pk: PublicKey<G>,
shared_secret_1: GenericArray<u8, G::PkLen>,
shared_secret_3: GenericArray<u8, G::PkLen>,
}
/// The second key exchange message
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: Key,
e_info: Vec<u8>,
mac: GenericArray<u8, HashLen>,
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Pk: serde::Deserialize<'de>",
serialize = "G::Pk: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
pub struct Ke2Message<G: Group, H: Hash>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
pub(super) server_nonce: GenericArray<u8, NonceLen>,
#[derive_where(skip(Zeroize))]
pub(super) server_e_pk: PublicKey<G>,
pub(super) mac: Output<H>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat()
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
type Error = PakeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
let hash_len = HashLen::to_usize();
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
hashed_transcript: GenericArray::clone_from_slice(
&checked_bytes[hash_len..2 * hash_len],
),
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
})
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.to_bytes_without_info_or_mac(),
&serialize(&self.e_info, 2),
&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>> TryFrom<&[u8]> for Ke2Message<HashLen> {
type Error = PakeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
let checked_server_e_pk = check_slice_size_atleast(
&checked_nonce[nonce_len..],
KEY_LEN,
"ke2_message server_e_pk",
)?;
let (e_info, remainder) = tokenize(&checked_server_e_pk[KEY_LEN..], 2)?;
let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: Key::from_bytes(&checked_server_e_pk[..KEY_LEN])?,
e_info,
mac: GenericArray::clone_from_slice(&checked_mac),
})
}
}
#[allow(clippy::upper_case_acronyms)]
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: Key,
sk1: Key,
pk2: Key,
sk2: Key,
pk3: Key,
sk3: Key,
}
#[allow(clippy::upper_case_acronyms)]
// Consists of a session key, followed by two mac keys and an encryption key: (session_key, km2, ke2, km3)
type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
);
/// The third key exchange message
pub struct Ke3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>,
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
pub struct Ke3Message<H: Hash>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
pub(super) mac: Output<H>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
impl<G: Group + 'static, H: Hash> KeyExchange for TripleDh<G, H>
where
G::Sk: DiffieHellman<G>,
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
type Group = G;
type Hash = H;
type KE1State = Ke1State<G>;
type KE2State<CS: CipherSuite> = Ke2State<H>;
type KE1Message = Ke1Message<G>;
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<G, H>;
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a PublicKey<G>;
type KE2BuilderInput<CS: CipherSuite> = GenericArray<u8, G::PkLen>;
type KE2Message = Ke2Message<G, H>;
type KE3Message = Ke3Message<H>;
fn generate_ke1<R: Rng + CryptoRng>(
rng: &mut R,
) -> Result<GenerateKe1Result<Self>, ProtocolError> {
shared::generate_ke1(rng)
}
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
rng: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
client_s_pk: PublicKey<G>,
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
context: SerializedContext<'a>,
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
let shared::Ke2BuilderCommon {
server_nonce,
transcript_hasher,
client_e_pk,
server_e_pk,
shared_secret_1,
shared_secret_3,
} = shared::ke2_builder_common::<G, H, CS, R>(
rng,
credential_request,
ke1_message,
credential_response,
client_s_pk,
identifiers,
context,
)?;
Ok(Ke2Builder {
server_nonce,
transcript_hasher,
client_e_pk,
server_e_pk,
shared_secret_1,
shared_secret_3,
})
}
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
builder: &'a Self::KE2Builder<'_, CS>,
) -> Self::KE2BuilderData<'a, CS> {
&builder.client_e_pk
}
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
builder: &Self::KE2Builder<'_, CS>,
_: &mut R,
server_s_sk: &PrivateKey<G>,
) -> Self::KE2BuilderInput<CS> {
server_s_sk.ke_diffie_hellman(&builder.client_e_pk)
}
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
mut builder: Self::KE2Builder<'_, CS>,
shared_secret_2: Self::KE2BuilderInput<CS>,
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
let transcript_digest = builder.transcript_hasher.clone().finalize();
let derived_keys = shared::derive_keys::<H>(
[
builder.shared_secret_1.as_slice(),
&shared_secret_2,
&builder.shared_secret_3,
]
.into_iter(),
&transcript_digest,
)?;
let (mac, expected_mac) = shared::compute_ke2_macs(
&mut builder.transcript_hasher,
&derived_keys,
&transcript_digest,
)?;
Ok(GenerateKe2Result {
state: Ke2State {
session_key: derived_keys.session_key,
expected_mac,
},
message: Ke2Message {
server_nonce: builder.server_nonce,
server_e_pk: builder.server_e_pk.clone(),
mac,
},
#[cfg(test)]
handshake_secret: derived_keys.handshake_secret,
#[cfg(test)]
km2: derived_keys.km2,
})
}
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
_: &mut R,
credential_request: SerializedCredentialRequest<CS>,
ke1_message: Self::KE1Message,
credential_response: SerializedCredentialResponse<CS>,
ke1_state: &Self::KE1State,
ke2_message: Self::KE2Message,
server_s_pk: PublicKey<G>,
client_s_sk: PrivateKey<G>,
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
context: SerializedContext<'_>,
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
let mut transcript_hasher = shared::transcript(
&context,
&identifiers,
&credential_request,
&ke1_message.to_iter(),
&credential_response,
ke2_message.server_nonce,
&ke2_message.server_e_pk.serialize(),
);
let shared_secret_1 = ke1_state
.client_e_sk
.ke_diffie_hellman(&ke2_message.server_e_pk);
let shared_secret_2 = ke1_state.client_e_sk.ke_diffie_hellman(&server_s_pk);
let shared_secret_3 = client_s_sk.ke_diffie_hellman(&ke2_message.server_e_pk);
let (derived_keys, client_mac) = shared::finalize_ke3_transcript(
&mut transcript_hasher,
[
shared_secret_1.as_slice(),
shared_secret_2.as_slice(),
shared_secret_3.as_slice(),
]
.into_iter(),
&ke2_message.mac,
)?;
Ok(GenerateKe3Result {
session_key: derived_keys.session_key,
message: Ke3Message { mac: client_mac },
#[cfg(test)]
handshake_secret: derived_keys.handshake_secret,
#[cfg(test)]
km3: derived_keys.km3,
})
}
fn finish_ke<CS: CipherSuite>(
ke2_state: &Self::KE2State<CS>,
ke3_message: Self::KE3Message,
_: Identifiers<'_>,
_: SerializedContext<'_>,
) -> Result<Output<H>, ProtocolError> {
CtOption::new(
ke2_state.session_key.clone(),
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
)
.into_option()
.ok_or(ProtocolError::InvalidLoginError)
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke3Message<HashLen> {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&bytes, HashLen::to_usize(), "ke3_message")?;
////////////////////////////////////////////////
// Trait Implementations //
// ========================================== //
////////////////////////////////////////////////
impl<H: Hash> Deserialize for Ke2State<H>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
{
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
mac: GenericArray::clone_from_slice(&checked_bytes),
session_key: input.take_array("session key")?.into_ha0_4(),
expected_mac: input.take_array("expected mac")?.into_ha0_4(),
})
}
}
// Helper functions
impl<H: Hash> Serialize for Ke2State<H>
where
H::Core: ProxyHash,
<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
OutputSize<H>: ArrayLength,
// Ke2State: Hash + Hash
OutputSize<H>: Add<OutputSize<H>>,
Sum<OutputSize<H>, OutputSize<H>>: ArrayLength,
{
type Len = Sum<OutputSize<H>, OutputSize<H>>;
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the session key and two MAC keys
fn derive_3dh_keys<D: Hash, G: Group>(
dh: TripleDHComponents,
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();
fn serialize(&self) -> GenericArray<u8, Self::Len> {
let sk: GenericArray<u8, OutputSize<H>> =
GenericArray::from_slice(self.session_key.as_slice()).clone();
let mac: GenericArray<u8, OutputSize<H>> =
GenericArray::from_slice(self.expected_mac.as_slice()).clone();
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>(
&extracted_ikm,
&STR_HANDSHAKE_SECRET,
&hashed_derivation_transcript,
)?;
let session_key = derive_secrets::<D>(
&extracted_ikm,
&STR_SESSION_SECRET,
&hashed_derivation_transcript,
)?;
let km2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let ke2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_HANDSHAKE_ENC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let km3 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_CLIENT_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
Ok((
GenericArray::clone_from_slice(&session_key),
GenericArray::clone_from_slice(&km2),
GenericArray::clone_from_slice(&ke2),
GenericArray::clone_from_slice(&km3),
))
sk.cat(mac)
}
}
fn hkdf_expand_label<D: Hash>(
secret: &[u8],
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalPakeError::HkdfError)?;
hkdf_expand_label_extracted(&h, label, context, length)
/// 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_extracted<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let mut okm = vec![0u8; length];
let mut hkdf_label: Vec<u8> = Vec::new();
hkdf_label.extend_from_slice(&length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
let mut opaque_label: Vec<u8> = Vec::new();
opaque_label.extend_from_slice(&STR_OPAQUE);
opaque_label.extend_from_slice(&label);
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1));
hkdf_label.extend_from_slice(&serialize(&context, 1));
hkdf.expand(&hkdf_label, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok(okm)
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 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<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(),
})
}
}
// 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)
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()
}
}
+743
View File
@@ -0,0 +1,743 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! TripleDH-KEM is a variant of the OPAQUE Triple Diffie-Hellman handshake in
//! which the client supplies a KEM public key in KE1 and the server performs a
//! KEM encapsulation in KE2 instead of relying solely on the final Diffie-
//! Hellman hop. The server bundles the KEM ciphertext alongside the classic
//! `TripleDH` payload, both parties absorb the ciphertext into the transcript
//! and mix the encapsulated shared secret with the three Diffie-Hellman
//! products when deriving handshake keys, and the client decapsulates during
//! KE3 to recover that shared secret before validating the server MAC. This
//! file contains the data model and trait glue that layer
//! the generic `ml-kem` abstractions into the existing OPAQUE key-exchange
//! pipeline.
use core::fmt::Debug;
use core::marker::PhantomData;
use core::ops::Add;
use derive_where::derive_where;
use digest::Output;
use digest::block_api::{CoreProxy, SmallBlockSizeUser};
use generic_array::typenum::{Cmp, IsLess, Le, NonZero, Sum, U256};
use generic_array::{ArrayLength, GenericArray};
use hybrid_array::ArraySize;
#[allow(deprecated)]
use ml_kem::ExpandedKeyEncoding;
use ml_kem::kem::{
Ciphertext as MlKemCiphertext, Decapsulate, Encapsulate, Kem as MlKemTrait, KeyExport,
KeySizeUser, TryKeyInit,
};
use rand::{CryptoRng, Rng};
use subtle::{ConstantTimeEq, CtOption};
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::shared::{self, Ke1Message, Ke1State, NonceLen};
use super::{
Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
SerializedIdentifiers,
};
use crate::ciphersuite::{CipherSuite, KeGroup};
use crate::errors::ProtocolError;
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
use crate::keypair::{PrivateKey, PublicKey};
use crate::opaque::Identifiers;
use crate::serialization::{ConcatExt, SliceExt};
/// Adapter trait that augments the `ml-kem` core traits with the metadata
/// required by OPAQUE (e.g. fixed lengths and serialization hooks).
pub trait KemCoreWrapper {
/// Public key type used for encapsulation operations.
type EncapsulationKey: Clone;
/// Secret key type used for decapsulation operations.
type DecapsulationKey: Clone + ZeroizeOnDrop;
/// Length (in bytes) of the serialized public key.
type EncapsulationKeyLen: ArrayLength + ArraySize;
/// Length (in bytes) of the serialized secret key.
type DecapsulationKeyLen: ArrayLength + ArraySize;
/// Length (in bytes) of the encapsulated ciphertext.
type CiphertextLen: ArrayLength + ArraySize;
/// Length (in bytes) of the shared secret output by the KEM.
type SharedSecretLen: ArrayLength + ArraySize;
/// Generates a fresh KEM key pair.
fn generate<R: Rng + CryptoRng>(
rng: &mut R,
) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError>;
/// Serializes the public encapsulation key.
fn serialize_encapsulation_key(
key: &Self::EncapsulationKey,
) -> GenericArray<u8, Self::EncapsulationKeyLen>;
/// Deserializes the public encapsulation key, advancing the input slice.
fn deserialize_encapsulation_key(
input: &mut &[u8],
) -> Result<Self::EncapsulationKey, ProtocolError>;
/// Serializes the secret decapsulation key.
fn serialize_decapsulation_key(
key: &Self::DecapsulationKey,
) -> GenericArray<u8, Self::DecapsulationKeyLen>;
/// Deserializes the secret decapsulation key, advancing the input slice.
fn deserialize_decapsulation_key(
input: &mut &[u8],
) -> Result<Self::DecapsulationKey, ProtocolError>;
/// Encapsulates to the given public key, returning the ciphertext and
/// shared secret.
#[allow(clippy::type_complexity)]
fn encapsulate<R: Rng + CryptoRng>(
key: &Self::EncapsulationKey,
rng: &mut R,
) -> Result<
(
GenericArray<u8, Self::CiphertextLen>,
GenericArray<u8, Self::SharedSecretLen>,
),
ProtocolError,
>;
/// Decapsulates the shared secret from the provided ciphertext.
fn decapsulate(
key: &Self::DecapsulationKey,
encapsulated_key: &GenericArray<u8, Self::CiphertextLen>,
) -> Result<GenericArray<u8, Self::SharedSecretLen>, ProtocolError>;
}
/// Adapter to bridge `rand 0.8` (`rand_core 0.6`) RNGs to `rand_core 0.10`
/// which is required by `ml-kem 0.3.x`.
struct RngCompat<'a, R>(&'a mut R);
impl<R: Rng> rand::rand_core::TryRng for RngCompat<'_, R> {
type Error = core::convert::Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
Ok(self.0.next_u32())
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
Ok(self.0.next_u64())
}
fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
self.0.fill_bytes(dst);
Ok(())
}
}
impl<R: Rng + CryptoRng> rand::rand_core::TryCryptoRng for RngCompat<'_, R> {}
type RcEncapsulationKeyLen<K> = <<K as MlKemTrait>::EncapsulationKey as KeySizeUser>::KeySize;
#[allow(deprecated)]
type RcDecapsulationKeyLen<K> =
<<K as MlKemTrait>::DecapsulationKey as ExpandedKeyEncoding>::EncodedSize;
type RcCiphertextLen<K> = <K as MlKemTrait>::CiphertextSize;
type RcSharedSecretLen<K> = <K as MlKemTrait>::SharedKeySize;
#[allow(deprecated)]
impl<K> KemCoreWrapper for K
where
K: MlKemTrait,
K::EncapsulationKey: Encapsulate<Kem = K> + KeyExport + TryKeyInit + Clone,
K::DecapsulationKey: Decapsulate<Kem = K> + ExpandedKeyEncoding + Clone + ZeroizeOnDrop,
RcEncapsulationKeyLen<K>: ArrayLength + ArraySize,
RcDecapsulationKeyLen<K>: ArrayLength + ArraySize,
RcCiphertextLen<K>: ArrayLength + ArraySize,
RcSharedSecretLen<K>: ArrayLength + ArraySize,
{
type EncapsulationKey = K::EncapsulationKey;
type DecapsulationKey = K::DecapsulationKey;
type EncapsulationKeyLen = RcEncapsulationKeyLen<K>;
type DecapsulationKeyLen = RcDecapsulationKeyLen<K>;
type CiphertextLen = RcCiphertextLen<K>;
type SharedSecretLen = RcSharedSecretLen<K>;
fn generate<R: Rng + CryptoRng>(
rng: &mut R,
) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError> {
Ok(K::generate_keypair_from_rng(&mut RngCompat(rng)))
}
fn serialize_encapsulation_key(
key: &Self::EncapsulationKey,
) -> GenericArray<u8, Self::EncapsulationKeyLen> {
GenericArray::from_slice(key.to_bytes().as_slice()).clone()
}
fn deserialize_encapsulation_key(
input: &mut &[u8],
) -> Result<Self::EncapsulationKey, ProtocolError> {
let bytes: GenericArray<u8, RcEncapsulationKeyLen<K>> =
input.take_array("kem encapsulation key")?;
let key = ml_kem::array::Array::try_from(bytes.as_slice())
.map_err(|_| ProtocolError::SerializationError)?;
TryKeyInit::new(&key).map_err(|_| ProtocolError::SerializationError)
}
fn serialize_decapsulation_key(
key: &Self::DecapsulationKey,
) -> GenericArray<u8, Self::DecapsulationKeyLen> {
GenericArray::from_slice(key.to_expanded_bytes().as_slice()).clone()
}
fn deserialize_decapsulation_key(
input: &mut &[u8],
) -> Result<Self::DecapsulationKey, ProtocolError> {
let bytes: GenericArray<u8, RcDecapsulationKeyLen<K>> =
input.take_array("kem decapsulation key")?;
let key = ml_kem::array::Array::try_from(bytes.as_slice())
.map_err(|_| ProtocolError::SerializationError)?;
K::DecapsulationKey::from_expanded_bytes(&key)
.map_err(|_| ProtocolError::SerializationError)
}
fn encapsulate<R: Rng + CryptoRng>(
key: &Self::EncapsulationKey,
rng: &mut R,
) -> Result<
(
GenericArray<u8, Self::CiphertextLen>,
GenericArray<u8, Self::SharedSecretLen>,
),
ProtocolError,
> {
let (ciphertext, shared) = key.encapsulate_with_rng(&mut RngCompat(rng));
Ok((
GenericArray::from_slice(ciphertext.as_slice()).clone(),
GenericArray::from_slice(shared.as_slice()).clone(),
))
}
fn decapsulate(
key: &Self::DecapsulationKey,
encapsulated_key: &GenericArray<u8, Self::CiphertextLen>,
) -> Result<GenericArray<u8, Self::SharedSecretLen>, ProtocolError> {
let ciphertext = MlKemCiphertext::<K>::try_from(encapsulated_key.as_slice())
.map_err(|_| ProtocolError::SerializationError)?;
let shared = key.decapsulate(&ciphertext);
Ok(GenericArray::from_slice(shared.as_slice()).clone())
}
}
/// Triple Diffie-Hellman-style key exchange that offloads the second hop to a
/// generic KEM.
#[derive(Clone, Debug)]
pub struct TripleDhKem<G, H, K>(PhantomData<(G, H, K)>);
/// Client state combining the classic `TripleDH` state with a KEM secret key.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "Ke1State<G>: serde::Deserialize<'de>, K::DecapsulationKey: \
serde::Deserialize<'de>",
serialize = "Ke1State<G>: serde::Serialize, K::DecapsulationKey: serde::Serialize",
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; Ke1State<G>, K::DecapsulationKey)]
pub struct KemKe1State<G: Group, K: KemCoreWrapper> {
dh_state: Ke1State<G>,
kem_decapsulation_key: K::DecapsulationKey,
}
/// Client message including the ephemeral Diffie-Hellman component alongside a
/// serialized KEM public key.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "Ke1Message<G>: serde::Deserialize<'de>",
serialize = "Ke1Message<G>: serde::Serialize",
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; Ke1Message<G>)]
pub struct KemKe1Message<G: Group, K: KemCoreWrapper> {
dh_message: Ke1Message<G>,
kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
}
/// Server state mirrors the `TripleDH` state and carries the 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())
}
}
+474 -143
View File
@@ -1,185 +1,516 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Contains the keypair types that must be supplied for the OPAQUE API
use crate::errors::InternalPakeError;
use crate::group::Group;
use generic_array::{typenum::U32, GenericArray};
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
#[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;
#![allow(unsafe_code)]
/// 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))
}
}
use derive_where::derive_where;
use digest::{Output, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, Rng};
// blanket implementation
impl<T> SizedBytesExt for T where T: SizedBytes {}
use crate::ciphersuite::CipherSuite;
use crate::errors::ProtocolError;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::DiffieHellman;
use crate::key_exchange::sigma_i::{Message, MessageBuilder, SignatureProtocol};
use crate::serialization::SliceExt;
/// A Keypair trait with public-private verification
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyPair<G> {
pk: Key,
sk: Key,
_g: PhantomData<G>,
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Pk: serde::Deserialize<'de>, SK: serde::Deserialize<'de>",
serialize = "G::Pk: serde::Serialize, SK: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk, SK)]
pub struct KeyPair<G: Group, SK: Clone = PrivateKey<G>> {
pk: PublicKey<G>,
sk: SK,
}
impl<G: Group> KeyPair<G> {
impl<G: Group, SK: Clone> KeyPair<G, SK> {
/// Creates a new [`KeyPair`] from the given keys.
pub fn new(sk: SK, pk: PublicKey<G>) -> Self {
Self { pk, sk }
}
/// The public key component
pub fn public(&self) -> &Key {
pub fn public(&self) -> &PublicKey<G> {
&self.pk
}
/// The private key component
pub fn private(&self) -> &Key {
pub fn private(&self) -> &SK {
&self.sk
}
/// A constructor that receives public and private key independently as
/// bytes
pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
Ok(Self {
pk: public,
sk: private,
_g: PhantomData,
})
}
/// Generating a random key pair given a cryptographic rng
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_scalar(rng);
let sk_bytes = G::scalar_as_bytes(&sk);
let pk = G::base_point().mult_by_slice(&sk_bytes);
Self {
pk: Key(pk.to_arr().to_vec()),
sk: Key(sk_bytes.to_vec()),
_g: PhantomData,
}
}
/// Obtaining a public key from secret bytes. At all times, we should have
/// &public_from_private(self.private()) == self.public()
pub(crate) fn public_from_private(bytes: &Key) -> Key {
let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]);
Key(G::base_point().mult_by_slice(&bytes_data).to_arr().to_vec())
}
/// Check whether a public key is valid. This is meant to be applied on
/// material provided through the network which fits the key
/// representation (i.e. can be mapped to a curve point), but presents
/// some risk - e.g. small subgroup check
pub(crate) fn check_public_key(key: Key) -> Result<Key, InternalPakeError> {
G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key)
}
/// Computes the diffie hellman function on a public key and private key
pub(crate) fn diffie_hellman(pk: Key, sk: Key) -> Result<Vec<u8>, InternalPakeError> {
let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]);
let point = G::from_element_slice(&pk_data)?;
let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]);
Ok(G::mult_by_slice(&point, &secret_data).to_arr().to_vec())
}
/// Obtains a KeyPair from a slice representing the private key
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
let sk = Key::from_arr(GenericArray::from_slice(&input))?;
let pk = Self::public_from_private(&sk);
Self::new(pk, sk)
}
}
impl<G: Group> KeyPair<G> {
pub(crate) fn random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_sk(rng);
let pk = G::public_key(&sk);
Self {
pk: PublicKey(pk),
sk: PrivateKey(sk),
}
}
/// Generating a random key pair given a cryptographic rng
pub(crate) fn derive_random<R: Rng + CryptoRng>(rng: &mut R) -> Self {
let mut scalar_bytes = GenericArray::<_, <G as Group>::SkLen>::default();
rng.fill_bytes(&mut scalar_bytes);
let sk = G::derive_scalar(scalar_bytes).unwrap();
let pk = G::public_key(&sk);
Self {
pk: PublicKey(pk),
sk: PrivateKey(sk),
}
}
}
/// Wrapper around a Key to enforce that it's a private one.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Sk: serde::Deserialize<'de>",
serialize = "G::Sk: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)]
pub struct PrivateKey<G: Group>(G::Sk);
impl<G: Group> PrivateKey<G> {
pub(crate) fn new(key: G::Sk) -> Self {
Self(key)
}
/// Returns public key from private key
pub fn public_key(&self) -> PublicKey<G> {
PublicKey(G::public_key(&self.0))
}
/// Serializes this private key to a fixed-length byte array.
pub fn serialize(&self) -> GenericArray<u8, G::SkLen> {
G::serialize_sk(&self.0)
}
/// Creates a [`PrivateKey`] from the given bytes.
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut input)
}
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
G::deserialize_take_sk(input).map(Self)
}
}
impl<G: Group> PrivateKey<G>
where
G::Sk: DiffieHellman<G>,
{
/// Diffie-Hellman key exchange implementation
pub(crate) fn ke_diffie_hellman(&self, pk: &PublicKey<G>) -> GenericArray<u8, G::PkLen> {
self.0.diffie_hellman(&pk.0)
}
}
impl<G: Group> PrivateKey<G> {
/// Private-key signing implementation
pub(crate) fn sign<
R: CryptoRng + Rng,
CS: CipherSuite,
SIG: SignatureProtocol<Group = G>,
KE: Group,
>(
&self,
rng: &mut R,
message: &Message<CS, KE>,
) -> (SIG::Signature, SIG::VerifyState<CS, KE>) {
SIG::sign(&self.0, rng, message)
}
}
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
pub trait PrivateKeySerialization<G: Group>: Clone {
/// Custom error type that can be passed down to `ProtocolError::Custom`
type Error;
/// Serialization size in bytes.
type Len: ArrayLength;
/// Serialization into bytes
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take_key_pair(
bytes: &mut &[u8],
) -> Result<KeyPair<G, Self>, ProtocolError<Self::Error>>;
}
impl<G: Group> PrivateKeySerialization<G> for PrivateKey<G> {
type Error = core::convert::Infallible;
type Len = G::SkLen;
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len> {
key_pair.private().serialize()
}
fn deserialize_take_key_pair(input: &mut &[u8]) -> Result<KeyPair<G, Self>, ProtocolError> {
let sk = PrivateKey::deserialize_take(input)?;
let pk = sk.public_key();
Ok(KeyPair::new(sk, pk))
}
}
/// Wrapper around a Key to enforce that it's a public one.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "G::Pk: serde::Deserialize<'de>",
serialize = "G::Pk: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
pub struct PublicKey<G: Group + ?Sized>(G::Pk);
impl<G: Group> PublicKey<G> {
/// Convert from bytes
pub fn deserialize(mut key_bytes: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut key_bytes)
}
pub(crate) fn deserialize_take(key_bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
G::deserialize_take_pk(key_bytes).map(Self)
}
/// Convert to bytes
pub fn serialize(&self) -> GenericArray<u8, G::PkLen> {
G::serialize_pk(&self.0)
}
/// Returns the inner [`Group::Pk`].
pub fn to_group_type(&self) -> &G::Pk {
&self.0
}
}
impl<G: Group> PublicKey<G> {
/// Public-key verifying implementation
pub(crate) fn verify<CS: CipherSuite, SIG: SignatureProtocol<Group = G>, KE: Group>(
&self,
message_builder: MessageBuilder<'_, CS>,
state: SIG::VerifyState<CS, KE>,
signature: &SIG::Signature,
) -> Result<(), ProtocolError> {
SIG::verify(&self.0, message_builder, state, signature)
}
}
/// Default OPRF seed container.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct OprfSeed<H: OutputSizeUser>(pub(crate) Output<H>);
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
///
/// Will be called with `E` being [`PrivateKeySerialization::Error`].
pub trait OprfSeedSerialization<H, E>: Sized {
/// Serialization size in bytes.
type Len: ArrayLength;
/// Serialization into bytes
fn serialize(&self) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
///
/// The deserialized bytes must be taken from `bytes`.
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError<E>>;
}
impl<H: OutputSizeUser, E> OprfSeedSerialization<H, E> for OprfSeed<H>
where
H::OutputSize: ArrayLength,
{
type Len = H::OutputSize;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
GenericArray::from_slice(self.0.as_slice()).clone()
}
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError<E>> {
Ok(Self(
input
.take_array("OPRF seed")
.map_err(ProtocolError::into_custom)?
.into_ha0_4(),
))
}
}
//////////////////////////
// Test Implementations //
//===================== //
//////////////////////////
#[cfg(test)]
impl<G: Group + Debug> KeyPair<G> {
impl<G: Group> KeyPair<G>
where
G::Pk: core::fmt::Debug,
G::Sk: core::fmt::Debug,
{
/// Test-only strategy returning a proptest Strategy based on
/// generate_random
fn uniform_keypair_strategy() -> BoxedStrategy<Self> {
// The no_shrink is because keypairs should be fixed -- shrinking would cause a different
// keypair to be generated, which appears to not be very useful.
/// [`Self::derive_random`]
fn uniform_keypair_strategy() -> proptest::prelude::BoxedStrategy<Self> {
use proptest::prelude::*;
use rand::SeedableRng;
use rand::rngs::StdRng;
// The no_shrink is because keypairs should be fixed -- shrinking would cause a
// different keypair to be generated, which appears to not be very useful.
any::<[u8; 32]>()
.prop_filter_map("valid random keypair", |seed| {
let mut rng = StdRng::from_seed(seed);
Some(Self::generate_random(&mut rng))
Some(Self::derive_random(&mut rng))
})
.no_shrink()
.boxed()
}
}
/// A minimalist key type built around a \[u8; 32\]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(transparent)]
pub struct Key(Vec<u8>);
impl Deref for Key {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl SizedBytes for Key {
type Len = U32;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
GenericArray::clone_from_slice(&self.0[..])
}
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
Ok(Key(key_bytes.to_vec()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use curve25519_dalek::ristretto::RistrettoPoint;
use crate::ciphersuite::{KeGroup, OprfHash};
use crate::{
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult,
ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters,
ClientRegistrationFinishResult, ClientRegistrationStartResult, ServerLogin,
ServerLoginParameters, ServerLoginStartResult, ServerRegistration,
ServerRegistrationStartResult, ServerSetup,
};
use hkdf::Hkdf;
use rand::rand_core::UnwrapErr;
use rand::rngs::SysRng;
proptest! {
#[test]
fn test_ristretto_check(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
}
macro_rules! test {
($mod:ident, $point:ty) => {
mod $mod {
#[test]
fn test_ristretto_pub_from_priv(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&KeyPair::<RistrettoPoint>::public_from_private(sk), pk);
}
use proptest::prelude::*;
#[test]
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
use super::*;
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
proptest! {
#[test]
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(sk.public_key().serialize(), pk.serialize());
}
prop_assert_eq!(dh1, dh2);
}
#[test]
fn dh(kp1 in KeyPair::<$point>::uniform_keypair_strategy(),
kp2 in KeyPair::<$point>::uniform_keypair_strategy()) {
#[test]
fn test_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
let dh1 = kp2.private().ke_diffie_hellman(&kp1.public());
let dh2 = kp1.private().ke_diffie_hellman(kp2.public());
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
let kp2_private_bytes = kp2.private().to_vec();
prop_assert_eq!(dh1, dh2);
}
prop_assert_eq!(sk_bytes, kp2_private_bytes);
}
#[test]
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().serialize().to_vec();
let kp2 = PrivateKey::<$point>::deserialize_take_key_pair(&mut (sk_bytes.as_slice()))?;
let kp2_private_bytes = kp2.private().serialize().to_vec();
prop_assert_eq!(sk_bytes, kp2_private_bytes);
}
}
}
};
}
#[cfg(feature = "ristretto255")]
test!(ristretto, crate::Ristretto255);
test!(p256, ::p256::NistP256);
test!(p384, ::p384::NistP384);
test!(p521, ::p521::NistP521);
struct Default;
impl CipherSuite for Default {
#[cfg(feature = "ristretto255")]
type OprfCs = crate::Ristretto255;
#[cfg(not(feature = "ristretto255"))]
type OprfCs = ::p256::NistP256;
#[cfg(feature = "ristretto255")]
type KeyExchange = crate::TripleDh<crate::Ristretto255, sha2::Sha512>;
#[cfg(not(feature = "ristretto255"))]
type KeyExchange = crate::TripleDh<::p256::NistP256, sha2::Sha256>;
type Ksf = crate::ksf::Identity;
}
#[derive(Clone)]
struct RemoteSeed<H: OutputSizeUser>(Output<H>);
#[derive(Clone)]
struct RemoteKey(PrivateKey<KeGroup<Default>>);
const PASSWORD: &str = "password";
#[test]
fn remote_key() {
let sk = PrivateKey(KeGroup::<Default>::random_sk(&mut UnwrapErr(SysRng)));
let pk = sk.public_key();
let sk = RemoteKey(sk);
let keypair = KeyPair::new(sk, pk);
let server_setup =
ServerSetup::<Default, RemoteKey>::new_with_key_pair(&mut UnwrapErr(SysRng), keypair);
let ClientRegistrationStartResult {
message,
state: client,
} = ClientRegistration::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes())
.unwrap();
let ServerRegistrationStartResult { message, .. } =
ServerRegistration::start(&server_setup, message, &[]).unwrap();
let ClientRegistrationFinishResult { message, .. } = client
.finish(
&mut UnwrapErr(SysRng),
PASSWORD.as_bytes(),
message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let file = ServerRegistration::finish(message);
let ClientLoginStartResult {
message,
state: client,
} = ClientLogin::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes()).unwrap();
let builder = ServerLogin::builder(
&mut UnwrapErr(SysRng),
&server_setup,
Some(file),
message,
&[],
ServerLoginParameters::default(),
)
.unwrap();
let shared_secret = builder.private_key().0.ke_diffie_hellman(builder.data());
let ServerLoginStartResult {
message,
state: server,
..
} = builder.build(shared_secret).unwrap();
let ClientLoginFinishResult { message, .. } = client
.finish(
&mut UnwrapErr(SysRng),
PASSWORD.as_bytes(),
message,
ClientLoginFinishParameters::default(),
)
.unwrap();
server
.finish(message, ServerLoginParameters::default())
.unwrap();
}
#[test]
fn remote_seed() {
let mut oprf_seed = RemoteSeed::<OprfHash<Default>>(GenericArray::default().into_ha0_4());
UnwrapErr(SysRng).fill_bytes(&mut oprf_seed.0);
let sk = PrivateKey(KeGroup::<Default>::random_sk(&mut UnwrapErr(SysRng)));
let pk = sk.public_key();
let sk = RemoteKey(sk);
let keypair = KeyPair::new(sk, pk);
let server_setup = ServerSetup::<Default, _, _>::new_with_key_pair_and_seed(
&mut UnwrapErr(SysRng),
keypair,
oprf_seed,
);
let ClientRegistrationStartResult {
message,
state: client,
} = ClientRegistration::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes())
.unwrap();
let km = server_setup.key_material_info(&[]);
let mut ikm = GenericArray::default();
Hkdf::<OprfHash<Default>>::from_prk(&km.ikm.0)
.unwrap()
.expand_multi_info(&km.info, &mut ikm)
.unwrap();
let ServerRegistrationStartResult { message, .. } =
ServerRegistration::start_with_key_material(&server_setup, ikm, message).unwrap();
let ClientRegistrationFinishResult { message, .. } = client
.finish(
&mut UnwrapErr(SysRng),
PASSWORD.as_bytes(),
message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let file = ServerRegistration::finish(message);
let ClientLoginStartResult {
message,
state: client,
} = ClientLogin::<Default>::start(&mut UnwrapErr(SysRng), PASSWORD.as_bytes()).unwrap();
let km = server_setup.key_material_info(&[]);
let mut ikm = GenericArray::default();
Hkdf::<OprfHash<Default>>::from_prk(&km.ikm.0)
.unwrap()
.expand_multi_info(&km.info, &mut ikm)
.unwrap();
let builder = ServerLogin::builder_with_key_material(
&mut UnwrapErr(SysRng),
&server_setup,
ikm,
Some(file),
message,
ServerLoginParameters::default(),
)
.unwrap();
let shared_secret = builder.private_key().0.ke_diffie_hellman(builder.data());
let ServerLoginStartResult {
message,
state: server,
..
} = builder.build(shared_secret).unwrap();
let ClientLoginFinishResult { message, .. } = client
.finish(
&mut UnwrapErr(SysRng),
PASSWORD.as_bytes(),
message,
ClientLoginFinishParameters::default(),
)
.unwrap();
server
.finish(message, ServerLoginParameters::default())
.unwrap();
}
}
+44
View File
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Trait specifying a key stretching function
use generic_array::{ArrayLength, GenericArray};
use crate::errors::InternalError;
/// Used for the key stretching function in OPAQUE
pub trait Ksf: Default {
/// Computes the key stretching function
fn hash<L: ArrayLength>(
&self,
input: GenericArray<u8, L>,
) -> Result<GenericArray<u8, L>, InternalError>;
}
/// A no-op hash which simply returns its input
#[derive(Default)]
pub struct Identity;
impl Ksf for Identity {
fn hash<L: ArrayLength>(
&self,
input: GenericArray<u8, L>,
) -> Result<GenericArray<u8, L>, InternalError> {
Ok(input)
}
}
#[cfg(feature = "argon2")]
impl Ksf for argon2::Argon2<'_> {
fn hash<L: ArrayLength>(
&self,
input: GenericArray<u8, L>,
) -> Result<GenericArray<u8, L>, InternalError> {
let mut output = GenericArray::default();
self.hash_password_into(&input, &[0; argon2::RECOMMENDED_SALT_LEN], &mut output)
.map_err(|_| InternalError::KsfError)?;
Ok(output)
}
}
+1068 -393
View File
File diff suppressed because it is too large Load Diff
-225
View File
@@ -1,225 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
//! curve point
use crate::errors::InternalPakeError;
use crate::group::Group;
use crate::hash::Hash;
use crate::serialization::i2osp;
use curve25519_dalek::ristretto::RistrettoPoint;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// A subtrait of Group specifying how to hash a password into a point
pub trait GroupWithMapToCurve: Group {
/// The ciphersuite identifier as dictated by
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
/// Generates the contextString parameter as defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
fn get_context_string(mode: u8) -> Vec<u8> {
[i2osp(mode as usize, 1), i2osp(Self::SUITE_ID, 2)].concat()
}
}
impl GroupWithMapToCurve for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
// Implements the hash_to_ristretto255() function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError> {
let uniform_bytes =
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
Ok(<Self as Group>::hash_to_curve(
&GenericArray::clone_from_slice(&uniform_bytes[..]),
))
}
}
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
let additive = (x % y != 0) as usize;
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalPakeError> {
if x.len() != y.len() {
return Err(InternalPakeError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
}
// Corresponds to the expand_message_xmd() function defined in
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
pub(crate) fn expand_message_xmd<H: Hash>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, InternalPakeError> {
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
let ell = div_ceil(len_in_bytes, b_in_bytes);
if ell > 255 {
return Err(InternalPakeError::HashToCurveError);
}
let dst_prime = [dst, &i2osp(dst.len(), 1)].concat();
let z_pad = i2osp(0, r_in_bytes);
let l_i_b_str = i2osp(len_in_bytes, 2);
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1), &dst_prime].concat();
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
}
#[cfg(test)]
mod tests {
struct Params {
msg: &'static str,
len_in_bytes: usize,
uniform_bytes: &'static str,
}
#[test]
fn test_expand_message_xmd() {
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
let test_vectors: Vec<Params> = vec![
Params {
msg: "",
len_in_bytes: 0x20,
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
92181df928fca88",
},
Params {
msg: "abc",
len_in_bytes: 0x20,
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
5a1151775fe4da7",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x20,
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x20,
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
1716b1b964e1c642",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x20,
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
350db46f429b771b",
},
Params {
msg: "",
len_in_bytes: 0x80,
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
},
Params {
msg: "abc",
len_in_bytes: 0x80,
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x80,
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x80,
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x80,
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
}
}
+423 -195
View File
@@ -1,250 +1,478 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! Contains the messages used for OPAQUE
use crate::{
ciphersuite::CipherSuite,
envelope::Envelope,
errors::{
utils::{check_slice_size, check_slice_size_atleast},
PakeError, ProtocolError,
},
group::Group,
key_exchange::traits::{KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytesExt},
};
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use std::convert::TryFrom;
use core::ops::Add;
// Messages
// =========
use derive_where::derive_where;
use digest::Output;
use generic_array::typenum::{Sum, Unsigned};
use generic_array::{ArrayLength, GenericArray};
use hybrid_array::Array;
use rand::{CryptoRng, Rng};
use voprf::{BlindedElement, BlindedElementLen, EvaluationElement, EvaluationElementLen};
use zeroize::Zeroizing;
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
use crate::envelope::{Envelope, EnvelopeLen};
use crate::errors::ProtocolError;
use crate::hash::OutputSize;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::NonceLen;
use crate::key_exchange::{
Deserialize, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, Serialize,
SerializedCredentialRequest, SerializedCredentialResponse,
};
use crate::keypair::PublicKey;
use crate::opaque::{
MaskedResponse, MaskedResponseLen, ServerLogin, ServerLoginStartResult, ServerSetup,
};
use crate::serialization::{ConcatExt, SliceExt};
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
/// The message sent by the client to the server, to initiate registration
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::BlindedElement<CS::OprfCs>)]
pub struct RegistrationRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::Group,
}
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.alpha.to_arr().to_vec()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = check_slice_size(&input, elem_len, "first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(checked_slice);
let alpha = CS::Group::from_element_slice(arr)?;
Ok(Self { alpha })
}
pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
}
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<KeGroup<CS> as Group>::Pk: serde::Deserialize<'de>",
serialize = "<KeGroup<CS> as Group>::Pk: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::EvaluationElement<CS::OprfCs>, <KeGroup<CS> as Group>::Pk
)]
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) beta: CS::Group,
pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
/// Server's static public key
pub(crate) server_s_pk: Vec<u8>,
}
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[self.beta.to_arr().to_vec(), self.server_s_pk.clone()].concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size(&input, elem_len + key_len, "registration_response_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let beta = CS::Group::from_element_slice(arr)?;
Ok(Self {
server_s_pk: checked_slice[elem_len..].to_vec(),
beta,
})
}
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::Hash>,
/// The "envelope" generated by the user, containing sealed cryptographic
/// identifiers
pub(crate) envelope: Envelope<CS>,
/// The masking key used to mask the envelope
pub(crate) masking_key: Output<OprfHash<CS>>,
/// The user's public key
pub(crate) client_s_pk: Key,
}
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
self.client_s_pk.to_arr().to_vec(),
self.envelope.serialize(),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice = check_slice_size_atleast(&input, key_len, "registration_upload_bytes")?;
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[key_len..])?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.into());
}
Ok(Self {
envelope,
client_s_pk: KeyPair::<CS::Group>::check_public_key(Key::from_bytes(
&checked_slice[..key_len],
)?)?,
})
}
#[derive_where(skip(Zeroize))]
pub(crate) client_s_pk: PublicKey<KeGroup<CS>>,
}
/// The message sent by the user to the server, to initiate registration
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
voprf::BlindedElement<CS::OprfCs>,
<CS::KeyExchange as KeyExchange>::KE1Message,
)]
pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::Group,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message,
pub(crate) blinded_element: BlindedElement<CS::OprfCs>,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message,
}
impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let mut credential_request: Vec<u8> = Vec::new();
credential_request.extend_from_slice(&self.alpha.to_arr());
credential_request.extend_from_slice(&self.ke1_message.to_bytes());
credential_request
/// 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)
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
/// The handle to the corresponding [`ServerSetup`]s private key.
pub fn private_key(&self) -> &SK {
&self.server_s_sk
}
let checked_slice =
check_slice_size_atleast(&input, elem_len, "login_first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let alpha = CS::Group::from_element_slice(arr)?;
let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(
&checked_slice[elem_len..],
)?;
Ok(Self { alpha, ke1_message })
/// 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
/// The answer sent by the server to the user, upon reception of the login
/// attempt
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
EvaluationElement<CS::OprfCs>,
<CS::KeyExchange as KeyExchange>::KE2Message,
)]
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) beta: CS::Group,
pub(crate) server_s_pk: Key,
/// the user's sealed information,
pub(crate) envelope: Envelope<CS::Hash>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
pub(crate) evaluation_element: EvaluationElement<CS::OprfCs>,
pub(crate) masking_nonce: GenericArray<u8, NonceLen>,
pub(crate) masked_response: MaskedResponse<CS>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message,
}
impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope),
self.ke2_message.to_bytes(),
]
.concat()
/// The answer sent by the client to the server, upon reception of the sealed
/// envelope
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
<CS::KeyExchange as KeyExchange>::KE3Message,
)]
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message,
}
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
/// Length of [`RegistrationRequest`] in bytes for serialization.
pub type RegistrationRequestLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes
#[cfg(test)]
pub(crate) fn get_blinded_element_for_testing(&self) -> BlindedElement<CS::OprfCs> {
self.blinded_element.clone()
}
pub(crate) fn serialize_without_ke(
beta: &CS::Group,
server_s_pk: &Key,
envelope: &Envelope<CS::Hash>,
) -> Vec<u8> {
[
&beta.to_arr(),
&server_s_pk.to_arr()[..],
&envelope.to_bytes(),
]
.concat()
/// Serialization into bytes
pub fn serialize(&self) -> Array<u8, RegistrationRequestLen<CS>> {
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.blinded_element.value())
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let beta_bytes = &checked_slice[..elem_len];
let arr = GenericArray::from_slice(beta_bytes);
let beta = CS::Group::from_element_slice(arr)?;
let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
let (envelope, remainder) =
Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len + key_len..])?;
let ke2_message_size = CS::KeyExchange::ke2_message_size();
let checked_remainder =
check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
let ke2_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::try_from(
&checked_remainder,
)?;
Ok(Self {
beta,
server_s_pk,
envelope,
ke2_message,
blinded_element: BlindedElement::deserialize(input)?,
})
}
}
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message,
}
/// Length of [`RegistrationResponse`] in bytes for serialization.
pub type RegistrationResponseLen<CS: CipherSuite> =
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, <KeGroup<CS> as Group>::PkLen>;
impl<CS: CipherSuite> CredentialFinalization<CS> {
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.ke3_message.to_bytes()
pub fn serialize(&self) -> GenericArray<u8, RegistrationResponseLen<CS>>
where
// RegistrationResponse: KgPk + KePk
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen> + ArrayLength,
RegistrationResponseLen<CS>: ArrayLength,
{
let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
self.evaluation_element.value(),
));
elem.cat(self.server_s_pk.serialize())
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let ke3_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(input)?;
Ok(Self { ke3_message })
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
let evaluation_element = EvaluationElement::deserialize(input)?;
input = &input[EvaluationElementLen::<CS::OprfCs>::USIZE..];
Ok(Self {
evaluation_element,
server_s_pk: PublicKey::deserialize_take(&mut input)?,
})
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the
/// reflection error case
pub(crate) fn set_evaluation_element_for_testing(
&self,
beta: <OprfGroup<CS> as voprf::Group>::Elem,
) -> Self {
Self {
evaluation_element: EvaluationElement::from_value_unchecked(beta),
server_s_pk: self.server_s_pk.clone(),
}
}
}
/// Length of [`RegistrationUpload`] in bytes for serialization.
pub type RegistrationUploadLen<CS: CipherSuite> =
Sum<Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>, EnvelopeLen<CS>>;
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, RegistrationUploadLen<CS>>
where
// RegistrationUpload: (KePk + Hash) + Envelope
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength,
{
self.client_s_pk
.serialize()
.cat(GenericArray::from_slice(self.masking_key.as_slice()).clone())
.cat(self.envelope.serialize())
}
/// Deserialization from bytes
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
client_s_pk: PublicKey::deserialize_take(&mut input)?,
masking_key: input.take_array("masking key")?.into_ha0_4(),
envelope: Envelope::deserialize_take(&mut input)?,
})
}
// Creates a dummy instance used for faking a [CredentialResponse]
pub(crate) fn dummy<R: Rng + CryptoRng, SK: Clone, OS: Clone>(
rng: &mut R,
server_setup: &ServerSetup<CS, SK, OS>,
) -> Self {
let mut masking_key = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut masking_key);
Self {
envelope: Envelope::<CS>::dummy(),
masking_key,
client_s_pk: server_setup.dummy_pk.clone(),
}
}
}
/// Length of [`CredentialRequest`] in bytes for serialization.
pub type CredentialRequestLen<CS: CipherSuite> =
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, Ke1MessageLen<CS>>;
impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialRequestLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
// CredentialRequest: KgPk + Ke1Message
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>> + ArrayLength,
CredentialRequestLen<CS>: ArrayLength,
{
let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
self.blinded_element.value(),
));
elem.cat(self.ke1_message.serialize())
}
/// Deserialization from bytes
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
{
Self::deserialize_take(&mut input)
}
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
{
let blinded_element = BlindedElement::deserialize(input)?;
*input = &input[BlindedElementLen::<CS::OprfCs>::USIZE..];
Ok(Self {
blinded_element,
ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message::deserialize_take(input)?,
})
}
pub(crate) fn to_parts(&self) -> SerializedCredentialRequest<CS> {
SerializedCredentialRequest::new(&self.blinded_element)
}
/// Only used for testing purposes
#[cfg(test)]
pub(crate) fn get_blinded_element_for_testing(&self) -> BlindedElement<CS::OprfCs> {
self.blinded_element.clone()
}
}
/// Length of [`CredentialResponse`] in bytes for serialization.
pub type CredentialResponseLen<CS: CipherSuite> =
Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialResponseLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen> + ArrayLength,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength,
{
let elem = GenericArray::from_ha0_4(<OprfGroup<CS> as voprf::Group>::serialize_elem(
self.evaluation_element.value(),
));
elem.cat(self.masking_nonce)
.cat(self.masked_response.serialize())
.cat(self.ke2_message.serialize())
}
/// Deserialization from bytes
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize,
{
let evaluation_element = EvaluationElement::deserialize(input)?;
input = &input[EvaluationElementLen::<CS::OprfCs>::USIZE..];
Ok(Self {
evaluation_element,
masking_nonce: input.take_array("masking nonce")?,
masked_response: MaskedResponse::deserialize_take(&mut input)?,
ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message::deserialize_take(
&mut input,
)?,
})
}
pub(crate) fn to_parts(&self) -> SerializedCredentialResponse<CS> {
SerializedCredentialResponse::new(
&self.evaluation_element,
self.masking_nonce,
self.masked_response.clone(),
)
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the
/// reflection error case
pub(crate) fn set_evaluation_element_for_testing(
&self,
beta: <OprfGroup<CS> as voprf::Group>::Elem,
) -> Self {
Self {
evaluation_element: EvaluationElement::from_value_unchecked(beta),
masking_nonce: self.masking_nonce,
masked_response: self.masked_response.clone(),
ke2_message: self.ke2_message.clone(),
}
}
}
/// Length of [`CredentialFinalization`] in bytes for serialization.
pub type CredentialFinalizationLen<CS: CipherSuite> = Ke3MessageLen<CS>;
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
{
self.ke3_message.serialize()
}
/// Deserialization from bytes
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
{
Ok(Self {
ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(
&mut input,
)?,
})
}
}
+1041 -733
View File
File diff suppressed because it is too large Load Diff
-162
View File
@@ -1,162 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
serialization::serialize,
};
use digest::Digest;
use generic_array::GenericArray;
use rand::{CryptoRng, RngCore};
/// Used to store the OPRF input and blinding factor
pub struct Token<Grp: Group> {
pub(crate) data: Vec<u8>,
pub(crate) blind: Grp::Scalar,
}
static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-";
static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-";
static MODE_BASE: u8 = 0x00;
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
/// an HKDF computation.
pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), InternalPakeError> {
let blind = G::random_scalar(blinding_factor_rng);
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
let mapped_point = G::map_to_curve::<H>(input, &dst)?;
let blind_token = mapped_point * &blind;
Ok((
Token {
data: input.to_vec(),
blind,
},
blind_token,
))
}
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the server (who holds the OPRF key) to the client.
pub(crate) fn evaluate<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
point * oprf_key
}
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
/// the client unblinds the server's message.
pub(crate) fn 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);
}
}
+134 -35
View File
@@ -1,57 +1,156 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use crate::errors::PakeError;
use core::ops::Add;
use crate::errors::ProtocolError;
use digest::Update;
use generic_array::sequence::Concat;
use generic_array::typenum::Sum;
use generic_array::{ArrayLength, GenericArray};
use hybrid_array::{Array, ArraySize};
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp(input: usize, length: usize) -> Vec<u8> {
if length <= std::mem::size_of::<usize>() {
return (&input.to_be_bytes()[std::mem::size_of::<usize>() - length..]).to_vec();
pub(crate) fn i2osp<L: ArrayLength>(input: usize) -> Result<GenericArray<u8, L>, ProtocolError> {
const SIZEOF_USIZE: usize = size_of::<usize>();
// Make sure input fits in output.
if (SIZEOF_USIZE as u32 - input.leading_zeros() / 8) > L::U32 {
return Err(ProtocolError::SerializationError);
}
let mut output = vec![0u8; length];
output.splice(
length - std::mem::size_of::<usize>()..length,
input.to_be_bytes().iter().cloned(),
);
output
let mut output = GenericArray::default();
output[L::USIZE.saturating_sub(SIZEOF_USIZE)..]
.copy_from_slice(&input.to_be_bytes()[SIZEOF_USIZE.saturating_sub(L::USIZE)..]);
Ok(output)
}
// Corresponds to the OS2IP() function from RFC8017
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, PakeError> {
if input.len() > std::mem::size_of::<usize>() {
return Err(PakeError::SerializationError);
#[cfg(test)]
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, ProtocolError> {
if input.len() > size_of::<usize>() {
return Err(ProtocolError::SerializationError);
}
let mut output_array = [0u8; std::mem::size_of::<usize>()];
output_array[std::mem::size_of::<usize>() - input.len()..].copy_from_slice(input);
let mut output_array = [0u8; size_of::<usize>()];
output_array[size_of::<usize>() - input.len()..].copy_from_slice(input);
Ok(usize::from_be_bytes(output_array))
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Vec<u8> {
[&i2osp(input.len(), max_bytes), input].concat()
pub(crate) trait UpdateExt {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>);
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self;
}
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
// (input, remainder)
pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec<u8>, Vec<u8>), PakeError> {
if size_bytes > std::mem::size_of::<usize>() || input.len() < size_bytes {
return Err(PakeError::SerializationError);
impl<T: Update> UpdateExt for T {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>) {
for bytes in iter {
self.update(bytes);
}
}
let size = os2ip(&input[..size_bytes])?;
if size_bytes + size > input.len() {
return Err(PakeError::SerializationError);
}
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self {
let mut self_ = self;
Ok((
input[size_bytes..size_bytes + size].to_vec(),
input[size_bytes + size..].to_vec(),
))
for bytes in iter {
self_ = self_.chain(bytes);
}
self_
}
}
pub(crate) trait SliceExt {
fn take_array<L: ArrayLength + ArraySize>(
self: &mut &Self,
name: &'static str,
) -> Result<GenericArray<u8, L>, ProtocolError>;
}
impl SliceExt for [u8] {
fn take_array<L: ArrayLength + ArraySize>(
self: &mut &Self,
name: &'static str,
) -> Result<GenericArray<u8, L>, ProtocolError> {
if L::USIZE > self.len() {
return Err(ProtocolError::SizeError {
name,
len: L::USIZE,
actual_len: self.len(),
});
}
let (front, back) = self.split_at(L::USIZE);
*self = back;
let arr: Array<u8, L> = Array::try_from(front).unwrap();
Ok(GenericArray::from(arr))
}
}
pub(crate) trait GenericArrayExt<O: ArrayLength> {
type Output: ArrayLength;
/// This allows us to concat two [`GenericArray`]s but with `where` bounds
/// `Other + Self`. Because sometimes `Self + Other` doesn't imply the
/// bounds, and we have to add them to every call.
fn concat_ext(&self, rest: &GenericArray<u8, O>) -> GenericArray<u8, Self::Output>;
}
impl<L: ArrayLength, O: ArrayLength> GenericArrayExt<O> for GenericArray<u8, L>
where
O: Add<L>,
Sum<O, L>: ArrayLength,
{
type Output = Sum<O, L>;
fn concat_ext(&self, other: &GenericArray<u8, O>) -> GenericArray<u8, Self::Output> {
let mut output = GenericArray::<u8, O>::default().concat(GenericArray::<u8, L>::default());
output[..L::USIZE].copy_from_slice(self);
output[L::USIZE..].copy_from_slice(other);
output
}
}
pub(crate) trait ConcatExt<N: ArrayLength>: Sized {
fn cat<M: ArrayLength>(self, other: GenericArray<u8, M>) -> GenericArray<u8, Sum<N, M>>
where
N: Add<M>,
Sum<N, M>: ArrayLength;
}
impl<N: ArrayLength> ConcatExt<N> for GenericArray<u8, N> {
fn cat<M: ArrayLength>(self, other: GenericArray<u8, M>) -> GenericArray<u8, Sum<N, M>>
where
N: Add<M>,
Sum<N, M>: ArrayLength,
{
Concat::concat(self, other)
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod unit_tests {
use generic_array::typenum::{U1, U2};
use super::*;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp::<U1>(0).is_ok());
assert!(i2osp::<U1>(255).is_ok());
assert!(i2osp::<U1>(256).is_err());
assert!(i2osp::<U1>(257).is_err());
assert!(i2osp::<U2>(256 * 256 - 1).is_ok());
assert!(i2osp::<U2>(256 * 256).is_err());
assert!(i2osp::<U2>(256 * 256 + 1).is_err());
}
}
+934 -310
View File
File diff suppressed because it is too large Load Diff
-53
View File
@@ -1,53 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Trait specifying a slow hashing function
use crate::{errors::InternalPakeError, hash::Hash};
use digest::Digest;
#[cfg(feature = "slow-hash")]
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// Used for the slow hashing function in OPAQUE
pub trait SlowHash<D: Hash> {
/// Computes the slow hashing function
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError>;
}
/// A no-op hash which simply returns its input
pub struct NoOpHash;
impl<D: Hash> SlowHash<D> for NoOpHash {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
Ok(input.to_vec())
}
}
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_LOG_N: u8 = 15u8;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_R: u32 = 8u32;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_P: u32 = 1u32;
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
let params =
scrypt::ScryptParams::new(DEFAULT_SCRYPT_LOG_N, DEFAULT_SCRYPT_R, DEFAULT_SCRYPT_P)
.map_err(|_| InternalPakeError::SlowHashError)?;
let mut output = vec![0u8; <D as Digest>::OutputSize::to_usize()];
scrypt::scrypt(&input, &[], &params, &mut output)
.map_err(|_| InternalPakeError::SlowHashError)?;
Ok(output)
}
}
+1143 -428
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+33 -27
View File
@@ -1,24 +1,24 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use rand::{CryptoRng, Error, RngCore};
use std::cmp::min;
use core::cmp::min;
use std::vec::Vec;
/// A simple implementation of `RngCore` for testing purposes.
use core::convert::Infallible;
use rand::rand_core::{TryCryptoRng, TryRng};
/// A simple implementation of `Rng` for testing purposes.
///
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
///
///
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct CycleRng {
v: Vec<u8>,
}
impl CycleRng {
/// Create a `CycleRng`, yielding a sequence starting with
/// `initial` and looping thereafter
/// Create a `CycleRng`, yielding a sequence starting with `initial` and
/// looping thereafter
pub fn new(initial: Vec<u8>) -> Self {
CycleRng { v: initial }
}
@@ -35,29 +35,35 @@ fn rotate_left<T>(data: &mut [T], steps: usize) {
data.reverse();
}
impl RngCore for CycleRng {
fn next_u32(&mut self) -> u32 {
unimplemented!()
impl TryRng for CycleRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
let mut buf = [0u8; 4];
self.try_fill_bytes(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
#[inline]
fn next_u64(&mut self) -> u64 {
unimplemented!()
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
let mut buf = [0u8; 8];
self.try_fill_bytes(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
dest[..len].copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
Ok(())
}
}
// This is meant for testing only
impl CryptoRng for CycleRng {}
impl TryCryptoRng for CycleRng {}
+16 -6
View File
@@ -1,9 +1,19 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use serde_json::Value;
use std::vec::Vec;
mod full_test;
#[rustfmt::skip]
#[allow(dead_code)]
mod full_test_vectors;
pub mod mock_rng;
mod opaque_test_vectors;
mod voprf_test_vectors;
mod parser;
mod rfc9807_vectors;
mod test_opaque_vectors;
pub(crate) fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key].as_str().and_then(|s| hex::decode(s).ok())
}
-583
View File
@@ -1,583 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::*,
key_exchange::tripledh::TripleDH,
keypair::{Key, SizedBytesExt},
opaque::*,
slow_hash::NoOpHash,
tests::mock_rng::CycleRng,
*,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use serde_json::Value;
// Tests
// =====
struct Ristretto255Sha512NoSlowHash;
impl CipherSuite for Ristretto255Sha512NoSlowHash {
type Group = RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
#[derive(PartialEq)]
pub enum EnvelopeMode {
Base,
CustomIdentifier,
}
#[allow(non_snake_case)]
pub struct TestVectorParameters {
pub envelope_mode: EnvelopeMode,
pub client_public_key: Vec<u8>,
pub client_private_key: Vec<u8>,
pub client_keyshare: Vec<u8>,
pub client_private_keyshare: Vec<u8>,
pub server_public_key: Vec<u8>,
pub server_private_key: Vec<u8>,
pub server_keyshare: Vec<u8>,
pub server_private_keyshare: Vec<u8>,
pub client_identity: Vec<u8>,
pub server_identity: Vec<u8>,
pub password: Vec<u8>,
pub blind_registration: Vec<u8>,
pub oprf_key: Vec<u8>,
pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
pub client_info: Vec<u8>,
pub server_info: Vec<u8>,
pub registration_request: Vec<u8>,
pub registration_response: Vec<u8>,
pub registration_upload: Vec<u8>,
pub KE1: Vec<u8>,
pub blind_login: Vec<u8>,
pub KE2: Vec<u8>,
pub KE3: Vec<u8>,
pub export_key: Vec<u8>,
pub session_key: Vec<u8>,
}
// Pulled from "OPAQUE-3DH Test Vector 1" and "OPAQUE-3DH Test Vector 6"
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
static TEST_VECTORS: &[&str] = &[
r#"
OPRF: 0001
Hash: SHA512
SlowHash: Identity
EnvelopeMode: 01
Group: ristretto255
Nh: 64
Npk: 32
Nsk: 32
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da54
20d3eebcd2b
client_private_key: 8bcb0b70dac18de24eef12e737d6b28724d3e37774e0b092f
9f70b255defaf04
client_public_key: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa1905
df6ea129343b09
server_private_key: f3a0829898a89239dce29ccc98ec8b449a34b255ba1e6f944
829d18e0d589b0f
server_public_key: 66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb74
9c22c762389c3c
client_info: 68656c6c6f20626f62
server_info: 6772656574696e677320616c696365
server_nonce: 98b8081059f60ffed9336f026fd8e124737205ac73f5348ae5bebdb
49456c70f
client_nonce: 58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e
36427ca44
server_keyshare: 5214e3ddc73db786480b79fa2da787f2080b82cbe922c2a9592b
44597d9a702e
client_keyshare: a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
b98265a5e651
server_private_keyshare: c4d002aa4cfcf281657cf36fe562bc60d9133e0e72a7
4432f685b2b6a4b42a0c
client_private_keyshare: de2e98f422bf7b99be19f7da7cac62f1599d35a225ec
6340149a0aaff3102003
blind_registration: 7e5bcbf82a46109ee0d24e9bcab41fc830a6ce8b82fc1e921
3a043b743b95800
blind_login: c4d5a15f0d5ffc354e340454ec779f575e4573a3886ab5e57e4da298
4bdd5306
oprf_key: 080d0a4d352de92672ab709b1ae1888cb48dfabc2d6ca5b914b335512fe
70508
auth_key: 7bb7f2b831ee30d3e5cc4012c8f721a4d8f9dd494932d53776e043df9bd
2aa284025b8b006fd8449536446ff50698f46c73fccb53f20d80898f185307d1d39e5
prk: b0aefddbb21d1b97bc40c07b172e0bf172ec740de4f6274f69d46350a447e9b1
b3fb1e4cefc7d8e393ff58a5c45c74d0615ee0eecde116f3d4e744142eb2ee89
pseudorandom_pad: 36a828b3b57bf242c4c47ccd9cb84e5b3cefaffe09629c6b94d
eba0ccec5fa39
envelope: 01cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3
eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a55
3d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e
906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
handshake_secret: 2b041dcf12ac9b75dded88f891c25d76746ce9e2c1a43118ac4
aa5721cdc1bc2f0691e6c012a1ea9eb95ab4899b3e7058d37fe9546c46b0511877e40
f55aac6c
handshake_encrypt_key: ceef10f15d869a4cea8174fa98d0d96c7aaf8602d006fe
0c5274a40173db76cac820138c5890bb63fb974d1e3e925850cc2464e2c10f0a9a776
9a45e80889b1e
server_mac_key: f8fd7fdc349b5ae1339515e05912c89a795f561a117cdc84d8d8b
5f05b05751abfb87fa01c799c5d367244d1e32eab67ff926833c6025c556acffa4af1
f3871a
client_mac_key: 92a30cc82c374c06895aa07e81f0cf5f25309a24b595faefcd225
1f9219b47e47d17da4fe8b572dedefa350ed365f87b217973e90d0b647a2ccf1d796a
8970f6
registration_request: ec9027daa5e9a901d641286a7ded51364142936ac7636e1
42e3f4368b4bd8124
registration_response: 8867d7c8c2c576a6322d49d46078ea32f479aed917c70a
636d3ada4397ea1c0e66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749
c22c762389c3c
registration_upload: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa19
05df6ea129343b0901cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da
5420d3eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129
932a553d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3cc
d8699e906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
KE1: e06a32011e1b1704eb686b263e5d132fff4e9f6429cd93b98db107485006792c
58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e36427ca4400096
8656c6c6f20626f62a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
b98265a5e651
KE2: 66f6b5fa1a4eb6bd7a0c93ed2639a31cba0d02e2df744003641d5a30a4a12364
66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749c22c762389c3c01cc7
abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3eebcd2bbd6323c3
6fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a553d469ffa9999fcb
d37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e906027842694b62
93b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e98b8081059f60ffed9336f0
26fd8e124737205ac73f5348ae5bebdb49456c70f5214e3ddc73db786480b79fa2da7
87f2080b82cbe922c2a9592b44597d9a702e000f72f38a0945819089c44c86820c51d
89cc35f77df03d330101bbed3b2f69066112f32529bdda0998657350fc9f8da4cde73
408ad931f4c2ea6237ccae4696483388b174f50cf96d439139b0f8680c3b
KE3: 9f0e4f73455ca9fe06bb52ad02670b09be5a03db11a73be4422f19963be082b0
eb55871022e8d1d87adc3ab50de7c738058eb659866d091648f2fed12e23fd53
export_key: 66c0b72aa829f13a166fb1a1168f1e26023921f0eed1126def4f81ba0
4924ad6012e42b63656ec199ba27670d1e7f23dc0a927714edc140134dde5a5d2063d
fc
session_key: 951c2bb1b876725fa7d3829db791dddd406a688507b47e24101bd0cc
5d071760b6fba59e8758a6ea6d7e5f51a715b49a47c50fee9a7c8a0451243c3ee837f
d30
"#,
r#"
OPRF: 0001
Hash: SHA512
SlowHash: Identity
EnvelopeMode: 02
Group: ristretto255
Nh: 64
Npk: 32
Nsk: 32
client_identity: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613b2
e16a6dde6b05
server_identity: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8e
de8d6d272c65
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb241809
0438877888c
client_private_key: dc70a99bbabf1ebe98b192e93cedceb9c0164e95b891bd8bc
81721b83d66b00b
client_public_key: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613
b2e16a6dde6b05
server_private_key: 709687a36c94592ab76579f42ce1be6961f0700496e71df80
6ebd5320554720d
server_public_key: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c
8ede8d6d272c65
client_info: 68656c6c6f20626f62
server_info: 6772656574696e677320616c696365
server_nonce: ef49d83cef5f1411ea30abb82b08bd85423aadb86e2c19df5930b3c
8498b9f97
client_nonce: 4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d6
77165cd3b
server_keyshare: 96a9587e233e67f2397f10fec6355b68102534f1f1b115b4ddf7
485840efcd7c
client_keyshare: 54f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
e5033632611a
server_private_keyshare: 6650d64df70618a878504ce73dcca27b1af125c67e48
1e7bd49d0b24709b200f
client_private_keyshare: ebb01c59f99bc955df622548e247f7ef180732909ff3
c5f87ff8c7867b8be704
blind_registration: 308f1d3fa1fea402f3c90b04601274050a3c6f467387c2f48
878823949b0e109
blind_login: 141e21373228a44b09d4c00da9a6bbaf9a5e54a1687c07f327833643
4245510b
oprf_key: b7126967aa0cb69c311b71343843ea041bae30e2bde41b548b8fbd8bced
97604
auth_key: 9f761a5a56a74269b382403aeba47c1d24b9e200e1839efcb616fb280da
1b6ba7bd71d6455dd3cd979c545608cfbfc1c4e9ba677e1d40848054a00696c4b2589
prk: 30bc3f37a757890ac17ce46043f3c5ed30c96fb8743205e77e84dc167d98e114
6093150ff7b4d002f793bfe717e88d174ed2669abdd9e96af473a7ac82973b0a
pseudorandom_pad: b909f990c94b9c5949a2b8d0874d602f846b7981b331fd79978
b530cc46c8670
envelope: 02f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438
877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a36
7bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc
439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
handshake_secret: ff89f264f8c3974238f4c8d736af7b0a55f2e4edc487cbf3e5c
7b4bbf21acd7c1d28354c2c8555fba57c4d4b1fbb4b772bfdf909881f67dd517cc9f4
f6ebaeac
handshake_encrypt_key: 3071b181f639062cf70b74d0ffe5ec8fa695da13cd2f00
e74b8b7ef348ae7a5df9c3a32c9f7aeaad5a28379712cf849b9707e221dce124abfad
d0225a8e8e045
server_mac_key: 7c37b344d189cbbeff80bbb4b78e2703d1a80dc28239923094287
62f7ce2a93b11f6e85dd45c02809afe8583d4aad6377e72788773af92eef33c690692
20ae76
client_mac_key: 2c4aff12ba7aa911a51f9e5b7a7c01439d854c97e4b8ec842a9db
d78345760328fd5a72424e49e25ec8fe1b6d9d42f774516f400948bd5a105d995d000
2fc83b
registration_request: 3c8b89966e261a5aaf7aeb6dcdd94c87ce311bf197221b8
7ef44632d58f18a05
registration_response: caf9243d7ef3e267815632bf79c85a27a23f218a438815
2a523f6a310949807beae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8
ede8d6d272c65
registration_upload: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc6
13b2e16a6dde6b0502f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418
090438877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4
f90a367bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d
2566cc439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
KE1: 8261a1efd78bea73faf256a23c200d729259886530fa43b875c1ca124b09bc7e
4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d677165cd3b00096
8656c6c6f20626f6254f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
e5033632611a
KE2: fa1f33a43a03123ebe35345ef93aa23b57ea8bfbee7022b05a179d60768ba02e
eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8ede8d6d272c6502f41
e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438877888c6579500b
73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a367bdb425fa1dd4c4
9e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc439c83030464eff
ecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282ef49d83cef5f1411ea30abb
82b08bd85423aadb86e2c19df5930b3c8498b9f9796a9587e233e67f2397f10fec635
5b68102534f1f1b115b4ddf7485840efcd7c000f7ebe71d4ab326006a3aeca802435d
c995a38ac6662221f974cb920992d82b8ef8d147c77e29b628a82b5ccb01ea2f7bb60
af94cd1860e1bd974a11a1c9bd827789f663c4758eb71058c244138de0c2
KE3: d81f93397cdba85a43993d4d9afbdc67f147adfa2b223213b19692cb820eef48
5073eda4c8236b2f47702404ad60d9a875d189626fc7b7cc861825385470ae54
export_key: 03192555940b5b42e64e6200bf55cc701f1bace3d402a2f8d83977843
51a1e3fa1f07a471b783b208acb1d92be47903b6fa3a0df9f4d4b7956ee4f431e2950
f6
session_key: 58a7fa98bf3b7b52da21406abfb11d98734354edd47d7b32462c0513
f0617c89824ea6031d4147a86fc9f6c6837ce640c12fb937d764f296d1a9421ad1b2a
5d5
"#,
];
macro_rules! parse {
( $v:ident, $s:expr ) => {
match decode(&$v, $s) {
Some(x) => x,
None => vec![],
}
};
}
macro_rules! rfc_to_params {
( $v:ident ) => {
$v.iter()
.map(|x| populate_test_vectors(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap()))
.collect::<Vec<TestVectorParameters>>()
};
}
fn rfc_to_json(input: &str) -> String {
let mut json = vec![];
for line in input.lines() {
// If line contains colon, then
if line.contains(":") {
if json.len() > 0 {
// Adding closing quote for previous line, comma, and newline
json.push("\",\n".to_string());
}
let mut iter = line.split(":");
let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap();
json.push(format!(" \"{}\": \"{}", key, val));
} else {
let s = line.trim().to_string();
if s.len() > 0 {
json.push(s);
}
}
}
format!("{{\n{}\"\n}}", json.join(""))
}
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
TestVectorParameters {
envelope_mode: match values["EnvelopeMode"].as_str() {
Some("01") => EnvelopeMode::Base,
Some("02") => EnvelopeMode::CustomIdentifier,
_ => panic!("Could not match envelope mode"),
},
client_public_key: parse!(values, "client_public_key"),
client_private_key: parse!(values, "client_private_key"),
client_keyshare: parse!(values, "client_keyshare"),
client_private_keyshare: parse!(values, "client_private_keyshare"),
server_public_key: parse!(values, "server_public_key"),
server_private_key: parse!(values, "server_private_key"),
server_keyshare: parse!(values, "server_keyshare"),
server_private_keyshare: parse!(values, "server_private_keyshare"),
client_identity: parse!(values, "client_identity"),
server_identity: parse!(values, "server_identity"),
password: parse!(values, "password"),
blind_registration: parse!(values, "blind_registration"),
oprf_key: parse!(values, "oprf_key"),
envelope_nonce: parse!(values, "envelope_nonce"),
client_nonce: parse!(values, "client_nonce"),
server_nonce: parse!(values, "server_nonce"),
client_info: parse!(values, "client_info"),
server_info: parse!(values, "server_info"),
registration_request: parse!(values, "registration_request"),
registration_response: parse!(values, "registration_response"),
registration_upload: parse!(values, "registration_upload"),
KE1: parse!(values, "KE1"),
KE2: parse!(values, "KE2"),
KE3: parse!(values, "KE3"),
blind_login: parse!(values, "blind_login"),
export_key: parse!(values, "export_key"),
session_key: parse!(values, "session_key"),
}
}
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key.clone());
let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
)?;
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
Ok(password_file.serialize())
}
#[test]
fn test_registration_request() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut rng,
&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 mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
)?;
assert_eq!(
hex::encode(parameters.registration_response),
hex::encode(server_registration_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_registration_upload() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut rng,
&parameters.password,
)?;
let sk_u_and_nonce: Vec<u8> =
[parameters.client_private_key, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
let result = client_registration_start_result.state.finish(
&mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ClientRegistrationFinishParameters::WithIdentifiers(
parameters.client_identity,
parameters.server_identity,
)
} else {
ClientRegistrationFinishParameters::default()
},
)?;
assert_eq!(
hex::encode(parameters.registration_upload),
hex::encode(result.message.serialize())
);
assert_eq!(
hex::encode(parameters.export_key),
hex::encode(result.export_key.to_vec())
);
}
Ok(())
}
#[test]
fn test_ke1() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let client_login_start = [
parameters.blind_login,
parameters.client_private_keyshare,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?;
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 password_file_bytes = get_password_file_bytes(&parameters)?;
let mut server_private_keyshare_and_nonce_rng =
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
&Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.server_info.to_vec(),
parameters.client_identity,
parameters.server_identity,
)
} else {
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
},
)?;
assert_eq!(
hex::encode(&parameters.client_info),
hex::encode(server_login_start_result.plain_info),
);
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,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?;
let client_login_finish_result = client_login_start_result.state.finish(
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?,
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ClientLoginFinishParameters::WithIdentifiers(
parameters.client_identity,
parameters.server_identity,
)
} else {
ClientLoginFinishParameters::default()
},
)?;
assert_eq!(
hex::encode(&parameters.server_info),
hex::encode(&client_login_finish_result.confidential_info)
);
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 password_file_bytes = get_password_file_bytes(&parameters)?;
let mut server_private_keyshare_and_nonce_rng =
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
&Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.server_info.to_vec(),
parameters.client_identity,
parameters.server_identity,
)
} else {
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
},
)?;
let server_login_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&parameters.KE3[..])?)?;
assert_eq!(
hex::encode(parameters.session_key),
hex::encode(server_login_result.session_key)
);
}
Ok(())
}
+102
View File
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use std::string::{String, ToString};
use std::vec::Vec;
use std::{format, vec};
pub(crate) fn rfc_to_json(input: &str) -> String {
format!("{{\n{}\n}}", parse_vector_types(input))
}
fn parse_vector_types(input: &str) -> String {
let re = regex::Regex::new(r" {2}(?P<type>.+?) Test Vectors").unwrap();
let mut vector_types = vec![];
let chunks: Vec<&str> = re.split(input).collect();
for (count, caps) in (1..).zip(re.captures_iter(input)) {
let vector_type = format!(
"\"{}\": [\n {} \n]",
&caps["type"].trim(),
parse_ciphersuites(chunks[count])
);
vector_types.push(vector_type);
}
vector_types.join(",\n")
}
fn parse_ciphersuites(input: &str) -> String {
let re = regex::Regex::new(
r" Configuration\n([\s\S])*?OPRF: (?P<oprf>.*?)\n([\s\S])*?Group: (?P<group>.*?)\n",
)
.unwrap();
let mut ciphersuites = vec![];
let chunks: Vec<&str> = re.split(input).collect();
for (count, caps) in (1..).zip(re.captures_iter(input)) {
let ciphersuite = format!(
"{{ \"{}, {}\": {{ {} }} }}",
&caps["oprf"],
&caps["group"],
parse_params(chunks[count])
);
ciphersuites.push(ciphersuite);
}
ciphersuites.join(",\n")
}
fn parse_params(input: &str) -> String {
let mut params = vec![];
let mut param = String::new();
let mut lines = input.lines();
loop {
match lines.next() {
None => {
// Clear out any existing string and flush to params
param += "\"";
params.push(param);
return params.join(",\n");
}
Some(line) => {
// First, trim out any whitespace
let line = line.trim();
// If line contains :, then
if line.contains(':') {
// Clear out any existing string and flush to params
if !param.is_empty() {
param += "\"";
params.push(param);
}
let mut iter = line.split(':');
let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap();
param = format!(" \"{key}\": \"{val}");
} else {
let s = line.trim().to_string();
if s.contains('~') || s.contains('#') {
// Ignore comment lines
continue;
}
if s.contains("C.") {
// Ignore section lines
continue;
}
if !s.is_empty() {
param += &s;
}
}
}
}
}
}
+806
View File
@@ -0,0 +1,806 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
//! The OPAQUE test vectors taken from Appendix C of
//! [RFC 9807](https://www.rfc-editor.org/rfc/rfc9807.txt)
pub(crate) static VECTORS: &str = r#"
C.1. Real Test Vectors
C.1.1. OPAQUE-3DH Real Test Vector 1
C.1.1.1. Configuration
OPRF: ristretto255-SHA512
Hash: SHA512
KSF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
C.1.1.2. Input Values
oprf_seed: f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b0
54a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989e
f
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d2
3ba7a38dfec
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
f612fdfc6d
server_private_key: 47451a85372f8b3537e249d7b54188091fb18edde78094b43
e2ba42b5eb89f0d
server_public_key: b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a
382c9b79df1a78
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
138e3d4a1
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
0cae6a6cc
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
1091933541936304b
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
1e85ff80da12f982f
blind_registration: 76cfbfe758db884bebb33582331ba9f159720ca8784a2a070
a265d9c2d6abe01
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
b0790308
C.1.1.3. Intermediate Values
client_public_key: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbac
cafb57ac5c3675
auth_key: 6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa9003773
0494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822
randomized_password: aac48c25ab036e30750839d31d6e73007344cb1155289fb7
d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf15
0031d1435d9
envelope: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a3
8dfec634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b
8d9e9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5
handshake_secret: 81263cb85a0cfa12450f0f388de4e92291ec4c7c7a0878b6245
50ff528726332f1298fc6cc822a432c89504347c7a2ccd70316ae3da6a15e0399e6db
3f7c1b12
server_mac_key: 0d36b26cfe38f51f804f0a9361818f32ee1ce2a4e5578653b5271
84af058d3b2d8075c296fd84d24677913d1baa109290cd81a13ed383f9091a3804e65
298dfc
client_mac_key: 91750adbac54a5e8e53b4c233cc8d369fe83b0de1b6a3cd85575e
eb0bb01a6a90a086a2cf5fe75fff2a9379c30ba9049510a33b5b0b1444a88800fc3ee
e2260d
oprf_key: 5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4
c6d05
C.1.1.4. Output Values
registration_request: 5059ff249eb1551b7ce4991f3336205bde44a105a032e74
7d21bf382e75f7a71
registration_response: 7408a268083e03abc7097fc05b587834539065e86fb0c7
b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a3
82c9b79df1a78
registration_upload: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccb
accafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e
66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa8
2785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfe
c634b0f5b96109c198a8027da51854c35bee90d1e1c781806d07d49b76de6a28b8d9e
9b6c93b9f8b64d16dddd9c5bfb5fea48ee8fd2f75012a8b308605cdd8ba5
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29b
ee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326
KE2: 7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb471
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec6
0bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced
4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fedc80188ca46743c
52786e0382f95ad85c08f6afcd1ccfbff95e2bdeb015b166c6b20b92f832cc6df01e0
b86a7efd92c1c804ff865781fa93f2f20b446c8371b671cd9960ecef2fe0d0f749498
6fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f197
1b29eb1d5d0bd733e40816c91f7912cc4a660c48dae03e57aaa38f3d0cffcfc21852e
bc8b405d15bd6744945ba1a93438a162b6111699d98a16bb55b7bdddfe0fc5608b23d
a246e7bd73b47369169c5c90
KE3: 4455df4f810ac31a6748835888564b536e6da5d9944dfea9e34defb9575fe5e2
661ef61d2ae3929bcf57e53d464113d364365eb7d1a57b629707ca48da18e442
export_key: 1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a
970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f
16
session_key: 42afde6f5aca0cfa5c163763fbad55e73a41db6b41bc87b8e7b62214
a8eedc6731fa3cb857d657ab9b3764b89a84e91ebcb4785166fbb02cedfcbdfda215b
96f
C.1.2. OPAQUE-3DH Real Test Vector 2
C.1.2.1. Configuration
OPRF: ristretto255-SHA512
Hash: SHA512
KSF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
C.1.2.2. Input Values
client_identity: 616c696365
server_identity: 626f62
oprf_seed: f433d0227b0b9dd54f7c4422b600e764e47fb503f1f9a0f0a47c6606b0
54a7fdc65347f1a08f277e22358bbabe26f823fca82c7848e9a75661f4ec5d5c1989e
f
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d2
3ba7a38dfec
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
f612fdfc6d
server_private_key: 47451a85372f8b3537e249d7b54188091fb18edde78094b43
e2ba42b5eb89f0d
server_public_key: b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a
382c9b79df1a78
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
138e3d4a1
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
0cae6a6cc
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
1091933541936304b
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
1e85ff80da12f982f
blind_registration: 76cfbfe758db884bebb33582331ba9f159720ca8784a2a070
a265d9c2d6abe01
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
b0790308
C.1.2.3. Intermediate Values
client_public_key: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccbac
cafb57ac5c3675
auth_key: 6cd32316f18d72a9a927a83199fa030663a38ce0c11fbaef82aa9003773
0494fc555c4d49506284516edd1628c27965b7555a4ebfed2223199f6c67966dde822
randomized_password: aac48c25ab036e30750839d31d6e73007344cb1155289fb7
d329beb932e9adeea73d5d5c22a0ce1952f8aba6d66007615cd1698d4ac85ef1fcf15
0031d1435d9
envelope: ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a3
8dfec1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b68
0b2b3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c
handshake_secret: 5e723bed1e5276de2503419eba9da61ead573109c4012268323
98c7e08155b885bfe7bc93451f9d887a0c1d0c19233e40a8e47b347a9ac3907f94032
a4cff64f
server_mac_key: dad66bb9251073d17a13f8e5500f36e5998e3cde520ca0738e708
5af62fd97812eb79a745c94d0bf8a6ac17f980cf435504cf64041eeb6bb237796d2c7
f81e9a
client_mac_key: f816fe2914f7c5b29852385615d7c7f31ac122adf202d7ccd4976
06d7aabd48930323d1d02b1cc9ecd456c4de6f46c7950becb18bffd921dd5876381b5
486ffe
oprf_key: 5d4c6a8b7c7138182afb4345d1fae6a9f18a1744afbcc3854f8f5a2b4b4
c6d05
C.1.2.4. Output Values
registration_request: 5059ff249eb1551b7ce4991f3336205bde44a105a032e74
7d21bf382e75f7a71
registration_response: 7408a268083e03abc7097fc05b587834539065e86fb0c7
b6342fcf5e01e5b019b2fe7af9f48cc502d016729d2fe25cdd433f2c4bc904660b2a3
82c9b79df1a78
registration_upload: 76a845464c68a5d2f7e442436bb1424953b17d3e2e289ccb
accafb57ac5c36751ac5844383c7708077dea41cbefe2fa15724f449e535dd7dd562e
66f5ecfb95864eadddec9db5874959905117dad40a4524111849799281fefe3c51fa8
2785c5ac13171b2f17bc2c74997f0fce1e1f35bec6b91fe2e12dbd323d23ba7a38dfe
c1ac902dc5589e9a5f0de56ad685ea8486210ef41449cd4d8712828913c5d2b680b2b
3af4a26c765cff329bfb66d38ecf1d6cfa9e7a73c222c6efe0d9520f7d7c
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc6e29b
ee50701498605b2c085d7b241ca15ba5c32027dd21ba420b94ce60da326
KE2: 7e308140890bcde30cbcea28b01ea1ecfbd077cff62c4def8efa075aabcbb471
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6dd6ec6
0bcdb26dc455ddf3e718f1020490c192d70dfc7e403981179d8073d1146a4f9aa1ced
4e4cd984c657eb3b54ced3848326f70331953d91b02535af44d9fea502150b67fe367
95dd8914f164e49f81c7688a38928372134b7dccd50e09f8fed9518b7b2f94835b3c4
fe4c8475e7513f20eb97ff0568a39caee3fd6251876f71cd9960ecef2fe0d0f749498
6fa3d8b2bb01963537e60efb13981e138e3d4a1c4f62198a9d6fa9170c42c3c71f197
1b29eb1d5d0bd733e40816c91f7912cc4a292371e7809a9031743e943fb3b56f51de9
03552fc91fba4e7419029951c3970b2e2f0a9dea218d22e9e4e0000855bb6421aa361
0d6fc0f4033a6517030d4341
KE3: 7a026de1d6126905736c3f6d92463a08d209833eb793e46d0f7f15b3e0f62c76
43763c02bbc6b8d3d15b63250cae98171e9260f1ffa789750f534ac11a0176d5
export_key: 1ef15b4fa99e8a852412450ab78713aad30d21fa6966c9b8c9fb3262a
970dc62950d4dd4ed62598229b1b72794fc0335199d9f7fcc6eaedde92cc04870e63f
16
session_key: ae7951123ab5befc27e62e63f52cf472d6236cb386c968cc47b7e34f
866aa4bc7638356a73cfce92becf39d6a7d32a1861f12130e824241fe6cab34fbd471
a57
C.1.3. OPAQUE-3DH Real Test Vector 3
C.1.3.1. Configuration
OPRF: ristretto255-SHA512
Hash: SHA512
KSF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
Group: curve25519
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
C.1.3.2. Input Values
oprf_seed: a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67
a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1
b
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cc
a9bf44d6e0b
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
f612fdfc6d
server_private_key: c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
a7725bf0e79d564
server_public_key: a41e28269b4e97a66468cc00c5a57753e192e1527669897706
88aa90486ef031
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
138e3d4a1
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
0cae6a6cc
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
1091933541936304b
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
1e85ff80da12f982f
blind_registration: c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee
04baddb2034a70a
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
b0790308
C.1.3.3. Intermediate Values
client_public_key: 0936ea94ab030ec332e29050d266c520e916731a052d05ced7
e0cfe751142b48
auth_key: 7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d576
4739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c
randomized_password: 3a602c295a9c323d9362fe286f104567ed6862b25dbe30fa
da844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8
f3963fbb2e1
envelope: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44
d6e0b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac
150b4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb
handshake_secret: 178c8c15e025252380c3edb1c6ad8ac52573b38d536099e2f86
5786f5e31c642608550c0c6f281c37ce259667dd72768af31630e0eb36f1096a2e642
1c2aa163
server_mac_key: f3c6a8e069c54bb0d8905139f723c9e22f5c662dc08848243a665
4c8223800019b9823523d84da2ef67ca1c14277630aace464c113be8a0a658c39e181
a8bb71
client_mac_key: b1ee7ce52dbd0ab72872924ff11596cb196bbabfc319e74aca78a
de54a0f74dd15dcf5621f6d2e79161b0c9b701381d494836dedbb86e584a65b34267a
370e01
oprf_key: 62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea14
6030a
C.1.3.4. Output Values
registration_request: 26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f65538
1a67de02c59fd5436
registration_response: 506e8f1b89c098fb89b5b6210a05f7898cafdaea221761
e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e15276698977068
8aa90486ef031
registration_upload: 0936ea94ab030ec332e29050d266c520e916731a052d05ce
d7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e
7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9e
f53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0
b20c1e81fef28e92e897ca8287d49a55075b47c3988ff0fff367d79a3e350ccac150b
4a3ff48b4770c8e84e437b3d4e68d2b95833f7788f7eb93fa6a8afb85ecb
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83
b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911
KE2: 9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d00
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc3
1127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151c
eb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812fd9afddf98af8cd
58067ca43b0633b6cadd0e9d987f89623fed4d3583bdf6910c425600e90dab3c6b351
3188a465461a67f6bbc47aeba808f7f7e2c6d66f5c3271cd9960ecef2fe0d0f749498
6fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad
75865ee7efef95f4cb6c25d477f720502676f06a3b806da262139bf3fa76a1090b94d
ac78bc3bc6f8747d5b35acf94eff3ec2ebe7d49b8cf16be64120b279fe92664e47be5
da7e60f08f12e91192652f79
KE3: 550e923829a544496d8316c490da2b979b78c730dd75be3a17f237a26432c19f
bba54b6a0467b1c22ecbd6794bc5fa5b04215ba1ef974c6b090baa42c5bb984f
export_key: 9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5
070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c
38
session_key: fd2fdd07c1bcc88e81c1b1d1de5ad62dfdef1c0b8209ff9d671e1fac
55ce9c34d381c1fb2703ff53a797f77daccbe33047ccc167b8105171e10ec962eea20
3aa
C.1.4. OPAQUE-3DH Real Test Vector 4
C.1.4.1. Configuration
OPRF: ristretto255-SHA512
Hash: SHA512
KSF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
Group: curve25519
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
C.1.4.2. Input Values
client_identity: 616c696365
server_identity: 626f62
oprf_seed: a78342ab84d3d30f08d5a9630c79bf311c31ed7f85d9d4959bf492ec67
a0eec8a67dfbf4497248eebd49e878aab173e5e4ff76354288fdd53e949a5f7c9f7f1
b
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cc
a9bf44d6e0b
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
f612fdfc6d
server_private_key: c06139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
a7725bf0e79d564
server_public_key: a41e28269b4e97a66468cc00c5a57753e192e1527669897706
88aa90486ef031
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
138e3d4a1
client_nonce: da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb38
0cae6a6cc
client_keyshare_seed: 82850a697b42a505f5b68fcdafce8c31f0af2b581f063cf
1091933541936304b
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
1e85ff80da12f982f
blind_registration: c575731ffe1cb0ca5ba63b42c4699767b8b9ab78ba39316ee
04baddb2034a70a
blind_login: 6ecc102d2e7a7cf49617aad7bbe188556792d4acd60a1a8a8d2b65d4
b0790308
C.1.4.3. Intermediate Values
client_public_key: 0936ea94ab030ec332e29050d266c520e916731a052d05ced7
e0cfe751142b48
auth_key: 7e880ab484f750e80e6f839d975aff476070ce65066d85ea62523d1d576
4739d91307fac47186a4ab935e6a5c7f70cb47faa9473311947502c022cc67ae9440c
randomized_password: 3a602c295a9c323d9362fe286f104567ed6862b25dbe30fa
da844f19e41cf40047424b7118e15dc2c1a815a70fea5c8de6c30aa61440cd4b4b5e8
f3963fbb2e1
envelope: 40d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44
d6e0bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d
69061d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759
handshake_secret: 13e7dc6afa5334b9dfffe26bee3caf744ef4add176caee464cd
eb3d37303b90de35a8bf095df84471ac77d705f12fe232f1571de1d6a001d3e808998
73a142dc
server_mac_key: a58135acfb2bde92d506cf59119729a6404ad94eba294e4b52a63
baf58cfe03f21bcf735222c7f2c27a60bd958be7f6aed50dc03a78f64e7ae4ac1ff07
1b95aa
client_mac_key: 1e1a8ba156aadc4a302f707d2193c9dab477b355f430d450dd407
ce40dc75613f76ec33dec494f8a6bfdcf951eb060dac33e6572c693954fe92e33730c
9ab0a2
oprf_key: 62ef7f7d9506a14600c34f642aaf6ef8019cc82a6755db4fded5248ea14
6030a
C.1.4.4. Output Values
registration_request: 26f3dbfd76b8e5f85b4da604f42889a7d4b1bc919f65538
1a67de02c59fd5436
registration_response: 506e8f1b89c098fb89b5b6210a05f7898cafdaea221761
e8d5272fc39e0f9f08a41e28269b4e97a66468cc00c5a57753e192e15276698977068
8aa90486ef031
registration_upload: 0936ea94ab030ec332e29050d266c520e916731a052d05ce
d7e0cfe751142b486d23c6ed818882f9bdfdcf91389fcbc0b7a3faf92bd0bd6be4a1e
7730277b694fc7c6ba327fbe786af18487688e0f7c148bbd54dc2fc80c28e7a976d9e
f53c3540d6b67fdd7da7c49894750754514dbd2070a407166bd2a5237cca9bf44d6e0
bb4c0eab6143959a650c5f6b32acf162b1fbe95bb36c5c4f99df53865c4d3537d6906
1d80522d772cd0efdbe91f817f6bf7259a56e20b4eb9cbe9443702f4b759
KE1: c4dedb0ba6ed5d965d6f250fbe554cd45cba5dfcce3ce836e4aee778aa3cd44d
da7e07376d6d6f034cfa9bb537d11b8c6b4238c334333d1f0aebb380cae6a6cc10a83
b9117d3798cb2957fbdb0268a0d63dbf9d66bde5c00c78affd80026c911
KE2: 9a0e5a1514f62e005ea098b0d8cf6750e358c4389e6add1c52aed9500fa19d00
38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d22cc3
1127d6f0096755be3c3d2dd6287795c317aeea10c9485bf4f419a786642c19a8f151c
eb5e8767d175248c62c017de94057398d28bf0ed00d1b50ee4f812699bff7663be3c5
d59de94d8e7e58817c7da005b39c25d25555c929e1c5cf6c1b82837b1367c839aab56
a422c0d97719426a79a16f9869cf852100597b23b5a071cd9960ecef2fe0d0f749498
6fa3d8b2bb01963537e60efb13981e138e3d4a141f55f0bef355cfb34ccd468fdacad
75865ee7efef95f4cb6c25d477f72050267cc22c87edbf3ecaca64cb33bc60dc3bfc5
51e365f0d46a7fed0e09d96f9afbb48868f5bb3c3e05a86ed8c9476fc22c58306c5a2
91be34388e09548ba9d70f39
KE3: d16344e791c3f18594d22ba068984fa18ec1e9bead662b75f66826ffd627932f
cd1ec40cd01dcf5f63f4055ebe45c7717a57a833aad360256cf1e1c20c0eae1c
export_key: 9dec51d6d0f6ce7e4345f10961053713b07310cc2e45872f57bbd2fe5
070fdf0fb5b77c7ddaa2f3dc5c35132df7417ad7fefe0f690ad266e5a54a21d045c9c
38
session_key: f6116d3aa0e4089a179713bad4d98ed5cb57e5443cae8d36ef78996f
a60f3dc6e9fcdd63c001596b06dbc1285d80211035cc0e485506b3f7a650cbf78c5bf
fc9
C.1.5. OPAQUE-3DH Real Test Vector 5
C.1.5.1. Configuration
OPRF: P256-SHA256
Hash: SHA256
KSF: Identity
KDF: HKDF-SHA256
MAC: HMAC-SHA256
Group: P256_XMD:SHA-256_SSWU_RO_
Context: 4f50415155452d504f43
Nh: 32
Npk: 33
Nsk: 32
Nm: 32
Nx: 32
Nok: 32
C.1.5.2. Input Values
oprf_seed: 62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0
a8dce2
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebd
cf65670e51f
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
f612fdfc6d
server_private_key: c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
a7725bf0e79d5e5
server_public_key: 035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9
f2092d6067784874
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
138e3d4a1
client_nonce: ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f
91fdaeeb1
client_keyshare_seed: 633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d
7e74d783b7cfab8dc
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
1e85ff80da12f982f
blind_registration: 411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34
ca527d597078153
blind_login: c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9
942256c1
C.1.5.3. Intermediate Values
client_public_key: 03b218507d978c3db570ca994aaf36695a731ddb2db272c817
f79746fc37ae5214
auth_key: 5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd
0987b
randomized_password: 06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa3
19151b9382258fb0
envelope: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6567
0e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad67b8
handshake_secret: 83a932431a8f25bad042f008efa2b07c6cd0faa8285f335b636
3546a9f9b235f
server_mac_key: 13e928581febfad28855e3e7f03306d61bd69489686f621535d44
a1365b73b0d
client_mac_key: afdc53910c25183b08b930e6953c35b3466276736d9de2e9c5efa
f150f4082c5
oprf_key: 2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66
f7f31
C.1.5.4. Output Values
registration_request: 029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f507
0d26a5315a1b50229f8
registration_response: 0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2
f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd
9f2092d6067784874
registration_upload: 03b218507d978c3db570ca994aaf36695a731ddb2db272c8
17f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea0
29148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6
5670e51fad30bbcfc1f8eda0211553ab9aaf26345ad59a128e80188f035fe4924fad6
7b8
KE1: 037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e
07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022
ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6
KE2: 0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc147648
1138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0
c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe
0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae26837b6ce688bf
9af2542f04eec9ab96a1b9328812dc2f5c89182ed47fead61f09f71cd9960ecef2fe0
d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf33
7bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b50a73b145bc87a157e8c58c0
342e2047ee22ae37b63db17e0a82a30fcc4ecf7b
KE3: e97cab4433aa39d598e76f13e768bba61c682947bdcf9936035e8a3a3ebfb66e
export_key: c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a
853c64b
session_key: 484ad345715ccce138ca49e4ea362c6183f0949aaaa1125dc3bc3f80
876e7cd1
C.1.6. OPAQUE-3DH Real Test Vector 6
C.1.6.1. Configuration
OPRF: P256-SHA256
Hash: SHA256
KSF: Identity
KDF: HKDF-SHA256
MAC: HMAC-SHA256
Group: P256_XMD:SHA-256_SSWU_RO_
Context: 4f50415155452d504f43
Nh: 32
Npk: 33
Nsk: 32
Nm: 32
Nx: 32
Nok: 32
C.1.6.2. Input Values
client_identity: 616c696365
server_identity: 626f62
oprf_seed: 62f60b286d20ce4fd1d64809b0021dad6ed5d52a2c8cf27ae6582543a0
a8dce2
credential_identifier: 31323334
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebd
cf65670e51f
masking_nonce: 38fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80
f612fdfc6d
server_private_key: c36139381df63bfc91c850db0b9cfbec7a62e86d80040a41a
a7725bf0e79d5e5
server_public_key: 035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd9
f2092d6067784874
server_nonce: 71cd9960ecef2fe0d0f7494986fa3d8b2bb01963537e60efb13981e
138e3d4a1
client_nonce: ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f
91fdaeeb1
client_keyshare_seed: 633b875d74d1556d2a2789309972b06db21dfcc4f5ad51d
7e74d783b7cfab8dc
server_keyshare_seed: 05a4f54206eef1ba2f615bc0aa285cb22f26d1153b5b40a
1e85ff80da12f982f
blind_registration: 411bf1a62d119afe30df682b91a0a33d777972d4f2daa4b34
ca527d597078153
blind_login: c497fddf6056d241e6cf9fb7ac37c384f49b357a221eb0a802c989b9
942256c1
C.1.6.3. Intermediate Values
client_public_key: 03b218507d978c3db570ca994aaf36695a731ddb2db272c817
f79746fc37ae5214
auth_key: 5bd4be1602516092dc5078f8d699f5721dc1720a49fb80d8e5c16377abd
0987b
randomized_password: 06be0a1a51d56557a3adad57ba29c5510565dcd8b5078fa3
19151b9382258fb0
envelope: a921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6567
0e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0971
handshake_secret: 80bdcc498f22de492e90ee8101fcc7c101e158dd49c77f7c283
816ae329ed62f
server_mac_key: 0f82432fbdb5b90daf27a91a3acc42299a9590dba1b77932c2207
b4cb3d4a157
client_mac_key: 7f629eb0b1b69979b07ca1f564b3e92ed22f07569fd1d11725d93
e46731fbe71
oprf_key: 2dfb5cb9aa1476093be74ca0d43e5b02862a05f5d6972614d7433acdc66
f7f31
C.1.6.4. Output Values
registration_request: 029e949a29cfa0bf7c1287333d2fb3dc586c41aa652f507
0d26a5315a1b50229f8
registration_response: 0350d3694c00978f00a5ce7cd08a00547e4ab5fb5fc2b2
f6717cdaa6c89136efef035f40ff9cf88aa1f5cd4fe5fd3da9ea65a4923a5594f84fd
9f2092d6067784874
registration_upload: 03b218507d978c3db570ca994aaf36695a731ddb2db272c8
17f79746fc37ae52147f0ed53532d3ae8e505ecc70d42d2b814b6b0e48156def71ea0
29148b2803aafa921f2a014513bd8a90e477a629794e89fec12d12206dde662ebdcf6
5670e51f4d7773a36a208a866301dbb2858e40dc5638017527cf91aef32d3848eebe0
971
KE1: 037342f0bcb3ecea754c1e67576c86aa90c1de3875f390ad599a26686cdfee6e
07ab3d33bde0e93eda72392346a7a73051110674bbf6b1b7ffab8be4f91fdaeeb1022
ed3f32f318f81bab80da321fecab3cd9b6eea11a95666dfa6beeaab321280b6
KE2: 0246da9fe4d41d5ba69faa6c509a1d5bafd49a48615a47a8dd4b0823cc147648
1138fe59af0df2c79f57b8780278f5ae47355fe1f817119041951c80f612fdfc6d2f0
c547f70deaeca54d878c14c1aa5e1ab405dec833777132eea905c2fbb12504a67dcbe
0e66740c76b62c13b04a38a77926e19072953319ec65e41f9bfd2ae268d7f10604202
1c80300e4c6f585980cf39fc51a4a6bba41b0729f9b240c729e5671cd9960ecef2fe0
d0f7494986fa3d8b2bb01963537e60efb13981e138e3d4a103c1701353219b53acf33
7bf6456a83cefed8f563f1040b65afbf3b65d3bc9a19b84922c7e5d074838a8f27859
2c53f61fb59f031e85ad480c0c71086b871e1b24
KE3: 46833578cee137775f6be3f01b80748daac5a694101ad0e9e7025480552da56a
export_key: c3c9a1b0e33ac84dd83d0b7e8af6794e17e7a3caadff289fbd9dc769a
853c64b
session_key: 27766fabd8dd88ff37fbd0ef1a491e601d10d9f016c2b28c4bd1b0fb
7511a3c3
C.2. Fake Test Vectors
C.2.1. OPAQUE-3DH Fake Test Vector 1
C.2.1.1. Configuration
OPRF: ristretto255-SHA512
Hash: SHA512
KSF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
Group: ristretto255
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
C.2.1.2. Input Values
client_identity: 616c696365
server_identity: 626f62
oprf_seed: 743fc168d1f826ad43738933e5adb23da6fb95f95a1b069f0daa0522d0
a78b617f701fc6aa46d3e7981e70de7765dfcd6b1e13e3369a582eb8dc456b10aa53b
0
credential_identifier: 31323334
masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61
9e27b6e5a6
client_private_key: 2b98980aa95ab53a0f39f0291903d2fdf04b00c167f081416
9922df873002409
client_public_key: 84f43f9492e19c22d8bdaa4447cc3d4db1cdb5427a9f852c47
07921212c36251
server_private_key: c788585ae8b5ba2942b693b849be0c0426384e41977c18d2e
81fbe30fd7c9f06
server_public_key: 825f832667480f08b0c9069da5083ac4d0e9ee31b49c4e0310
031fea04d52966
server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813
055ae2d12
client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276
d1e15bdeb4c355e94
server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc
5b4f6b62df07f78c2
masking_key: 39ebd51f0e39a07a1c2d2431995b0399bca9996c5d10014d6ebab445
3dc10ce5cef38ed3df6e56bfff40c2d8dd4671c2b4cf63c3d54860f31fe40220d690b
b71
KE1: b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be448905
42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ab641d
7f20a245a09f1d4dbb6e301661af7f352beb0791d055e48d3645232f77f
C.2.1.3. Output Values
KE2: 928f79ad8df21963e91411b9f55165ba833dea918f441db967cdc09521d22925
9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a632b5a
b1bff96636144faa4f9f9afaac75dd88ea99cf5175902ae3f3b2195693f165f11929b
a510a5978e64dcdabecbd7ee1e4380ce270e58fea58e6462d92964a1aaef72698bca1
c673baeb04cc2bf7de5f3c2f5553464552d3a0f7698a9ca7f9c5e70c6cb1f706b2f17
5ab9d04bbd13926e816b6811a50b4aafa9799d5ed7971e10f6eeab2a7a420bf09da9b
27a4639645622c46358de9cf7ae813055ae2d1298251c5ba55f6b0b2d58d9ff0c88fe
4176484be62a96db6e2a8c4d431bd1bf27fe6c1d0537603835217d42ebf7b25819827
32e74892fd28211b31ed33863f0beaf75ba6f59474c0aaf9d78a60a9b2f4cd24d7ab5
4131b3c8efa192df6b72db4c
C.2.2. OPAQUE-3DH Fake Test Vector 2
C.2.2.1. Configuration
OPRF: ristretto255-SHA512
Hash: SHA512
KSF: Identity
KDF: HKDF-SHA512
MAC: HMAC-SHA512
Group: curve25519
Context: 4f50415155452d504f43
Nh: 64
Npk: 32
Nsk: 32
Nm: 64
Nx: 64
Nok: 32
C.2.2.2. Input Values
client_identity: 616c696365
server_identity: 626f62
oprf_seed: 66e650652a8266b2205f31fdd68adeb739a05b5e650b19e7edc75e734a
1296d6088188ca46c31ae8ccbd42a52ed338c06e53645387a7efbc94b6a0449526155
e
credential_identifier: 31323334
masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61
9e27b6e5a6
client_private_key: 288bf63470199221847bb035d99f96531adf8badd14cb1571
b48f7a506649660
client_public_key: 3c64a3153854cc9f0c23aab3c1a19106ec8bab4730736d1d00
3880a1d5a59005
server_private_key: 30fbe7e830be1fe8d2187c97414e3826040cbe49b893b6422
9bab5e85a588846
server_public_key: 78b3040047ff26572a7619617601a61b9c81899bee92f00cfc
aa5eed96863555
server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813
055ae2d12
client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276
d1e15bdeb4c355e94
server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc
5b4f6b62df07f78c2
masking_key: 79ad2621b0757a447dff7108a8ae20a068ce67872095620f415ea611
c9dcc04972fa359538cd2fd6528775ca775487b2b56db642049b8a90526b975a38484
c6a
KE1: b0a26dcaca2230b8f5e4b1bcab9c84b586140221bb8b2848486874b0be448905
42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0ac059b
7ba2aec863933ae48816360c7a9022e83d822704f3b0b86c0502a66e574
C.2.2.3. Output Values
KE2: 6606b6fedbb33f19a81a1feb5149c600fe77252f58acd3080d7504d3dad4922f
9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a67db39
8c0f65d8c298eac430abdae4c80e82b552fb940c00f0cbcea853c0f96c1c15099f3d4
b0e83ecc249613116d605b8d77bb68bdf76994c2bc507e2dcae4176f00afed68ad25c
f3040a0e991acece31ca532117f5c12816997372ff031ad04ebcdce06c501da24e7b4
db95343456e2ed260895ec362694230a1fa20e24a9c71e10f6eeab2a7a420bf09da9b
27a4639645622c46358de9cf7ae813055ae2d122d9055eb8f83e1b497370adad5cc2a
417bf9be436a792def0c7b7ccb92b9e275d7c663104ea4655bd70570d975c05351655
d55fbfb392286edb55600a23b55ce18f8c60e0d1960c960412dd08eabc81ba7ca8ae2
b04aad65462321f51c298010
C.2.3. OPAQUE-3DH Fake Test Vector 3
C.2.3.1. Configuration
OPRF: P256-SHA256
Hash: SHA256
KSF: Identity
KDF: HKDF-SHA256
MAC: HMAC-SHA256
Group: P256_XMD:SHA-256_SSWU_RO_
Context: 4f50415155452d504f43
Nh: 32
Npk: 33
Nsk: 32
Nm: 32
Nx: 32
Nok: 32
C.2.3.2. Input Values
client_identity: 616c696365
server_identity: 626f62
oprf_seed: bb1cd59e16ac09bc0cb6d528541695d7eba2239b1613a3db3ade77b362
80f725
credential_identifier: 31323334
masking_nonce: 9c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c61
9e27b6e5a6
client_private_key: d423b87899fc61d014fc8330a4e26190fcfa470a3afe59243
24294af7dbbc1dd
client_public_key: 03b81708eae026a9370616c22e1e8542fe9dbebd36ce8a2661
b708e9628f4a57fc
server_private_key: 34fbe7e830be1fe8d2187c97414e3826040cbe49b893b6422
9bab5e85a5888c7
server_public_key: 0221e034c0e202fe883dcfc96802a7624166fed4cfcab4ae30
cf5f3290d01c88bf
server_nonce: 1e10f6eeab2a7a420bf09da9b27a4639645622c46358de9cf7ae813
055ae2d12
client_keyshare_seed: a270dc715dc2b4612bc7864312a05c3e9788ee1bad1f276
d1e15bdeb4c355e94
server_keyshare_seed: 360b0937f47d45f6123a4d8f0d0c0814b6120d840ebb8bc
5b4f6b62df07f78c2
masking_key: caecc6ccb4cae27cb54d8f3a1af1bac52a3d53107ce08497cdd362b1
992e4e5e
KE1: 0396875da2b4f7749bba411513aea02dc514a48d169d8a9531bd61d3af3fa9ba
ae42d4e61ed3f8d64cdd3b9d153343eca15b9b0d5e388232793c6376bd2d9cfd0a021
47a6583983cc9973b5082db5f5070890cb373d70f7ac1b41ed2305361009784
C.2.3.3. Output Values
KE2: 0201198dcd13f9792eb75dcfa815f61b049abfe2e3e9456d4bbbceec5f442efd
049c035896a043e70f897d87180c543e7a063b83c1bb728fbd189c619e27b6e5a6fac
da65ce0a97b9085e7af07f61fd3fdd046d257cbf2183ce8766090b8041a8bf28d79dd
4c9031ddc75bb6ddb4c291e639937840e3d39fc0d5a3d6e7723c09f7945df485bcf9a
efe3fe82d149e84049e259bb5b33d6a2ff3b25e4bfb7eff0962821e10f6eeab2a7a42
0bf09da9b27a4639645622c46358de9cf7ae813055ae2d12023f82bbb24e75b8683fd
13b843cd566efae996cd0016cffdcc24ee2bc937d026f80144878749a69565b433c10
40aff67e94f79345de888a877422b9bbe21ec329
"#;
+688
View File
@@ -0,0 +1,688 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
use core::ops::Add;
use std::vec;
use std::vec::Vec;
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
use crate::envelope::EnvelopeLen;
use crate::errors::*;
use crate::hash::OutputSize;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::NonceLen;
use crate::key_exchange::{Deserialize, Ke1MessageLen, Ke2MessageLen, KeyExchange, Serialize};
use crate::ksf::Identity;
use crate::messages::{
CredentialRequestLen, CredentialResponseLen, CredentialResponseWithoutKeLen,
RegistrationResponseLen, RegistrationUploadLen,
};
use crate::opaque::*;
use crate::tests::decode;
use crate::tests::mock_rng::CycleRng;
use crate::*;
use digest::OutputSizeUser;
use generic_array::typenum::Sum;
use generic_array::{ArrayLength, GenericArray};
use rand::Rng;
use rand::rand_core::UnwrapErr;
use rand::rngs::SysRng;
use serde_json::Value;
#[allow(non_snake_case)]
#[derive(Debug)]
pub struct OpaqueTestVectorParameters {
pub dummy_public_key: Vec<u8>,
pub dummy_masking_key: Vec<u8>,
pub context: Vec<u8>,
#[allow(dead_code)] // client_private_key is not tested in the test vectors
pub client_private_key: Option<Vec<u8>>,
pub client_keyshare_seed: Vec<u8>,
pub server_public_key: Vec<u8>,
pub server_private_key: Vec<u8>,
pub server_keyshare_seed: Vec<u8>,
pub client_identity: Option<Vec<u8>>,
pub server_identity: Option<Vec<u8>>,
pub credential_identifier: Vec<u8>,
pub password: Vec<u8>,
pub blind_registration: Vec<u8>,
pub oprf_seed: Vec<u8>,
pub masking_nonce: Vec<u8>,
pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
pub registration_request: Vec<u8>,
pub registration_response: Vec<u8>,
pub registration_upload: Vec<u8>,
pub KE1: Vec<u8>,
pub blind_login: Vec<u8>,
pub KE2: Vec<u8>,
pub KE3: Vec<u8>,
pub export_key: Vec<u8>,
pub session_key: Vec<u8>,
pub auth_key: Vec<u8>,
pub randomized_pwd: Vec<u8>,
pub handshake_secret: Vec<u8>,
pub server_mac_key: Vec<u8>,
pub client_mac_key: Vec<u8>,
pub oprf_key: Vec<u8>,
}
macro_rules! parse {
( $v:ident, $s:expr ) => {
parse_default!($v, $s, vec![])
};
}
macro_rules! parse_default {
( $v:ident, $s:expr, $d:expr ) => {
match decode(&$v, $s) {
Some(x) => x,
None => $d,
}
};
}
fn populate_test_vectors<CS: CipherSuite>(values: &Value) -> OpaqueTestVectorParameters {
let mut rng = UnwrapErr(SysRng);
OpaqueTestVectorParameters {
dummy_public_key: {
decode(values, "client_public_key").unwrap_or_else(|| {
KeGroup::<CS>::serialize_sk(&KeGroup::<CS>::random_sk(&mut UnwrapErr(SysRng)))
.to_vec()
})
},
dummy_masking_key: {
match decode(values, "masking_key") {
Some(value) => value,
None => {
let mut bytes =
GenericArray::<u8, <OprfHash<CS> as OutputSizeUser>::OutputSize>::default();
rng.fill_bytes(&mut bytes);
bytes.to_vec()
}
}
},
context: parse!(values, "Context"),
client_private_key: decode(values, "client_private_key"),
client_keyshare_seed: parse!(values, "client_keyshare_seed"),
server_public_key: parse!(values, "server_public_key"),
server_private_key: parse!(values, "server_private_key"),
server_keyshare_seed: parse!(values, "server_keyshare_seed"),
client_identity: decode(values, "client_identity"),
server_identity: decode(values, "server_identity"),
credential_identifier: parse!(values, "credential_identifier"),
password: parse!(values, "password"),
blind_registration: parse!(values, "blind_registration"),
oprf_seed: parse!(values, "oprf_seed"),
masking_nonce: parse!(values, "masking_nonce"),
envelope_nonce: parse!(values, "envelope_nonce"),
client_nonce: parse!(values, "client_nonce"),
server_nonce: parse!(values, "server_nonce"),
registration_request: parse!(values, "registration_request"),
registration_response: parse!(values, "registration_response"),
registration_upload: parse!(values, "registration_upload"),
KE1: parse!(values, "KE1"),
KE2: parse!(values, "KE2"),
KE3: parse!(values, "KE3"),
blind_login: parse!(values, "blind_login"),
export_key: parse!(values, "export_key"),
session_key: parse!(values, "session_key"),
auth_key: parse!(values, "auth_key"),
randomized_pwd: parse!(values, "randomized_password"),
handshake_secret: parse!(values, "handshake_secret"),
server_mac_key: parse!(values, "server_mac_key"),
client_mac_key: parse!(values, "client_mac_key"),
oprf_key: parse!(values, "oprf_key"),
}
}
fn get_password_file_bytes<CS: CipherSuite>(parameters: &OpaqueTestVectorParameters) -> Vec<u8>
where
// RegistrationUpload: (KePk + Hash) + Envelope
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength,
// ServerRegistration = RegistrationUpload
{
let password_file = ServerRegistration::<CS>::finish(
RegistrationUpload::deserialize(&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
@@ -1,124 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::tests::mock_rng::CycleRng;
use crate::{errors::*, group::Group, oprf};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::GenericArray;
use serde_json::Value;
use sha2::Sha512;
struct VOPRFTestVectorParameters {
sksm: Vec<u8>,
input: Vec<u8>,
blind: Vec<u8>,
blinded_element: Vec<u8>,
evaluation_element: Vec<u8>,
output: Vec<u8>,
}
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
// in base mode
static OPRF_RISTRETTO255_SHA512: &'static [&str] = &[
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "00",
"blind": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"blinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
"evaluation_element": "fc6c2b854553bf1ed6674072ed0bde1a9911e02b4bd64aa02cfb428f30251e77",
"output": "d8ed12382086c74564ae19b7a2b5ed9bdc52656d1fc151faaae51aaba86291e8df0b2143a92f24d44d5efd0892e2e26721d27d88745343493634a66d3a925e3a"
}
"#,
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
"blinded_element": "28a5e797b710f76d20a52507145fbf320a574ec2c8ab0e33e65dd2c277d0ee56",
"evaluation_element": "345e140b707257ae83d4911f7ead3177891e7a62c54097732802c4c7a98ab25a",
"output": "4d5f4221b5ebfd4d1a9dd54830e1ed0bce5a8f30a792723a6fddfe6cfe9f86bb1d95a3725818aeb725eb0b1b52e01ee9a72f47042372ef66c307770054d674fc"
}
"#,
];
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
sksm: decode(&values, "sksm").unwrap(),
input: decode(&values, "input").unwrap(),
blind: decode(&values, "blind").unwrap(),
blinded_element: decode(&values, "blinded_element").unwrap(),
evaluation_element: decode(&values, "evaluation_element").unwrap(),
output: decode(&values, "output").unwrap(),
}
}
// Tests input -> blind, blinded_element
#[test]
fn test_blind() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let mut rng = CycleRng::new(parameters.blind.to_vec());
let (token, blinded_element) =
oprf::blind::<_, RistrettoPoint, Sha512>(&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
@@ -0,0 +1,3 @@
[formatting]
allowed_blank_lines = 1
reorder_keys = true
+653
View File
@@ -0,0 +1,653 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) VexaHub and contributors.
// Copyright (c) Meta Platforms, Inc. and affiliates.
#![cfg(test_hsm)]
#![allow(type_alias_bounds)]
use std::env;
use std::sync::{LazyLock, Mutex};
use std::vec::Vec;
#[cfg(feature = "ecdsa")]
use ::ecdsa::SignatureSize;
use cryptoki::context::{CInitializeArgs, Pkcs11};
use cryptoki::mechanism::Mechanism;
use cryptoki::mechanism::elliptic_curve::{EcKdf, Ecdh1DeriveParams};
use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle};
use cryptoki::session::{Session, UserType};
use cryptoki::types::AuthPin;
#[cfg(feature = "ecdsa")]
use digest::Digest;
use digest::OutputSizeUser;
#[cfg(feature = "ecdsa")]
use elliptic_curve::PrimeCurve;
use elliptic_curve::group::Curve;
use elliptic_curve::group::prime::PrimeCurveAffine;
use elliptic_curve::pkcs8::der::asn1::{OctetString, OctetStringRef};
use elliptic_curve::pkcs8::der::{Decode, Encode};
use elliptic_curve::pkcs8::{AssociatedOid, ObjectIdentifier};
use elliptic_curve::point::{AffineCoordinates, DecompressPoint};
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, Tag, ToEncodedPoint};
use elliptic_curve::{AffinePoint, CurveArithmetic, FieldBytesSize, Group as _, ProjectivePoint};
use generic_array::typenum::Unsigned;
use generic_array::{ArrayLength, GenericArray};
use opaque_vx::key_exchange::KeyExchange;
use opaque_vx::key_exchange::group::Group;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
use opaque_vx::key_exchange::group::ed25519::{self, Ed25519};
use opaque_vx::key_exchange::group::elliptic_curve::NonIdentity;
#[cfg(feature = "ecdsa")]
use opaque_vx::key_exchange::sigma_i::ecdsa::{self, Ecdsa, PreHash};
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
use opaque_vx::key_exchange::sigma_i::pure_eddsa::PureEddsa;
#[cfg(feature = "ecdsa")]
use opaque_vx::key_exchange::sigma_i::{CachedMessage, HashOutput, Message, SigmaI};
use opaque_vx::key_exchange::tripledh::TripleDh;
use opaque_vx::keypair::{KeyPair, PublicKey};
use opaque_vx::ksf::Identity;
use opaque_vx::{
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginStartResult,
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationStartResult,
ServerLogin, ServerLoginParameters, ServerLoginStartResult, ServerRegistration, ServerSetup,
};
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
use opaque_vx::{Curve25519, Ristretto255};
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
use rand::rngs::OsRng;
use sha2::{Sha256, Sha384, Sha512};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
type OprfGroup<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Group;
type OprfHash<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Hash;
type KeGroup<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Group;
#[test]
fn triple_dh_p256() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP256;
type KeyExchange = TripleDh<NistP256, Sha256>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP256::OID,
Attribute::Derive(true),
Mechanism::Sha256Hmac,
);
}
#[test]
fn triple_dh_p384() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP384;
type KeyExchange = TripleDh<NistP384, Sha384>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP384::OID,
Attribute::Derive(true),
Mechanism::Sha384Hmac,
);
}
#[test]
fn triple_dh_p521() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP521;
type KeyExchange = TripleDh<NistP521, Sha512>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP521::OID,
Attribute::Derive(true),
Mechanism::Sha512Hmac,
);
}
#[test]
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
fn triple_dh_curve25519() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = Ristretto255;
type KeyExchange = TripleDh<Curve25519, Sha512>;
type Ksf = Identity;
}
test::<Suite>(
// This should be [`Mechanism::EccMontgomeryKeyPairGen`], but SoftHSM has an incorrect
// implementation. See https://github.com/softhsm/SoftHSMv2/issues/647.
Mechanism::EccEdwardsKeyPairGen,
ObjectIdentifier::new("1.3.101.110").unwrap(),
Attribute::Derive(true),
Mechanism::Sha512Hmac,
);
}
#[test]
#[cfg(feature = "ecdsa")]
fn sigma_i_p256() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP256;
type KeyExchange = SigmaI<Ecdsa<NistP256, Sha256>, NistP256, Sha256>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP256::OID,
Attribute::Sign(true),
Mechanism::Sha256Hmac,
);
}
#[test]
#[cfg(feature = "ecdsa")]
fn sigma_i_p384() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP384;
type KeyExchange = SigmaI<Ecdsa<NistP384, Sha384>, NistP384, Sha384>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP384::OID,
Attribute::Sign(true),
Mechanism::Sha384Hmac,
);
}
#[test]
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
fn sigma_i_ed25519() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = Ristretto255;
type KeyExchange = SigmaI<PureEddsa<Ed25519>, Ristretto255, Sha512>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccEdwardsKeyPairGen,
ObjectIdentifier::new_unwrap("1.3.101.112"),
Attribute::Sign(true),
Mechanism::Sha512Hmac,
);
}
#[derive(Clone)]
struct RemoteKey(ObjectHandle);
trait Pkcs11PublicKey
where
Self: Group,
{
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Self>;
}
trait Pkcs11KeyExchange<KE: KeyExchange> {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<KE::Group>,
data: KE::KE2BuilderData<'_, CS>,
) -> KE::KE2BuilderInput<CS>;
}
fn test<CS: 'static + CipherSuite>(
dh_mechanism: Mechanism,
oid: ObjectIdentifier,
attribute: Attribute,
hmac_mechanism: Mechanism,
) where
KeGroup<CS>: Pkcs11PublicKey,
RemoteKey: Pkcs11KeyExchange<CS::KeyExchange>,
{
let (remote_key, pk) = pkcs11_generate_key_pair(dh_mechanism, oid, attribute);
let keypair = KeyPair::new(RemoteKey(remote_key), pk);
let oprf_seed = pkcs11_generate_oprf_seed(<OprfHash<CS> as OutputSizeUser>::OutputSize::U64);
let server_setup = ServerSetup::new_with_key_pair_and_seed(&mut OsRng, keypair, oprf_seed);
const PASSWORD: &str = "password";
let ClientRegistrationStartResult {
message,
state: client,
} = ClientRegistration::<CS>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
let key_material_info = server_setup.key_material_info(&[]);
let key_material = pkcs11_hkdf::<CS>(
key_material_info.ikm,
hmac_mechanism,
Vec::from_iter(key_material_info.info.into_iter().flatten().copied()),
);
let message = ServerRegistration::start_with_key_material(&server_setup, key_material, message)
.unwrap()
.message;
let message = client
.finish(
&mut OsRng,
PASSWORD.as_bytes(),
message,
ClientRegistrationFinishParameters::default(),
)
.unwrap()
.message;
let file = ServerRegistration::finish(message);
let ClientLoginStartResult {
message,
state: client,
} = ClientLogin::<CS>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
let key_material_info = server_setup.key_material_info(&[]);
let key_material = pkcs11_hkdf::<CS>(
key_material_info.ikm,
hmac_mechanism,
Vec::from_iter(key_material_info.info.into_iter().flatten().copied()),
);
let builder = ServerLogin::builder_with_key_material(
&mut OsRng,
&server_setup,
key_material,
Some(file),
message,
ServerLoginParameters::default(),
)
.unwrap();
let shared_secret = builder
.private_key()
.pkcs11_key_exchange(server_setup.keypair().public(), builder.data());
let ServerLoginStartResult {
message,
state: server,
..
} = builder.clone().build(shared_secret).unwrap();
let message = client
.clone()
.finish(
&mut OsRng,
PASSWORD.as_bytes(),
message,
ClientLoginFinishParameters::default(),
)
.map(|result| result.message);
message
.map(|message| {
server
.finish(message, ServerLoginParameters::default())
.unwrap()
})
.unwrap();
}
static SESSION: LazyLock<Mutex<Session>> = LazyLock::new(|| {
let module = env::var("PKCS11_MODULE").expect("`PKCS11_MODULE` environment variable");
let pkcs11 = Pkcs11::new(module).unwrap();
pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();
let slot = pkcs11.get_slots_with_token().unwrap()[0];
let so_pin = AuthPin::new("abcdef".into());
pkcs11.init_token(slot, &so_pin, "Test Token").unwrap();
let user_pin = AuthPin::new("fedcba".into());
{
let session = pkcs11.open_rw_session(slot).unwrap();
session.login(UserType::So, Some(&so_pin)).unwrap();
session.init_pin(&user_pin).unwrap();
}
let session = pkcs11.open_rw_session(slot).unwrap();
session.login(UserType::User, Some(&user_pin)).unwrap();
Mutex::new(session)
});
fn pkcs11_generate_key_pair<G: Group + Pkcs11PublicKey>(
mechanism: Mechanism,
oid: ObjectIdentifier,
attribute: Attribute,
) -> (ObjectHandle, PublicKey<G>) {
let session = SESSION.lock().unwrap();
let (pk, remote_key) = session
.generate_key_pair(
&mechanism,
&[
Attribute::Token(false),
Attribute::EcParams(oid.to_der().unwrap()),
],
&[Attribute::Token(false), attribute],
)
.unwrap();
let Attribute::EcPoint(pk) = session
.get_attributes(pk, &[AttributeType::EcPoint])
.unwrap()
.pop()
.unwrap()
else {
unreachable!()
};
drop(session);
let pk = OctetString::from_der(&pk).unwrap();
let pk = G::pkcs11_public_key(pk.as_bytes());
(remote_key, pk)
}
fn pkcs11_generate_oprf_seed(length: u64) -> ObjectHandle {
SESSION
.lock()
.unwrap()
.generate_key(
&Mechanism::GenericSecretKeyGen,
&[Attribute::Token(false), Attribute::ValueLen(length.into())],
)
.unwrap()
}
// SoftHSM, nor any other popular HSM at the time of writing, supports HKDF. So
// we instead implement HKDF by hand on top of the HSMs HMAC, which is supported
// by almost all HSMs and still protects the OPRF seed.
fn pkcs11_hkdf<CS: CipherSuite>(
hmac: ObjectHandle,
mechanism: Mechanism,
info: Vec<u8>,
) -> GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen> {
let mut okm = GenericArray::default();
let mut prev: Option<Vec<u8>> = None;
let chunk_len = <OprfHash<CS> as OutputSizeUser>::OutputSize::USIZE;
if okm.len() > chunk_len * 255 {
panic!("invalid length");
}
let session = SESSION.lock().unwrap();
for (block_n, block) in (0..).zip(okm.chunks_mut(chunk_len)) {
let mut data = Vec::new();
if let Some(ref prev) = prev {
data.extend(prev.as_slice())
};
data.extend(&info);
data.extend(&[block_n + 1]);
let output = session.sign(&mechanism, hmac, &data).unwrap();
block.copy_from_slice(&output[..block.len()]);
prev = Some(output);
}
okm
}
impl Pkcs11PublicKey for NistP256 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP256> {
pkcs11_ec_public_key(data)
}
}
impl Pkcs11PublicKey for NistP384 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP384> {
pkcs11_ec_public_key(data)
}
}
impl Pkcs11PublicKey for NistP521 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP521> {
pkcs11_ec_public_key(data)
}
}
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
impl Pkcs11PublicKey for Curve25519 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Curve25519> {
PublicKey::deserialize(data).unwrap()
}
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
impl Pkcs11PublicKey for Ed25519 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Ed25519> {
PublicKey::deserialize(data).unwrap()
}
}
impl Pkcs11KeyExchange<TripleDh<NistP256, Sha256>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<NistP256>,
client_pk: &PublicKey<NistP256>,
) -> GenericArray<u8, <NistP256 as Group>::PkLen> {
pkcs_11_ecdsa_derive_secret::<NistP256>(self.0, server_pk, client_pk)
}
}
impl Pkcs11KeyExchange<TripleDh<NistP384, Sha384>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<NistP384>,
client_pk: &PublicKey<NistP384>,
) -> GenericArray<u8, <NistP384 as Group>::PkLen> {
pkcs_11_ecdsa_derive_secret::<NistP384>(self.0, server_pk, client_pk)
}
}
impl Pkcs11KeyExchange<TripleDh<NistP521, Sha512>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<NistP521>,
client_pk: &PublicKey<NistP521>,
) -> GenericArray<u8, <NistP521 as Group>::PkLen> {
pkcs_11_ecdsa_derive_secret::<NistP521>(self.0, server_pk, client_pk)
}
}
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
impl Pkcs11KeyExchange<TripleDh<Curve25519, Sha512>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<Curve25519>,
pk: &PublicKey<Curve25519>,
) -> GenericArray<u8, <Curve25519 as Group>::PkLen> {
let shared_secret = pkcs_11_dh_derive_secret(self.0, &pk.serialize());
GenericArray::clone_from_slice(&shared_secret)
}
}
#[cfg(feature = "ecdsa")]
impl Pkcs11KeyExchange<SigmaI<Ecdsa<NistP256, Sha256>, NistP256, Sha256>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<NistP256>,
message: &Message<CS, NistP256>,
) -> (ecdsa::Signature<NistP256>, PreHash<Sha256>) {
pkcs_11_ecdsa_sign::<NistP256, Sha256>(self.0, message.hash())
}
}
#[cfg(feature = "ecdsa")]
impl Pkcs11KeyExchange<SigmaI<Ecdsa<NistP384, Sha384>, NistP384, Sha384>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<NistP384>,
message: &Message<CS, NistP384>,
) -> (ecdsa::Signature<NistP384>, PreHash<Sha384>) {
pkcs_11_ecdsa_sign::<NistP384, Sha384>(self.0, message.hash())
}
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
impl Pkcs11KeyExchange<SigmaI<PureEddsa<Ed25519>, Ristretto255, Sha512>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<Ed25519>,
message: &Message<CS, Ristretto255>,
) -> (ed25519::Signature, CachedMessage<CS, Ristretto255>) {
pkcs_11_eddsa_sign(self.0, message)
}
}
fn pkcs11_ec_public_key<G>(data: &[u8]) -> PublicKey<G>
where
G: Group<Pk = NonIdentity<G>> + CurveArithmetic,
FieldBytesSize<G>: ModulusSize,
AffinePoint<G>:
FromEncodedPoint<G> + ToEncodedPoint<G> + PrimeCurveAffine<Curve = ProjectivePoint<G>>,
{
PublicKey::deserialize(
elliptic_curve::PublicKey::<G>::from_sec1_bytes(data)
.unwrap()
.to_encoded_point(true)
.as_bytes(),
)
.unwrap()
}
fn pkcs_11_dh_derive_secret(sk: ObjectHandle, pk: &[u8]) -> Vec<u8> {
let session = SESSION.lock().unwrap();
let shared_secret = session
.derive_key(
&Mechanism::Ecdh1Derive(Ecdh1DeriveParams::new(EcKdf::null(), pk)),
sk,
&[
Attribute::Token(false),
Attribute::KeyType(KeyType::GENERIC_SECRET),
Attribute::Class(ObjectClass::SECRET_KEY),
Attribute::Extractable(true),
],
)
.unwrap();
let Attribute::Value(shared_secret) = session
.get_attributes(shared_secret, &[AttributeType::Value])
.unwrap()
.pop()
.unwrap()
else {
unreachable!()
};
drop(session);
shared_secret
}
fn pkcs_11_ecdsa_derive_secret<G>(
server_sk: ObjectHandle,
server_pk: &PublicKey<G>,
client_pk: &PublicKey<G>,
) -> GenericArray<u8, <G as Group>::PkLen>
where
G: Group<Pk = NonIdentity<G>> + CurveArithmetic,
AffinePoint<G>: DecompressPoint<G> + ToEncodedPoint<G>,
FieldBytesSize<G>: ModulusSize,
{
let client_pk_point = client_pk.to_group_type();
let client_pk = client_pk.serialize();
let client_pk = OctetStringRef::new(&client_pk).unwrap();
let client_pk = client_pk.to_der().unwrap();
let shared_secret_bytes = pkcs_11_dh_derive_secret(server_sk, &client_pk);
let shared_secret_point = AffinePoint::<G>::decompress(
&GenericArray::clone_from_slice(&shared_secret_bytes),
Choice::from(0),
)
.unwrap();
let mut shared_secret = GenericArray::default();
shared_secret[1..].copy_from_slice(&shared_secret_bytes);
let shifted_client_pk = client_pk_point.0.to_point() + ProjectivePoint::<G>::generator();
let shifted_client_pk = shifted_client_pk.to_affine().to_encoded_point(true);
let shifted_client_pk = OctetStringRef::new(shifted_client_pk.as_bytes()).unwrap();
let shifted_client_pk = shifted_client_pk.to_der().unwrap();
let check_point = pkcs_11_dh_derive_secret(server_sk, &shifted_client_pk);
let shifted_server_pk = server_pk.to_group_type().0.to_point() + shared_secret_point;
let shifted_server_pk = shifted_server_pk.to_affine();
let tag = u8::conditional_select(
&(Tag::CompressedEvenY as u8),
&(Tag::CompressedOddY as u8),
check_point.ct_ne(&shifted_server_pk.x()),
);
shared_secret[0] = tag;
shared_secret
}
#[cfg(feature = "ecdsa")]
fn pkcs_11_ecdsa_sign<G: CurveArithmetic + PrimeCurve, H: Clone + Digest>(
sk: ObjectHandle,
hashes: HashOutput<H>,
) -> (ecdsa::Signature<G>, PreHash<H>)
where
SignatureSize<G>: ArrayLength<u8>,
{
let sign_pre_hash = hashes.sign.finalize();
let session = SESSION.lock().unwrap();
let signature = session.sign(&Mechanism::Ecdsa, sk, &sign_pre_hash).unwrap();
drop(session);
let signature = ::ecdsa::Signature::from_slice(&signature).unwrap();
(
ecdsa::Signature(signature),
PreHash(hashes.verify.finalize()),
)
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
fn pkcs_11_eddsa_sign<CS: CipherSuite>(
sk: ObjectHandle,
message: &Message<CS, Ristretto255>,
) -> (ed25519::Signature, CachedMessage<CS, Ristretto255>) {
use cryptoki::mechanism::eddsa::{EddsaParams, EddsaSignatureScheme};
let mut message_bytes = Vec::new();
message
.sign_message()
.for_each(|bytes| message_bytes.extend_from_slice(bytes));
let session = SESSION.lock().unwrap();
let signature = session
.sign(
&Mechanism::Eddsa(EddsaParams::new(EddsaSignatureScheme::Pure)),
sk,
&message_bytes,
)
.unwrap();
drop(session);
let signature = ed25519::Signature::from_slice(&signature).unwrap();
(signature, message.to_cached())
}