86 Commits
Author SHA1 Message Date
Kevin LewiandGitHub 8ccb5ad510 Publishing v0.2.1 (#66) 2020-10-22 10:23:25 -07:00
François GarillotandGitHub 9b5f7f455d Merge pull request #64 from huitseeker/hash-public
Make Hash module public
2020-10-22 09:41:09 -04:00
François Garillot 25ae7ff9bf Make Hash module public
Fixes #63
2020-10-21 18:24:55 -04:00
François GarillotandGitHub aabdb53581 Merge pull request #54 from huitseeker/fix_vec_in_trait
Fix use of `TryFrom<Vec<u8>>` in key_exchange
2020-09-28 08:56:42 -04:00
François Garillot 587ef935a7 Fix use of TryFrom<Vec<u8>> in key_exchange
We can directly require the HRTB on `for<'r> TryFrom<&'r [u8]>`.
Eliminates needless to_vec.
2020-09-24 14:31:29 +00:00
François GarillotandGitHub 288105861d Merge pull request #53 from huitseeker/ci_fix
Don't duplicate CI runs on PR & push
2020-09-23 21:28:54 -04:00
François Garillot 6b9583e928 Don't duplicate CI runs on PR & push 2020-09-23 17:57:15 -04:00
François GarillotandGitHub 958d0b7a27 Merge pull request #52 from huitseeker/the_last_constraint
Remove the last superfluous Repr = Key
2020-09-23 09:43:06 -04:00
François Garillot 40ea015d6d Remove the last superfluous constant
This required purgin the usage of the low-level Key access, but now we
should be able to replace the Key type piecemeal.
2020-09-22 08:50:39 -04:00
François GarillotandGitHub 39ec88674a Merge pull request #51 from huitseeker/over_simplification
Make KeyExchange generic in KeyFormat
2020-09-21 15:21:33 -04:00
François Garillot c97ddaf5d2 Make KeyExchange generic in KeyFormat 2020-09-21 14:46:04 -04:00
François GarillotandGitHub ae890dbac4 Merge pull request #50 from huitseeker/simplifications
Simplifications and Normalizations
2020-09-21 14:41:16 -04:00
François Garillot 56d38dba2c Reverse use of Tryinto<[u8; 64]> since it's not yet on stable 2020-09-20 10:13:43 -04:00
François Garillot 786bc51fdd Normalize generic parameters for LoginSecondMessage
(aka use the CipherSuite just like the others)

Repair the generics in KE trait: it makes sense that generate_ke[1-3] should operate on the same key representation.
2020-09-19 19:16:36 -04:00
François Garillot 887b4577fa fix misuse of constants in KE2Message 2020-09-19 19:16:34 -04:00
François Garillot 3c2a208606 surface the potential breakages out of copy_from_slice 2020-09-19 19:16:31 -04:00
François Garillot 3c555e82ae Make tripleDH manipulate GenericArray<u8, N> over Vec<u8>
upgrade some of the structure contents of the KE types
2020-09-19 19:16:28 -04:00
84cc0e9b72 Updating changelog / README / Cargo.toml to version 0.2.0 (#47)
Rust CI / test (u32_backend) (push) Has been cancelled
Rust CI / test (u64_backend) (push) Has been cancelled
Rust CI / cargo bench compilation (push) Has been cancelled
Rust CI / cargo clippy (push) Has been cancelled
Rust CI / cargo fmt (push) Has been cancelled
Rust CI / cargo-deny check (push) Has been cancelled
* Updating changelog / README / Cargo.toml to version 0.2.0

* Update CHANGELOG.md

Co-authored-by: François Garillot <[email protected]>

Co-authored-by: François Garillot <[email protected]>
2020-09-03 13:21:27 -07:00
François GarillotandGitHub 7945661f67 Merge pull request #48 from huitseeker/fix-benches
Fix benches + run their compilation in CI
2020-09-03 16:10:26 -04:00
François Garillot b673f840a5 Compile the benchmarks in CI 2020-09-03 15:58:40 -04:00
François Garillot 81353cb844 fix benchmarks 2020-09-03 15:54:46 -04:00
François GarillotandGitHub 48412c8ee4 Merge pull request #46 from huitseeker/elligator2
Implement and use Elligator2 for the Curve25519 larger subgroup instance
2020-09-03 15:21:01 -04:00
François Garillot ab8db3bf03 Propagate usage of Elligator2 map
Fixes #30, #37
2020-09-03 14:17:45 -04:00
François Garillot dede91a5b7 Add additional test vectors from Signal 2020-09-03 14:17:44 -04:00
François Garillot f1bdfcd3a0 Add elligator mapping for hash-to-curve 2020-09-03 14:17:43 -04:00
François GarillotandGitHub cc4a292b6d Merge pull request #45 from huitseeker/crash_tests
Proptests exercising `try_from` for most structures based on random data
2020-09-02 17:40:08 -04:00
François Garillot 2c9e47d14d Proptests exercising try_from for most structures based on random data
This also fixes crashes in:
- RegisterFirstMessage,
- LoginFirstMessage,
- ClientRegistration,
- ClientLogin
2020-09-02 10:29:22 -04:00
Kevin LewiandGitHub 195a698ad8 Fix omission of nonce in hmac computation for envelope (#44) 2020-09-01 11:23:00 -07:00
Kevin LewiandGitHub cdf0c7bf72 Update copyright in benches/oprf.rs 2020-09-01 10:23:39 -07:00
François GarillotandGitHub 59af252168 Merge pull request #42 from huitseeker/dependency-updates
Update all dependencies to the latest version
2020-08-26 16:00:30 -04:00
François Garillot 6dde7c5351 Update all dependencies to the lates version
Not. now using the same upcoming dalek versions (curve25519-dalek v 3)
2020-08-26 09:44:23 -04:00
François Garillot 694c51c982 remove unneeded Repr = Key 2020-08-24 15:16:51 -04:00
François Garillot 07a702c1be make clippy happier 2020-08-24 15:13:20 -04:00
François Garillot 29ba1b93b5 Check for small subgroup components when using the EdwardsPoint group for the OPRF
A malicious attacker sending a small point could lead the server / user to leak private information.

The check avoiding mixed order points (rather than just small) is prohibitively expensive, and I don't know how an attacker would extract any data from that => we focus on a small order check.

Fixes #34.
2020-08-24 14:50:29 -04:00
Kevin LewiandGitHub c2edb2d95e Adding a hash type to CipherSuite (#24) 2020-07-27 15:25:04 -07:00
François GarillotandGitHub 72a3928cbb Merge pull request #29 from huitseeker/maintenance
Maintenance
2020-07-23 14:53:36 -04:00
François Garillot 6b1e485763 Update minor versions as appropriate 2020-07-23 14:49:49 -04:00
François Garillot 7b11da3bd6 Remove 2x superfluous to_vec 2020-07-23 13:45:46 -04:00
François GarillotandGitHub 4aae404e29 Merge pull request #27 from huitseeker/curve2559_backends
Pass-through x,curve2559 backends
2020-07-22 17:56:03 -04:00
François GarillotandGitHub 1f8d786be8 Merge pull request #28 from huitseeker/quickfix_26
Re-establish `cargo check --all-targets`
2020-07-22 17:55:53 -04:00
François Garillot 8885fe5aa1 Re-establish cargo check --all-targets
The bench in benches/oprf.rs requires a feature to have visibility over private members, and doesn't work without it.
`cargo check --all-targets` is the standard quick way to check code under build & test targets, but does not specify features.

This simply skips the `benches` when the `"bench"` feature it depends on is not activated.
2020-07-22 15:22:47 -04:00
François Garillot b0b9cd0ab2 Restructure GH tests, run on u64 & u32 2020-07-22 15:04:06 -04:00
François Garillot 4fda240a7d Pass the {curve, x}25519-dalek features through the build 2020-07-22 14:59:06 -04:00
François GarillotandGitHub 5a7b60bec7 Merge pull request #26 from huitseeker/basic-benches
Benchmarks for the OPRF
2020-07-22 14:38:34 -04:00
François Garillot 91137903c6 Add benchmarks for OPRF functions (under a bench feature) 2020-07-22 14:32:58 -04:00
François Garillot e93e8fd5a8 Make the group mod public 2020-07-22 11:58:00 -04:00
Kevin LewiandGitHub f8285c60ba Introducing a trait for key exchange (#20) 2020-07-13 15:23:29 -07:00
François GarillotandGitHub 2959290582 Merge pull request #22 from huitseeker/missing_docs
Activate #![deny(missing_docs)], #![deny(unsafe_code)]
2020-07-06 16:01:42 -04:00
François Garillot 69449cca3e Activate #![deny(missing_docs)], #![deny(unsafe_code)] 2020-07-06 15:40:35 -04:00
François GarillotandGitHub 988b9bae77 Merge pull request #21 from huitseeker/digest-on-group
Restructure password-hashing-to-the-curve as an extension trait of Group
2020-07-03 21:10:11 -04:00
François GarillotandKevinLewi 9d40aa7659 As a way to roll back the genericity, implement an extension trait of group for password-to-curve hashing,
This supersedes #18.

Co-authored-by: KevinLewi <[email protected]>
2020-07-03 21:05:18 -04:00
François Garillot 41cd80ccb5 Make oprf::generate_oprf1 generic in the Digest, as long as it matches the hash-to-curve intake of the group
We used to have three problems:
- overuse of the <Sha256 as Digest>::OutputSize, which is just, well, U32. Sometimes used as a parameter (as in generate_oprf1), sometimes as a constant (as in generate_oprf3).
- the `hash_to_curve` operation for `RistrettoPoint` which requires 64 bits of input entropy, is fed 64 bits of which the last 32 are zero,
- the `hash_to_curve` operation for `Curve25519Point` which requires 32 bits of input entropy, is fed 64 bits of which the last 32 are discarded,

This corrects all three and uses U32 where the size of the digest is not meant to be a constraint.

Addresses #15 partially.
2020-07-03 18:28:34 -04:00
François GarillotandGitHub 45ea7b6e84 Merge pull request #19 from huitseeker/simplify_types
Straightforward cleanups
2020-07-03 17:37:28 -04:00
François Garillot 52429dd2fa fix a clone-on-copy-type 2020-07-03 17:30:33 -04:00
François Garillot bb9c365998 Rename Group::to_bytes() into Group::to_arr(),
This brings the `Group` and `SizedBytes` traits into some sort of name
coherence (they both return a GenericArray). This also uses
`&my_generic_array[..]` (i.e. the `Deref` impl) over
`my_generic_array.as_slice()`.
2020-07-03 17:30:32 -04:00
François Garillot 3e41f2441d mention issue for direct call into uniform_bytes in RistrattoPoint 2020-07-03 16:12:14 -04:00
François Garillot 59d997900e Simplify "<CS as CipherSuite>" where possible 2020-07-03 15:58:42 -04:00
Kevin LewiandGitHub 6d02c72aae Updating to draft-krawczyk-cfrg-opaque-06, reworking envelope construction and removing AEAD (#14)
Updating to draft-krawczyk-cfrg-opaque-06, reworking envelope construction and removing AEAD
2020-07-02 12:24:53 -07:00
François GarillotandKevin Lewi 4a638b8a22 add deny check to CI 2020-06-29 15:52:30 -07:00
zer0x64andKevin Lewi 1a5e704cf6 updated CONTRIBUTING.md 2020-06-22 12:26:24 -07:00
François GarillotandGitHub d5e585db1d Merge pull request #12 from huitseeker/backports
Backports from the Key Exchange customization PR
2020-06-19 15:33:31 -04:00
François Garillot 642d5188f5 Run CI with all-features 2020-06-19 15:24:04 -04:00
François Garillot c9d467e368 Add (normal) macros showing how to generate SizedBytes to/from TryFrom + to_bytes
This is useful for getting serialization of the KEXState, KEXMessage formats without too much boilerplate

Add client_login, login_first_message roundtrip serialization tests
2020-06-19 15:23:21 -04:00
François Garillot 738b90188d add build badge 2020-06-19 15:23:04 -04:00
François Garillot 7a2d309263 Moves the keypair generation to an extension trait
This technical change lets us avoid polluting the code with derives of Debug.
2020-06-19 15:23:03 -04:00
François GarillotandGitHub f989330807 Merge pull request #9 from huitseeker/updates
Update digest & hash libraries to their latest versions
2020-06-16 09:27:23 -04:00
François Garillot 4f9b7fe86f Update digest & hashes libraries to their latest versions 2020-06-16 06:24:14 -07:00
Kevin LewiandGitHub aab8ff8e19 Merge pull request #8 from kevinlewi/bundle_api
Adding CipherSuite trait to handle bundling of underlying crypto primitives
2020-06-15 17:42:18 -07:00
Kevin Lewi 2cf6808665 Adding documentation to each component of CipherSuite 2020-06-15 16:40:46 -07:00
Kevin Lewi ab1b1d6209 Adding CipherSuite trait to handle bundling of underlying crypto primitives 2020-06-14 23:29:25 -07:00
Kevin Lewi 0cf13c2266 Adding CipherSuite trait to handle bundling of underlying crypto primitives 2020-06-14 23:25:31 -07:00
Kevin Lewi f030fca1cb index on bundle_api: 57e152b Adding password hashing functionality 2020-06-14 06:10:26 -07:00
François GarillotandGitHub 5cf29fba62 Merge pull request #6 from huitseeker/key_cleanup
Key cleanup: prove X25519KeyPair == SignalKeyPair, remove SignalKeyPair
2020-06-13 09:24:55 -04:00
François Garillot 3ae1c58d46 test DH as well 2020-06-13 09:07:57 -04:00
François Garillot e9fa474c58 Remove SignalKeyPair, rename all instances of SignalKeyPair to X25519KeyPair, pass all tests. 2020-06-13 09:07:55 -04:00
François Garillot e9add9fd7a add tests showing X25519KeyPair == SignalKeyPair 2020-06-13 09:07:53 -04:00
François Garillot b401cfc89c Added proptest strategy to generate a random keypair 2020-06-13 09:07:51 -04:00
François GarillotandGitHub 3a1aaab6cf Merge pull request #7 from huitseeker/Cleanup_CI
Make CI run on PRs as well
2020-06-13 08:49:53 -04:00
Kevin LewiandGitHub 5a374562d1 Merge pull request #2 from kevinlewi/add_pw_hash
Adding password hashing functionality
2020-06-12 18:32:33 -07:00
François Garillot cd46a42634 Have CI run more often 2020-06-12 18:49:32 -04:00
François Garillot ce1d97002c cleaner to_bytes() for a few structures 2020-06-12 18:38:14 -04:00
Kevin Lewi 80fc493a60 Adding slow-hash feature 2020-06-11 14:56:37 -07:00
Kevin LewiandGitHub 8362804946 Merge branch 'master' into add_pw_hash 2020-06-10 14:54:27 -07:00
Kevin LewiandGitHub 05fb76d5b7 Merge pull request #5 from kevinlewi/add_readme_precaution
Adding note on draft status in doc comments
2020-06-10 14:53:06 -07:00
Kevin Lewi af22bfa54f Adding note on draft status in doc comments 2020-06-09 15:13:42 -07:00
Kevin Lewi 57e152b028 Adding password hashing functionality 2020-06-08 21:02:01 -07:00
27 changed files with 3576 additions and 1665 deletions
+85 -13
View File
@@ -1,8 +1,84 @@
name: Rust CI
on: [push]
on:
push:
branches:
- tls
- master
pull_request:
types: [opened, repoened, synchronize]
jobs:
combo:
name: test + Clippy + rustfmt
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ matrix.backend_feature }}
benches:
name: cargo bench compilation
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo bench --no-run
uses: actions-rs/cargo@v1
with:
command: bench
args: --features "bench" --no-run
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
format:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
@@ -22,13 +98,9 @@ jobs:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
deny-check:
name: cargo-deny check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: EmbarkStudios/cargo-deny-action@v1
+16
View File
@@ -1,5 +1,21 @@
# Changelog
## 0.2.1 (October 22, 2020)
* Changed visibility of hash module to be public
## 0.2.0 (September 3, 2020)
* Added CipherSuite API for specifying underlying primitives
* Added support for specifying a slow password hashing function
* Collapsed SignalKeyPair to X25519KeyPair
* Updated the envelope implementation to match the suggested XOR-based
construction in https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06
* Included randomized tests for testing try_from crashes
* Implemented Elligator2 map instead of try-and-increment for hash-to-curve
* Added extensibility for supporting different key exchange protocols
* Added benchmarks for the OPRF & switchable dalek backend depending on platform
## 0.1.0 (June 5, 2020)
* Initial release
+1 -1
View File
@@ -26,5 +26,5 @@ disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to Ristretto255.js, you agree that your contributions will be
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.
Generated
+818 -240
View File
File diff suppressed because it is too large Load Diff
+31 -13
View File
@@ -1,6 +1,6 @@
[package]
name = "opaque-ke"
version = "0.1.0"
version = "0.2.1"
repository = "https://github.com/novifinancial/opaque-ke"
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
@@ -9,22 +9,40 @@ license = "MIT"
edition = "2018"
readme = "README.md"
[features]
default = ["u64_backend"]
slow-hash = ["scrypt"]
bench = []
u64_backend = ["curve25519-dalek/u64_backend", "x25519-dalek/u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend", "x25519-dalek/u32_backend"]
[dependencies]
aead = "0.2.0"
curve25519-dalek = "2.0.0"
generic-array = "0.12.3"
hkdf = "0.8.0"
hmac = "0.7.1"
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] }
digest = "0.9.0"
displaydoc = "0.1.7"
fiat-crypto = { version = "0.1.5"}
generic-array = "0.14.4"
hkdf = "0.9.0"
hmac = "0.9.0"
rand_core = "0.5.1"
sha2 = "0.8"
thiserror = "1"
x25519-dalek = "0.6.0"
scrypt = { version = "0.4.1", optional = true }
sha2 = "0.9.1"
subtle = { version = "^2.2.1", default-features = false }
thiserror = "1.0.20"
x25519-dalek = { version = "1.0.1", default-features = false, features = ["std"] }
zeroize = "1.1"
[dev-dependencies]
aes-gcm = "0.5.0"
base64 = "0.11.0"
chacha20poly1305 = "0.4.1"
anyhow = "1.0.32"
base64 = "0.12.3"
criterion = "0.3.3"
hex = "0.4.2"
lazy_static = "1.4.0"
serde_json = "1.0"
serde_json = "1.0.57"
proptest = "0.10.1"
rand = "0.7"
[[bench]]
name = "oprf"
harness = false
required-features = ["bench"]
+3 -3
View File
@@ -1,4 +1,4 @@
## The OPAQUE key exchange protocol
## The OPAQUE key exchange protocol ![Build Status](https://github.com/novifinancial/opaque-ke/workflows/Rust%20CI/badge.svg)
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an 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.
@@ -22,14 +22,14 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
opaque-ke = "0.1.0"
opaque-ke = "0.2.1"
```
Resources
---------
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
- [draft-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05), containing a specification for the OPAQUE protocol
- [draft-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06), containing a specification for the OPAQUE protocol
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
Contributors
+145
View File
@@ -0,0 +1,145 @@
// 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::edwards::EdwardsPoint;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::arr;
use opaque_ke::{
group::Group,
oprf::{generate_oprf1_shim, generate_oprf2_shim, generate_oprf3_shim, OprfClientBytes},
};
use rand::{prelude::ThreadRng, thread_rng};
use sha2::Sha256;
fn oprf1(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
c.bench_function("generate_oprf1 with Ristretto", move |b| {
b.iter(|| {
let OprfClientBytes {
alpha: _alpha,
blinding_factor: _blinding_factor,
} = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
})
});
}
fn oprf1_edwards(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
c.bench_function("generate_oprf1 with Edwards", move |b| {
b.iter(|| {
let OprfClientBytes {
alpha: _alpha,
blinding_factor: _blinding_factor,
} = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
})
});
}
fn oprf2(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let OprfClientBytes {
alpha,
blinding_factor: _blinding_factor,
} = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
c.bench_function("generate_oprf2 with Ristretto", move |b| {
b.iter(|| {
let _beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
})
});
}
fn oprf2_edwards(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let OprfClientBytes {
alpha,
blinding_factor: _blinding_factor,
} = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
c.bench_function("generate_oprf2 with Edwards", move |b| {
b.iter(|| {
let _beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
})
});
}
fn oprf3(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
let beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
c.bench_function("generate_oprf3 with Ristretto", move |b| {
b.iter(|| {
let _res = generate_oprf3_shim::<RistrettoPoint, Sha256>(input, beta, &blinding_factor)
.unwrap();
})
});
}
fn oprf3_edwards(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
let beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
c.bench_function("generate_oprf3 with Edwards", move |b| {
b.iter(|| {
let _res =
generate_oprf3_shim::<EdwardsPoint, Sha256>(input, beta, &blinding_factor).unwrap();
})
});
}
criterion_group!(
oprf_benches,
oprf1,
oprf2,
oprf3,
oprf1_edwards,
oprf2_edwards,
oprf3_edwards
);
criterion_main!(oprf_benches);
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE
use crate::{
errors::InternalPakeError, hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
};
use rand_core::{CryptoRng, RngCore};
/// Configures the underlying primitives used in OPAQUE
/// * `Group`: a finite cyclic group along with a point representation, along
/// with an extension trait PasswordToCurve that allows some customization on
/// how to hash a password to a curve point. See `group::Group` and
/// `map_to_curve::GroupWithMapToCurve`.
/// * `KeyFormat`: a keypair type composed of public and private components
/// * `KeyExchange`: The key exchange protocol to use in the login step
/// * `Hash`: The main hashing function to use
/// * `SlowHash`: A slow hashing function, typically used for password hashing
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;
/// A keypair type composed of public and private components
type KeyFormat: KeyPair + PartialEq;
/// A key exchange protocol
type KeyExchange: KeyExchange<Self::Hash, Self::KeyFormat>;
/// The main hash function use (for HKDF computations and hashing transcripts)
type Hash: Hash;
/// A slow hashing function, typically used for password hashing
type SlowHash: SlowHash<Self::Hash>;
/// Generating a random key pair given a cryptographic rng
fn generate_random_keypair<R: RngCore + CryptoRng>(
rng: &mut R,
) -> Result<Self::KeyFormat, InternalPakeError> {
Self::KeyFormat::generate_random(rng)
}
}
+353
View File
@@ -0,0 +1,353 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#![allow(clippy::let_and_return)]
//! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit
//! limbs with \\(128\\)-bit products.
use core::fmt::Debug;
use core::ops::Neg;
use core::ops::{Add, AddAssign};
use core::ops::{Mul, MulAssign};
use subtle::Choice;
use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable;
use subtle::ConstantTimeEq;
use zeroize::Zeroize;
use fiat_crypto::curve25519_64::*;
/// A `FieldElement51` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// In the 64-bit implementation, a `FieldElement` is represented in
/// radix \\(2\^{51}\\) as five `u64`s; the coefficients are allowed to
/// grow up to \\(2\^{54}\\) between reductions modulo \\(p\\).
///
/// # Note
///
/// The `curve25519_dalek::field` module provides a type alias
/// `curve25519_dalek::field::FieldElement` to either `FieldElement51`
/// or `FieldElement2625`.
///
/// The backend-specific type `FieldElement51` should not be used
/// outside of the `curve25519_dalek::field` module.
#[derive(Copy, Clone)]
pub struct FieldElement51(pub(crate) [u64; 5]);
impl Debug for FieldElement51 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "FieldElement51({:?})", &self.0[..])
}
}
impl Zeroize for FieldElement51 {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl ConstantTimeEq for FieldElement51 {
/// Test equality between two `FieldElement`s. Since the
/// internal representation is not canonical, the field elements
/// are normalized to wire format before comparison.
fn ct_eq(&self, other: &FieldElement51) -> Choice {
self.to_bytes().ct_eq(&other.to_bytes())
}
}
impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 {
fn add_assign(&mut self, _rhs: &'b FieldElement51) {
let input = self.0;
fiat_25519_add(&mut self.0, &input, &_rhs.0);
let input = self.0;
fiat_25519_carry(&mut self.0, &input);
}
}
impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn add(self, _rhs: &'b FieldElement51) -> FieldElement51 {
let mut output = *self;
fiat_25519_add(&mut output.0, &self.0, &_rhs.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
output
}
}
impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 {
fn mul_assign(&mut self, _rhs: &'b FieldElement51) {
let input = self.0;
fiat_25519_carry_mul(&mut self.0, &input, &_rhs.0);
}
}
impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn mul(self, _rhs: &'b FieldElement51) -> FieldElement51 {
let mut output = *self;
fiat_25519_carry_mul(&mut output.0, &self.0, &_rhs.0);
output
}
}
impl<'a> Neg for &'a FieldElement51 {
type Output = FieldElement51;
fn neg(self) -> FieldElement51 {
let mut output = *self;
fiat_25519_opp(&mut output.0, &self.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
output
}
}
impl ConditionallySelectable for FieldElement51 {
fn conditional_select(
a: &FieldElement51,
b: &FieldElement51,
choice: Choice,
) -> FieldElement51 {
let mut output = [0u64; 5];
fiat_25519_selectznz(&mut output, choice.unwrap_u8() as fiat_25519_u1, &a.0, &b.0);
FieldElement51(output)
}
fn conditional_swap(a: &mut FieldElement51, b: &mut FieldElement51, choice: Choice) {
u64::conditional_swap(&mut a.0[0], &mut b.0[0], choice);
u64::conditional_swap(&mut a.0[1], &mut b.0[1], choice);
u64::conditional_swap(&mut a.0[2], &mut b.0[2], choice);
u64::conditional_swap(&mut a.0[3], &mut b.0[3], choice);
u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice);
}
fn conditional_assign(&mut self, _rhs: &FieldElement51, choice: Choice) {
let mut output = [0u64; 5];
let choicebit = choice.unwrap_u8() as fiat_25519_u1;
fiat_25519_cmovznz_u64(&mut output[0], choicebit, self.0[0], _rhs.0[0]);
fiat_25519_cmovznz_u64(&mut output[1], choicebit, self.0[1], _rhs.0[1]);
fiat_25519_cmovznz_u64(&mut output[2], choicebit, self.0[2], _rhs.0[2]);
fiat_25519_cmovznz_u64(&mut output[3], choicebit, self.0[3], _rhs.0[3]);
fiat_25519_cmovznz_u64(&mut output[4], choicebit, self.0[4], _rhs.0[4]);
*self = FieldElement51(output);
}
}
impl FieldElement51 {
/// Construct zero.
pub fn zero() -> FieldElement51 {
FieldElement51([0, 0, 0, 0, 0])
}
/// Construct one.
pub fn one() -> FieldElement51 {
FieldElement51([1, 0, 0, 0, 0])
}
pub fn is_negative(&self) -> Choice {
let bytes = self.to_bytes();
(bytes[0] & 1).into()
}
/// Raise this field element to the power (p-5)/8 = 2^252 -3.
fn pow_p58(&self) -> FieldElement51 {
// The bits of (p-5)/8 are 101111.....11.
//
// nonzero bits of exponent
let (t19, _) = self.pow22501(); // 249..0
let t20 = t19.pow2k(2); // 251..2
let t21 = self * &t20; // 251..2,0
t21
}
/// Given a nonzero field element, compute its inverse.
///
/// The inverse is computed as self^(p-2), since
/// x^(p-2)x = x^(p-1) = 1 (mod p).
///
/// This function returns zero on input zero.
pub fn invert(&self) -> FieldElement51 {
// The bits of p-2 = 2^255 -19 -2 are 11010111111...11.
//
// nonzero bits of exponent
let (t19, t3) = self.pow22501(); // t19: 249..0 ; t3: 3,1,0
let t20 = t19.pow2k(5); // 254..5
let t21 = &t20 * &t3; // 254..5,3,1,0
t21
}
/// Compute (self^(2^250-1), self^11), used as a helper function
/// within invert() and pow22523().
fn pow22501(&self) -> (FieldElement51, FieldElement51) {
// Instead of managing which temporary variables are used
// for what, we define as many as we need and leave stack
// allocation to the compiler
//
// Each temporary variable t_i is of the form (self)^e_i.
// Squaring t_i corresponds to multiplying e_i by 2,
// so the pow2k function shifts e_i left by k places.
// Multiplying t_i and t_j corresponds to adding e_i + e_j.
//
// Temporary t_i Nonzero bits of e_i
//
let t0 = self.square(); // 1 e_0 = 2^1
let t1 = t0.square().square(); // 3 e_1 = 2^3
let t2 = self * &t1; // 3,0 e_2 = 2^3 + 2^0
let t3 = &t0 * &t2; // 3,1,0
let t4 = t3.square(); // 4,2,1
let t5 = &t2 * &t4; // 4,3,2,1,0
let t6 = t5.pow2k(5); // 9,8,7,6,5
let t7 = &t6 * &t5; // 9,8,7,6,5,4,3,2,1,0
let t8 = t7.pow2k(10); // 19..10
let t9 = &t8 * &t7; // 19..0
let t10 = t9.pow2k(20); // 39..20
let t11 = &t10 * &t9; // 39..0
let t12 = t11.pow2k(10); // 49..10
let t13 = &t12 * &t7; // 49..0
let t14 = t13.pow2k(50); // 99..50
let t15 = &t14 * &t13; // 99..0
let t16 = t15.pow2k(100); // 199..100
let t17 = &t16 * &t15; // 199..0
let t18 = t17.pow2k(50); // 249..50
let t19 = &t18 * &t13; // 249..0
(t19, t3)
}
/// Load a `FieldElement51` from the low 255 bits of a 256-bit
/// input.
///
/// # Warning
///
/// This function does not check that the input used the canonical
/// representative. It masks the high bit, but it will happily
/// decode 2^255 - 18 to 1. Applications that require a canonical
/// encoding of every field element should decode, re-encode to
/// the canonical encoding, and check that the input was
/// canonical.
///
pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
let mut temp = [0u8; 32];
temp.copy_from_slice(bytes);
temp[31] &= 127u8;
let mut output = [0u64; 5];
fiat_25519_from_bytes(&mut output, &temp);
FieldElement51(output)
}
/// Serialize this `FieldElement51` to a 32-byte array. The
/// encoding is canonical.
pub fn to_bytes(&self) -> [u8; 32] {
let mut bytes = [0u8; 32];
fiat_25519_to_bytes(&mut bytes, &self.0);
bytes
}
/// Given `k > 0`, return `self^(2^k)`.
pub fn pow2k(&self, mut k: u32) -> FieldElement51 {
let mut output = *self;
loop {
let input = output.0;
fiat_25519_carry_square(&mut output.0, &input);
k -= 1;
if k == 0 {
return output;
}
}
}
/// Given `FieldElements` `u` and `v`, compute either `sqrt(u/v)`
/// or `sqrt(i*u/v)` in constant time.
///
/// This function always returns the nonnegative square root.
///
/// # Return
///
/// - `(Choice(1), +sqrt(u/v)) ` if `v` is nonzero and `u/v` is square;
/// - `(Choice(1), zero) ` if `u` is zero;
/// - `(Choice(0), zero) ` if `v` is zero and `u` is nonzero;
/// - `(Choice(0), +sqrt(i*u/v))` if `u/v` is nonsquare (so `i*u/v` is square).
///
pub fn sqrt_ratio_i(u: &FieldElement51, v: &FieldElement51) -> (Choice, FieldElement51) {
// Using the same trick as in ed25519 decoding, we merge the
// inversion, the square root, and the square test as follows.
//
// To compute sqrt(α), we can compute β = α^((p+3)/8).
// Then β^2 = ±α, so multiplying β by sqrt(-1) if necessary
// gives sqrt(α).
//
// To compute 1/sqrt(α), we observe that
// 1/β = α^(p-1 - (p+3)/8) = α^((7p-11)/8)
// = α^3 * (α^7)^((p-5)/8).
//
// We can therefore compute sqrt(u/v) = sqrt(u)/sqrt(v)
// by first computing
// r = u^((p+3)/8) v^(p-1-(p+3)/8)
// = u u^((p-5)/8) v^3 (v^7)^((p-5)/8)
// = (uv^3) (uv^7)^((p-5)/8).
//
// If v is nonzero and u/v is square, then r^2 = ±u/v,
// so vr^2 = ±u.
// If vr^2 = u, then sqrt(u/v) = r.
// If vr^2 = -u, then sqrt(u/v) = r*sqrt(-1).
//
// If v is zero, r is also zero.
let v3 = &v.square() * v;
let v7 = &v3.square() * v;
let mut r = &(u * &v3) * &(u * &v7).pow_p58();
let check = v * &r.square();
let i = &SQRT_M1;
let correct_sign_sqrt = check.ct_eq(u);
let flipped_sign_sqrt = check.ct_eq(&(-u));
let flipped_sign_sqrt_i = check.ct_eq(&(&(-u) * i));
let r_prime = &SQRT_M1 * &r;
r.conditional_assign(&r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i);
// Choose the nonnegative square root.
let r_is_negative = r.is_negative();
r.conditional_negate(r_is_negative);
let was_nonzero_square = correct_sign_sqrt | flipped_sign_sqrt;
(was_nonzero_square, r)
}
/// Returns the square of this field element.
pub fn square(&self) -> FieldElement51 {
let mut output = *self;
fiat_25519_carry_square(&mut output.0, &self.0);
output
}
/// Returns 2 times the square of this field element.
pub fn square2(&self) -> FieldElement51 {
let mut output = *self;
let mut temp = *self;
// Void vs return type, measure cost of copying self
fiat_25519_carry_square(&mut temp.0, &self.0);
fiat_25519_add(&mut output.0, &temp.0, &temp.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
output
}
}
/// Precomputed value of one of the square roots of -1 (mod p)
pub(crate) const SQRT_M1: FieldElement51 = FieldElement51([
1718705420411056,
234908883556509,
2233514472574048,
2117202627021982,
765476049583133,
]);
+177
View File
@@ -0,0 +1,177 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#![allow(non_snake_case)]
mod field;
use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint};
use field::FieldElement51;
use sha2::Digest;
use subtle::{ConditionallyNegatable, ConditionallySelectable};
const MONT_A: FieldElement51 = FieldElement51([486662, 0, 0, 0, 0]);
fn elligator_signal(r_0: &FieldElement51) -> MontgomeryPoint {
let minus_a = -&MONT_A; /* A = 486662 */
let one = FieldElement51::one();
let d_1 = &one + &r_0.square2(); /* 2r^2 */
let d = &minus_a * &(d_1.invert()); /* A/(1+2r^2) */
let d_sq = &d.square();
let au = &MONT_A * &d;
let inner = &(d_sq + &au) + &one;
let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */
let (eps_is_sq, _eps) = FieldElement51::sqrt_ratio_i(&eps, &one);
let zero = FieldElement51::zero();
let Atemp = FieldElement51::conditional_select(&MONT_A, &zero, eps_is_sq); /* 0, or A if nonsquare*/
let mut u = &d + &Atemp; /* d, or d+A if nonsquare */
u.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */
MontgomeryPoint(u.to_bytes())
}
pub fn hash_to_point(bytes: &[u8]) -> EdwardsPoint {
let mut hash = sha2::Sha512::new();
hash.update(bytes);
let h = hash.finalize();
let mut res = [0u8; 32];
res.copy_from_slice(&h[..32]);
let sign_bit = (res[31] & 0x80) >> 7;
let fe = FieldElement51::from_bytes(&res);
let M1 = elligator_signal(&fe);
let E1_opt = M1.to_edwards(sign_bit);
E1_opt
.expect("Montgomery conversion to Edwards point in Elligator failed")
.mul_by_cofactor()
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Signal tests from //
// https://github.com/signalapp/libsignal-protocol-c/blob/master/src/curve25519/ed25519/tests/internal_fast_tests.c#L222-L282 //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const ELLIGATOR_CORRECT_OUTPUT: [u8; 32] = [
0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac,
0x22, 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95,
0xf4, 0x6e,
];
#[test]
fn elligator_correct() {
let bytes: Vec<u8> = (0u8..32u8).collect();
let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
let fe = FieldElement51::from_bytes(&bits_in);
let eg = elligator_signal(&fe);
assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
}
#[test]
fn elligator_zero_zero() {
let zero = [0u8; 32];
let fe = FieldElement51::from_bytes(&zero);
let eg = elligator_signal(&fe);
assert_eq!(eg.to_bytes(), zero);
}
const HASHTOPOINT_CORRECT_OUTPUT1: [u8; 32] = [
0xce, 0x89, 0x9f, 0xb2, 0x8f, 0xf7, 0x20, 0x91, 0x5e, 0x14, 0xf5, 0xb7, 0x99, 0x08, 0xab,
0x17, 0xaa, 0x2e, 0xe2, 0x45, 0xb4, 0xfc, 0x2b, 0xf6, 0x06, 0x36, 0x29, 0x40, 0xed, 0x7d,
0xe7, 0xed,
];
const HASHTOPOINT_CORRECT_OUTPUT2: [u8; 32] = [
0xa0, 0x35, 0xbb, 0xa9, 0x4d, 0x30, 0x55, 0x33, 0x0d, 0xce, 0xc2, 0x7f, 0x83, 0xde, 0x79,
0xd0, 0x89, 0x67, 0x72, 0x4c, 0x07, 0x8d, 0x68, 0x9d, 0x61, 0x52, 0x1d, 0xf9, 0x2c, 0x5c,
0xba, 0x77,
];
#[test]
fn test_hash_to_point_1() {
let bits: Vec<u8> = (0u8..32u8).collect();
let hashed = hash_to_point(&bits);
assert_eq!(hashed.compress().to_bytes(), HASHTOPOINT_CORRECT_OUTPUT1);
}
#[test]
fn test_hash_to_point_2() {
let bits: Vec<u8> = (0u8..32u8).map(|u| u + 1).collect();
let hashed = hash_to_point(&bits);
assert_eq!(hashed.compress().to_bytes(), HASHTOPOINT_CORRECT_OUTPUT2);
}
/////////////////////////////////////////
// Additional test vectors from Signal //
/////////////////////////////////////////
fn test_vectors() -> Vec<Vec<&'static str>> {
vec![
vec![
"214f306e1576f5a7577636fe303ca2c625b533319f52442b22a9fa3b7ede809f",
"c95becf0f93595174633b9d4d6bbbeb88e16fa257176f877ce426e1424626052",
],
vec![
"2eb10d432702ea7f79207da95d206f82d5a3b374f5f89f17a199531f78d3bea6",
"d8f8b508edffbb8b6dab0f602f86a9dd759f800fe18f782fdcac47c234883e7f",
],
vec![
"84cbe9accdd32b46f4a8ef51c85fd39d028711f77fb00e204a613fc235fd68b9",
"93c73e0289afd1d1fc9e4e78a505d5d1b2642fbdf91a1eff7d281930654b1453",
],
vec![
"c85165952490dc1839cb69012a3d9f2cc4b02343613263ab93a26dc89fd58267",
"43cbe8685fd3c90665b91835debb89ff1477f906f5170f38a192f6a199556537",
],
vec![
"26e7fc4a78d863b1a4ccb2ce0951fbcd021e106350730ee4157bacb4502e1b76",
"b6fc3d738c2c40719479b2f23818180cdafa72a14254d4016bbed8f0b788a835",
],
vec![
"1618c08ef0233f94f0f163f9435ec7457cd7a8cd4bb6b160315d15818c30f7a2",
"da0b703593b29dbcd28ebd6e7baea17b6f61971f3641cae774f6a5137a12294c",
],
vec![
"48b73039db6fcdcb6030c4a38e8be80b6390d8ae46890e77e623f87254ef149c",
"ca11b25acbc80566603eabeb9364ebd50e0306424c61049e1ce9385d9f349966",
],
vec![
"a744d582b3a34d14d311b7629da06d003045ae77cebceeb4e0e72734d63bd07d",
"fad25a5ea15d4541258af8785acaf697a886c1b872c793790e60a6837b1adbc0",
],
vec![
"80a6ff33494c471c5eff7efb9febfbcf30a946fe6535b3451cda79f2154a7095",
"57ac03913309b3f8cd3c3d4c49d878bb21f4d97dc74a1eaccbe5c601f7f06f47",
],
vec![
"f06fc939bc10551a0fd415aebf107ef0b9c4ee1ef9a164157bdd089127782617",
"785b2a6a00a5579cc9da1ff997ce8339b6f9fb46c6f10cf7a12ff2986341a6e0",
],
]
}
#[test]
fn additional_signal_test_vectors() {
for vector in test_vectors().iter() {
let input = hex::decode(vector[0]).unwrap();
let output = hex::decode(vector[1]).unwrap();
let point = hash_to_point(&input);
assert_eq!(point.compress().to_bytes(), output[..]);
}
}
}
+185
View File
@@ -0,0 +1,185 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::errors::InternalPakeError;
use crate::hash::Hash;
use digest::Digest;
use generic_array::{
typenum::{Unsigned, U32},
GenericArray,
};
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand_core::{CryptoRng, RngCore};
// Constant string used as salt for HKDF computation
const STR_ENVU: &[u8] = b"EnvU";
/// The length of the "export key" output by the client registration
/// and login finish steps
pub(crate) type ExportKeySize = U32;
const NONCE_LEN: usize = 32;
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
///
/// Note that earlier versions of this specification described an
/// implementation of this envelope using an encryption scheme that
/// satisfied random-key robustness
/// (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> {
nonce: Vec<u8>,
ciphertext: Vec<u8>,
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
}
pub(crate) struct OpenedEnvelope {
pub(crate) plaintext: Vec<u8>,
pub(crate) export_key: GenericArray<u8, ExportKeySize>,
}
impl<D: Hash> Envelope<D> {
/// The additional number of bytes added to the plaintext
pub(crate) fn additional_size() -> usize {
NONCE_LEN + <D as Digest>::OutputSize::to_usize()
}
fn hmac_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
fn hmac_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
fn export_key_size() -> usize {
ExportKeySize::to_usize()
}
pub(crate) fn new(
nonce: Vec<u8>,
ciphertext: Vec<u8>,
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Self {
Self {
nonce,
ciphertext,
hmac,
}
}
/// The format of the output is:
/// nonce | ciphertext | hmac
/// nonce_size bytes | variable length | hmac_size bytes
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let ciphertext_start = NONCE_LEN;
let ciphertext_end = bytes.len() - Self::hmac_size();
Ok(Self::new(
bytes[..ciphertext_start].to_vec(),
bytes[ciphertext_start..ciphertext_end].to_vec(),
GenericArray::clone_from_slice(&bytes[ciphertext_end..]),
))
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
[&self.nonce[..], &self.ciphertext[..], &self.hmac[..]].concat()
}
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
/// Note that a new nonce is sampled for each call to seal.
pub(crate) fn seal<R: RngCore + CryptoRng>(
key: &[u8],
plaintext: &[u8],
aad: &[u8],
rng: &mut R,
) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let h = Hkdf::<D>::new(Some(&nonce), &key);
let mut okm = vec![0u8; plaintext.len() + Self::hmac_key_size() + Self::export_key_size()];
h.expand(STR_ENVU, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
let xor_key = &okm[..plaintext.len()];
let hmac_key = &okm[plaintext.len()..plaintext.len() + Self::hmac_key_size()];
let export_key = &okm[plaintext.len() + Self::hmac_key_size()..];
let ciphertext: Vec<u8> = xor_key
.iter()
.zip(plaintext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&nonce);
hmac.update(&ciphertext);
hmac.update(&aad);
Ok((
Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()),
*GenericArray::from_slice(&export_key),
))
}
/// Attempts to decrypt the envelope using a key, which is successful only if the key and
/// aad used to construct the envelope are the same.
pub(crate) fn open(&self, key: &[u8], aad: &[u8]) -> Result<OpenedEnvelope, InternalPakeError> {
let h = Hkdf::<D>::new(Some(&self.nonce), &key);
let mut okm =
vec![0u8; self.ciphertext.len() + Self::hmac_key_size() + Self::export_key_size()];
h.expand(STR_ENVU, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
let xor_key = &okm[..self.ciphertext.len()];
let hmac_key = &okm[self.ciphertext.len()..self.ciphertext.len() + Self::hmac_key_size()];
let export_key = &okm[self.ciphertext.len() + Self::hmac_key_size()..];
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.nonce);
hmac.update(&self.ciphertext);
hmac.update(aad);
if hmac.verify(&self.hmac).is_err() {
return Err(InternalPakeError::SealOpenHmacError);
}
let plaintext: Vec<u8> = xor_key
.iter()
.zip(self.ciphertext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok(OpenedEnvelope {
plaintext,
export_key: *GenericArray::from_slice(&export_key),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_core::OsRng;
#[test]
fn seal_and_open() {
let mut rng = OsRng;
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let (envelope, export_key_1) =
Envelope::<sha2::Sha256>::seal(&key, &msg, b"aad", &mut rng).unwrap();
let opened_envelope = envelope.open(&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());
}
}
+32 -27
View File
@@ -4,54 +4,59 @@
// LICENSE file in the root directory of this source tree.
//! A list of error types which are produced during an execution of the protocol
use displaydoc::Display;
use thiserror::Error;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Debug, Error)]
#[derive(Debug, Display, Error)]
pub enum InternalPakeError {
#[error("Invalid length for {name}: expected {len}, but is actually {actual_len}.")]
/// Invalid length for {name}: expected {len}, but is actually {actual_len}.
SizeError {
/// name
name: &'static str,
/// length
len: usize,
/// actual
actual_len: usize,
},
#[error("Could not decompress point.")]
/// Could not decompress point.
PointError,
#[error("Key belongs to a small subgroup!")]
/// Key belongs to a small subgroup!
SubGroupError,
#[error("hashing to a key failed")]
/// hashing to a key failed
HashingFailure,
#[error("Computing HKDF failed while deriving subkeys")]
/// Computing HKDF failed while deriving subkeys
HkdfError,
#[error("Computing HMAC failed while supplying a secret key")]
/// Computing HMAC failed while supplying a secret key
HmacError,
/// Computing the 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,
}
/// Represents an error in password checking
#[derive(Debug, Error)]
#[derive(Debug, Display, Error)]
pub enum PakeError {
/// This error results from an internal error during PRF construction
///
#[error("Internal error during PRF verification: {0}")]
/// Internal error during PRF verification: {0}
CryptoError(InternalPakeError),
/// This error occurs when the symmetric encryption fails
#[error("Symmetric encryption failed.")]
EncryptionError,
/// This error occurs when the symmetric decryption fails
#[error("Symmetric decryption failed.")]
DecryptionError,
/// This error occurs when the symmetric decryption's hmac check fails
#[error("HMAC check in symmetric decryption failed.")]
DecryptionHmacError,
/// This error occurs when the server object that is being called finish() on is malformed
#[error("Incomplete set of keys passed into finish() function")]
/// Incomplete set of keys passed into finish() function
IncompleteKeysError,
#[error("The provided server public key doesn't match the encrypted one")]
/// The provided server public key doesn't match the sealed one
IncompatibleServerStaticPublicKeyError,
#[error("Error in key exchange protocol when attempting to validate MACs")]
/// Error in key exchange protocol when attempting to validate MACs
KeyExchangeMacValidationError,
#[error("Error in validating credentials")]
/// Error in validating credentials
InvalidLoginError,
}
@@ -64,17 +69,17 @@ impl From<InternalPakeError> for PakeError {
}
/// Represents an error in protocol handling
#[derive(Debug, Error)]
#[derive(Debug, Display, Error)]
pub enum ProtocolError {
/// This error results from an error during password verification
///
#[error("Internal error during password verification: {0}")]
/// Internal error during password verification: {0}
VerificationError(PakeError),
/// This error occurs when the server answer cannot be handled
#[error("Server response cannot be handled.")]
/// Server response cannot be handled.
ServerError,
/// This error occurs when the client request cannot be handled
#[error("Client request cannot be handled.")]
/// Client request cannot be handled.
ClientError,
}
+86 -31
View File
@@ -3,6 +3,10 @@
// 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::elligator;
use crate::errors::InternalPakeError;
use curve25519_dalek::{
@@ -15,8 +19,6 @@ use generic_array::{
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::ops::Mul;
use zeroize::Zeroize;
@@ -45,13 +47,15 @@ pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self>
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError>;
/// Serializes the `self` group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen>;
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;
}
@@ -83,20 +87,23 @@ impl Group for RistrettoPoint {
) -> Result<Self, InternalPakeError> {
CompressedRistretto::from_slice(element_bits)
.decompress()
.ok_or_else(|| InternalPakeError::PointError)
.ok_or(InternalPakeError::PointError)
}
// serialization of a group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen> {
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 {
let mut bits = [0u8; 64];
bits.copy_from_slice(uniform_bytes);
// This could really be a from_uniform_bytes!
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
// 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)
}
}
@@ -126,35 +133,83 @@ impl Group for EdwardsPoint {
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> {
CompressedEdwardsY::from_slice(element_bits)
let point = CompressedEdwardsY::from_slice(element_bits)
.decompress()
.ok_or_else(|| InternalPakeError::PointError)
.ok_or(InternalPakeError::PointError)?;
if point.is_small_order() {
return Err(InternalPakeError::SubGroupError);
}
Ok(point)
}
// serialization of a group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen> {
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
type UniformBytesLen = U32;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
let mut result = [0u8; 32];
let mut counter = 0;
let mut wrapped_point: Option<EdwardsPoint> = None;
while wrapped_point.is_none() {
result.copy_from_slice(
&Sha256::new()
.chain(&uniform_bytes[..32])
.chain(&[counter])
.result()[..32],
);
wrapped_point = CompressedEdwardsY::from_slice(&result).decompress();
counter += 1;
}
wrapped_point
.expect("guarded by loop exit condition")
.mul_by_cofactor()
elligator::hash_to_point(uniform_bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::{anyhow, Result};
use std::convert::TryInto;
const EIGHT_TORSION: [[u8; 32]; 8] = [
[
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
[
199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250,
44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122,
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 128,
],
[
38, 232, 149, 143, 194, 178, 39, 176, 69, 195, 244, 137, 242, 239, 152, 240, 213, 223,
172, 5, 211, 198, 51, 57, 177, 56, 2, 136, 109, 83, 252, 5,
],
[
236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127,
],
[
38, 232, 149, 143, 194, 178, 39, 176, 69, 195, 244, 137, 242, 239, 152, 240, 213, 223,
172, 5, 211, 198, 51, 57, 177, 56, 2, 136, 109, 83, 252, 133,
],
[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
],
[
199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250,
44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 250,
],
];
fn deserialize_point(pt: &[u8]) -> Result<EdwardsPoint> {
let bytes: [u8; 32] = (&pt[..32])
.try_into()
.expect("Slice pattern invariant broken");
curve25519_dalek::edwards::CompressedEdwardsY(bytes)
.decompress()
.ok_or_else(|| anyhow!("Point decompression failed!"))
}
#[test]
fn test_small_subgroup_edwards() {
for pt in &EIGHT_TORSION[..] {
assert!(deserialize_point(&pt[..]).is_ok());
assert!(EdwardsPoint::from_element_slice(GenericArray::from_slice(&pt[..])).is_err());
}
}
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! A convenience trait for digest bounds used throughout the library
use digest::{BlockInput, FixedOutput, Reset, Update};
/// 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 {}
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
-406
View File
@@ -1,406 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
keypair::{Key, KeyPair, SizedBytes},
};
use generic_array::GenericArray;
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
/// This module is a somewhat minimalistic implementation of a key Exchange
/// protocol based on 3DH. It assumes a pre-exchange has allowed client and
/// server to learn each other's static public key.
///
/// This private module may undergo significant changes in the near term.
const KEY_LEN: usize = 32;
pub(crate) const NONCE_LEN: usize = 32;
pub(crate) const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
static STR_3DH: &[u8] = b"3DH keys";
pub(crate) struct KE1State {
client_e_sk: Key,
client_nonce: Vec<u8>,
hashed_l1: Vec<u8>,
}
pub(crate) struct KE1Message {
pub(crate) client_nonce: Vec<u8>,
pub(crate) client_e_pk: Key,
}
impl TryFrom<&[u8]> for KE1State {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
})
}
}
impl KE1State {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.client_e_sk.to_arr(),
&self.client_nonce[..],
&self.hashed_l1[..],
]
.concat();
output
}
}
impl KE1Message {
pub fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl TryFrom<&[u8]> for KE1Message {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes =
check_slice_size(ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
Ok(Self {
client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
})
}
}
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
l1_component: Vec<u8>,
rng: &mut R,
) -> Result<(KE1State, KE1Message), ProtocolError> {
let client_e_kp = KeyFormat::generate_random(rng)?;
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let ke1_message = KE1Message {
client_nonce: client_nonce.to_vec(),
client_e_pk: client_e_kp.public().clone(),
};
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
let mut hasher = Sha256::new();
hasher.input(&l1_data);
let hashed_l1 = hasher.result();
Ok((
KE1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce: client_nonce.to_vec(),
hashed_l1: hashed_l1.to_vec(),
},
ke1_message,
))
}
pub(crate) struct KE2State {
km3: Vec<u8>,
hashed_transcript: Vec<u8>,
shared_secret: Vec<u8>,
}
pub(crate) struct KE2Message {
server_nonce: Vec<u8>,
server_e_pk: Key,
mac: Vec<u8>,
}
impl KE2State {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.km3[..],
&self.hashed_transcript[..],
&self.shared_secret[..],
]
.concat();
output
}
}
impl TryFrom<&[u8]> for KE2State {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
Ok(Self {
km3: checked_bytes[..KEY_LEN].to_vec(),
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
})
}
}
impl KE2Message {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.server_nonce[..],
&self.server_e_pk.to_arr(),
&self.mac[..],
]
.concat();
output
}
}
impl TryFrom<&[u8]> for KE2Message {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
Ok(Self {
server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
})
}
}
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: Key,
sk1: Key,
pk2: Key,
sk2: Key,
pk3: Key,
sk3: Key,
}
// Consists of a shared secret, followed by two mac keys
type TripleDHDerivationResult = (
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
);
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the shared secret and two MAC keys
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>>(
dh: TripleDHComponents,
client_nonce: &[u8],
server_nonce: &[u8],
client_s_pk: KeyFormat::Repr,
server_s_pk: KeyFormat::Repr,
) -> Result<TripleDHDerivationResult, ProtocolError> {
let ikm: Vec<u8> = [
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
]
.concat();
let info: Vec<u8> = [
STR_3DH,
&client_nonce,
&server_nonce,
&client_s_pk.to_arr(),
&server_s_pk.to_arr(),
]
.concat();
const OUTPUT_SIZE: usize = 32;
let mut okm = [0u8; 3 * OUTPUT_SIZE];
let h = Hkdf::<Sha256>::new(None, &ikm);
h.expand(&info, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok((
*GenericArray::from_slice(&okm[..OUTPUT_SIZE]),
*GenericArray::from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
*GenericArray::from_slice(&okm[2 * OUTPUT_SIZE..]),
))
}
pub(crate) fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
client_e_pk: KeyFormat::Repr,
client_s_pk: KeyFormat::Repr,
server_s_sk: KeyFormat::Repr,
client_nonce: Vec<u8>,
) -> Result<(KE2State, KE2Message), ProtocolError> {
let server_e_kp = KeyFormat::generate_random(rng)?;
let mut server_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut server_nonce);
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
TripleDHComponents {
pk1: client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: client_e_pk,
sk2: server_s_sk.clone(),
pk3: client_s_pk.clone(),
sk3: server_e_kp.private().clone(),
},
&client_nonce,
&server_nonce,
client_s_pk,
KeyFormat::public_from_private(&server_s_sk),
)?;
let mut hasher = Sha256::new();
hasher.input(&l1_bytes);
let hashed_l1 = hasher.result();
let transcript2: Vec<u8> = [
&hashed_l1[..],
&l2_bytes[..],
&server_nonce[..],
&server_e_kp.public().to_arr(),
]
.concat();
let mut hasher2 = Sha256::new();
hasher2.input(&transcript2);
let hashed_transcript = hasher2.result();
let mut mac = Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac.input(&hashed_transcript);
Ok((
KE2State {
km3: km3.to_vec(),
hashed_transcript: hashed_transcript.to_vec(),
shared_secret: shared_secret.to_vec(),
},
KE2Message {
server_nonce: server_nonce.to_vec(),
server_e_pk: server_e_kp.public().clone(),
mac: mac.result().code().to_vec(),
},
))
}
pub(crate) struct KE3State {
pub(crate) shared_secret: Vec<u8>,
}
pub(crate) struct KE3Message {
mac: Vec<u8>,
}
impl TryFrom<&[u8]> for KE3State {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_state")?;
Ok(Self {
shared_secret: checked_bytes.to_vec(),
})
}
}
impl KE3Message {
pub fn to_bytes(&self) -> Vec<u8> {
self.mac.clone()
}
}
impl TryFrom<&[u8]> for KE3Message {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
Ok(Self {
mac: checked_bytes.to_vec(),
})
}
}
pub(crate) fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
l2_component: Vec<u8>,
ke2_message: KE2Message,
ke1_state: &KE1State,
server_s_pk: KeyFormat::Repr,
client_s_sk: KeyFormat::Repr,
) -> Result<(KE3State, KE3Message), ProtocolError> {
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk.clone(),
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk.clone(),
},
&ke1_state.client_nonce,
&ke2_message.server_nonce,
KeyFormat::public_from_private(&client_s_sk),
server_s_pk,
)?;
let transcript: Vec<u8> = [
&ke1_state.hashed_l1[..],
&l2_component[..],
&ke2_message.server_nonce[..],
&ke2_message.server_e_pk[..],
]
.concat();
let mut hasher = Sha256::new();
hasher.input(&transcript);
let hashed_transcript = hasher.result();
let mut server_mac =
Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.input(&hashed_transcript);
if ke2_message.mac != server_mac.result().code().to_vec() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
let mut client_mac =
Hmac::<Sha256>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.input(&hashed_transcript);
Ok((
KE3State {
shared_secret: shared_secret.to_vec(),
},
KE3Message {
mac: client_mac.result().code().to_vec(),
},
))
}
// Outputs a shared secret
pub(crate) fn finish_ke(
ke3_message: KE3Message,
ke2_state: &KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<Sha256>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.input(&ke2_state.hashed_transcript);
if ke3_message.mac != client_mac.result().code().to_vec() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.shared_secret.to_vec())
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Includes instantiations of key exchange protocols used in the
//! login step for OPAQUE
pub(crate) mod traits;
pub mod tripledh;
+56
View File
@@ -0,0 +1,56 @@
// 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, ProtocolError},
hash::Hash,
keypair::KeyPair,
};
use rand_core::{CryptoRng, RngCore};
use std::convert::TryFrom;
pub trait KeyExchange<D: Hash, KeyFormat: KeyPair> {
type KE1State: for<'r> TryFrom<&'r [u8], Error = InternalPakeError> + ToBytes;
type KE2State: for<'r> TryFrom<&'r [u8], Error = InternalPakeError> + ToBytes;
type KE1Message: for<'r> TryFrom<&'r [u8], Error = InternalPakeError> + ToBytes;
type KE2Message: for<'r> TryFrom<&'r [u8], Error = InternalPakeError> + ToBytes;
type KE3Message: for<'r> TryFrom<&'r [u8], Error = InternalPakeError> + ToBytes;
fn generate_ke1<R: RngCore + CryptoRng>(
l1_component: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: KeyFormat::Repr,
server_s_sk: KeyFormat::Repr,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
server_s_pk: KeyFormat::Repr,
client_s_sk: KeyFormat::Repr,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError>;
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError>;
fn ke1_state_size() -> usize;
fn ke2_message_size() -> usize;
}
pub trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
}
+430
View File
@@ -0,0 +1,430 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! An implementation of the Triple Diffie-Hellman key exchange protocol
use crate::{
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes},
keypair::{KeyPair, SizedBytes},
};
use digest::{Digest, FixedOutput};
use generic_array::{
typenum::{Unsigned, U32},
ArrayLength, GenericArray,
};
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand_core::{CryptoRng, RngCore};
use std::convert::TryFrom;
const KEY_LEN: usize = 32;
pub(crate) const NONCE_LEN: usize = 32;
pub(crate) type NonceLen = U32;
const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
static STR_3DH: &[u8] = b"3DH keys";
/// The Triple Diffie-Hellman key exchange implementation
pub struct TripleDH;
impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
type KE1State = KE1State<<D as FixedOutput>::OutputSize, KeyFormat>;
type KE2State = KE2State<<D as FixedOutput>::OutputSize>;
type KE1Message = KE1Message<KeyFormat>;
type KE2Message = KE2Message<<D as FixedOutput>::OutputSize, KeyFormat>;
type KE3Message = KE3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng>(
l1_component: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyFormat::generate_random(rng)?;
let client_nonce: GenericArray<u8, NonceLen> = {
let mut client_nonce_bytes = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce_bytes);
client_nonce_bytes.into()
};
let ke1_message = KE1Message {
client_nonce,
client_e_pk: client_e_kp.public().clone(),
};
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
let mut hasher = D::new();
hasher.update(&l1_data);
let hashed_l1 = hasher.finalize();
Ok((
KE1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce,
hashed_l1,
},
ke1_message,
))
}
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: KeyFormat::Repr,
server_s_sk: KeyFormat::Repr,
) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyFormat::generate_random(rng)?;
let server_nonce: GenericArray<u8, NonceLen> = {
let mut server_nonce_bytes = [0u8; NONCE_LEN];
rng.fill_bytes(&mut server_nonce_bytes);
server_nonce_bytes.into()
};
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
TripleDHComponents {
pk1: ke1_message.client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: ke1_message.client_e_pk,
sk2: server_s_sk.clone(),
pk3: client_s_pk.clone(),
sk3: server_e_kp.private().clone(),
},
&ke1_message.client_nonce,
&server_nonce,
client_s_pk,
KeyFormat::public_from_private(&server_s_sk),
)?;
let mut hasher = D::new();
hasher.update(&l1_bytes);
let hashed_l1 = hasher.finalize();
let transcript2: Vec<u8> = [
&hashed_l1[..],
&l2_bytes[..],
&server_nonce[..],
&server_e_kp.public().to_arr(),
]
.concat();
let mut hasher2 = D::new();
hasher2.update(&transcript2);
let hashed_transcript = hasher2.finalize();
let mut mac = Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac.update(&hashed_transcript);
Ok((
KE2State {
km3,
hashed_transcript,
shared_secret,
},
KE2Message {
server_nonce,
server_e_pk: server_e_kp.public().clone(),
mac: mac.finalize().into_bytes(),
},
))
}
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
server_s_pk: KeyFormat::Repr,
client_s_sk: KeyFormat::Repr,
) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk.clone(),
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk.clone(),
},
&ke1_state.client_nonce,
&ke2_message.server_nonce,
KeyFormat::public_from_private(&client_s_sk),
server_s_pk,
)?;
let transcript: Vec<u8> = [
&ke1_state.hashed_l1[..],
&l2_component[..],
&ke2_message.server_nonce[..],
&ke2_message.server_e_pk.to_arr(),
]
.concat();
let mut hasher = D::new();
hasher.update(&transcript);
let hashed_transcript = hasher.finalize();
let mut server_mac =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&hashed_transcript);
if ke2_message.mac != server_mac.finalize().into_bytes() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
let mut client_mac =
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&hashed_transcript);
Ok((
shared_secret.to_vec(),
KE3Message {
mac: client_mac.finalize().into_bytes(),
},
))
}
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.shared_secret.to_vec())
}
fn ke1_state_size() -> usize {
KE1_STATE_LEN
}
fn ke2_message_size() -> usize {
NONCE_LEN + 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<HashLen: ArrayLength<u8>, KeyFormat: KeyPair> {
client_e_sk: KeyFormat::Repr,
client_nonce: GenericArray<u8, NonceLen>,
hashed_l1: GenericArray<u8, HashLen>,
}
/// The first key exchange message
#[derive(PartialEq, Eq)]
pub struct KE1Message<KeyFormat: KeyPair> {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) client_e_pk: KeyFormat::Repr,
}
impl<HashLen: ArrayLength<u8>, KeyFormat: KeyPair> TryFrom<&[u8]> for KE1State<HashLen, KeyFormat> {
type Error = InternalPakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(
bytes,
KEY_LEN + NONCE_LEN + HashLen::to_usize(),
"ke1_state",
)?;
Ok(Self {
client_e_sk: KeyFormat::Repr::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN],
),
hashed_l1: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN + NONCE_LEN..]),
})
}
}
impl<HashLen: ArrayLength<u8>, KeyFormat: KeyPair> ToBytes for KE1State<HashLen, KeyFormat> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.client_e_sk.to_arr(),
&self.client_nonce[..],
&self.hashed_l1[..],
]
.concat();
output
}
}
impl<KeyFormat: KeyPair> ToBytes for KE1Message<KeyFormat> {
fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl<KeyFormat: KeyPair> TryFrom<&[u8]> for KE1Message<KeyFormat> {
type Error = InternalPakeError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes =
check_slice_size(ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]),
client_e_pk: KeyFormat::Repr::from_bytes(&checked_bytes[NONCE_LEN..])?,
})
}
}
/// 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>,
shared_secret: GenericArray<u8, HashLen>,
}
/// The second key exchange message
pub struct KE2Message<HashLen: ArrayLength<u8>, KeyFormat: KeyPair> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: KeyFormat::Repr,
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for KE2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.km3[..],
&self.hashed_transcript[..],
&self.shared_secret[..],
]
.concat();
output
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE2State<HashLen> {
type Error = InternalPakeError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..KEY_LEN]),
hashed_transcript: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN..2 * KEY_LEN]),
shared_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
})
}
}
impl<HashLen: ArrayLength<u8>, KeyFormat: KeyPair> ToBytes for KE2Message<HashLen, KeyFormat> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.server_nonce[..],
&self.server_e_pk.to_arr(),
&self.mac[..],
]
.concat();
output
}
}
impl<HashLen: ArrayLength<u8>, KeyFormat: KeyPair> TryFrom<&[u8]>
for KE2Message<HashLen, KeyFormat>
{
type Error = InternalPakeError;
fn try_from(ke2_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let ke2_message_len = NONCE_LEN + KEY_LEN + HashLen::to_usize();
let checked_bytes = check_slice_size(ke2_message_bytes, ke2_message_len, "ke2_message")?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]),
server_e_pk: KeyFormat::Repr::from_bytes(
&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN],
)?,
mac: GenericArray::clone_from_slice(&checked_bytes[NONCE_LEN + KEY_LEN..]),
})
}
}
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents<KeyFormat: KeyPair> {
pk1: KeyFormat::Repr,
sk1: KeyFormat::Repr,
pk2: KeyFormat::Repr,
sk2: KeyFormat::Repr,
pk3: KeyFormat::Repr,
sk3: KeyFormat::Repr,
}
// Consists of a shared secret, followed by two mac keys
type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
);
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the shared secret and two MAC keys
fn derive_3dh_keys<KeyFormat: KeyPair, D: Hash>(
dh: TripleDHComponents<KeyFormat>,
client_nonce: &GenericArray<u8, NonceLen>,
server_nonce: &GenericArray<u8, NonceLen>,
client_s_pk: KeyFormat::Repr,
server_s_pk: KeyFormat::Repr,
) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
let ikm: Vec<u8> = [
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
]
.concat();
let info: Vec<u8> = [
STR_3DH,
&client_nonce,
&server_nonce,
&client_s_pk.to_arr(),
&server_s_pk.to_arr(),
]
.concat();
const OUTPUT_SIZE: usize = 32;
let mut okm = [0u8; 3 * OUTPUT_SIZE];
let h = Hkdf::<D>::new(None, &ikm);
h.expand(&info, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok((
GenericArray::clone_from_slice(&okm[..OUTPUT_SIZE]),
GenericArray::clone_from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
GenericArray::clone_from_slice(&okm[2 * OUTPUT_SIZE..]),
))
}
/// The third key exchange message
pub struct KE3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE3Message<HashLen> {
type Error = InternalPakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
Ok(Self {
mac: GenericArray::clone_from_slice(&checked_bytes),
})
}
}
+81 -87
View File
@@ -11,7 +11,13 @@ use generic_array::{
typenum::{Sum, Unsigned, U32},
ArrayLength, GenericArray,
};
#[cfg(test)]
use proptest::prelude::*;
#[cfg(test)]
use rand::{rngs::StdRng, SeedableRng};
use rand_core::{CryptoRng, RngCore};
use std::convert::TryInto;
use std::fmt::Debug;
use x25519_dalek::{PublicKey, StaticSecret};
use std::convert::TryFrom;
@@ -65,6 +71,52 @@ pub trait KeyPair: Sized {
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8>;
}
#[cfg(test)]
trait KeyPairExt: KeyPair + 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.
any::<[u8; 32]>()
.prop_filter_map("valid random keypair", |seed| {
let mut rng = StdRng::from_seed(seed);
Self::generate_random(&mut rng).ok()
})
.no_shrink()
.boxed()
}
}
// blanket implementation
#[cfg(test)]
impl<KP> KeyPairExt for KP where KP: KeyPair + Debug {}
/// This assumes you have defined a SizedBytes instance for a `T`, and defines:
/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
///
/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
/// not, it's better to use this macro than the one above, where possible.
macro_rules! try_from_and_to_bytes_using_sized_bytes {
($sized_type: ident) => {
impl TryFrom<&[u8]> for $sized_type {
type Error = InternalPakeError;
fn try_from(bytes: &[u8]) -> Result<Self, InternalPakeError> {
<$sized_type as SizedBytes>::from_bytes(bytes)
}
}
#[allow(dead_code)]
impl $sized_type {
fn to_bytes(&self) -> Vec<u8> {
self.to_arr().to_vec()
}
}
};
}
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
/// with any length of keys. This encodes that we serialize the public key
/// first, followed by the private key in binary formats (and expect it in this
@@ -95,7 +147,7 @@ where
}
/// A minimalist key type built around [u8;32]
#[derive(PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[repr(transparent)]
pub struct Key(Vec<u8>);
@@ -107,14 +159,6 @@ impl Deref for Key {
}
}
impl TryFrom<Vec<u8>> for Key {
type Error = InternalPakeError;
fn try_from(key_bytes: Vec<u8>) -> Result<Self, Self::Error> {
Key::from_bytes(&key_bytes[..])
}
}
impl SizedBytes for Key {
type Len = U32;
@@ -129,8 +173,10 @@ impl SizedBytes for Key {
}
}
try_from_and_to_bytes_using_sized_bytes!(Key);
/// A representation of an X25519 keypair according to RFC7748
#[derive(PartialEq)]
#[derive(Debug, PartialEq, Eq)]
pub struct X25519KeyPair {
pk: Key,
sk: Key,
@@ -171,15 +217,15 @@ impl KeyPair for X25519KeyPair {
}
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
let mut secret_data = [0u8; 32];
secret_data.copy_from_slice(&secret.0[..]);
let secret_data: [u8; 32] = (&secret.0[..])
.try_into()
.expect("Keypair::Repr invariant broken");
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
}
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let key_bytes: [u8; 32] = (&key[..]).try_into().expect("Key invariant broken");
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
@@ -199,85 +245,33 @@ impl KeyPair for X25519KeyPair {
}
}
/// A custom, minimalistic Key pair struct built on Key, aimed at reproducing the behavior of libsignal's keypairs
#[derive(PartialEq)]
pub struct SignalKeyPair {
pk: Key,
sk: Key,
}
#[cfg(test)]
mod tests {
use super::*;
impl SignalKeyPair {
fn clamp_scalar(mut scalar: [u8; 32]) -> ::curve25519_dalek::scalar::Scalar {
scalar[0] &= 248;
scalar[31] &= 127;
scalar[31] |= 64;
proptest! {
#[test]
fn test_x25519_check(kp in X25519KeyPair::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(X25519KeyPair::check_public_key(pk.clone()).is_ok());
}
::curve25519_dalek::scalar::Scalar::from_bits(scalar)
}
#[test]
fn test_x25519_pub_from_priv(kp in X25519KeyPair::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&X25519KeyPair::public_from_private(sk), pk);
}
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
let mut bits = [0u8; 32];
rng.fill_bytes(&mut bits);
// It's proper to sanitize the scalar here, and reproduces x25519::StaticSecret::new
let sk = SignalKeyPair::clamp_scalar(bits);
let pk = ::curve25519_dalek::constants::X25519_BASEPOINT * sk;
#[test]
fn test_x25519_dh(kp1 in X25519KeyPair::uniform_keypair_strategy(),
kp2 in X25519KeyPair::uniform_keypair_strategy()) {
(pk.as_bytes().to_vec(), sk.as_bytes().to_vec())
}
}
let dh1 = X25519KeyPair::diffie_hellman(kp1.public().clone(), kp2.private().clone());
let dh2 = X25519KeyPair::diffie_hellman(kp2.public().clone(), kp1.private().clone());
impl KeyPair for SignalKeyPair {
type Repr = Key;
fn public(&self) -> &Self::Repr {
&self.pk
}
fn private(&self) -> &Self::Repr {
&self.sk
}
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
Ok(SignalKeyPair {
pk: public,
sk: private,
})
}
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
let (public, private) = SignalKeyPair::gen(rng);
Ok(SignalKeyPair {
pk: Key(public),
sk: Key(private),
})
}
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
let mut secret_data = [0u8; 32];
secret_data.copy_from_slice(&secret.0[..]);
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
}
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
if !point.is_torsion_free() {
Err(InternalPakeError::SubGroupError)
} else {
Ok(key)
prop_assert_eq!(dh1,dh2);
}
}
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
let mut pk_data = [0; 32];
pk_data.copy_from_slice(&pk.0[..]);
let mut sk_data = [0; 32];
sk_data.copy_from_slice(&sk.0[..]);
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
}
}
+216 -120
View File
@@ -5,34 +5,55 @@
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
//!
//! Note: This implementation is in sync with [draft-krawczyk-cfrg-opaque-06](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06),
//! but this specification is subject to change, until the final version published by the IETF.
//!
//! # Overview
//!
//! OPAQUE is a protocol between a client and a server. They must first agree on a collection of primitives
//! to be kept consistent throughout protocol execution. These include:
//! * an authenticated encryption scheme,
//! * a finite cyclic group along with a point representation, and
//! * a keypair type.
//! * a finite cyclic group along with a point representation,
//! * a keypair type,
//! * a key exchange protocol,
//! * a hashing function, and
//! * a slow hashing function.
//!
//! We will use the following choices in this example:
//! ```
//! use chacha20poly1305::ChaCha20Poly1305;
//! use curve25519_dalek::ristretto::RistrettoPoint;
//! use opaque_ke::keypair::X25519KeyPair;
//! use opaque_ke::ciphersuite::CipherSuite;
//! struct Default;
//! impl CipherSuite for Default {
//! type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! type Hash = sha2::Sha256;
//! type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! }
//! ```
//!
//! This implementation is in sync with [draft-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05),
//! with a concrete instantiation of the authenticated key exchange protocol using 3DH. In the future, we plan to
//! add support for other KE protocols as well.
//! Note that our choice of slow hashing function in this example, `NoOpHash`, is selected only to ensure
//! that the tests execute quickly. A real application should use an actual slow hashing function, such as `Scrypt`.
//!
//! We have included a concrete instantiation of the authenticated key exchange protocol using 3DH. In the future, we plan to
//! add support for other KE protocols as well.
//!
//! ## Setup
//! To setup the protocol, the server begins by generating a static keypair:
//! ```
//! # use opaque_ke::keypair::{KeyPair, X25519KeyPair, SizedBytes};
//! # use opaque_ke::errors::ProtocolError;
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! use rand_core::{OsRng, RngCore};
//! let mut rng = OsRng;
//! let server_kp = X25519KeyPair::generate_random(&mut rng)?;
//! let server_kp = Default::generate_random_keypair(&mut rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! The server must persist this keypair for the registration and login steps, where the public component will be
@@ -48,13 +69,24 @@
//! In the first step (client registration start), the client chooses a registration password and an optional "pepper", and
//! runs `ClientRegistration::start` to produce a message `r1`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! use rand_core::{OsRng, RngCore};
//! let mut client_rng = OsRng;
//! let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! let (r1, client_state) = ClientRegistration::<Default>::start(
//! b"password",
//! Some(b"pepper"),
//! &mut client_rng,
@@ -67,23 +99,30 @@
//! In the second step (server registration start), the server takes as input the `r1` message from the client and runs
//! `ServerRegistration::start` to produce `r2`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! let mut server_rng = OsRng;
//! let (r2, server_state) =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! r1,
//! &mut server_rng,
//! )?;
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r2` is returned to the client, and `server_state` must be persisted on the server for the final step of server
@@ -91,56 +130,69 @@
//!
//! In the third step (client registration finish), the client takes as input the `r2` message from the server, along
//! with the server's static public key `server_kp.public()`, and uses `client_state` from the first step to run
//! `finish` and produce a message `r3` along with the key derivation key `kd_key_registration`:
//! `finish` and produce a message `r3` along with the export key `export_key_registration`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! let (r3, kd_key_registration) =
//! client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
//! let (r3, export_key_registration) =
//! client_state.finish(r2, server_kp.public(), &mut client_rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r3` is sent to the server, and the client can optionally use `kd_key_registration` for applications that choose to
//! `r3` is sent to the server, and the client can optionally use `export_key_registration` for applications that choose to
//! process user information beyond the OPAQUE functionality (e.g., additional secrets or credentials).
//!
//! In the fourth step of registration, the server takes as input the `r3` message from the client and uses
//! `server_state` from the second step to run `finish` and produce `password_file`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key_registration) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
//! let password_file = server_state.finish(r3)?;
//! # Ok::<(), ProtocolError>(())
//! ```
@@ -158,13 +210,24 @@
//! In the first step (client login start), the client chooses a registration password and an optional "pepper", and runs
//! `ClientLogin::start` to produce a message `l1`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! let mut client_rng = OsRng;
//! let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! let (l1, client_state) = ClientLogin::<Default>::start(
//! b"password",
//! Some(b"pepper"),
//! &mut client_rng,
@@ -177,37 +240,40 @@
//! private key `server_kp.private()`, along with a serialized version of the password file, `password_file_bytes`, and
//! runs `ServerLogin::start` to produce `l2`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key_registration) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # let (l1, client_state) = ClientLogin::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! use std::convert::TryFrom;
//! let password_file =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! &password_file_bytes[..],
//! )?;
//! let password_file = ServerRegistration::<Default>::try_from(&password_file_bytes[..])?;
//! let mut server_rng = OsRng;
//! let (l2, server_state) =
//! ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
@@ -217,93 +283,105 @@
//!
//! In the third step (client login finish), the client takes as input the `l2` message from the server, along with the
//! server's static public key `server_kp.public()`, and uses `client_state` from the first step to run `finish` and produce
//! a message `l3`, the shared secret `client_shared_secret`, and the key derivation key `kd_key_login`:
//! a message `l3`, the shared secret `client_shared_secret`, and the export key `export_key_login`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key_registration) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # let (l1, client_state) = ClientLogin::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! # ServerRegistration::<Default>::try_from(
//! # &password_file_bytes[..],
//! # )?;
//! # let (l2, server_state) =
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! let (l3, client_shared_secret, kd_key_login) = client_state.finish(
//! let (l3, client_shared_secret, export_key_login) = client_state.finish(
//! l2,
//! &server_kp.public(),
//! &mut client_rng,
//! )?;
//! assert_eq!(kd_key_registration, kd_key_login);
//! assert_eq!(export_key_registration, export_key_login);
//! # Ok::<(), ProtocolError>(())
//! ```
//! Note that if the client supplies a tuple (password, pepper, server public key) that does not match the tuple
//! used to create the password file, then at this point the `finish` algorithm outputs the error `InvalidLoginError`.
//!
//! If `finish` completes successfully, then `l3` is sent to the server, and (similarly to registration) the client
//! can use `kd_key_login` for applications that can take advantage of the fact that this key is identical to
//! `kd_key_registration`.
//! can use `export_key_login` for applications that can take advantage of the fact that this key is identical to
//! `export_key_registration`.
//!
//! In the fourth step of login, the server takes as input the `l3` message from the client and uses `server_state` from
//! the second step to run `finish`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
//! # keypair::{KeyPair, X25519KeyPair, SizedBytes},
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyFormat = opaque_ke::keypair::X25519KeyPair;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha256;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # let (r1, client_state) = ClientRegistration::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
//! # let (r3, export_key) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # let (l1, client_state) = ClientLogin::<Default>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! # ServerRegistration::<Default>::try_from(
//! # &password_file_bytes[..],
//! # )?;
//! # let (l2, server_state) =
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! # let (l3, client_shared_secret, kd_key) = client_state.finish(
//! # let (l3, client_shared_secret, export_key) = client_state.finish(
//! # l2,
//! # &server_kp.public(),
//! # &mut client_rng,
@@ -316,21 +394,39 @@
//! match `client_shared_secret`. Otherwise, on failure, the `finish` algorithm outputs the error `InvalidLoginError`.
//!
#![cfg_attr(not(feature = "bench"), deny(missing_docs))]
#![deny(unsafe_code)]
#[cfg(not(any(feature = "u64_backend", feature = "u32_backend",)))]
compile_error!(
"no dalek arithmetic backend cargo feature enabled! \
please enable one of: u64_backend, u32_backend"
);
// Error types
pub mod errors;
// High-level API
pub mod opaque;
// Your choice of RKR encryption
mod rkr_encryption;
// Your choice of KE
mod key_exchange;
pub mod ciphersuite;
mod envelope;
pub mod hash;
mod elligator;
pub mod group;
pub mod map_to_curve;
pub mod key_exchange;
pub mod keypair;
// Low-level API contains OPRF stuff
#[cfg(feature = "bench")]
pub mod oprf;
#[cfg(not(feature = "bench"))]
mod oprf;
// Technical module for your choice of cyclic subgroup to
// do the oprf on
mod group;
pub mod slow_hash;
#[cfg(test)]
mod tests;
+33
View File
@@ -0,0 +1,33 @@
// 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::group::Group;
use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint};
use hkdf::Hkdf;
use sha2::{Sha256, Sha512};
/// A subtrait of Group specifying how to hash a password into a point
pub trait GroupWithMapToCurve: Group {
/// transforms a password and optional pepper into a curve point
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self;
}
impl GroupWithMapToCurve for RistrettoPoint {
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
let (hashed_input, _) = Hkdf::<Sha512>::extract(pepper, password);
<Self as Group>::hash_to_curve(&hashed_input)
}
}
impl GroupWithMapToCurve for EdwardsPoint {
fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, password);
<Self as Group>::hash_to_curve(&hashed_input)
}
}
+367 -318
View File
File diff suppressed because it is too large Load Diff
+59 -31
View File
@@ -3,34 +3,31 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{errors::InternalPakeError, group::Group};
use generic_array::{typenum::U64, GenericArray};
use crate::{
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
};
use digest::Digest;
use generic_array::GenericArray;
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
// Low-level API
// =============
// This file contains an implementation of an oblivious pseudorandom function (OPRF), as well as password hashing and encryption functions.
pub(crate) struct OprfClientBytes<Grp: Group> {
pub(crate) alpha: Grp,
pub(crate) blinding_factor: Grp::Scalar,
pub struct OprfClientBytes<Grp: Group> {
pub alpha: Grp,
pub blinding_factor: Grp::Scalar,
}
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
/// an HKDF computation.
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: Group<UniformBytesLen = U64>>(
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
input: &[u8],
pepper: Option<&[u8]>,
blinding_factor_rng: &mut R,
) -> Result<OprfClientBytes<G>, InternalPakeError> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let mapped_point = G::map_to_curve(input, pepper);
let blinding_factor = G::random_scalar(blinding_factor_rng);
let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor;
let alpha = mapped_point * &blinding_factor;
Ok(OprfClientBytes {
alpha,
blinding_factor,
@@ -48,17 +45,47 @@ pub(crate) fn generate_oprf2<G: Group>(
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
/// the client unblinds the server's message.
pub(crate) fn generate_oprf3<G: Group>(
pub(crate) fn generate_oprf3<G: Group, H: Hash>(
input: &[u8],
point: G,
blinding_factor: &G::Scalar,
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, InternalPakeError> {
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
let unblinded = point * &G::scalar_invert(&blinding_factor);
let ikm: Vec<u8> = [&unblinded.to_bytes(), input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
let ikm: Vec<u8> = [&unblinded.to_arr()[..], input].concat();
let (prk, _) = Hkdf::<H>::extract(None, &ikm);
Ok(prk)
}
// Benchmarking shims
#[cfg(feature = "bench")]
#[inline]
pub fn generate_oprf1_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
input: &[u8],
pepper: Option<&[u8]>,
blinding_factor_rng: &mut R,
) -> Result<OprfClientBytes<G>, InternalPakeError> {
generate_oprf1(input, pepper, blinding_factor_rng)
}
#[cfg(feature = "bench")]
#[inline]
pub fn generate_oprf2_shim<G: Group>(
point: G,
oprf_key: &G::Scalar,
) -> Result<G, InternalPakeError> {
generate_oprf2(point, oprf_key)
}
#[cfg(feature = "bench")]
#[inline]
pub fn generate_oprf3_shim<G: Group, H: Hash>(
input: &[u8],
point: G,
blinding_factor: &G::Scalar,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
generate_oprf3::<G, H>(input, point, blinding_factor)
}
// Tests
// =====
@@ -67,21 +94,21 @@ mod tests {
use super::*;
use crate::group::Group;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::{arr, arr_impl, GenericArray};
use generic_array::{arr, GenericArray};
use hkdf::Hkdf;
use rand_core::OsRng;
use sha2::{Sha256, Sha512};
fn prf(
input: &[u8],
oprf_key: &[u8; 32],
) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&curve_input));
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input));
let scalar =
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar;
let ikm: Vec<u8> = [res.to_bytes().as_slice(), &input].concat();
let ikm: Vec<u8> = [&res.to_arr()[..], &input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
prk
@@ -101,7 +128,7 @@ mod tests {
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes)?;
let beta = generate_oprf2::<RistrettoPoint>(alpha, &salt)?;
let res = generate_oprf3::<RistrettoPoint>(input, beta, &blinding_factor)?;
let res = generate_oprf3::<RistrettoPoint, sha2::Sha256>(input, beta, &blinding_factor)?;
let res2 = prf(&input[..], &salt.as_bytes());
assert_eq!(res, res2);
Ok(())
@@ -116,15 +143,16 @@ mod tests {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
let res = generate_oprf3::<RistrettoPoint>(&input, alpha, &blinding_factor).unwrap();
let res = generate_oprf3::<RistrettoPoint, sha2::Sha256>(&input, alpha, &blinding_factor)
.unwrap();
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let mut curve_input: Vec<u8> = Vec::new();
curve_input.extend_from_slice(&hashed_input);
curve_input.extend_from_slice(&[0u8; 32]);
let point = RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&curve_input);
let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
let mut bits = [0u8; 64];
bits.copy_from_slice(&hashed_input);
let point = RistrettoPoint::from_uniform_bytes(&bits);
let mut ikm: Vec<u8> = Vec::new();
ikm.extend_from_slice(&point.to_bytes());
ikm.extend_from_slice(&point.to_arr());
ikm.extend_from_slice(&input);
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
-202
View File
@@ -1,202 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::errors::{utils::check_slice_size, InternalPakeError, PakeError};
use aead::{Aead, NewAead};
use generic_array::{typenum::Unsigned, GenericArray};
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
/// This trait encapsulates an encryption scheme that satisfies random-key robustness (RKR), which is implemented
/// through encrypt-then-HMAC -- see Section 3.1.1 of
/// https://www.ietf.org/id/draft-krawczyk-cfrg-opaque-03.txt
/// We require an Aead implementation with a 32-bit key size, since we
/// will derive the symmetric key from pw using Sha256
pub trait RKRCipher: Sized {
/// The requirement of KeySize = U32 is so that we can use a 32-bit hash
/// for key derivation form the user's password
type AEAD: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead;
// Required members
fn new(
aead_output: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
) -> Self;
fn aead_output(&self) -> &Vec<u8>;
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize>;
fn nonce(&self) -> &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>;
fn to_bytes(&self) -> Vec<u8>;
// Provided members for enc / dec
fn key_len() -> usize {
<Self::AEAD as NewAead>::KeySize::to_usize()
}
fn nonce_size() -> usize {
<Self::AEAD as Aead>::NonceSize::to_usize()
}
fn hmac_size() -> usize {
<Sha256 as Digest>::OutputSize::to_usize()
}
/// This estimates the size of the ciphertext once we encode —very specifically—
/// the payload we have planned for the protocol's env_u
fn ciphertest_size() -> usize {
Self::key_len() + <Self::AEAD as Aead>::TagSize::to_usize() + Self::hmac_size()
}
fn rkr_with_nonce_size() -> usize {
Self::ciphertest_size() + Self::nonce_size()
}
/// The format of the output ciphertext here is:
/// encryption_output | tag | hmac | nonce
/// variable length | AEAD_TAG_SIZE bytes | HMAC_SIZE bytes | NONCE_SIZE bytes
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes = check_slice_size(&bytes[..], Self::rkr_with_nonce_size(), "bytes")?;
let nonce_start = bytes.len() - Self::nonce_size();
let hmac_start = nonce_start - Self::hmac_size();
Ok(<Self as RKRCipher>::new(
bytes[..hmac_start].to_vec(),
GenericArray::from_slice(&checked_bytes[hmac_start..nonce_start]),
GenericArray::from_slice(&checked_bytes[nonce_start..]),
))
}
/// Encrypt with AEAD. Note that this encryption scheme needs to satisfy "random-key robustness" (RKR).
fn encrypt<R: RngCore + CryptoRng>(
encryption_key: &[u8],
hmac_key: &[u8],
plaintext: &[u8],
aad: &[u8],
rng: &mut R,
) -> Result<Self, PakeError> {
let mut nonce = vec![0u8; Self::nonce_size()];
rng.fill_bytes(&mut nonce);
let gen_nonce = GenericArray::from_slice(&nonce[..]);
let ciphertext = <Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key))
.encrypt(
GenericArray::from_slice(&nonce),
aead::Payload {
msg: &plaintext,
aad: &aad,
},
)
.map_err(|_| PakeError::EncryptionError)?;
let mut mac =
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
mac.input(&ciphertext);
Ok(<Self as RKRCipher>::new(
ciphertext,
&mac.result().code(),
gen_nonce,
))
}
fn decrypt(
&self,
encryption_key: &[u8],
hmac_key: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, PakeError> {
let mut mac =
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
mac.input(self.aead_output());
if mac.verify(self.hmac()).is_err() {
return Err(PakeError::DecryptionHmacError);
}
Aead::decrypt(
&<Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key)),
self.nonce(),
aead::Payload {
msg: self.aead_output(),
aad: &aad,
},
)
.map_err(|_| PakeError::DecryptionError)
}
}
/// This struct is a straightforward instantiation of the trait separating the
/// three components in Vecs
pub struct RKRCiphertext<T> {
aead_choice: std::marker::PhantomData<T>,
aead_output: Vec<u8>,
hmac: Vec<u8>,
nonce: Vec<u8>,
}
impl<T: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead> RKRCipher for RKRCiphertext<T> {
type AEAD = T;
fn new(
aead_output: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
) -> Self {
Self {
aead_choice: std::marker::PhantomData,
aead_output,
hmac: hmac.to_vec(),
nonce: nonce.to_vec(),
}
}
fn aead_output(&self) -> &Vec<u8> {
&self.aead_output
}
fn to_bytes(&self) -> Vec<u8> {
[&self.aead_output[..], &self.hmac[..], &self.nonce[..]].concat()
}
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize> {
GenericArray::from_slice(&self.hmac[..])
}
fn nonce(&self) -> &GenericArray<u8, <T as Aead>::NonceSize> {
GenericArray::from_slice(&self.nonce[..])
}
}
#[cfg(test)]
mod tests {
use super::*;
use chacha20poly1305::ChaCha20Poly1305;
use rand_core::OsRng;
#[test]
fn encrypt_and_decrypt() {
let mut rng = OsRng;
let mut encryption_key = [0u8; 32];
rng.fill_bytes(&mut encryption_key);
let mut hmac_key = [0u8; 32];
rng.fill_bytes(&mut hmac_key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
&encryption_key,
&hmac_key,
&msg,
b"",
&mut rng,
)
.unwrap();
let decrypted = ciphertext.decrypt(&encryption_key, &hmac_key, b"").unwrap();
assert_eq!(&msg.to_vec(), &decrypted);
}
}
+43
View File
@@ -0,0 +1,43 @@
// 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;
use crate::hash::Hash;
use digest::Digest;
use generic_array::GenericArray;
/// Used for the slow hashing function in OPAQUE
pub trait SlowHash<D: Hash> {
/// Computes the slow hashing function
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError>;
}
/// A no-op hash which simply returns its input
pub struct NoOpHash;
impl<D: Hash> SlowHash<D> for NoOpHash {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
Ok(input.to_vec())
}
}
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
let params = scrypt::ScryptParams::new(15, 8, 1).unwrap();
let mut output = [0u8; <D as Digest>::OutputSize::to_usize()];
scrypt::scrypt(&input, &[], &params, &mut output)
.map_err(|_| InternalPakeError::SlowHashError)?;
Ok(output.to_vec())
}
}
+140 -129
View File
@@ -4,14 +4,15 @@
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::*,
group::Group,
key_exchange::NONCE_LEN,
keypair::{Key, KeyPair, SignalKeyPair},
key_exchange::tripledh::{TripleDH, NONCE_LEN},
keypair::{Key, KeyPair, SizedBytes, X25519KeyPair},
opaque::*,
slow_hash::NoOpHash,
tests::mock_rng::CycleRng,
};
use aes_gcm::Aes256Gcm;
use curve25519_dalek::edwards::EdwardsPoint;
use rand_core::{OsRng, RngCore};
use serde_json::Value;
@@ -20,6 +21,15 @@ use std::convert::TryFrom;
// Tests
// =====
struct X255193dhNoSlowHash;
impl CipherSuite for X255193dhNoSlowHash {
type Group = EdwardsPoint;
type KeyFormat = X25519KeyPair;
type KeyExchange = TripleDH;
type Hash = sha2::Sha256;
type SlowHash = NoOpHash;
}
pub struct TestVectorParameters {
pub client_s_pk: Vec<u8>,
pub client_s_sk: Vec<u8>,
@@ -48,41 +58,41 @@ pub struct TestVectorParameters {
client_login_state: Vec<u8>,
server_login_state: Vec<u8>,
pub password_file: Vec<u8>,
pub opaque_key: Vec<u8>,
pub export_key: Vec<u8>,
pub shared_secret: Vec<u8>,
}
static TEST_VECTOR: &str = r#"
{
"client_s_pk": "f7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
"client_s_sk": "601ed276a42ec5795b3471f1a64e312f192e17ff252ce6053c8ecaf210138273",
"client_e_pk": "57260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
"client_e_sk": "e89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea68",
"server_s_pk": "a2b4e12d0621ebfb2631e00f5c872ab749e1a33915f16fb11203658b2189cc5e",
"server_s_sk": "90b6ca2ea8a37306060c7cd0998d4cdae59e972af7760312f7cf77099e78f940",
"server_e_pk": "64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073",
"server_e_sk": "883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f",
"client_s_pk": "b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29",
"client_s_sk": "701e8cd1263abd2f2a22d4dc94b1d5fe3c9cb14030e7e7c154745825b059fd7f",
"client_e_pk": "97cb1eb93a69542597517b110ccca457d5ce8d8bfcbfb2a9258bb7b4bd7f716e",
"client_e_sk": "80616968ed8daae02c02d3ba41a70104ed0deecd2276e058994d601a1351b359",
"server_s_pk": "e12d737e520eaf8504fbf302c2945011bff360bdf02ee102f2ebd6a883c80e02",
"server_s_sk": "9075d3d3c5b6bc2f6218e7672c0532c619ce09dddf196006c5ffdaf628a3d760",
"server_e_pk": "f73d27d7ca78ded52209bc3bae000f9d95b147360edac1e97c148a3a7396a279",
"server_e_sk": "a0e59a07908fc793c590fd83343003a54330e24af908ed31c921e6e6504c3248",
"password": "70617373776f7264",
"blinding_factor_raw": "b85e0df2ad0495771edf09a04b1073045e6472e2f86a41e9bab3143ebfb8eb08a3462503eb3750bf006dc82c93b37e07cdf3768018c22b431cf5146a9caeda1c",
"blinding_factor": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f",
"blinding_factor_raw": "ca2d8ae51794579bd0f46044d7daccf222b4590053536b48575bc169f7478fd0a0b580fb0aae948c26ba403a2e7b98f563e434a0aad93f4105419c474453c34e",
"blinding_factor": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e02",
"pepper": "706570706572",
"oprf_key": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
"envelope_nonce": "c87e44792a9dfd8858db676e",
"client_nonce": "1f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23",
"server_nonce": "d448cb1f58c38605fc29069ac688ec9c667c99d0316b38cd1b2609c1bc14aa90",
"r1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd897",
"r2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb",
"r3": "374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676ef7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
"l1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd8971f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a2357260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
"l2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073d81a1104fbd599ef56228bdbe9bf7be4a38ae907a8717ca0883b9d69b2efc529",
"l3": "a01332643e8aa7113f6f160205a9b3bd0705f3b33d8e4ea8eab9eae6685a6adb",
"client_registration_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f70617373776f7264",
"client_login_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60fe89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea681f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23dd1a7c2b4e9f9be94bd36f3b6c7f23aa9f1e6b3fda9030412a918d1288b4af1970617373776f7264",
"server_registration_state": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
"server_login_state": "809f95143f8f7fc1d0b42f578a83f714f58cfd96d9499aacee730ad296b37b19c18c903396e85da607d02542d4d07456e5357ff2e2eade3aaa42e532d4e9364f66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d",
"password_file": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0bf7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e",
"opaque_key": "682f2868a3e1460fed5a16767bd8778c33b4aecac6607270f848aa61c95a1a68",
"shared_secret": "66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d"
"oprf_key": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907",
"envelope_nonce": "b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8",
"client_nonce": "b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d05572",
"server_nonce": "a213c02274e7f20fc3b571d25e98854c5dae2cfde6c9bf228a66bf3eff3e2a97",
"r1": "7e2c67a156ab27490f20008fcae9e9f722d8a9f4eeac373a711259981ca05dd5",
"r2": "710fdd19883e869e784c84f2864fa0bfc227662404b77cc8a54d79ae7fb931ea",
"r3": "b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29",
"l1": "7e2c67a156ab27490f20008fcae9e9f722d8a9f4eeac373a711259981ca05dd5b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d0557297cb1eb93a69542597517b110ccca457d5ce8d8bfcbfb2a9258bb7b4bd7f716e",
"l2": "710fdd19883e869e784c84f2864fa0bfc227662404b77cc8a54d79ae7fb931eab0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933a0e59a07908fc793c590fd83343003a54330e24af908ed31c921e6e6504c3248f73d27d7ca78ded52209bc3bae000f9d95b147360edac1e97c148a3a7396a27939ccf2a17a5b281068665b4865e6c6331533461a8e10a4ceffc4c6a6609c326a",
"l3": "127144e6469e001d56237a58c8c869a8173e042bf2ff19d8331441d36ada9c3f",
"client_registration_state": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e0270617373776f7264",
"client_login_state": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e0280616968ed8daae02c02d3ba41a70104ed0deecd2276e058994d601a1351b359b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d05572f258311568d792d6ebecee225c0fde4512139e29a435e9f9a0b82dc3809a83ab70617373776f7264",
"server_registration_state": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907",
"server_login_state": "ebc0953924d55ad66aa801a7c85f47f35889b90002451a04fb7134b8a2a5a33cd69098c0a81ce06f58cbe4fd6ba23c9c1404ad6f639ba64d5f0f7bf0a041fc5872b17f13bd41cbfbdfa8d74bc94ec1abcc77b9a3da8fbad918ca0a5f84a81443",
"password_file": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933",
"export_key": "da3a52148a58168c9f804df5e216e3d3f16e935d4d70a5eb249433d88e02ae4c",
"shared_secret": "72b17f13bd41cbfbdfa8d74bc94ec1abcc77b9a3da8fbad918ca0a5f84a81443"
}
"#;
@@ -121,7 +131,7 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
server_registration_state: decode(&values, "server_registration_state").unwrap(),
server_login_state: decode(&values, "server_login_state").unwrap(),
password_file: decode(&values, "password_file").unwrap(),
opaque_key: decode(&values, "opaque_key").unwrap(),
export_key: decode(&values, "export_key").unwrap(),
shared_secret: decode(&values, "shared_secret").unwrap(),
}
}
@@ -204,27 +214,38 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
)
.as_str(),
);
s.push_str(format!("\"opaque_key\": \"{}\",\n", hex::encode(&p.opaque_key)).as_str());
s.push_str(format!("\"export_key\": \"{}\",\n", hex::encode(&p.export_key)).as_str());
s.push_str(format!("\"shared_secret\": \"{}\"\n", hex::encode(&p.shared_secret)).as_str());
s.push_str("}\n");
s
}
fn generate_parameters() -> TestVectorParameters {
fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters
where
// Unsightly constraints due to the (required) use of the SizedBytes
// instance for KP in ServerRegistration::start. See also the impl
// Tryfrom<&[u8]> for ServerRegistration (those are the same constraints).
<<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len:
std::ops::Add<<<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len>,
generic_array::typenum::Sum<
<<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len,
<<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len,
>: generic_array::ArrayLength<u8>,
{
let mut rng = OsRng;
// Inputs
let server_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let server_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let client_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let client_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let server_s_kp = CS::generate_random_keypair(&mut rng).unwrap();
let server_e_kp = CS::generate_random_keypair(&mut rng).unwrap();
let client_s_kp = CS::generate_random_keypair(&mut rng).unwrap();
let client_e_kp = CS::generate_random_keypair(&mut rng).unwrap();
let password = b"password";
let pepper = b"pepper";
let mut blinding_factor_raw = [0u8; 64];
rng.fill_bytes(&mut blinding_factor_raw);
let mut oprf_key_raw = [0u8; 32];
rng.fill_bytes(&mut oprf_key_raw);
let mut envelope_nonce = [0u8; 12];
let mut envelope_nonce = [0u8; 32];
rng.fill_bytes(&mut envelope_nonce);
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
@@ -232,32 +253,30 @@ fn generate_parameters() -> TestVectorParameters {
rng.fill_bytes(&mut server_nonce);
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
let (r1, client_registration) = ClientRegistration::<CS>::start(
password,
Some(pepper),
&mut blinding_factor_registration_rng,
)
.unwrap();
let r1_bytes = r1.to_bytes().to_vec();
let blinding_factor_bytes = client_registration.blinding_factor.to_bytes();
let blinding_factor_bytes =
CS::Group::scalar_as_bytes(&client_registration.blinding_factor).clone();
let client_registration_state = client_registration.to_bytes().to_vec();
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
let (r2, server_registration) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(r1, &mut oprf_key_rng)
.unwrap();
let (r2, server_registration) = ServerRegistration::<CS>::start(r1, &mut oprf_key_rng).unwrap();
let r2_bytes = r2.to_bytes().to_vec();
let oprf_key = server_registration.oprf_key;
let oprf_key_bytes = EdwardsPoint::scalar_as_bytes(&oprf_key);
let oprf_key_bytes = CS::Group::scalar_as_bytes(&server_registration.oprf_key).clone();
let server_registration_state = server_registration.to_bytes().to_vec();
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private());
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().to_arr());
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let (r3, opaque_key_registration) = client_registration
.finish::<_, SignalKeyPair>(r2, server_s_kp.public(), &mut finish_registration_rng)
let (r3, export_key_registration) = client_registration
.finish(r2, server_s_kp.public(), &mut finish_registration_rng)
.unwrap();
let r3_bytes = r3.to_bytes().to_vec();
@@ -266,21 +285,17 @@ fn generate_parameters() -> TestVectorParameters {
let mut client_login_start: Vec<u8> = Vec::new();
client_login_start.extend_from_slice(&blinding_factor_raw);
client_login_start.extend_from_slice(&client_e_kp.private());
client_login_start.extend_from_slice(&client_e_kp.private().to_arr());
client_login_start.extend_from_slice(&client_nonce);
let mut client_login_start_rng = CycleRng::new(client_login_start);
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
password,
Some(pepper),
&mut client_login_start_rng,
)
.unwrap();
let (l1, client_login) =
ClientLogin::<CS>::start(password, Some(pepper), &mut client_login_start_rng).unwrap();
let l1_bytes = l1.to_bytes().to_vec();
let client_login_state = client_login.to_bytes().to_vec();
let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_vec());
let (l2, server_login) = ServerLogin::start(
let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_arr().to_vec());
let (l2, server_login) = ServerLogin::<CS>::start(
password_file,
server_s_kp.private(),
l1,
@@ -290,21 +305,21 @@ fn generate_parameters() -> TestVectorParameters {
let l2_bytes = l2.to_bytes().to_vec();
let server_login_state = server_login.to_bytes().to_vec();
let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_vec());
let (l3, client_shared_secret, _opaque_key_login) = client_login
let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_arr().to_vec());
let (l3, client_shared_secret, _export_key_login) = client_login
.finish(l2, server_s_kp.public(), &mut client_e_sk_rng)
.unwrap();
let l3_bytes = l3.to_bytes().to_vec();
TestVectorParameters {
client_s_pk: client_s_kp.public().to_vec(),
client_s_sk: client_s_kp.private().to_vec(),
client_e_pk: client_e_kp.public().to_vec(),
client_e_sk: client_e_kp.private().to_vec(),
server_s_pk: server_s_kp.public().to_vec(),
server_s_sk: server_s_kp.private().to_vec(),
server_e_pk: server_e_kp.public().to_vec(),
server_e_sk: server_e_kp.private().to_vec(),
client_s_pk: client_s_kp.public().to_arr().to_vec(),
client_s_sk: client_s_kp.private().to_arr().to_vec(),
client_e_pk: client_e_kp.public().to_arr().to_vec(),
client_e_sk: client_e_kp.private().to_arr().to_vec(),
server_s_pk: server_s_kp.public().to_arr().to_vec(),
server_s_sk: server_s_kp.private().to_arr().to_vec(),
server_e_pk: server_e_kp.public().to_arr().to_vec(),
server_e_sk: server_e_kp.private().to_arr().to_vec(),
password: password.to_vec(),
blinding_factor_raw: blinding_factor_raw.to_vec(),
blinding_factor: blinding_factor_bytes.to_vec(),
@@ -325,13 +340,13 @@ fn generate_parameters() -> TestVectorParameters {
client_login_state,
server_login_state,
shared_secret: client_shared_secret,
opaque_key: opaque_key_registration.to_vec(),
export_key: export_key_registration.to_vec(),
}
}
#[test]
fn generate_test_vectors() {
let parameters = generate_parameters();
let parameters = generate_parameters::<X255193dhNoSlowHash>();
println!("{}", stringify_test_vectors(&parameters));
}
@@ -339,7 +354,7 @@ fn generate_test_vectors() {
fn test_r1() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start(
&parameters.password,
Some(&parameters.pepper),
&mut blinding_factor_rng,
@@ -357,12 +372,11 @@ fn test_r1() -> Result<(), PakeError> {
fn test_r2() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let (r2, server_registration) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
RegisterFirstMessage::try_from(&parameters.r1[..]).unwrap(),
&mut oprf_key_rng,
)
.unwrap();
let (r2, server_registration) = ServerRegistration::<X255193dhNoSlowHash>::start(
RegisterFirstMessage::try_from(&parameters.r1[..]).unwrap(),
&mut oprf_key_rng,
)
.unwrap();
assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
assert_eq!(
hex::encode(&parameters.server_registration_state),
@@ -378,21 +392,21 @@ fn test_r3() -> Result<(), PakeError> {
let client_s_sk_and_nonce: Vec<u8> =
[parameters.client_s_sk, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let (r3, opaque_key_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::try_from(
let (r3, export_key_registration) = ClientRegistration::<X255193dhNoSlowHash>::try_from(
&parameters.client_registration_state[..],
)
.unwrap()
.finish::<CycleRng, SignalKeyPair>(
.finish(
RegisterSecondMessage::try_from(&parameters.r2[..]).unwrap(),
&Key::try_from(parameters.server_s_pk).unwrap(),
&Key::try_from(&parameters.server_s_pk[..]).unwrap(),
&mut finish_registration_rng,
)
.unwrap();
assert_eq!(hex::encode(parameters.r3), hex::encode(r3.to_bytes()));
assert_eq!(
hex::encode(parameters.opaque_key),
hex::encode(opaque_key_registration.to_vec())
hex::encode(parameters.export_key),
hex::encode(export_key_registration.to_vec())
);
Ok(())
@@ -402,11 +416,10 @@ fn test_r3() -> Result<(), PakeError> {
fn test_password_file() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let server_registration =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
&parameters.server_registration_state[..],
)
.unwrap();
let server_registration = ServerRegistration::<X255193dhNoSlowHash>::try_from(
&parameters.server_registration_state[..],
)
.unwrap();
let password_file = server_registration
.finish(RegisterThirdMessage::try_from(&parameters.r3[..]).unwrap())
.unwrap();
@@ -429,7 +442,7 @@ fn test_l1() -> Result<(), PakeError> {
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
let (l1, client_login) = ClientLogin::<X255193dhNoSlowHash>::start(
&parameters.password,
Some(&parameters.pepper),
&mut client_login_start_rng,
@@ -448,10 +461,10 @@ fn test_l2() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
let (l2, server_login) = ServerLogin::start::<_, Aes256Gcm, _, SignalKeyPair>(
let (l2, server_login) = ServerLogin::<X255193dhNoSlowHash>::start(
ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
&Key::try_from(parameters.server_s_sk).unwrap(),
LoginFirstMessage::<EdwardsPoint>::try_from(&parameters.l1[..]).unwrap(),
&Key::try_from(&parameters.server_s_sk[..]).unwrap(),
LoginFirstMessage::<X255193dhNoSlowHash>::try_from(&parameters.l1[..]).unwrap(),
&mut server_e_sk_rng,
)
.unwrap();
@@ -469,17 +482,15 @@ fn test_l3() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
let (l3, shared_secret, opaque_key_login) =
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
&parameters.client_login_state[..],
)
.unwrap()
.finish(
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(&parameters.l2[..]).unwrap(),
&Key::try_from(parameters.server_s_pk)?,
&mut client_e_sk_rng,
)
.unwrap();
let (l3, shared_secret, export_key_login) =
ClientLogin::<X255193dhNoSlowHash>::try_from(&parameters.client_login_state[..])
.unwrap()
.finish(
LoginSecondMessage::<X255193dhNoSlowHash>::try_from(&parameters.l2[..]).unwrap(),
&Key::try_from(&parameters.server_s_pk[..])?,
&mut client_e_sk_rng,
)
.unwrap();
assert_eq!(
hex::encode(&parameters.shared_secret),
@@ -487,8 +498,8 @@ fn test_l3() -> Result<(), PakeError> {
);
assert_eq!(hex::encode(&parameters.l3), hex::encode(l3.to_bytes()));
assert_eq!(
hex::encode(&parameters.opaque_key),
hex::encode(opaque_key_login)
hex::encode(&parameters.export_key),
hex::encode(export_key_login)
);
Ok(())
@@ -498,10 +509,11 @@ fn test_l3() -> Result<(), PakeError> {
fn test_server_login_finish() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let shared_secret = ServerLogin::try_from(&parameters.server_login_state[..])
.unwrap()
.finish(LoginThirdMessage::try_from(&parameters.l3[..])?)
.unwrap();
let shared_secret =
ServerLogin::<X255193dhNoSlowHash>::try_from(&parameters.server_login_state[..])
.unwrap()
.finish(LoginThirdMessage::try_from(&parameters.l3[..])?)
.unwrap();
assert_eq!(
hex::encode(parameters.shared_secret),
@@ -517,34 +529,31 @@ fn test_complete_flow(
) -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_kp = SignalKeyPair::generate_random(&mut server_rng)?;
let (register_m1, client_state) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
let server_kp = X255193dhNoSlowHash::generate_random_keypair(&mut server_rng)?;
let (register_m1, client_state) = ClientRegistration::<X255193dhNoSlowHash>::start(
registration_password,
None,
&mut client_rng,
)?;
let (register_m2, server_state) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
register_m1,
&mut server_rng,
)?;
let (register_m3, registration_opaque_key) =
ServerRegistration::<X255193dhNoSlowHash>::start(register_m1, &mut server_rng)?;
let (register_m3, registration_export_key) =
client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
let p_file = server_state.finish(register_m3)?;
let (login_m1, client_login_state) =
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
login_password,
None,
&mut client_rng,
)?;
let (login_m2, server_login_state) =
ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
ClientLogin::<X255193dhNoSlowHash>::start(login_password, None, &mut client_rng)?;
let (login_m2, server_login_state) = ServerLogin::<X255193dhNoSlowHash>::start(
p_file,
&server_kp.private(),
login_m1,
&mut server_rng,
)?;
let client_login_result =
client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng);
if hex::encode(registration_password) == hex::encode(login_password) {
let (login_m3, client_shared_secret, login_opaque_key) = client_login_result?;
let (login_m3, client_shared_secret, login_export_key) = client_login_result?;
let server_shared_secret = server_login_state.finish(login_m3)?;
assert_eq!(
@@ -552,14 +561,16 @@ fn test_complete_flow(
hex::encode(client_shared_secret)
);
assert_eq!(
hex::encode(registration_opaque_key),
hex::encode(login_opaque_key)
hex::encode(registration_export_key),
hex::encode(login_export_key)
);
} else {
let res = match client_login_result {
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
_ => false,
};
let res = matches!(
client_login_result,
Err(ProtocolError::VerificationError(
PakeError::InvalidLoginError
))
);
assert!(res);
}
+150 -44
View File
@@ -4,23 +4,46 @@
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
envelope::Envelope,
group::Group,
keypair::{KeyPair, SignalKeyPair, SizedBytes},
key_exchange::{
traits::{KeyExchange, ToBytes},
tripledh::{TripleDH, NONCE_LEN},
},
keypair::{KeyPair, SizedBytes, X25519KeyPair},
opaque::*,
rkr_encryption::{RKRCipher as _, RKRCiphertext},
};
use curve25519_dalek::ristretto::RistrettoPoint;
use chacha20poly1305::ChaCha20Poly1305;
use generic_array::typenum::Unsigned;
use proptest::{collection::vec, prelude::*};
use rand_core::{OsRng, RngCore};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
struct Default;
impl CipherSuite for Default {
type Group = RistrettoPoint;
type KeyFormat = crate::keypair::X25519KeyPair;
type KeyExchange = TripleDH;
type Hash = sha2::Sha256;
type SlowHash = crate::slow_hash::NoOpHash;
}
fn random_ristretto_point() -> RistrettoPoint {
let mut rng = OsRng;
let mut random_bits = [0u8; 64];
rng.fill_bytes(&mut random_bits);
// This is because RistrettoPoint is on an obsolete sha2 version
let mut bits = [0u8; 64];
rng.fill_bytes(&mut bits);
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
let mut hasher = sha2::Sha512::new();
hasher.update(&random_bits[..]);
bits.copy_from_slice(&hasher.finalize());
RistrettoPoint::from_uniform_bytes(&bits)
}
#[test]
@@ -29,10 +52,8 @@ fn client_registration_roundtrip() {
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
// serialization order: scalar, password
let mut bytes: Vec<u8> = vec![];
bytes.extend_from_slice(sc.as_bytes());
bytes.extend_from_slice(pw);
let reg = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::try_from(&bytes[..]).unwrap();
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
let reg = ClientRegistration::<Default>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
@@ -45,27 +66,24 @@ fn server_registration_roundtrip() {
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(sc.as_bytes());
let reg = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(
&oprf_bytes[..],
)
.unwrap();
let reg = ServerRegistration::<Default>::try_from(&oprf_bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, oprf_bytes);
// If we do have envelope and client pk, the server registration contains
// the whole kit
let rkr_size = RKRCiphertext::<ChaCha20Poly1305>::rkr_with_nonce_size();
let mut mock_rkr_bytes = vec![0u8; rkr_size];
rng.fill_bytes(&mut mock_rkr_bytes);
println!("{}", mock_rkr_bytes.len());
let mock_client_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let key_len =
<<<Default as CipherSuite>::KeyFormat as KeyPair>::Repr as SizedBytes>::Len::to_usize();
let envelope_size = key_len + Envelope::<sha2::Sha256>::additional_size();
let mut mock_envelope_bytes = vec![0u8; envelope_size];
rng.fill_bytes(&mut mock_envelope_bytes);
println!("{}", mock_envelope_bytes.len());
let mock_client_kp = Default::generate_random_keypair(&mut rng).unwrap();
// serialization order: scalar, public key, envelope
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(sc.as_bytes());
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
bytes.extend_from_slice(&mock_rkr_bytes);
let reg =
ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(&bytes[..])
.unwrap();
bytes.extend_from_slice(&mock_envelope_bytes);
let reg = ServerRegistration::<Default>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
@@ -73,7 +91,7 @@ fn server_registration_roundtrip() {
#[test]
fn register_first_message_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_bytes();
let pt_bytes = pt.to_arr();
let r1 = RegisterFirstMessage::<RistrettoPoint>::try_from(pt_bytes.as_slice()).unwrap();
let r1_bytes = r1.to_bytes();
assert_eq!(pt_bytes, r1_bytes);
@@ -82,7 +100,7 @@ fn register_first_message_roundtrip() {
#[test]
fn register_second_message_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_bytes();
let pt_bytes = pt.to_arr();
let message = pt_bytes.to_vec();
let r2 = RegisterSecondMessage::<RistrettoPoint>::try_from(&message[..]).unwrap();
@@ -93,32 +111,120 @@ fn register_second_message_roundtrip() {
#[test]
fn register_third_message_roundtrip() {
let mut rng = OsRng;
let skp = SignalKeyPair::generate_random(&mut rng).unwrap();
let skp = Default::generate_random_keypair(&mut rng).unwrap();
let pubkey_bytes = skp.public().to_arr();
let mut encryption_key = [0u8; 32];
rng.fill_bytes(&mut encryption_key);
let mut hmac_key = [0u8; 32];
rng.fill_bytes(&mut hmac_key);
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
&encryption_key,
&hmac_key,
&msg,
&pubkey_bytes,
&mut rng,
)
.unwrap();
let (ciphertext, _) =
Envelope::<sha2::Sha256>::seal(&key, &msg, &pubkey_bytes, &mut rng).unwrap();
let mut message = Vec::new();
message.extend_from_slice(&ciphertext.to_bytes());
message.extend_from_slice(&pubkey_bytes);
let r3 =
RegisterThirdMessage::<ChaCha20Poly1305, SignalKeyPair>::try_from(&message[..]).unwrap();
let message: Vec<u8> = [&ciphertext.to_bytes(), &pubkey_bytes[..]].concat();
let r3 = RegisterThirdMessage::<X25519KeyPair, sha2::Sha256>::try_from(&message[..]).unwrap();
let r3_bytes = r3.to_bytes();
assert_eq!(message, r3_bytes);
}
#[test]
fn client_login_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat();
let mut hasher = Sha256::new();
hasher.update(l1_data);
let hashed_l1 = hasher.finalize();
// serialization order: scalar, password, ke1_state
let bytes: Vec<u8> = [
&sc.as_bytes()[..],
&pw[..],
client_e_kp.public(),
&client_nonce,
hashed_l1.as_slice(),
]
.concat();
let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn login_first_message_roundtrip() {
let mut rng = OsRng;
let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
let reg = <TripleDH as KeyExchange<sha2::Sha256, crate::keypair::X25519KeyPair>>::KE1Message::try_from(
&ke1m[..],
)
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, ke1m);
}
proptest! {
#[test]
fn test_nocrash_register_first_message(bytes in vec(any::<u8>(), 0..200)) {
RegisterFirstMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_register_second_message(bytes in vec(any::<u8>(), 0..200)) {
RegisterSecondMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_register_third_message(bytes in vec(any::<u8>(), 0..200)) {
RegisterThirdMessage::<crate::keypair::X25519KeyPair, sha2::Sha512>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_login_first_message(bytes in vec(any::<u8>(), 0..500)) {
LoginFirstMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) {
LoginSecondMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_login_third_message(bytes in vec(any::<u8>(), 0..500)) {
LoginThirdMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
ClientRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
ServerRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
ClientLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
ServerLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
}