Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84cc0e9b72 | ||
|
|
7945661f67 | ||
|
|
b673f840a5 | ||
|
|
81353cb844 | ||
|
|
48412c8ee4 | ||
|
|
ab8db3bf03 | ||
|
|
dede91a5b7 | ||
|
|
f1bdfcd3a0 | ||
|
|
cc4a292b6d | ||
|
|
2c9e47d14d | ||
|
|
195a698ad8 | ||
|
|
cdf0c7bf72 | ||
|
|
59af252168 | ||
|
|
6dde7c5351 | ||
|
|
694c51c982 | ||
|
|
07a702c1be | ||
|
|
29ba1b93b5 | ||
|
|
c2edb2d95e | ||
|
|
72a3928cbb | ||
|
|
6b1e485763 | ||
|
|
7b11da3bd6 | ||
|
|
4aae404e29 | ||
|
|
1f8d786be8 | ||
|
|
8885fe5aa1 | ||
|
|
b0b9cd0ab2 | ||
|
|
4fda240a7d | ||
|
|
5a7b60bec7 | ||
|
|
91137903c6 | ||
|
|
e93e8fd5a8 | ||
|
|
f8285c60ba | ||
|
|
2959290582 | ||
|
|
69449cca3e | ||
|
|
988b9bae77 | ||
|
|
9d40aa7659 | ||
|
|
41cd80ccb5 | ||
|
|
45ea7b6e84 | ||
|
|
52429dd2fa | ||
|
|
bb9c365998 | ||
|
|
3e41f2441d | ||
|
|
59d997900e | ||
|
|
6d02c72aae | ||
|
|
4a638b8a22 | ||
|
|
1a5e704cf6 | ||
|
|
d5e585db1d | ||
|
|
642d5188f5 | ||
|
|
c9d467e368 | ||
|
|
738b90188d | ||
|
|
7a2d309263 | ||
|
|
f989330807 | ||
|
|
4f9b7fe86f | ||
|
|
aab8ff8e19 | ||
|
|
2cf6808665 | ||
|
|
ab1b1d6209 | ||
|
|
0cf13c2266 | ||
|
|
f030fca1cb | ||
|
|
5cf29fba62 | ||
|
|
3ae1c58d46 | ||
|
|
e9fa474c58 | ||
|
|
e9add9fd7a | ||
|
|
b401cfc89c | ||
|
|
3a1aaab6cf | ||
|
|
5a374562d1 | ||
|
|
cd46a42634 | ||
|
|
ce1d97002c | ||
|
|
80fc493a60 | ||
|
|
8362804946 | ||
|
|
05fb76d5b7 | ||
|
|
af22bfa54f | ||
|
|
57e152b028 |
+82
-13
@@ -1,8 +1,81 @@
|
||||
name: Rust CI
|
||||
on: [push]
|
||||
on:
|
||||
push:
|
||||
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 +95,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
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 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
@@ -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
File diff suppressed because it is too large
Load Diff
+31
-13
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "opaque-ke"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
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"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## The OPAQUE key exchange protocol
|
||||
## The OPAQUE key exchange protocol 
|
||||
|
||||
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an asymmetric password-authenticated key exchange protocol. It allows a client to authenticate to a server using a password, without ever having to expose the plaintext password to the server.
|
||||
|
||||
@@ -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.0"
|
||||
```
|
||||
|
||||
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
@@ -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);
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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::{Key, 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<Repr = Key> + PartialEq;
|
||||
/// A key exchange protocol
|
||||
type KeyExchange: KeyExchange<Self::Hash>;
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
]);
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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::*;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 mut bits_in = [0u8; 32];
|
||||
bits_in.copy_from_slice(&bytes);
|
||||
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
@@ -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
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
+79
-29
@@ -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;
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ impl Group for RistrettoPoint {
|
||||
.ok_or_else(|| 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())
|
||||
}
|
||||
@@ -94,9 +98,9 @@ impl Group for RistrettoPoint {
|
||||
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)
|
||||
bits.copy_from_slice(&uniform_bytes);
|
||||
|
||||
RistrettoPoint::from_uniform_bytes(&bits)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,35 +130,81 @@ 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_else(|| 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};
|
||||
|
||||
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 mut bytes = [0u8; 32];
|
||||
bytes.copy_from_slice(&pt[..32]);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// 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 digest::{BlockInput, FixedOutput, Reset, Update};
|
||||
use generic_array::ArrayLength;
|
||||
|
||||
/// 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 {
|
||||
/// The block size for the hash function
|
||||
type BlockSize: ArrayLength<u8>;
|
||||
/// The output size of the hash function
|
||||
type OutputSize: ArrayLength<u8>;
|
||||
}
|
||||
|
||||
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {
|
||||
type BlockSize = T::BlockSize;
|
||||
type OutputSize = T::OutputSize;
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
|
||||
keypair::{Key, KeyPair, SizedBytes},
|
||||
};
|
||||
use generic_array::GenericArray;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// This module is a somewhat minimalistic implementation of a key Exchange
|
||||
/// protocol based on 3DH. It assumes a pre-exchange has allowed client and
|
||||
/// server to learn each other's static public key.
|
||||
///
|
||||
/// This private module may undergo significant changes in the near term.
|
||||
|
||||
const KEY_LEN: usize = 32;
|
||||
pub(crate) const NONCE_LEN: usize = 32;
|
||||
pub(crate) const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
|
||||
pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
|
||||
|
||||
static STR_3DH: &[u8] = b"3DH keys";
|
||||
|
||||
pub(crate) struct KE1State {
|
||||
client_e_sk: Key,
|
||||
client_nonce: Vec<u8>,
|
||||
hashed_l1: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct KE1Message {
|
||||
pub(crate) client_nonce: Vec<u8>,
|
||||
pub(crate) client_e_pk: Key,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE1State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
|
||||
|
||||
Ok(Self {
|
||||
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
|
||||
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
|
||||
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KE1State {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.client_e_sk.to_arr(),
|
||||
&self.client_nonce[..],
|
||||
&self.hashed_l1[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl KE1Message {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE1Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes =
|
||||
check_slice_size(ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
|
||||
|
||||
Ok(Self {
|
||||
client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
|
||||
client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
l1_component: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(KE1State, KE1Message), ProtocolError> {
|
||||
let client_e_kp = KeyFormat::generate_random(rng)?;
|
||||
let mut client_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut client_nonce);
|
||||
|
||||
let ke1_message = KE1Message {
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&l1_data);
|
||||
let hashed_l1 = hasher.result();
|
||||
|
||||
Ok((
|
||||
KE1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
hashed_l1: hashed_l1.to_vec(),
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) struct KE2State {
|
||||
km3: Vec<u8>,
|
||||
hashed_transcript: Vec<u8>,
|
||||
shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct KE2Message {
|
||||
server_nonce: Vec<u8>,
|
||||
server_e_pk: Key,
|
||||
mac: Vec<u8>,
|
||||
}
|
||||
|
||||
impl KE2State {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.shared_secret[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE2State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
|
||||
|
||||
Ok(Self {
|
||||
km3: checked_bytes[..KEY_LEN].to_vec(),
|
||||
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
|
||||
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KE2Message {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.server_nonce[..],
|
||||
&self.server_e_pk.to_arr(),
|
||||
&self.mac[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE2Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(ke1_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
|
||||
|
||||
Ok(Self {
|
||||
server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
|
||||
server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
|
||||
mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The triple of public and private components used in the 3DH computation
|
||||
struct TripleDHComponents {
|
||||
pk1: Key,
|
||||
sk1: Key,
|
||||
pk2: Key,
|
||||
sk2: Key,
|
||||
pk3: Key,
|
||||
sk3: Key,
|
||||
}
|
||||
|
||||
// Consists of a shared secret, followed by two mac keys
|
||||
type TripleDHDerivationResult = (
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
);
|
||||
|
||||
// Internal function which takes the public and private components of the client and server keypairs, along
|
||||
// with some auxiliary metadata, to produce the shared secret and two MAC keys
|
||||
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>>(
|
||||
dh: TripleDHComponents,
|
||||
client_nonce: &[u8],
|
||||
server_nonce: &[u8],
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
) -> Result<TripleDHDerivationResult, ProtocolError> {
|
||||
let ikm: Vec<u8> = [
|
||||
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
|
||||
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
|
||||
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let info: Vec<u8> = [
|
||||
STR_3DH,
|
||||
&client_nonce,
|
||||
&server_nonce,
|
||||
&client_s_pk.to_arr(),
|
||||
&server_s_pk.to_arr(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
const OUTPUT_SIZE: usize = 32;
|
||||
let mut okm = [0u8; 3 * OUTPUT_SIZE];
|
||||
let h = Hkdf::<Sha256>::new(None, &ikm);
|
||||
h.expand(&info, &mut okm)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
Ok((
|
||||
*GenericArray::from_slice(&okm[..OUTPUT_SIZE]),
|
||||
*GenericArray::from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
|
||||
*GenericArray::from_slice(&okm[2 * OUTPUT_SIZE..]),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
rng: &mut R,
|
||||
l1_bytes: Vec<u8>,
|
||||
l2_bytes: Vec<u8>,
|
||||
client_e_pk: KeyFormat::Repr,
|
||||
client_s_pk: KeyFormat::Repr,
|
||||
server_s_sk: KeyFormat::Repr,
|
||||
client_nonce: Vec<u8>,
|
||||
) -> Result<(KE2State, KE2Message), ProtocolError> {
|
||||
let server_e_kp = KeyFormat::generate_random(rng)?;
|
||||
let mut server_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
|
||||
TripleDHComponents {
|
||||
pk1: client_e_pk.clone(),
|
||||
sk1: server_e_kp.private().clone(),
|
||||
pk2: client_e_pk,
|
||||
sk2: server_s_sk.clone(),
|
||||
pk3: client_s_pk.clone(),
|
||||
sk3: server_e_kp.private().clone(),
|
||||
},
|
||||
&client_nonce,
|
||||
&server_nonce,
|
||||
client_s_pk,
|
||||
KeyFormat::public_from_private(&server_s_sk),
|
||||
)?;
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&l1_bytes);
|
||||
let hashed_l1 = hasher.result();
|
||||
|
||||
let transcript2: Vec<u8> = [
|
||||
&hashed_l1[..],
|
||||
&l2_bytes[..],
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public().to_arr(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.input(&transcript2);
|
||||
let hashed_transcript = hasher2.result();
|
||||
|
||||
let mut mac = Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.input(&hashed_transcript);
|
||||
|
||||
Ok((
|
||||
KE2State {
|
||||
km3: km3.to_vec(),
|
||||
hashed_transcript: hashed_transcript.to_vec(),
|
||||
shared_secret: shared_secret.to_vec(),
|
||||
},
|
||||
KE2Message {
|
||||
server_nonce: server_nonce.to_vec(),
|
||||
server_e_pk: server_e_kp.public().clone(),
|
||||
mac: mac.result().code().to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) struct KE3State {
|
||||
pub(crate) shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct KE3Message {
|
||||
mac: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE3State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_state")?;
|
||||
|
||||
Ok(Self {
|
||||
shared_secret: checked_bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl KE3Message {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.mac.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for KE3Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
|
||||
|
||||
Ok(Self {
|
||||
mac: checked_bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
|
||||
l2_component: Vec<u8>,
|
||||
ke2_message: KE2Message,
|
||||
ke1_state: &KE1State,
|
||||
server_s_pk: KeyFormat::Repr,
|
||||
client_s_sk: KeyFormat::Repr,
|
||||
) -> Result<(KE3State, KE3Message), ProtocolError> {
|
||||
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
|
||||
TripleDHComponents {
|
||||
pk1: ke2_message.server_e_pk.clone(),
|
||||
sk1: ke1_state.client_e_sk.clone(),
|
||||
pk2: server_s_pk.clone(),
|
||||
sk2: ke1_state.client_e_sk.clone(),
|
||||
pk3: ke2_message.server_e_pk.clone(),
|
||||
sk3: client_s_sk.clone(),
|
||||
},
|
||||
&ke1_state.client_nonce,
|
||||
&ke2_message.server_nonce,
|
||||
KeyFormat::public_from_private(&client_s_sk),
|
||||
server_s_pk,
|
||||
)?;
|
||||
|
||||
let transcript: Vec<u8> = [
|
||||
&ke1_state.hashed_l1[..],
|
||||
&l2_component[..],
|
||||
&ke2_message.server_nonce[..],
|
||||
&ke2_message.server_e_pk[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.input(&transcript);
|
||||
let hashed_transcript = hasher.result();
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
server_mac.input(&hashed_transcript);
|
||||
|
||||
if ke2_message.mac != server_mac.result().code().to_vec() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<Sha256>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.input(&hashed_transcript);
|
||||
|
||||
Ok((
|
||||
KE3State {
|
||||
shared_secret: shared_secret.to_vec(),
|
||||
},
|
||||
KE3Message {
|
||||
mac: client_mac.result().code().to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
// Outputs a shared secret
|
||||
pub(crate) fn finish_ke(
|
||||
ke3_message: KE3Message,
|
||||
ke2_state: &KE2State,
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let mut client_mac =
|
||||
Hmac::<Sha256>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.input(&ke2_state.hashed_transcript);
|
||||
|
||||
if ke3_message.mac != client_mac.result().code().to_vec() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ke2_state.shared_secret.to_vec())
|
||||
}
|
||||
@@ -0,0 +1,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;
|
||||
@@ -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::{Key, KeyPair},
|
||||
};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub trait KeyExchange<D: Hash> {
|
||||
type KE1State: TryFrom<Vec<u8>, Error = InternalPakeError> + ToBytes;
|
||||
type KE2State: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
|
||||
type KE1Message: TryFrom<Vec<u8>, Error = InternalPakeError> + ToBytes;
|
||||
type KE2Message: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
|
||||
type KE3Message: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
l1_component: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
|
||||
|
||||
fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
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<KeyFormat: KeyPair<Repr = Key>>(
|
||||
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>;
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
//! 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::{Key, KeyPair, SizedBytes},
|
||||
sized_bytes_using_constant_and_try_from,
|
||||
};
|
||||
use digest::Digest;
|
||||
use generic_array::{
|
||||
typenum::{U64, U96},
|
||||
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;
|
||||
const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
|
||||
const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
|
||||
|
||||
static STR_3DH: &[u8] = b"3DH keys";
|
||||
|
||||
/// The Triple Diffie-Hellman key exchange implementation
|
||||
pub struct TripleDH;
|
||||
|
||||
impl<D: Hash> KeyExchange<D> for TripleDH {
|
||||
type KE1State = KE1State;
|
||||
type KE2State = KE2State;
|
||||
type KE1Message = KE1Message;
|
||||
type KE2Message = KE2Message;
|
||||
type KE3Message = KE3Message;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
l1_component: Vec<u8>,
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::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 = D::new();
|
||||
hasher.update(&l1_data);
|
||||
let hashed_l1 = hasher.finalize();
|
||||
|
||||
Ok((
|
||||
KE1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce: client_nonce.to_vec(),
|
||||
hashed_l1: hashed_l1.to_vec(),
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||
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 mut server_nonce = [0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
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: 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.finalize().into_bytes().to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
|
||||
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[..],
|
||||
]
|
||||
.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().to_vec() {
|
||||
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().to_vec(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
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().to_vec() {
|
||||
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 {
|
||||
KE2_MESSAGE_LEN
|
||||
}
|
||||
}
|
||||
|
||||
/// The client state produced after the first key exchange message
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct KE1State {
|
||||
client_e_sk: Key,
|
||||
client_nonce: Vec<u8>,
|
||||
hashed_l1: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The first key exchange message
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct KE1Message {
|
||||
pub(crate) client_nonce: Vec<u8>,
|
||||
pub(crate) client_e_pk: Key,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE1State {
|
||||
type Error = InternalPakeError;
|
||||
|
||||
fn try_from(bytes: Vec<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 ToBytes for KE1State {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.client_e_sk.to_arr(),
|
||||
&self.client_nonce[..],
|
||||
&self.hashed_l1[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
sized_bytes_using_constant_and_try_from!(KE1State, U96);
|
||||
|
||||
impl ToBytes for KE1Message {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE1Message {
|
||||
type Error = InternalPakeError;
|
||||
|
||||
fn try_from(ke1_message_bytes: Vec<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..])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sized_bytes_using_constant_and_try_from!(KE1Message, U64);
|
||||
|
||||
/// The server state produced after the second key exchange message
|
||||
pub struct KE2State {
|
||||
km3: Vec<u8>,
|
||||
hashed_transcript: Vec<u8>,
|
||||
shared_secret: Vec<u8>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
pub struct KE2Message {
|
||||
server_nonce: Vec<u8>,
|
||||
server_e_pk: Key,
|
||||
mac: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ToBytes for KE2State {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.shared_secret[..],
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE2State {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke1_message_bytes: Vec<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 ToBytes for KE2Message {
|
||||
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<Vec<u8>> for KE2Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(ke2_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&ke2_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<D> = (
|
||||
GenericArray<u8, <D as Hash>::OutputSize>,
|
||||
GenericArray<u8, <D as Hash>::OutputSize>,
|
||||
GenericArray<u8, <D as Hash>::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>, D: Hash>(
|
||||
dh: TripleDHComponents,
|
||||
client_nonce: &[u8],
|
||||
server_nonce: &[u8],
|
||||
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 {
|
||||
mac: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ToBytes for KE3Message {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.mac.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KE3Message {
|
||||
type Error = ProtocolError;
|
||||
|
||||
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
let checked_bytes = check_slice_size(&bytes, KEY_LEN, "ke3_message")?;
|
||||
|
||||
Ok(Self {
|
||||
mac: checked_bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
+105
-83
@@ -11,7 +11,12 @@ 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::fmt::Debug;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
@@ -65,6 +70,81 @@ 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:
|
||||
/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
|
||||
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
|
||||
/// and it both of the above to produce a sensible SizedBytes implementation
|
||||
///
|
||||
/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
|
||||
/// not, it's better to use the macro below rather than this one, where possible.
|
||||
#[macro_export]
|
||||
macro_rules! sized_bytes_using_constant_and_try_from {
|
||||
($sized_type: ident, $len: ident) => {
|
||||
impl SizedBytes for $sized_type {
|
||||
type Len = $len;
|
||||
|
||||
fn to_arr(&self) -> generic_array::GenericArray<u8, Self::Len> {
|
||||
generic_array::GenericArray::clone_from_slice(&self.to_bytes())
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes = check_slice_size(
|
||||
bytes,
|
||||
<Self::Len as generic_array::typenum::Unsigned>::to_usize(),
|
||||
"bytes",
|
||||
)?;
|
||||
std::convert::TryFrom::try_from(checked_bytes.to_vec())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// 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 +175,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 +187,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 +201,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,
|
||||
@@ -199,85 +273,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
@@ -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;
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 generic_array::GenericArray;
|
||||
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(GenericArray::from_slice(&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(GenericArray::from_slice(&hashed_input))
|
||||
}
|
||||
}
|
||||
+378
-299
File diff suppressed because it is too large
Load Diff
+59
-31
@@ -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);
|
||||
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates.
|
||||
//
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::errors::{utils::check_slice_size, InternalPakeError, PakeError};
|
||||
use aead::{Aead, NewAead};
|
||||
use generic_array::{typenum::Unsigned, GenericArray};
|
||||
use hmac::{Hmac, Mac};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
/// This trait encapsulates an encryption scheme that satisfies random-key robustness (RKR), which is implemented
|
||||
/// through encrypt-then-HMAC -- see Section 3.1.1 of
|
||||
/// https://www.ietf.org/id/draft-krawczyk-cfrg-opaque-03.txt
|
||||
/// We require an Aead implementation with a 32-bit key size, since we
|
||||
/// will derive the symmetric key from pw using Sha256
|
||||
pub trait RKRCipher: Sized {
|
||||
/// The requirement of KeySize = U32 is so that we can use a 32-bit hash
|
||||
/// for key derivation form the user's password
|
||||
type AEAD: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead;
|
||||
|
||||
// Required members
|
||||
fn new(
|
||||
aead_output: Vec<u8>,
|
||||
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
|
||||
) -> Self;
|
||||
|
||||
fn aead_output(&self) -> &Vec<u8>;
|
||||
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize>;
|
||||
fn nonce(&self) -> &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>;
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
|
||||
// Provided members for enc / dec
|
||||
fn key_len() -> usize {
|
||||
<Self::AEAD as NewAead>::KeySize::to_usize()
|
||||
}
|
||||
|
||||
fn nonce_size() -> usize {
|
||||
<Self::AEAD as Aead>::NonceSize::to_usize()
|
||||
}
|
||||
|
||||
fn hmac_size() -> usize {
|
||||
<Sha256 as Digest>::OutputSize::to_usize()
|
||||
}
|
||||
|
||||
/// This estimates the size of the ciphertext once we encode —very specifically—
|
||||
/// the payload we have planned for the protocol's env_u
|
||||
fn ciphertest_size() -> usize {
|
||||
Self::key_len() + <Self::AEAD as Aead>::TagSize::to_usize() + Self::hmac_size()
|
||||
}
|
||||
|
||||
fn rkr_with_nonce_size() -> usize {
|
||||
Self::ciphertest_size() + Self::nonce_size()
|
||||
}
|
||||
|
||||
/// The format of the output ciphertext here is:
|
||||
/// encryption_output | tag | hmac | nonce
|
||||
/// variable length | AEAD_TAG_SIZE bytes | HMAC_SIZE bytes | NONCE_SIZE bytes
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let checked_bytes = check_slice_size(&bytes[..], Self::rkr_with_nonce_size(), "bytes")?;
|
||||
let nonce_start = bytes.len() - Self::nonce_size();
|
||||
let hmac_start = nonce_start - Self::hmac_size();
|
||||
|
||||
Ok(<Self as RKRCipher>::new(
|
||||
bytes[..hmac_start].to_vec(),
|
||||
GenericArray::from_slice(&checked_bytes[hmac_start..nonce_start]),
|
||||
GenericArray::from_slice(&checked_bytes[nonce_start..]),
|
||||
))
|
||||
}
|
||||
|
||||
/// Encrypt with AEAD. Note that this encryption scheme needs to satisfy "random-key robustness" (RKR).
|
||||
fn encrypt<R: RngCore + CryptoRng>(
|
||||
encryption_key: &[u8],
|
||||
hmac_key: &[u8],
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
rng: &mut R,
|
||||
) -> Result<Self, PakeError> {
|
||||
let mut nonce = vec![0u8; Self::nonce_size()];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
let gen_nonce = GenericArray::from_slice(&nonce[..]);
|
||||
|
||||
let ciphertext = <Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key))
|
||||
.encrypt(
|
||||
GenericArray::from_slice(&nonce),
|
||||
aead::Payload {
|
||||
msg: &plaintext,
|
||||
aad: &aad,
|
||||
},
|
||||
)
|
||||
.map_err(|_| PakeError::EncryptionError)?;
|
||||
|
||||
let mut mac =
|
||||
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.input(&ciphertext);
|
||||
|
||||
Ok(<Self as RKRCipher>::new(
|
||||
ciphertext,
|
||||
&mac.result().code(),
|
||||
gen_nonce,
|
||||
))
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
encryption_key: &[u8],
|
||||
hmac_key: &[u8],
|
||||
aad: &[u8],
|
||||
) -> Result<Vec<u8>, PakeError> {
|
||||
let mut mac =
|
||||
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
|
||||
mac.input(self.aead_output());
|
||||
if mac.verify(self.hmac()).is_err() {
|
||||
return Err(PakeError::DecryptionHmacError);
|
||||
}
|
||||
|
||||
Aead::decrypt(
|
||||
&<Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key)),
|
||||
self.nonce(),
|
||||
aead::Payload {
|
||||
msg: self.aead_output(),
|
||||
aad: &aad,
|
||||
},
|
||||
)
|
||||
.map_err(|_| PakeError::DecryptionError)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is a straightforward instantiation of the trait separating the
|
||||
/// three components in Vecs
|
||||
pub struct RKRCiphertext<T> {
|
||||
aead_choice: std::marker::PhantomData<T>,
|
||||
aead_output: Vec<u8>,
|
||||
hmac: Vec<u8>,
|
||||
nonce: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<T: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead> RKRCipher for RKRCiphertext<T> {
|
||||
type AEAD = T;
|
||||
|
||||
fn new(
|
||||
aead_output: Vec<u8>,
|
||||
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
|
||||
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
|
||||
) -> Self {
|
||||
Self {
|
||||
aead_choice: std::marker::PhantomData,
|
||||
aead_output,
|
||||
hmac: hmac.to_vec(),
|
||||
nonce: nonce.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn aead_output(&self) -> &Vec<u8> {
|
||||
&self.aead_output
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[&self.aead_output[..], &self.hmac[..], &self.nonce[..]].concat()
|
||||
}
|
||||
|
||||
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize> {
|
||||
GenericArray::from_slice(&self.hmac[..])
|
||||
}
|
||||
|
||||
fn nonce(&self) -> &GenericArray<u8, <T as Aead>::NonceSize> {
|
||||
GenericArray::from_slice(&self.nonce[..])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chacha20poly1305::ChaCha20Poly1305;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[test]
|
||||
fn encrypt_and_decrypt() {
|
||||
let mut rng = OsRng;
|
||||
let mut encryption_key = [0u8; 32];
|
||||
rng.fill_bytes(&mut encryption_key);
|
||||
let mut hmac_key = [0u8; 32];
|
||||
rng.fill_bytes(&mut hmac_key);
|
||||
|
||||
let mut msg = [0u8; 100];
|
||||
rng.fill_bytes(&mut msg);
|
||||
|
||||
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
|
||||
&encryption_key,
|
||||
&hmac_key,
|
||||
&msg,
|
||||
b"",
|
||||
&mut rng,
|
||||
)
|
||||
.unwrap();
|
||||
let decrypted = ciphertext.decrypt(&encryption_key, &hmac_key, b"").unwrap();
|
||||
assert_eq!(&msg.to_vec(), &decrypted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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, &[], ¶ms, &mut output)
|
||||
.map_err(|_| InternalPakeError::SlowHashError)?;
|
||||
Ok(output.to_vec())
|
||||
}
|
||||
}
|
||||
+114
-112
@@ -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, 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,27 @@ 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 {
|
||||
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,23 +242,21 @@ 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();
|
||||
@@ -256,8 +264,8 @@ fn generate_parameters() -> TestVectorParameters {
|
||||
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();
|
||||
|
||||
@@ -270,17 +278,13 @@ fn generate_parameters() -> TestVectorParameters {
|
||||
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 (l2, server_login) = ServerLogin::<CS>::start(
|
||||
password_file,
|
||||
server_s_kp.private(),
|
||||
l1,
|
||||
@@ -291,7 +295,7 @@ fn generate_parameters() -> TestVectorParameters {
|
||||
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 (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();
|
||||
@@ -325,13 +329,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(¶meters));
|
||||
}
|
||||
|
||||
@@ -339,7 +343,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(
|
||||
¶meters.password,
|
||||
Some(¶meters.pepper),
|
||||
&mut blinding_factor_rng,
|
||||
@@ -357,12 +361,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(¶meters.r1[..]).unwrap(),
|
||||
&mut oprf_key_rng,
|
||||
)
|
||||
.unwrap();
|
||||
let (r2, server_registration) = ServerRegistration::<X255193dhNoSlowHash>::start(
|
||||
RegisterFirstMessage::try_from(¶meters.r1[..]).unwrap(),
|
||||
&mut oprf_key_rng,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_registration_state),
|
||||
@@ -378,21 +381,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(
|
||||
¶meters.client_registration_state[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish::<CycleRng, SignalKeyPair>(
|
||||
.finish(
|
||||
RegisterSecondMessage::try_from(¶meters.r2[..]).unwrap(),
|
||||
&Key::try_from(parameters.server_s_pk).unwrap(),
|
||||
&Key::try_from(¶meters.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 +405,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(
|
||||
¶meters.server_registration_state[..],
|
||||
)
|
||||
.unwrap();
|
||||
let server_registration = ServerRegistration::<X255193dhNoSlowHash>::try_from(
|
||||
¶meters.server_registration_state[..],
|
||||
)
|
||||
.unwrap();
|
||||
let password_file = server_registration
|
||||
.finish(RegisterThirdMessage::try_from(¶meters.r3[..]).unwrap())
|
||||
.unwrap();
|
||||
@@ -429,7 +431,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(
|
||||
¶meters.password,
|
||||
Some(¶meters.pepper),
|
||||
&mut client_login_start_rng,
|
||||
@@ -448,10 +450,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(¶meters.password_file[..]).unwrap(),
|
||||
&Key::try_from(parameters.server_s_sk).unwrap(),
|
||||
LoginFirstMessage::<EdwardsPoint>::try_from(¶meters.l1[..]).unwrap(),
|
||||
&Key::try_from(¶meters.server_s_sk[..]).unwrap(),
|
||||
LoginFirstMessage::<X255193dhNoSlowHash>::try_from(¶meters.l1[..]).unwrap(),
|
||||
&mut server_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -469,17 +471,19 @@ fn test_l3() -> Result<(), PakeError> {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
|
||||
|
||||
let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
|
||||
let (l3, shared_secret, opaque_key_login) =
|
||||
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
|
||||
¶meters.client_login_state[..],
|
||||
let (l3, shared_secret, export_key_login) = ClientLogin::<X255193dhNoSlowHash>::try_from(
|
||||
¶meters.client_login_state[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish(
|
||||
LoginSecondMessage::<EdwardsPoint, X25519KeyPair, TripleDH, sha2::Sha256>::try_from(
|
||||
¶meters.l2[..],
|
||||
)
|
||||
.unwrap()
|
||||
.finish(
|
||||
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(¶meters.l2[..]).unwrap(),
|
||||
&Key::try_from(parameters.server_s_pk)?,
|
||||
&mut client_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap(),
|
||||
&Key::try_from(¶meters.server_s_pk[..])?,
|
||||
&mut client_e_sk_rng,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.shared_secret),
|
||||
@@ -487,8 +491,8 @@ fn test_l3() -> Result<(), PakeError> {
|
||||
);
|
||||
assert_eq!(hex::encode(¶meters.l3), hex::encode(l3.to_bytes()));
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.opaque_key),
|
||||
hex::encode(opaque_key_login)
|
||||
hex::encode(¶meters.export_key),
|
||||
hex::encode(export_key_login)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -498,10 +502,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(¶meters.server_login_state[..])
|
||||
.unwrap()
|
||||
.finish(LoginThirdMessage::try_from(¶meters.l3[..])?)
|
||||
.unwrap();
|
||||
let shared_secret =
|
||||
ServerLogin::<X255193dhNoSlowHash>::try_from(¶meters.server_login_state[..])
|
||||
.unwrap()
|
||||
.finish(LoginThirdMessage::try_from(¶meters.l3[..])?)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.shared_secret),
|
||||
@@ -517,34 +522,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,8 +554,8 @@ 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 {
|
||||
|
||||
+148
-44
@@ -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,118 @@ 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>>::KE1Message::try_from(ke1m[..].to_vec()).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::<RistrettoPoint, crate::keypair::X25519KeyPair, TripleDH, sha2::Sha512>::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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user