166 Commits
Author SHA1 Message Date
Kevin LewiandKevin Lewi 0c535c0989 Publishing v0.5.1 2021-07-16 00:54:26 -07:00
Kevin LewiandKevin Lewi 9c28c8597a Adding reflected value check on client side
(cherry picked from commit b4882f8810)
2021-07-15 00:46:10 -07:00
Kevin LewiandKevin Lewi e5619d48cd Switch CI to using stable instead of nightly toolchain
(cherry picked from commit 6062bdb5cd)
2021-07-14 22:04:08 -07:00
Kevin LewiandKevin Lewi a69ad9473a Adding identity element checks and ensuring non-zero scalar selection
(cherry picked from commit 98f1821897)
2021-07-14 21:42:32 -07:00
Kevin Lewi f15b37fda4 Adding i2osp error checking condition
(cherry picked from commit e6b5a5dcf6)
2021-07-11 18:23:25 -07:00
Kevin Lewi 27f6975136 Fixing minor nits: conversion to u16 and removing keypair constructor
(cherry picked from commit c8c57785af)
2021-07-07 13:39:03 -07:00
Kevin Lewi 3c4b7ce482 Ensuring mac operations are constant-time
(cherry picked from commit 940d1dcdb2)
2021-07-07 13:35:03 -07:00
Kevin Lewi 05427dd97d Add zeroize on drop for remaining intermediate API states and tests
(cherry picked from commit 8bc5e7dc02)
2021-07-07 13:34:56 -07:00
TonyandKevin Lewi ec8f87944b Zeroize keys on drop (#156)
(cherry picked from commit 468e0690d7)
2021-07-07 13:33:08 -07:00
Kevin LewiandGitHub ba8e940e08 Updating to 0.5 with removing generic_bytes_derive (#150) 2021-03-01 19:23:09 -08:00
Kevin LewiandGitHub ac08801f18 Updating to version 0.4.0 and simplifying transcript computation logic in TripleDH (#148) 2021-02-26 15:18:10 -08:00
Konstantinos ChalkiasandGitHub a49e089e54 Merge pull request #146 from novifinancial/kostas-duplicate-code-v2
Helper generate_nonce to avoid duplicated code + default values for Scrypt
2021-02-21 22:33:12 -08:00
Kostas Chalkias 37f98b3742 Helper function generate_nonce to avoid duplicates + default values for Scrypt 2021-02-21 20:29:26 -08:00
Kevin LewiandGitHub fa0fb48654 Fixing test vector bug with envelope ciphertext serialization and updating VOPRF (#145) 2021-02-21 18:44:36 -08:00
Kevin LewiandGitHub 4da6bbce0c Fixing key sizes and removing serialization (#144) 2021-02-20 03:51:04 -08:00
Kevin LewiandGitHub a457d82793 Updating transcript computation for 3DH (#143) 2021-02-19 12:02:44 -08:00
Kevin LewiandGitHub 782eeb7090 Converting to_bytes and try_from to serialize() and deserialize() (#138) 2021-02-17 02:47:00 -08:00
Kevin LewiandGitHub bb7d17a102 Fixing deny.toml (#142) 2021-02-17 02:28:49 -08:00
Kevin LewiandGitHub 7f84984f61 Making message type parameters set to CipherSuite (#140) 2021-02-17 02:13:03 -08:00
Kevin LewiandGitHub e4636e0a97 Releasing v0.3.1 (#141) 2021-02-11 18:20:11 -08:00
Kevin LewiandGitHub e694a88ef9 Exporting rand (#137) 2021-02-11 18:10:48 -08:00
Kevin LewiandGitHub f596e4bf0e Fixing clippy issues (#139) 2021-02-11 03:41:01 -08:00
Kevin LewiandGitHub 1dbfed8763 Updating 0.3.0 crate version (#134) 2021-02-08 13:00:20 -08:00
Kevin LewiandGitHub d3204203ca Publishing v0.3.0 (#132) 2021-02-08 12:52:46 -08:00
Kevin LewiandGitHub 160ac47ffa Renaming shared secret to session key (#133) 2021-02-08 11:19:06 -08:00
Kevin LewiandGitHub 6307ea9eed Updating rand_core to 0.6 (without curve25519-dalek library change) (#131) 2021-02-05 13:17:35 -08:00
Kevin LewiandGitHub e35410ab95 Fixing envelope mode inconsistency and matching against latest test vectors (#130) 2021-02-05 13:17:02 -08:00
Konstantinos ChalkiasandGitHub 1f608e464c Merge pull request #129 from novifinancial/kostas-hasher-reuse
Reuse hashers by `hasher.finalize_reset()`
2021-02-04 19:36:21 -08:00
Kostas Chalkias e32b240ce2 Reuse hashers by hasher.finalize_reset which is slightly faster than constructing the hasher object each time. 2021-02-04 17:00:17 -08:00
Kevin LewiandGitHub 099f887530 Applying envelope simplified construction and km3 mac changes (#128) 2021-02-04 15:57:06 -08:00
Kevin LewiandGitHub 9d3963f98e Addressing various TODOs and minor cleanups (#127) 2021-02-02 12:12:20 -08:00
Kevin LewiandGitHub be6d042d80 Restricting ciphersuite group to match hash output (#125) 2021-01-28 14:31:20 -08:00
Kevin LewiandGitHub d6ace87c21 Fixing examples to use Sha512 with Ristretto instead of Sha256 (#122) 2021-01-26 13:17:15 -08:00
Kevin LewiandGitHub 6eeeb0db28 Fixing test vectors and use of SHA512 (#120) 2021-01-26 09:27:30 -08:00
Kevin LewiandGitHub 6dc1c8b212 Turning KeyPair into a struct (#119) 2021-01-25 13:20:36 -08:00
Kevin LewiandGitHub e421aaad07 Fixing expect script for digital locker example (#118) 2021-01-21 22:09:34 -08:00
Kevin LewiandGitHub 511d371c9a Adding digital locker example (#117) 2021-01-21 20:18:57 -08:00
Kevin LewiandGitHub f73fc55254 Adding a from_private_key_slice on KeyPair (#116) 2021-01-15 15:36:37 -08:00
Kevin LewiandGitHub 2a2d0888a5 Adding encryption algorithm and removing unnecesssary info fields (#114) 2021-01-14 15:30:37 -08:00
Kevin LewiandGitHub ba53089199 Fixing construct_aad() bug (#112) 2021-01-07 17:44:27 -08:00
Kevin LewiandGitHub fa6752cdc5 Adding documentation for advanced usage + optional parameters (#110) 2021-01-05 10:13:00 -08:00
Kevin LewiandGitHub 9f6b32a5ea Updating envelope structure to support two fixed modes (#108)
* Moving id_u and id_s from ClientLoginStartParameters to ClientLoginFinishParameters

* Updating envelope format to support two fixed modes
2021-01-04 14:27:20 -08:00
Kevin LewiandGitHub f5b5391ee0 Moving id_u and id_s from the client API to registration finish instead of start (#102) 2020-12-17 14:00:32 -08:00
Kevin LewiandGitHub 605d2b639b Cleaning up docs + README (#98) 2020-12-12 21:53:33 -08:00
François GarillotandGitHub 9e3743a604 Merge pull request #99 from huitseeker/maintenance
Maintenance PR: panic-removal & upgrades
2020-12-10 11:17:54 -05:00
François Garillot 6b22064863 Fold a few panics
Removes a few panics we don't need by folding them in the Error case of their enclosing Result return.
2020-12-09 13:10:05 -08:00
François Garillot 94630dff7c Update dependencies 2020-12-09 12:44:24 -08:00
François GarillotandGitHub 2794dfaaa9 Fix #94 + Add slow-hash to CI (#96)
* Fixes #94

* add slow-hash testing to CI
2020-12-04 18:32:23 -08:00
9f414d4a82 Adding VOPRF draft's test vectors (#95)
Co-authored-by: Kevin Lewi <[email protected]>
2020-12-04 13:19:29 -08:00
Kevin LewiandGitHub 6f5a1fc6aa Removing protocol message bytes (#93) 2020-11-30 11:37:32 -08:00
4c82dfaf13 Adding info and einfo fields to test vectors (#92)
Co-authored-by: Kevin Lewi <[email protected]>
2020-11-29 23:06:55 -08:00
François GarillotandGitHub 7fcde1c50d Merge pull request #90 from huitseeker/copyright-header
add copyright stanza to expect script
2020-11-17 19:47:42 -05:00
François Garillot f8c976d659 add copyright stanza to expect script 2020-11-17 14:58:59 -05:00
Kevin LewiandGitHub 95152c3b35 Adding API for handling info fields (#88) 2020-11-16 14:05:43 -08:00
Kevin LewiandGitHub 51b3781e5c Updating AKE message format (#85) 2020-11-16 11:49:27 -08:00
f18eaea8cf Test examples (#87)
* Fixing simple_login example

* Adds script that tests the simple_login example

* Add GH action to run said script

Co-authored-by: Kevin Lewi <[email protected]>
2020-11-16 11:41:06 -08:00
Kevin LewiandGitHub 2d7b4292ff Fixing simple_login example (#86) 2020-11-16 11:40:52 -08:00
Kevin LewiandGitHub f165310c96 Adding expand_message_xmd and RFC compliance with VOPRF and H2C (#80) 2020-11-12 10:00:09 -08:00
François GarillotandGitHub 09f7cfdf78 Merge pull request #81 from huitseeker/cross-compilation
Add cross-compilation on 32-bits + fix the associated bugs
2020-11-12 12:03:21 -05:00
François Garillot 05514e49aa Correct platform size bug 2020-11-12 10:42:45 -05:00
François Garillot eb1a93c379 Add cross-compilation on 32-bits 2020-11-09 10:22:54 -05:00
Kevin LewiandGitHub 697cd7a788 Removing idU from RegistrationRequest and CredentialRequest (#79)
As per [cfrg/draft-irtf-cfrg-opaque#65](https://github.com/cfrg/draft-irtf-cfrg-opaque/issues/65)
2020-11-06 10:06:49 -08:00
François GarillotandGitHub d8906c092a Merge pull request #77 from huitseeker/sizedbytes
Start integrating the derivable SizedBytes
2020-11-04 20:34:51 -05:00
François GarillotandGitHub 3252ca96de Merge pull request #78 from huitseeker/secure_postprocessing
Uses conditional compilation to make sure postprocessing is only avai…
2020-11-04 20:17:02 -05:00
François Garillot 552a546b6c Uses conditional compilation to make sure postprocessing is only available in tests
See
https://github.com/rust-lang/rust/pull/64010/
2020-11-04 11:55:14 -05:00
François Garillot 230b1bcef6 Start integrating the derivable SizedBytes
- just derive it on Key and KeyPair for now
2020-11-03 16:45:37 -05:00
François GarillotandGitHub 3954cd23b8 Merge pull request #76 from huitseeker/version-bump
Bump Crate version => 0.3.0-pre.1
2020-11-03 16:35:32 -05:00
François Garillot aabd5266c3 Bump Crate version => 0.3.0-pre.1 2020-11-03 13:29:51 -08:00
François GarillotandGitHub bd37deb7a7 Merge pull request #75 from huitseeker/tls
Rebase TLS on master
2020-11-03 13:45:03 -05:00
François Garillot 6deddbe33c adjust to_bytes -> serialize & example 2020-11-02 20:36:26 -05:00
Kevin LewiandFrançois Garillot c8cbf56336 Conform to voprf spec (#71) 2020-11-02 20:15:08 -05:00
Kevin LewiandFrançois Garillot 28645a7cea Add key schedule / info changes in TripleDH computation (#69) 2020-11-02 20:15:07 -05:00
Kevin LewiandFrançois Garillot 07f8048a3d Updating to keep in sync with issue 62 fix in spec (#61) 2020-11-02 20:15:06 -05:00
Kevin LewiandFrançois Garillot 0fc3448777 Updating opaque interfaces to include ids from the internet draft (#56) 2020-11-02 20:15:06 -05:00
François Garillot 7cc1c0992a remove the last few constants 2020-11-02 20:15:05 -05:00
François Garillot 9c06c98ad6 Introduce enum structs to replicate TLS message format
This PR favors native coercions of enums to numerical types to help ser/de operations.
Small rearrangement of the serialization module.
2020-11-02 20:15:02 -05:00
François Garillot 344e8ad8d1 Updating serialization for RegisterFirstMessage (#35) 2020-11-02 20:14:47 -05:00
Kevin LewiandGitHub c7f6abb591 Adding simple login example and fixing some docs (#67) 2020-10-26 11:52:15 -07:00
François GarillotandGitHub 85b0ef031d Merge pull request #65 from huitseeker/auto-publish
Add publishing job
2020-10-22 15:01:02 -04:00
Kevin LewiandGitHub 8ccb5ad510 Publishing v0.2.1 (#66) 2020-10-22 10:23:25 -07:00
François Garillot 18bcd23102 Add publishing job
This published commits to master with a tag. Relies on a GHA token named crates_io.
2020-10-22 11:00:02 -04:00
François GarillotandGitHub 9b5f7f455d Merge pull request #64 from huitseeker/hash-public
Make Hash module public
2020-10-22 09:41:09 -04:00
François Garillot 25ae7ff9bf Make Hash module public
Fixes #63
2020-10-21 18:24:55 -04:00
François GarillotandGitHub aabdb53581 Merge pull request #54 from huitseeker/fix_vec_in_trait
Fix use of `TryFrom<Vec<u8>>` in key_exchange
2020-09-28 08:56:42 -04:00
François Garillot 587ef935a7 Fix use of TryFrom<Vec<u8>> in key_exchange
We can directly require the HRTB on `for<'r> TryFrom<&'r [u8]>`.
Eliminates needless to_vec.
2020-09-24 14:31:29 +00:00
François GarillotandGitHub 288105861d Merge pull request #53 from huitseeker/ci_fix
Don't duplicate CI runs on PR & push
2020-09-23 21:28:54 -04:00
François Garillot 6b9583e928 Don't duplicate CI runs on PR & push 2020-09-23 17:57:15 -04:00
François GarillotandGitHub 958d0b7a27 Merge pull request #52 from huitseeker/the_last_constraint
Remove the last superfluous Repr = Key
2020-09-23 09:43:06 -04:00
François Garillot 40ea015d6d Remove the last superfluous constant
This required purgin the usage of the low-level Key access, but now we
should be able to replace the Key type piecemeal.
2020-09-22 08:50:39 -04:00
François GarillotandGitHub 39ec88674a Merge pull request #51 from huitseeker/over_simplification
Make KeyExchange generic in KeyFormat
2020-09-21 15:21:33 -04:00
François Garillot c97ddaf5d2 Make KeyExchange generic in KeyFormat 2020-09-21 14:46:04 -04:00
François GarillotandGitHub ae890dbac4 Merge pull request #50 from huitseeker/simplifications
Simplifications and Normalizations
2020-09-21 14:41:16 -04:00
François Garillot 56d38dba2c Reverse use of Tryinto<[u8; 64]> since it's not yet on stable 2020-09-20 10:13:43 -04:00
François Garillot 786bc51fdd Normalize generic parameters for LoginSecondMessage
(aka use the CipherSuite just like the others)

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

* Update CHANGELOG.md

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

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

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

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

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

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

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

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

Add client_login, login_first_message roundtrip serialization tests
2020-06-19 15:23:21 -04:00
François Garillot 738b90188d add build badge 2020-06-19 15:23:04 -04:00
François Garillot 7a2d309263 Moves the keypair generation to an extension trait
This technical change lets us avoid polluting the code with derives of Debug.
2020-06-19 15:23:03 -04:00
François GarillotandGitHub f989330807 Merge pull request #9 from huitseeker/updates
Update digest & hash libraries to their latest versions
2020-06-16 09:27:23 -04:00
François Garillot 4f9b7fe86f Update digest & hashes libraries to their latest versions 2020-06-16 06:24:14 -07:00
Kevin LewiandGitHub aab8ff8e19 Merge pull request #8 from kevinlewi/bundle_api
Adding CipherSuite trait to handle bundling of underlying crypto primitives
2020-06-15 17:42:18 -07:00
Kevin Lewi 2cf6808665 Adding documentation to each component of CipherSuite 2020-06-15 16:40:46 -07:00
Kevin Lewi ab1b1d6209 Adding CipherSuite trait to handle bundling of underlying crypto primitives 2020-06-14 23:29:25 -07:00
Kevin Lewi 0cf13c2266 Adding CipherSuite trait to handle bundling of underlying crypto primitives 2020-06-14 23:25:31 -07:00
Kevin Lewi f030fca1cb index on bundle_api: 57e152b Adding password hashing functionality 2020-06-14 06:10:26 -07:00
François GarillotandGitHub 5cf29fba62 Merge pull request #6 from huitseeker/key_cleanup
Key cleanup: prove X25519KeyPair == SignalKeyPair, remove SignalKeyPair
2020-06-13 09:24:55 -04:00
François Garillot 3ae1c58d46 test DH as well 2020-06-13 09:07:57 -04:00
François Garillot e9fa474c58 Remove SignalKeyPair, rename all instances of SignalKeyPair to X25519KeyPair, pass all tests. 2020-06-13 09:07:55 -04:00
François Garillot e9add9fd7a add tests showing X25519KeyPair == SignalKeyPair 2020-06-13 09:07:53 -04:00
François Garillot b401cfc89c Added proptest strategy to generate a random keypair 2020-06-13 09:07:51 -04:00
François GarillotandGitHub 3a1aaab6cf Merge pull request #7 from huitseeker/Cleanup_CI
Make CI run on PRs as well
2020-06-13 08:49:53 -04:00
Kevin LewiandGitHub 5a374562d1 Merge pull request #2 from kevinlewi/add_pw_hash
Adding password hashing functionality
2020-06-12 18:32:33 -07:00
François Garillot cd46a42634 Have CI run more often 2020-06-12 18:49:32 -04:00
François Garillot ce1d97002c cleaner to_bytes() for a few structures 2020-06-12 18:38:14 -04:00
Kevin Lewi 80fc493a60 Adding slow-hash feature 2020-06-11 14:56:37 -07:00
Kevin LewiandGitHub 8362804946 Merge branch 'master' into add_pw_hash 2020-06-10 14:54:27 -07:00
Kevin LewiandGitHub 05fb76d5b7 Merge pull request #5 from kevinlewi/add_readme_precaution
Adding note on draft status in doc comments
2020-06-10 14:53:06 -07:00
Kevin Lewi af22bfa54f Adding note on draft status in doc comments 2020-06-09 15:13:42 -07:00
Kevin Lewi 57e152b028 Adding password hashing functionality 2020-06-08 21:02:01 -07:00
39 changed files with 8027 additions and 2868 deletions
+173 -15
View File
@@ -1,18 +1,180 @@
name: Rust CI
on: [push]
on:
push:
branches:
- master
pull_request:
types: [opened, repoened, synchronize]
jobs:
combo:
name: test + Clippy + rustfmt
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
toolchain:
- stable
- 1.41.0
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install ${{ matrix.toolchain }} toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ matrix.backend_feature }}
cross-test:
name: Test on ${{ matrix.target }} (using cross)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
# 32-bit x86
- i686-unknown-linux-gnu
backend_feature:
- u64_backend
- u32_backend
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: cargo install cross
# Note: just use `cross` as you would `cargo`, but always
# pass the `--target=${{ matrix.target }}` arg. (Yes, really).
- run: cross test --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.backend_feature }}
slow-hash-test:
name: Test on ${{ matrix.target }} with slow hash
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
steps:
- uses: actions/checkout@v2
- uses: hecrj/setup-rust-action@v1
- run: cargo test --verbose --features slow-hash --no-default-features --features ${{ matrix.backend_feature }}
simple-login-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- stable
- 1.41.0
name: test simple_login command-line example
steps:
- name: install expect
run: sudo apt-get install expect
- name: Checkout sources
uses: actions/checkout@v2
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/simple_login.exp
digital-locker-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain:
- stable
- 1.41.0
name: test digital_locker command-line example
steps:
- name: install expect
run: sudo apt-get install expect
- name: Checkout sources
uses: actions/checkout@v2
- name: install rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Run expect (which then runs cargo run)
run: expect -f scripts/digital_locker.exp
benches:
name: cargo bench compilation
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
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 stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
format:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
@@ -22,13 +184,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
+29
View File
@@ -0,0 +1,29 @@
name: Publish
on:
release:
types: [published] # Only publish to crates.io when we formally publish a release
# For more on how to formally release on Github, read https://help.github.com/en/articles/creating-releases
jobs:
publish:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
- name: Login to crates.io
run: cargo login $CRATES_IO_TOKEN
env:
CRATES_IO_TOKEN: ${{ secrets.crates_io_token }} # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets
- name: Dry run publish opaque-ke
run: cargo publish --dry-run --manifest-path Cargo.toml
- name: Publish opaque-ke
run: cargo publish --manifest-path Cargo.toml
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
+51
View File
@@ -1,5 +1,56 @@
# Changelog
## 0.5.1 (July 16, 2021)
* Various security improvements: non-zero scalars, zeroizing on drop,
constant-time operations, reflected value check, and adding an
i2osp error condition
## 0.5.0 (March 1, 2020)
* Removed dependency on generic-bytes-derive package
## 0.4.0 (February 26, 2020)
* Adherence to protocol format described in
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-03
* Renamed to_bytes() and try_from() to serialize() and deserialize() for
top-level structs
* Conformed all message type parameters to be parameterized in the
Ciphersuite object
## 0.3.1 (February 11, 2020)
* Re-exporting the rand library (and including it as a dependency instead of
just rand_core)
* Exposing a convenience function for converting from byte array to Key type
## 0.3.0 (February 8, 2020)
* General API and documentation improvements, including the support of custom
identifiers, optional result parameters, and the use of the export key
* Compliance with RFC 8017 on data serialization functions (I2OSP / OS2IP)
* Adherence to protocol format described in
https://tools.ietf.org/html/draft-irtf-cfrg-opaque-02
* Added parameters for key exchange additional data
* Added simple_login and digital_locker examples
## 0.2.1 (October 22, 2020)
* Changed visibility of hash module to be public
## 0.2.0 (September 3, 2020)
* Added CipherSuite API for specifying underlying primitives
* Added support for specifying a slow password hashing function
* Collapsed SignalKeyPair to X25519KeyPair
* Updated the envelope implementation to match the suggested XOR-based
construction in https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06
* Included randomized tests for testing try_from crashes
* Implemented Elligator2 map instead of try-and-increment for hash-to-curve
* Added extensibility for supporting different key exchange protocols
* Added benchmarks for the OPRF & switchable dalek backend depending on platform
## 0.1.0 (June 5, 2020)
* Initial release
+1 -1
View File
@@ -26,5 +26,5 @@ disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to Ristretto255.js, you agree that your contributions will be
By contributing to opaque-ke, you agree that your contributions will be
licensed under the LICENSE file in the root directory of this source tree.
Generated
+1041 -236
View File
File diff suppressed because it is too large Load Diff
+34 -15
View File
@@ -1,6 +1,6 @@
[package]
name = "opaque-ke"
version = "0.1.0"
version = "0.5.1"
repository = "https://github.com/novifinancial/opaque-ke"
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
@@ -9,22 +9,41 @@ license = "MIT"
edition = "2018"
readme = "README.md"
[features]
default = ["u64_backend"]
slow-hash = ["scrypt"]
bench = []
u64_backend = ["curve25519-dalek/u64_backend"]
u32_backend = ["curve25519-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"
rand_core = "0.5.1"
sha2 = "0.8"
thiserror = "1"
x25519-dalek = "0.6.0"
zeroize = "1.1"
constant_time_eq = "0.1.5"
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] }
digest = "0.9.0"
displaydoc = "0.1.7"
generic-array = "0.14.4"
generic-bytes = { version = "0.1.0" }
hkdf = "0.10.0"
hmac = "0.10.1"
rand = "0.8"
scrypt = { version = "0.5.0", optional = true }
subtle = { version = "2.3.0", default-features = false }
thiserror = "1.0.22"
zeroize = { version = "1.1.1", features = ["zeroize_derive"] }
[dev-dependencies]
aes-gcm = "0.5.0"
base64 = "0.11.0"
chacha20poly1305 = "0.4.1"
anyhow = "1.0.35"
base64 = "0.13.0"
chacha20poly1305 = "0.7.1"
criterion = "0.3.3"
hex = "0.4.2"
lazy_static = "1.4.0"
serde_json = "1.0"
serde_json = "1.0.60"
sha2 = "0.9.2"
proptest = "0.10.1"
rustyline = "6.3.0"
[[bench]]
name = "oprf"
harness = false
required-features = ["bench"]
+4 -4
View File
@@ -1,4 +1,4 @@
## The OPAQUE key exchange protocol
## The OPAQUE key exchange protocol ![Build Status](https://github.com/novifinancial/opaque-ke/workflows/Rust%20CI/badge.svg)
[OPAQUE](https://eprint.iacr.org/2018/163.pdf) is an asymmetric password-authenticated key exchange protocol. It allows a client to authenticate to a server using a password, without ever having to expose the plaintext password to the server.
@@ -22,14 +22,14 @@ Installation
Add the following line to the dependencies of your `Cargo.toml`:
```
opaque-ke = "0.1.0"
opaque-ke = "0.5.1"
```
Resources
---------
- [OPAQUE academic publication](https://eprint.iacr.org/2018/163.pdf), including formal definitions and a proof of security
- [draft-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05), containing a specification for the OPAQUE protocol
- [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html), containing a detailed (byte-level) specification for OPAQUE
- ["Let's talk about PAKE"](https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/), an introductory blog post written by Matthew Green that covers OPAQUE
Contributors
@@ -41,7 +41,7 @@ To learn more about contributing to this project, [see this document](./CONTRIBU
#### Acknowledgments
Special thanks go to Hugo Krawczyk for helping to clarify discrepancies and making suggestions for improving
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving
this implementation.
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
#[macro_use]
extern crate criterion;
use criterion::Criterion;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::arr;
use opaque_ke::{
group::Group,
oprf::{blind_shim, evaluate_shim, finalize_shim},
};
use rand::{prelude::ThreadRng, thread_rng};
use sha2::Sha512;
fn oprf1(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
c.bench_function("blind with Ristretto", move |b| {
b.iter(|| {
blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
})
});
}
fn oprf2(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let (_, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
c.bench_function("evaluate with Ristretto", move |b| {
b.iter(|| {
let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
})
});
}
fn oprf3(c: &mut Criterion) {
let mut csprng: ThreadRng = thread_rng();
let input = b"hunter2";
let (token, alpha) = blind_shim::<_, RistrettoPoint, Sha512>(&input[..], &mut csprng).unwrap();
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt);
c.bench_function("finalize with Ristretto", move |b| {
b.iter(|| {
let _res = finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
})
});
}
criterion_group!(oprf_benches, oprf1, oprf2, oprf3);
criterion_main!(oprf_benches);
+2 -2
View File
@@ -34,7 +34,7 @@ targets = [
# The path where the advisory database is cloned/fetched into
db-path = "~/.cargo/advisory-db"
# The url of the advisory database to use
db-url = "https://github.com/rustsec/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
# The lint level for security vulnerabilities
vulnerability = "deny"
# The lint level for unmaintained crates
@@ -44,7 +44,7 @@ yanked = "warn"
# The lint level for crates with security notices. Note that as of
# 2019-12-17 there are no security notice advisories in
# https://github.com/rustsec/advisory-db
notice = "warn"
notice = "deny"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
+326
View File
@@ -0,0 +1,326 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Demonstrates an implementation of a server-side secured digital locker using
//! the client's OPAQUE export key, over a command-line interface
//!
//! A client can password-protect a secret message to be stored in a digital locker,
//! controlled by the server. The locker's contents are only revealed to the holder
//! of the password when attempting to open the locker.
//!
//! The client-server interactions are executed in a three-step protocol
//! within the account_registration (for password registration) and
//! account_login (for password login) functions. These steps
//! must be performed in the specific sequence outlined in each of these
//! functions.
//!
//! The CipherSuite trait allows the application to configure the
//! primitives used by OPAQUE, but must be kept consistent across the steps
//! of the protocol.
//!
//! In a more realistic client-server interaction, the client must send
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
use chacha20poly1305::aead::{Aead, NewAead};
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::process::exit;
use opaque_ke::{
ciphersuite::CipherSuite,
rand::{rngs::OsRng, RngCore},
ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration,
};
// The ciphersuite trait allows to specify the underlying primitives
// that will be used in the OPAQUE protocol
#[allow(dead_code)]
struct Default;
impl CipherSuite for Default {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha512;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
struct Locker {
contents: Vec<u8>,
password_file: Vec<u8>,
}
// Given a key and plaintext, produce an AEAD ciphertext along with a nonce
fn encrypt(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
let mut rng = OsRng;
let mut nonce_bytes = [0u8; 12];
rng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, plaintext.as_ref()).unwrap();
[nonce_bytes.to_vec(), ciphertext].concat()
}
// Decrypt using a key and a ciphertext (nonce included) to recover the original plaintext
fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key[..32]));
cipher
.decrypt(
Nonce::from_slice(&ciphertext[..12]),
ciphertext[12..].as_ref(),
)
.unwrap()
}
// Password-based registration and encryption of client secret message between a client and server
fn register_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
password: String,
secret_message: String,
) -> Locker {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
// Server sends registration_response_bytes to client
let client_finish_registration_result = client_registration_start_result
.state
.finish(
&mut client_rng,
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
// Client encrypts secret message using export key
let ciphertext = encrypt(
&client_finish_registration_result.export_key,
secret_message.as_bytes(),
);
// Client sends message_bytes to server
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
.unwrap();
Locker {
contents: ciphertext,
password_file: password_file.serialize(),
}
}
// Open the contents of a locker with a password between a client and server
fn open_locker(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
password: String,
locker: &Locker,
) -> Result<String, String> {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
&mut client_rng,
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize().unwrap();
// Client sends credential_request_bytes to server
let password_file =
ServerRegistration::<Default>::deserialize(&locker.password_file[..]).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
password_file,
&server_kp.private(),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize().unwrap();
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
ClientLoginFinishParameters::default(),
);
if result.is_err() {
// Client-detected login failure
return Err(String::from("Incorrect password, please try again."));
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
// Client sends credential_finalization_bytes to server
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.unwrap();
// Server sends locker contents, encrypted under the session key, to the client
let encrypted_locker_contents =
encrypt(&server_login_finish_result.session_key, &locker.contents);
// Client decrypts contents of locker, first under the session key, and then under the export key
let plaintext = decrypt(
&client_login_finish_result.export_key,
&decrypt(
&client_login_finish_result.session_key,
&encrypted_locker_contents,
),
);
String::from_utf8(plaintext).map_err(|_| String::from("UTF8 error"))
}
fn main() {
let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut rng);
let mut rl = Editor::<()>::new();
let mut registered_lockers: Vec<Locker> = vec![];
loop {
display_lockers(&registered_lockers);
println!("Enter an option (1 or 2):");
println!("1) Register a locker");
println!("2) Open a locker\n");
let readline = rl.readline("> ");
match readline {
Ok(line) => {
if line != "1" && line != "2" {
println!("Error: Invalid option (either specify 1 or 2)");
continue;
}
match line.as_ref() {
"1" => {
let (password, secret_message) = get_two_strings(
"Choose a password",
"Set a secret message",
&mut rl,
None,
);
registered_lockers.push(register_locker(
&server_kp,
password,
secret_message,
));
continue;
}
"2" => {
let (locker, password) = get_two_strings(
"Choose a locker number",
"Enter the password",
&mut rl,
None,
);
let locker_index: usize = match locker.parse() {
Ok(index) => index,
Err(_) => {
println!("Error: Could not find locker number");
continue;
}
};
if locker_index >= registered_lockers.len() {
println!("Error: Could not find locker number");
continue;
}
match open_locker(&server_kp, password, &registered_lockers[locker_index]) {
Ok(contents) => {
println!("\n\nSuccess! Contents: {}\n\n", contents);
}
Err(err) => {
println!(
"\n\nError encountered, could not open locker: {}\n\n",
err
);
}
}
}
_ => exit(0),
}
}
Err(err) => {
handle_error(err);
exit(0)
}
}
}
}
// Helper functions
fn display_lockers(lockers: &Vec<Locker>) {
let mut locker_numbers = vec![];
for (i, _) in lockers.iter().enumerate() {
locker_numbers.push(i);
}
println!(
"\nCurrently registered locker numbers: {:?}\n",
locker_numbers
);
}
// Handle readline errors
fn handle_error(err: ReadlineError) {
match err {
ReadlineError::Interrupted => {
println!("CTRL-C");
}
ReadlineError::Eof => {
println!("CTRL-D");
}
err => {
println!("Error: {:?}", err);
}
}
}
// A function run on the client which extracts two strings from the CLI
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<()>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{}: ", query));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
None => get_two_strings(s1, s2, rl, Some(line)),
},
Err(err) => {
handle_error(err);
exit(0)
}
}
}
+233
View File
@@ -0,0 +1,233 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Demonstrates a simple client-server password-based login protocol
//! using OPAQUE, over a command-line interface
//!
//! The client-server interactions are executed in a three-step protocol
//! within the account_registration (for password registration) and
//! account_login (for password login) functions. These steps
//! must be performed in the specific sequence outlined in each of these
//! functions.
//!
//! The CipherSuite trait allows the application to configure the
//! primitives used by OPAQUE, but must be kept consistent across the steps
//! of the protocol.
//!
//! In a more realistic client-server interaction, the client must send
//! messages over "the wire" to the server. These bytes are serialized
//! and explicitly annotated in the below functions.
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::collections::HashMap;
use std::process::exit;
use opaque_ke::{
ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters,
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload, ServerLogin, ServerLoginStartParameters,
ServerRegistration,
};
// The ciphersuite trait allows to specify the underlying primitives
// that will be used in the OPAQUE protocol
#[allow(dead_code)]
struct Default;
impl CipherSuite for Default {
type Group = curve25519_dalek::ristretto::RistrettoPoint;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
type Hash = sha2::Sha512;
type SlowHash = opaque_ke::slow_hash::NoOpHash;
}
// Password-based registration between a client and server
fn account_registration(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
password: String,
) -> Vec<u8> {
let mut client_rng = OsRng;
let client_registration_start_result =
ClientRegistration::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
let registration_request_bytes = client_registration_start_result.message.serialize();
// Client sends registration_request_bytes to server
let mut server_rng = OsRng;
let server_registration_start_result = ServerRegistration::<Default>::start(
&mut server_rng,
RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
server_kp.public(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result.message.serialize();
// Server sends registration_response_bytes to client
let client_finish_registration_result = client_registration_start_result
.state
.finish(
&mut client_rng,
RegistrationResponse::deserialize(&registration_response_bytes[..]).unwrap(),
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let message_bytes = client_finish_registration_result.message.serialize();
// Client sends message_bytes to server
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
.unwrap();
password_file.serialize()
}
// Password-based login between a client and server
fn account_login(
server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
password: String,
password_file_bytes: &[u8],
) -> bool {
let mut client_rng = OsRng;
let client_login_start_result = ClientLogin::<Default>::start(
&mut client_rng,
password.as_bytes(),
ClientLoginStartParameters::default(),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize().unwrap();
// Client sends credential_request_bytes to server
let password_file = ServerRegistration::<Default>::deserialize(password_file_bytes).unwrap();
let mut server_rng = OsRng;
let server_login_start_result = ServerLogin::start(
&mut server_rng,
password_file,
&server_kp.private(),
CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
ServerLoginStartParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize().unwrap();
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
CredentialResponse::deserialize(&credential_response_bytes[..]).unwrap(),
ClientLoginFinishParameters::default(),
);
if result.is_err() {
// Client-detected login failure
return false;
}
let client_login_finish_result = result.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
// Client sends credential_finalization_bytes to server
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes[..]).unwrap())
.unwrap();
client_login_finish_result.session_key == server_login_finish_result.session_key
}
fn main() {
let mut rng = OsRng;
let server_kp = Default::generate_random_keypair(&mut rng);
let mut rl = Editor::<()>::new();
let mut registered_users = HashMap::<String, Vec<u8>>::new();
loop {
println!(
"\nCurrently registered usernames: {:?}\n",
registered_users.keys()
);
println!("Enter an option (1 or 2):");
println!("1) Register a user");
println!("2) Login as a user\n");
let readline = rl.readline("> ");
match readline {
Ok(line) => {
if line != "1" && line != "2" {
println!("Error: Invalid option (either specify 1 or 2)");
continue;
}
let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
match line.as_ref() {
"1" => {
registered_users
.insert(username, account_registration(&server_kp, password));
continue;
}
"2" => match registered_users.get(&username) {
Some(password_file_bytes) => {
if account_login(&server_kp, password, password_file_bytes) {
println!("\nLogin success!");
} else {
// Note that at this point, the client knows whether or not the login
// succeeded. In this example, we simply rely on client-reported result
// of login, but in a real client-server implementation, the server may not
// know the outcome of login yet, and extra care must be taken to ensure
// that the server can learn the outcome as well.
println!("\nIncorrect password, please try again.");
}
}
None => println!("Error: Could not find username registered"),
},
_ => exit(0),
}
}
Err(err) => {
handle_error(err);
exit(0)
}
}
}
}
// Helper functions
// Handle readline errors
fn handle_error(err: ReadlineError) {
match err {
ReadlineError::Interrupted => {
println!("CTRL-C");
}
ReadlineError::Eof => {
println!("CTRL-D");
}
err => {
println!("Error: {:?}", err);
}
}
}
// A function run on the client which extracts two strings from the CLI
fn get_two_strings(
s1: &str,
s2: &str,
rl: &mut Editor<()>,
string1: Option<String>,
) -> (String, String) {
let query = if string1.is_none() { s1 } else { s2 };
let readline = rl.readline(&format!("{}: ", query));
match readline {
Ok(line) => match string1 {
Some(x) => (x, line),
None => get_two_strings(s1, s2, rl, Some(line)),
},
Err(err) => {
handle_error(err);
exit(0)
}
}
}
+84
View File
@@ -0,0 +1,84 @@
#!/bin/expect -f
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set timeout 1
spawn cargo run --example digital_locker
match_max 100000
sleep 1
expect "*
\r
Currently registered locker numbers: \\\[\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a locker\r
2) Open a locker\r
\r
"
sleep .1
send -- "1\r"
expect "Choose a password: \r"
send -- "foo\r"
expect "Set a secret message: \r"
send -- "bar\r"
expect "*
\r
Currently registered locker numbers: \\\[0\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a locker\r
2) Open a locker\r
\r
"
sleep .1
send -- "2\r"
expect "Choose a locker number: \r"
send -- "1\r"
expect "Password: \r"
send -- "foo\r"
expect "*Error: Could not find locker number\r
\r
Currently registered locker numbers: \\\[0\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a locker\r
2) Open a locker\r
\r
"
sleep .1
send -- "2\r"
expect "Choose a locker number: \r"
send -- "0\r"
expect "Password: \r"
send -- "baz\r"
expect "*Error encountered, could not open locker: Incorrect password, please try again.\r
\r
Currently registered locker numbers: \\\[0\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a locker\r
2) Open a locker\r
\r
"
sleep .1
send -- "2\r"
expect "Choose a locker number: \r"
send -- "0\r"
expect "Password: \r"
send -- "foo\r"
expect "*
\r
*Success! Contents: bar\r
\r
Currently registered locker numbers: \\\[0\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a locker\r
2) Open a locker\r
\r
"
sleep .1
send -- ""
expect eof
+84
View File
@@ -0,0 +1,84 @@
#!/bin/expect -f
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set timeout 1
spawn cargo run --example simple_login
match_max 100000
sleep 1
expect "*
\r
Currently registered usernames: \\\[\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a user\r
2) Login as a user\r
\r
"
sleep .1
send -- "1\r"
expect "Username: \r"
send -- "foo\r"
expect "Password: \r"
send -- "bar\r"
expect "*
\r
Currently registered usernames: \\\[\"foo\"\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a user\r
2) Login as a user\r
\r
"
sleep .1
send -- "2\r"
expect "Username: \r"
send -- "baz\r"
expect "Password: \r"
send -- "quux\r"
expect "*Error: Could not find username registered\r
\r
Currently registered usernames: \\\[\"foo\"\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a user\r
2) Login as a user\r
\r
"
sleep .1
send -- "2\r"
expect "Username: \r"
send -- "foo\r"
expect "Password: \r"
send -- "baz\r"
expect "*Incorrect password, please try again.\r
\r
Currently registered usernames: \\\[\"foo\"\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a user\r
2) Login as a user\r
\r
"
sleep .1
send -- "2\r"
expect "Username: \r"
send -- "foo\r"
expect "Password: \r"
send -- "bar\r"
expect "*
\r
*Login success!\r
\r
Currently registered usernames: \\\[\"foo\"\\\]\r
\r
Enter an option (1 or 2):\r
1) Register a user\r
2) Login as a user\r
\r
"
sleep .1
send -- ""
expect eof
+41
View File
@@ -0,0 +1,41 @@
// 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::{
hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
};
use digest::Digest;
use rand::{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`.
/// * `KeyExchange`: The key exchange protocol to use in the login step
/// * `Hash`: The main hashing function to use
/// * `SlowHash`: A slow hashing function, typically used for password hashing
pub trait CipherSuite {
/// A finite cyclic group along with a point representation along with
/// an extension trait PasswordToCurve that allows some customization on
/// how to hash a password to a curve point. See `group::Group` and
/// `map_to_curve::GroupWithMapToCurve`.
type Group: GroupWithMapToCurve<UniformBytesLen = <Self::Hash as Digest>::OutputSize>;
/// A key exchange protocol
type KeyExchange: KeyExchange<Self::Hash, Self::Group>;
/// The main hash function use (for HKDF computations and hashing transcripts)
type Hash: Hash;
/// A slow hashing function, typically used for password hashing
type SlowHash: SlowHash<Self::Hash>;
/// Generating a random key pair given a cryptographic rng
fn generate_random_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> KeyPair<Self::Group> {
KeyPair::<Self::Group>::generate_random(rng)
}
}
+372
View File
@@ -0,0 +1,372 @@
// 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_atleast, InternalPakeError, PakeError, ProtocolError},
hash::Hash,
keypair::Key,
serialization::serialize,
};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use zeroize::Zeroize;
// Constant string used as salt for HKDF computation
const STR_PAD: &[u8] = b"Pad";
const STR_AUTH_KEY: &[u8] = b"AuthKey";
const STR_EXPORT_KEY: &[u8] = b"ExportKey";
const NONCE_LEN: usize = 32;
#[derive(Clone, Copy, PartialEq, Zeroize)]
#[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode {
Base = 1,
CustomIdentifier = 2,
}
impl TryFrom<u8> for InnerEnvelopeMode {
type Error = PakeError;
fn try_from(x: u8) -> Result<Self, Self::Error> {
match x {
1 => Ok(InnerEnvelopeMode::Base),
2 => Ok(InnerEnvelopeMode::CustomIdentifier),
_ => Err(PakeError::SerializationError),
}
}
}
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub(crate) struct InnerEnvelope {
mode: InnerEnvelopeMode,
nonce: Vec<u8>,
ciphertext: Vec<u8>,
}
impl InnerEnvelope {
pub(crate) fn serialize(&self) -> Vec<u8> {
[&[self.mode as u8], &self.nonce[..], &self.ciphertext[..]].concat()
}
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
if input.is_empty() {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let mode = InnerEnvelopeMode::try_from(input[0])?;
let key_len = <Key as SizedBytes>::Len::to_usize();
let bytes = &input[1..];
if bytes.len() < NONCE_LEN + key_len {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
Ok((
Self {
mode,
nonce: bytes[..NONCE_LEN].to_vec(),
ciphertext: bytes[NONCE_LEN..NONCE_LEN + key_len].to_vec(),
},
bytes[NONCE_LEN + key_len..].to_vec(),
))
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
/* Cannot easily get raw pointer of enum value, otherwise would do self.mode.as_ptr() */
(self.nonce.as_ptr(), self.nonce.len()),
(self.ciphertext.as_ptr(), self.ciphertext.len()),
]
}
}
/// 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.
#[derive(Clone)]
pub(crate) struct Envelope<D: Hash> {
inner_envelope: InnerEnvelope,
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
}
// Note that this struct represents an envelope that has been "opened" with the asssociated
// key. This key is also used to derive the export_key parameter, which is technically
// unrelated to the envelope's encrypted and authenticated contents.
pub(crate) struct OpenedEnvelope<D: Hash> {
pub(crate) client_s_sk: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
}
pub(crate) struct OpenedInnerEnvelope<D: Hash> {
pub(crate) plaintext: Vec<u8>,
pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
}
impl<D: Hash> Envelope<D> {
fn hmac_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
fn export_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
pub(crate) fn get_mode(&self) -> InnerEnvelopeMode {
self.inner_envelope.mode
}
/// The format of the output is:
/// mode | nonce | ciphertext | hmac
/// u8 | nonce_size bytes | variable length | hmac_size bytes
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let (result, remainder) = Self::deserialize(bytes)
.map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?;
if !remainder.is_empty() {
return Err(InternalPakeError::InvalidEnvelopeStructureError);
}
Ok(result)
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
self.serialize()
}
pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.inner_envelope.serialize(), &self.hmac[..]].concat()
}
pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?;
let hmac_key_size = Self::hmac_key_size();
let hmac_and_remainder =
check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
Ok((
Self {
inner_envelope,
hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]),
},
hmac_and_remainder[hmac_key_size..].to_vec(),
))
}
pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R,
key: &[u8],
client_s_sk: &[u8],
server_s_pk: &[u8],
optional_ids: Option<(Vec<u8>, Vec<u8>)>,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), ProtocolError> {
let aad = construct_aad(server_s_pk, &optional_ids)?;
Self::seal_raw(rng, key, &client_s_sk, &aad, mode_from_ids(&optional_ids))
}
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
/// Note that a new nonce is sampled for each call to seal.
pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
rng: &mut R,
key: &[u8],
plaintext: &[u8],
aad: &[u8],
mode: InnerEnvelopeMode,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), ProtocolError> {
let mut nonce = vec![0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
let h = Hkdf::<D>::new(Some(&nonce), &key);
let mut xor_key = vec![0u8; plaintext.len()];
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
h.expand(STR_PAD, &mut xor_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_AUTH_KEY, &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = xor_key
.iter()
.zip(plaintext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
let inner_envelope = InnerEnvelope {
mode,
nonce,
ciphertext,
};
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&inner_envelope.serialize());
hmac.update(&aad);
let hmac_bytes = hmac.finalize().into_bytes();
Ok((
Self {
inner_envelope,
hmac: hmac_bytes,
},
GenericArray::clone_from_slice(&export_key),
))
}
pub(crate) fn open(
&self,
key: &[u8],
server_s_pk: &[u8],
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
) -> Result<OpenedEnvelope<D>, ProtocolError> {
// First, check that mode matches
if self.inner_envelope.mode != mode_from_ids(optional_ids) {
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into());
}
let aad = construct_aad(server_s_pk, optional_ids)?;
let opened = self.open_raw(key, &aad)?;
if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
// Plaintext should consist of a single key
return Err(InternalPakeError::UnexpectedEnvelopeContentsError.into());
}
Ok(OpenedEnvelope {
client_s_sk: opened.plaintext,
export_key: opened.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_raw(
&self,
key: &[u8],
aad: &[u8],
) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> {
let h = Hkdf::<D>::new(Some(&self.inner_envelope.nonce), &key);
let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
let mut hmac_key = vec![0u8; Self::hmac_key_size()];
let mut export_key = vec![0u8; Self::export_key_size()];
h.expand(STR_PAD, &mut xor_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_AUTH_KEY, &mut hmac_key)
.map_err(|_| InternalPakeError::HkdfError)?;
h.expand(STR_EXPORT_KEY, &mut export_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let mut hmac =
Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
hmac.update(&self.inner_envelope.serialize());
hmac.update(aad);
if hmac.verify(&self.hmac).is_err() {
return Err(InternalPakeError::SealOpenHmacError);
}
let plaintext: Vec<u8> = xor_key
.iter()
.zip(self.inner_envelope.ciphertext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok(OpenedInnerEnvelope {
plaintext,
export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
&export_key,
),
})
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
[
self.inner_envelope.as_byte_ptrs(),
vec![(self.hmac.as_ptr(), self.hmac.len())],
]
.concat()
}
}
// This can't be derived because of the use of a phantom parameter
impl<D: Hash> Zeroize for Envelope<D> {
fn zeroize(&mut self) {
self.inner_envelope.zeroize();
self.hmac.zeroize();
}
}
impl<D: Hash> Drop for Envelope<D> {
fn drop(&mut self) {
self.zeroize();
}
}
// Helper functions
fn construct_aad(
server_s_pk: &[u8],
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
) -> Result<Vec<u8>, ProtocolError> {
let ids = match optional_ids {
Some((l, r)) => [serialize(l, 2)?, serialize(r, 2)?].concat(),
None => vec![],
};
Ok([server_s_pk.to_vec(), ids].concat())
}
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
match optional_ids {
Some(_) => InnerEnvelopeMode::CustomIdentifier,
None => InnerEnvelopeMode::Base,
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn seal_and_open() {
let mut rng = OsRng;
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
&mut rng,
&key,
&msg,
b"aad",
InnerEnvelopeMode::Base,
)
.unwrap();
let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
}
}
+88 -27
View File
@@ -4,55 +4,77 @@
// 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}.")]
/// Deserializing from a byte sequence failed
InvalidByteSequence,
/// 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 the hash-to-curve function failed
HashToCurveError,
/// 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,
/// This error occurs when the envelope cannot be constructed properly
/// based on the credentials that were specified to be required.
InvalidEnvelopeStructureError,
/// This error occurs when attempting to open an envelope of the wrong
/// type (base mode, custom identifier)
IncompatibleEnvelopeModeError,
/// This error occurs when the envelope is opened and deserialization
/// fails
UnexpectedEnvelopeContentsError,
}
/// 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,
/// Error with serializing / deserializing protocol messages
SerializationError,
/// Identity group element was encountered during deserialization, which is invalid
IdentityGroupElementError,
}
// This is meant to express future(ly) non-trivial ways of converting the
@@ -64,18 +86,24 @@ 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 server specifies an envelope credentials
/// format that is invalid
ServerInvalidEnvelopeCredentialsFormatError,
/// This error occurs when the client request cannot be handled
#[error("Client request cannot be handled.")]
/// Client request cannot be handled.
ClientError,
/// This error occurs when the client detects that the server has
/// reflected the OPRF value (beta == alpha)
ReflectedValueError,
}
// This is meant to express future(ly) non-trivial ways of converting the
@@ -103,6 +131,24 @@ impl From<::std::convert::Infallible> for ProtocolError {
}
}
impl From<generic_bytes::TryFromSizedBytesError> for InternalPakeError {
fn from(_: generic_bytes::TryFromSizedBytesError) -> Self {
InternalPakeError::InvalidByteSequence
}
}
impl From<generic_bytes::TryFromSizedBytesError> for PakeError {
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
PakeError::CryptoError(e.into())
}
}
impl From<generic_bytes::TryFromSizedBytesError> for ProtocolError {
fn from(e: generic_bytes::TryFromSizedBytesError) -> Self {
PakeError::CryptoError(e.into()).into()
}
}
pub(crate) mod utils {
use super::*;
@@ -120,4 +166,19 @@ pub(crate) mod utils {
}
Ok(slice)
}
pub fn check_slice_size_atleast<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], InternalPakeError> {
if slice.len() < expected_len {
return Err(InternalPakeError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
}
+76 -75
View File
@@ -3,36 +3,40 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the Group trait to specify the underlying prime order group used in
//! OPAQUE's OPRF
use crate::errors::InternalPakeError;
use curve25519_dalek::{
edwards::{CompressedEdwardsY, EdwardsPoint},
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use generic_array::{
typenum::{U32, U64},
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::convert::TryInto;
use rand::{CryptoRng, RngCore};
use std::ops::Mul;
use zeroize::Zeroize;
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
/// subgroup is noted additively — as in the draft RFC — in this trait.
pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
/// The type of base field scalars
type Scalar: Zeroize;
type Scalar: Zeroize + Clone;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8>;
/// Return a scalat from its fixed-length bytes representation
/// Return a scalar from its fixed-length bytes representation
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError>;
/// picks a scalar at random
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar
@@ -45,14 +49,28 @@ 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;
/// Get the base point for the group
fn base_point() -> Self;
/// Multiply the point by a scalar, represented as a slice
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool;
/// Compares in constant time if the group elements are equal
fn ct_equal(&self, other: &Self) -> bool;
}
/// The implementation of such a subgroup for Ristretto
@@ -66,8 +84,29 @@ impl Group for RistrettoPoint {
bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits))
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop {
let scalar = {
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
};
if scalar != Scalar::zero() {
break scalar;
}
}
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
@@ -83,78 +122,40 @@ impl Group for RistrettoPoint {
) -> Result<Self, InternalPakeError> {
CompressedRistretto::from_slice(element_bits)
.decompress()
.ok_or_else(|| InternalPakeError::PointError)
.ok_or(InternalPakeError::PointError)
}
// serialization of a group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen> {
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
let mut bits = [0u8; 64];
bits.copy_from_slice(uniform_bytes);
// This could really be a from_uniform_bytes!
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
}
}
/// The implementation of such a subgroup for points on the large Curve25519-subgroup
impl Group for EdwardsPoint {
type Scalar = Scalar;
type ScalarLen = U32;
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError> {
let mut bits = [0u8; 32];
bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits))
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert()
}
// The byte length necessary to represent group elements
type ElemLen = U32;
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> {
CompressedEdwardsY::from_slice(element_bits)
.decompress()
.ok_or_else(|| InternalPakeError::PointError)
}
// serialization of a group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
let mut result = [0u8; 32];
let mut counter = 0;
let mut wrapped_point: Option<EdwardsPoint> = None;
while wrapped_point.is_none() {
result.copy_from_slice(
&Sha256::new()
.chain(&uniform_bytes[..32])
.chain(&[counter])
.result()[..32],
);
wrapped_point = CompressedEdwardsY::from_slice(&result).decompress();
counter += 1;
}
wrapped_point
.expect("guarded by loop exit condition")
.mul_by_cofactor()
// https://caniuse.rs/features/array_gt_32_impls
let bits: [u8; 64] = {
let mut bytes = [0u8; 64];
bytes.copy_from_slice(uniform_bytes);
bytes
};
RistrettoPoint::from_uniform_bytes(&bits)
}
fn base_point() -> Self {
RISTRETTO_BASEPOINT_POINT
}
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self {
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
self * Scalar::from_bits(arr)
}
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self == &Self::identity()
}
fn ct_equal(&self, other: &Self) -> bool {
constant_time_eq::constant_time_eq(&self.to_arr(), &other.to_arr())
}
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! A convenience trait for digest bounds used throughout the library
use digest::{BlockInput, FixedOutput, Reset, Update};
/// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
// Associated types could be simplified when they are made as defaults:
// https://github.com/rust-lang/rust/issues/29661
pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
-406
View File
@@ -1,406 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
keypair::{Key, KeyPair, SizedBytes},
};
use generic_array::GenericArray;
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
/// This module is a somewhat minimalistic implementation of a key Exchange
/// protocol based on 3DH. It assumes a pre-exchange has allowed client and
/// server to learn each other's static public key.
///
/// This private module may undergo significant changes in the near term.
const KEY_LEN: usize = 32;
pub(crate) const NONCE_LEN: usize = 32;
pub(crate) const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
static STR_3DH: &[u8] = b"3DH keys";
pub(crate) struct KE1State {
client_e_sk: Key,
client_nonce: Vec<u8>,
hashed_l1: Vec<u8>,
}
pub(crate) struct KE1Message {
pub(crate) client_nonce: Vec<u8>,
pub(crate) client_e_pk: Key,
}
impl TryFrom<&[u8]> for KE1State {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
})
}
}
impl KE1State {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.client_e_sk.to_arr(),
&self.client_nonce[..],
&self.hashed_l1[..],
]
.concat();
output
}
}
impl KE1Message {
pub fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl TryFrom<&[u8]> for KE1Message {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes =
check_slice_size(ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
Ok(Self {
client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
})
}
}
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
l1_component: Vec<u8>,
rng: &mut R,
) -> Result<(KE1State, KE1Message), ProtocolError> {
let client_e_kp = KeyFormat::generate_random(rng)?;
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let ke1_message = KE1Message {
client_nonce: client_nonce.to_vec(),
client_e_pk: client_e_kp.public().clone(),
};
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
let mut hasher = Sha256::new();
hasher.input(&l1_data);
let hashed_l1 = hasher.result();
Ok((
KE1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce: client_nonce.to_vec(),
hashed_l1: hashed_l1.to_vec(),
},
ke1_message,
))
}
pub(crate) struct KE2State {
km3: Vec<u8>,
hashed_transcript: Vec<u8>,
shared_secret: Vec<u8>,
}
pub(crate) struct KE2Message {
server_nonce: Vec<u8>,
server_e_pk: Key,
mac: Vec<u8>,
}
impl KE2State {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.km3[..],
&self.hashed_transcript[..],
&self.shared_secret[..],
]
.concat();
output
}
}
impl TryFrom<&[u8]> for KE2State {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
Ok(Self {
km3: checked_bytes[..KEY_LEN].to_vec(),
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
})
}
}
impl KE2Message {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.server_nonce[..],
&self.server_e_pk.to_arr(),
&self.mac[..],
]
.concat();
output
}
}
impl TryFrom<&[u8]> for KE2Message {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
Ok(Self {
server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
})
}
}
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: Key,
sk1: Key,
pk2: Key,
sk2: Key,
pk3: Key,
sk3: Key,
}
// Consists of a shared secret, followed by two mac keys
type TripleDHDerivationResult = (
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
);
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the shared secret and two MAC keys
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>>(
dh: TripleDHComponents,
client_nonce: &[u8],
server_nonce: &[u8],
client_s_pk: KeyFormat::Repr,
server_s_pk: KeyFormat::Repr,
) -> Result<TripleDHDerivationResult, ProtocolError> {
let ikm: Vec<u8> = [
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
]
.concat();
let info: Vec<u8> = [
STR_3DH,
&client_nonce,
&server_nonce,
&client_s_pk.to_arr(),
&server_s_pk.to_arr(),
]
.concat();
const OUTPUT_SIZE: usize = 32;
let mut okm = [0u8; 3 * OUTPUT_SIZE];
let h = Hkdf::<Sha256>::new(None, &ikm);
h.expand(&info, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok((
*GenericArray::from_slice(&okm[..OUTPUT_SIZE]),
*GenericArray::from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
*GenericArray::from_slice(&okm[2 * OUTPUT_SIZE..]),
))
}
pub(crate) fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
client_e_pk: KeyFormat::Repr,
client_s_pk: KeyFormat::Repr,
server_s_sk: KeyFormat::Repr,
client_nonce: Vec<u8>,
) -> Result<(KE2State, KE2Message), ProtocolError> {
let server_e_kp = KeyFormat::generate_random(rng)?;
let mut server_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut server_nonce);
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
TripleDHComponents {
pk1: client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: client_e_pk,
sk2: server_s_sk.clone(),
pk3: client_s_pk.clone(),
sk3: server_e_kp.private().clone(),
},
&client_nonce,
&server_nonce,
client_s_pk,
KeyFormat::public_from_private(&server_s_sk),
)?;
let mut hasher = Sha256::new();
hasher.input(&l1_bytes);
let hashed_l1 = hasher.result();
let transcript2: Vec<u8> = [
&hashed_l1[..],
&l2_bytes[..],
&server_nonce[..],
&server_e_kp.public().to_arr(),
]
.concat();
let mut hasher2 = Sha256::new();
hasher2.input(&transcript2);
let hashed_transcript = hasher2.result();
let mut mac = Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac.input(&hashed_transcript);
Ok((
KE2State {
km3: km3.to_vec(),
hashed_transcript: hashed_transcript.to_vec(),
shared_secret: shared_secret.to_vec(),
},
KE2Message {
server_nonce: server_nonce.to_vec(),
server_e_pk: server_e_kp.public().clone(),
mac: mac.result().code().to_vec(),
},
))
}
pub(crate) struct KE3State {
pub(crate) shared_secret: Vec<u8>,
}
pub(crate) struct KE3Message {
mac: Vec<u8>,
}
impl TryFrom<&[u8]> for KE3State {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_state")?;
Ok(Self {
shared_secret: checked_bytes.to_vec(),
})
}
}
impl KE3Message {
pub fn to_bytes(&self) -> Vec<u8> {
self.mac.clone()
}
}
impl TryFrom<&[u8]> for KE3Message {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
Ok(Self {
mac: checked_bytes.to_vec(),
})
}
}
pub(crate) fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
l2_component: Vec<u8>,
ke2_message: KE2Message,
ke1_state: &KE1State,
server_s_pk: KeyFormat::Repr,
client_s_sk: KeyFormat::Repr,
) -> Result<(KE3State, KE3Message), ProtocolError> {
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk.clone(),
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk.clone(),
},
&ke1_state.client_nonce,
&ke2_message.server_nonce,
KeyFormat::public_from_private(&client_s_sk),
server_s_pk,
)?;
let transcript: Vec<u8> = [
&ke1_state.hashed_l1[..],
&l2_component[..],
&ke2_message.server_nonce[..],
&ke2_message.server_e_pk[..],
]
.concat();
let mut hasher = Sha256::new();
hasher.input(&transcript);
let hashed_transcript = hasher.result();
let mut server_mac =
Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.input(&hashed_transcript);
if ke2_message.mac != server_mac.result().code().to_vec() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
let mut client_mac =
Hmac::<Sha256>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.input(&hashed_transcript);
Ok((
KE3State {
shared_secret: shared_secret.to_vec(),
},
KE3Message {
mac: client_mac.result().code().to_vec(),
},
))
}
// Outputs a shared secret
pub(crate) fn finish_ke(
ke3_message: KE3Message,
ke2_state: &KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<Sha256>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.input(&ke2_state.hashed_transcript);
if ke3_message.mac != client_mac.result().code().to_vec() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.shared_secret.to_vec())
}
+10
View File
@@ -0,0 +1,10 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Includes instantiations of key exchange protocols used in the
//! login step for OPAQUE
pub(crate) mod traits;
pub mod tripledh;
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::{PakeError, ProtocolError},
group::Group,
hash::Hash,
keypair::Key,
};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use zeroize::Zeroize;
pub trait KeyExchange<D: Hash, G: Group> {
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytesWithPointers + Zeroize;
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytesWithPointers + Zeroize;
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes + Clone;
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError>;
fn ke2_message_size() -> usize;
}
pub trait ToBytes {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
}
pub trait ToBytesWithPointers {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
// Only used for tests to grab raw pointers to data
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)>;
}
+577
View File
@@ -0,0 +1,577 @@
// 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, check_slice_size_atleast},
InternalPakeError, PakeError, ProtocolError,
},
group::Group,
hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes, ToBytesWithPointers},
keypair::{Key, KeyPair, SizedBytesExt},
serialization::{serialize, tokenize},
};
use digest::{Digest, FixedOutput};
use generic_array::{
typenum::{Unsigned, U32},
ArrayLength, GenericArray,
};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
use zeroize::Zeroize;
const KEY_LEN: usize = 32;
pub(crate) type NonceLen = U32;
static STR_3DH: &[u8] = b"3DH";
static STR_CLIENT_MAC: &[u8] = b"client mac";
static STR_HANDSHAKE_SECRET: &[u8] = b"handshake secret";
static STR_SERVER_MAC: &[u8] = b"server mac";
static STR_HANDSHAKE_ENC: &[u8] = b"handshake enc";
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
static STR_SESSION_SECRET: &[u8] = b"session secret";
static STR_OPAQUE: &[u8] = b"OPAQUE ";
#[allow(clippy::upper_case_acronyms)]
/// The Triple Diffie-Hellman key exchange implementation
pub struct TripleDH;
impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
type KE1State = Ke1State;
type KE2State = Ke2State<<D as FixedOutput>::OutputSize>;
type KE1Message = Ke1Message;
type KE2Message = Ke2Message<<D as FixedOutput>::OutputSize>;
type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
let client_e_kp = KeyPair::<G>::generate_random(rng);
let client_nonce = generate_nonce::<R>(rng);
let ke1_message = Ke1Message {
client_nonce,
info,
client_e_pk: client_e_kp.public().clone(),
};
Ok((
Ke1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce,
},
ke1_message,
))
}
#[allow(clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
serialized_credential_request: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> {
let server_e_kp = KeyPair::<G>::generate_random(rng);
let server_nonce = generate_nonce::<R>(rng);
let mut transcript_hasher = D::new()
.chain(STR_3DH)
.chain(&serialize(&id_u, 2)?)
.chain(&serialized_credential_request[..])
.chain(&serialize(&id_s, 2)?)
.chain(&l2_bytes[..])
.chain(&server_nonce[..])
.chain(&server_e_kp.public().to_arr());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke1_message.client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: ke1_message.client_e_pk,
sk2: server_s_sk,
pk3: client_s_pk,
sk3: server_e_kp.private().clone(),
},
&transcript_hasher.clone().finalize(),
)?;
// Compute encryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let ciphertext: Vec<u8> = encryption_pad
.iter()
.zip(e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
transcript_hasher.update(&serialize(&ciphertext, 2)?);
let mut mac_hasher =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac_hasher.update(&transcript_hasher.clone().finalize());
let mac = mac_hasher.finalize().into_bytes();
transcript_hasher.update(&mac);
Ok((
ke1_message.info,
Ke2State {
km3,
hashed_transcript: transcript_hasher.finalize(),
session_key,
},
Ke2Message {
server_nonce,
server_e_pk: server_e_kp.public().clone(),
e_info: ciphertext,
mac,
},
))
}
#[allow(clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
let mut transcript_hasher = D::new()
.chain(STR_3DH)
.chain(&serialize(&id_u, 2)?)
.chain(&serialized_credential_request)
.chain(&serialize(&id_s, 2)?)
.chain(&l2_component[..])
.chain(&ke2_message.to_bytes_without_info_or_mac());
let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk,
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk,
},
&transcript_hasher.clone().finalize(),
)?;
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2)?);
let mut server_mac =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&transcript_hasher.clone().finalize());
if server_mac.verify(&ke2_message.mac).is_err() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
transcript_hasher.update(ke2_message.mac.to_vec());
let mut client_mac =
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&transcript_hasher.finalize());
// Compute decryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let plaintext: Vec<u8> = encryption_pad
.iter()
.zip(ke2_message.e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok((
plaintext,
session_key.to_vec(),
Ke3Message {
mac: client_mac.finalize().into_bytes(),
},
))
}
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript);
if client_mac.verify(&ke3_message.mac).is_err() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.session_key.to_vec())
}
fn ke2_message_size() -> usize {
NonceLen::to_usize() + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
}
}
/// The client state produced after the first key exchange message
#[derive(PartialEq, Eq, Zeroize)]
#[zeroize(drop)]
pub struct Ke1State {
client_e_sk: Key,
client_nonce: GenericArray<u8, NonceLen>,
}
/// The first key exchange message
#[derive(PartialEq, Eq)]
pub struct Ke1Message {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) info: Vec<u8>,
pub(crate) client_e_pk: Key,
}
impl TryFrom<&[u8]> for Ke1State {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
),
})
}
}
impl ToBytesWithPointers for Ke1State {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
Ok(output)
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(
self.client_e_sk.as_ptr(),
<Key as SizedBytes>::Len::to_usize(),
),
(self.client_nonce.as_ptr(), NonceLen::to_usize()),
]
}
}
impl ToBytes for Ke1Message {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
&self.client_nonce[..],
&serialize(&self.info, 2)?,
&self.client_e_pk.to_arr(),
]
.concat())
}
}
impl TryFrom<&[u8]> for Ke1Message {
type Error = PakeError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_nonce =
check_slice_size_atleast(ke1_message_bytes, nonce_len, "ke1_message nonce")?;
let (info, remainder) = tokenize(&checked_nonce[nonce_len..], 2)?;
let checked_client_e_pk = check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
info,
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
})
}
}
/// The server state produced after the second key exchange message
pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>,
}
// This can't be derived because of the use of a phantom parameter
impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
fn zeroize(&mut self) {
self.km3.zeroize();
self.hashed_transcript.zeroize();
self.session_key.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat())
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.km3.as_ptr(), HashLen::to_usize()),
(self.hashed_transcript.as_ptr(), HashLen::to_usize()),
(self.session_key.as_ptr(), HashLen::to_usize()),
]
}
}
/// The second key exchange message
#[derive(Clone)]
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: Key,
e_info: Vec<u8>,
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
type Error = PakeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
let hash_len = HashLen::to_usize();
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
hashed_transcript: GenericArray::clone_from_slice(
&checked_bytes[hash_len..2 * hash_len],
),
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
})
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
&self.to_bytes_without_info_or_mac(),
&serialize(&self.e_info, 2)?,
&self.mac[..],
]
.concat())
}
}
impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
[&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2Message<HashLen> {
type Error = PakeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
let checked_server_e_pk = check_slice_size_atleast(
&checked_nonce[nonce_len..],
KEY_LEN,
"ke2_message server_e_pk",
)?;
let (e_info, remainder) = tokenize(&checked_server_e_pk[KEY_LEN..], 2)?;
let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: Key::from_bytes(&checked_server_e_pk[..KEY_LEN])?,
e_info,
mac: GenericArray::clone_from_slice(&checked_mac),
})
}
}
#[allow(clippy::upper_case_acronyms)]
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: Key,
sk1: Key,
pk2: Key,
sk2: Key,
pk3: Key,
sk3: Key,
}
#[allow(clippy::upper_case_acronyms)]
// Consists of a session key, followed by two mac keys and an encryption key: (session_key, km2, ke2, km3)
type TripleDHDerivationResult<D> = (
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
GenericArray<u8, <D as FixedOutput>::OutputSize>,
);
/// The third key exchange message
pub struct Ke3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
Ok(self.mac.to_vec())
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke3Message<HashLen> {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(&bytes, HashLen::to_usize(), "ke3_message")?;
Ok(Self {
mac: GenericArray::clone_from_slice(&checked_bytes),
})
}
}
// Helper functions
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the session key and two MAC keys
fn derive_3dh_keys<D: Hash, G: Group>(
dh: TripleDHComponents,
hashed_derivation_transcript: &[u8],
) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
let ikm: Vec<u8> = [
&KeyPair::<G>::diffie_hellman(dh.pk1, dh.sk1)?[..],
&KeyPair::<G>::diffie_hellman(dh.pk2, dh.sk2)?[..],
&KeyPair::<G>::diffie_hellman(dh.pk3, dh.sk3)?[..],
]
.concat();
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>(
&extracted_ikm,
&STR_HANDSHAKE_SECRET,
&hashed_derivation_transcript,
)?;
let session_key = derive_secrets::<D>(
&extracted_ikm,
&STR_SESSION_SECRET,
&hashed_derivation_transcript,
)?;
let km2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_SERVER_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let ke2 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_HANDSHAKE_ENC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
let km3 = hkdf_expand_label::<D>(
&handshake_secret,
&STR_CLIENT_MAC,
b"",
<D as Digest>::OutputSize::to_usize(),
)?;
Ok((
GenericArray::clone_from_slice(&session_key),
GenericArray::clone_from_slice(&km2),
GenericArray::clone_from_slice(&ke2),
GenericArray::clone_from_slice(&km3),
))
}
fn hkdf_expand_label<D: Hash>(
secret: &[u8],
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalPakeError::HkdfError)?;
hkdf_expand_label_extracted(&h, label, context, length)
}
fn hkdf_expand_label_extracted<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
context: &[u8],
length: usize,
) -> Result<Vec<u8>, ProtocolError> {
let mut okm = vec![0u8; length];
let mut hkdf_label: Vec<u8> = Vec::new();
let length_u16: u16 = u16::try_from(length).map_err(|_| PakeError::SerializationError)?;
hkdf_label.extend_from_slice(&length_u16.to_be_bytes());
let mut opaque_label: Vec<u8> = Vec::new();
opaque_label.extend_from_slice(&STR_OPAQUE);
opaque_label.extend_from_slice(&label);
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1)?);
hkdf_label.extend_from_slice(&serialize(&context, 1)?);
hkdf.expand(&hkdf_label, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok(okm)
}
fn derive_secrets<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
hashed_derivation_transcript: &[u8],
) -> Result<Vec<u8>, ProtocolError> {
hkdf_expand_label_extracted::<D>(
hkdf,
label,
&hashed_derivation_transcript,
<D as Digest>::OutputSize::to_usize(),
)
}
// Generate a random nonce up to NonceLen::to_usize() bytes.
fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
let mut nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut nonce_bytes);
GenericArray::clone_from_slice(&nonce_bytes)
}
+176 -217
View File
@@ -5,97 +5,144 @@
//! Contains the keypair types that must be supplied for the OPAQUE API
use crate::errors::{utils::check_slice_size, InternalPakeError};
use generic_array::{
sequence::Concat,
typenum::{Sum, Unsigned, U32},
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
use x25519_dalek::{PublicKey, StaticSecret};
#![allow(unsafe_code)]
use std::convert::TryFrom;
use crate::errors::InternalPakeError;
use crate::group::Group;
#[cfg(test)]
use generic_array::typenum::Unsigned;
use generic_array::{typenum::U32, GenericArray};
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
#[cfg(test)]
use proptest::prelude::*;
#[cfg(test)]
use rand::{rngs::StdRng, SeedableRng};
use rand::{CryptoRng, RngCore};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::ops::Deref;
use zeroize::Zeroize;
use std::ops::{Add, Deref};
/// A trait for sized key material that can be represented within a fixed byte
/// array size, used to represent our DH key types
pub trait SizedBytes: Sized + PartialEq {
/// The typed representation of the byte length
type Len: ArrayLength<u8>;
/// Converts this sized key material to a `GenericArray` of the same
/// size. One can convert this to a `&[u8]` with `GenericArray::as_slice()`
/// but the size information is then lost from the type.
fn to_arr(&self) -> GenericArray<u8, Self::Len>;
/// How to parse such sized material from a byte slice.
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError>;
/// Convenience extension trait of SizedBytes
pub trait SizedBytesExt: SizedBytes {
/// Convert from bytes
fn from_bytes(bytes: &[u8]) -> Result<Self, TryFromSizedBytesError> {
<Self as SizedBytes>::from_arr(GenericArray::from_slice(bytes))
}
}
/// A Keypair trait with public-private verification
pub trait KeyPair: Sized {
/// The single key representation must have a specific byte size itself
type Repr: SizedBytes + Clone;
// blanket implementation
impl<T> SizedBytesExt for T where T: SizedBytes {}
/// A Keypair trait with public-private verification
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyPair<G> {
pk: Key,
sk: Key,
_g: PhantomData<G>,
}
// This can't be derived because of the use of a phantom parameter
impl<G> Zeroize for KeyPair<G> {
fn zeroize(&mut self) {
self.pk.zeroize();
self.sk.zeroize();
}
}
impl<G> Drop for KeyPair<G> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<G: Group> KeyPair<G> {
/// The public key component
fn public(&self) -> &Self::Repr;
pub fn public(&self) -> &Key {
&self.pk
}
/// The private key component
fn private(&self) -> &Self::Repr;
/// A constructor that receives public and private key independently as
/// bytes
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError>;
pub fn private(&self) -> &Key {
&self.sk
}
/// Generating a random key pair given a cryptographic rng
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError>;
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_nonzero_scalar(rng);
let sk_bytes = G::scalar_as_bytes(&sk);
let pk = G::base_point().mult_by_slice(&sk_bytes);
Self {
pk: Key(pk.to_arr().to_vec()),
sk: Key(sk_bytes.to_vec()),
_g: PhantomData,
}
}
/// Obtaining a public key from secret bytes. At all times, we should have
/// &public_from_private(self.private()) == self.public()
fn public_from_private(secret: &Self::Repr) -> Self::Repr;
pub(crate) fn public_from_private(bytes: &Key) -> Key {
let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]);
Key(G::base_point().mult_by_slice(&bytes_data).to_arr().to_vec())
}
/// Check whether a public key is valid. This is meant to be applied on
/// material provided through the network which fits the key
/// representation (i.e. can be mapped to a curve point), but presents
/// some risk - e.g. small subgroup check
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError>;
pub(crate) fn check_public_key(key: Key) -> Result<Key, InternalPakeError> {
G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key)
}
/// Computes the diffie hellman function on a public key and private key
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8>;
}
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
/// with any length of keys. This encodes that we serialize the public key
/// first, followed by the private key in binary formats (and expect it in this
/// order upon decoding).
impl<T, KP> SizedBytes for KP
where
T: SizedBytes + Clone,
KP: KeyPair<Repr = T> + PartialEq,
T::Len: Add<T::Len>,
Sum<T::Len, T::Len>: ArrayLength<u8>,
{
type Len = Sum<T::Len, T::Len>;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
let private = self.private().to_arr();
let public = self.public().to_arr();
public.concat(private)
pub(crate) fn diffie_hellman(pk: Key, sk: Key) -> Result<Vec<u8>, InternalPakeError> {
let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]);
let point = G::from_element_slice(&pk_data)?;
let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]);
Ok(G::mult_by_slice(&point, &secret_data).to_arr().to_vec())
}
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes =
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
let single_key_len = <<KP::Repr as SizedBytes>::Len as Unsigned>::to_usize();
let public = <T as SizedBytes>::from_bytes(&checked_bytes[..single_key_len])?;
let private = <T as SizedBytes>::from_bytes(&checked_bytes[single_key_len..])?;
KP::new(public, private)
/// Obtains a KeyPair from a slice representing the private key
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
let sk = Key::from_arr(GenericArray::from_slice(&input))?;
let pk = Self::public_from_private(&sk);
Ok(Self {
pk,
sk,
_g: PhantomData,
})
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.pk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
(self.sk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
]
}
}
/// A minimalist key type built around [u8;32]
#[derive(PartialEq, Eq, Clone)]
#[cfg(test)]
impl<G: Group + Debug> KeyPair<G> {
/// 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);
Some(Self::generate_random(&mut rng))
})
.no_shrink()
.boxed()
}
}
/// A minimalist key type built around a \[u8; 32\]
#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
// Ensure Key material is zeroed after use.
#[zeroize(drop)]
#[repr(transparent)]
pub struct Key(Vec<u8>);
@@ -107,14 +154,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;
@@ -122,162 +161,82 @@ impl SizedBytes for Key {
GenericArray::clone_from_slice(&self.0[..])
}
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes =
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
Ok(Key(checked_bytes.to_vec()))
fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
Ok(Key(key_bytes.to_vec()))
}
}
/// A representation of an X25519 keypair according to RFC7748
#[derive(PartialEq)]
pub struct X25519KeyPair {
pk: Key,
sk: Key,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::*;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use rand::rngs::OsRng;
use std::slice::from_raw_parts;
impl X25519KeyPair {
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
let sk = StaticSecret::new(rng);
let pk = PublicKey::from(&sk);
(pk.as_bytes().to_vec(), sk.to_bytes().to_vec())
}
}
#[test]
fn test_zeroize_key() -> Result<(), ProtocolError> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let mut key = Key(vec![1u8; key_len]);
let ptr = key.as_ptr();
impl KeyPair for X25519KeyPair {
type Repr = Key;
key.zeroize();
fn public(&self) -> &Self::Repr {
&self.pk
let bytes = unsafe { from_raw_parts(ptr, key_len) };
assert!(bytes.iter().all(|&x| x == 0));
Ok(())
}
fn private(&self) -> &Self::Repr {
&self.sk
#[test]
fn test_zeroize_keypair() -> Result<(), ProtocolError> {
let mut rng = OsRng;
let mut keypair = KeyPair::<RistrettoPoint>::generate_random(&mut rng);
let ptrs = keypair.as_byte_ptrs();
keypair.zeroize();
for (ptr, len) in ptrs {
let bytes = unsafe { from_raw_parts(ptr, len) };
assert!(bytes.iter().all(|&x| x == 0));
}
Ok(())
}
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
Ok(X25519KeyPair {
pk: public,
sk: private,
})
}
proptest! {
#[test]
fn test_ristretto_check(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
prop_assert!(KeyPair::<RistrettoPoint>::check_public_key(pk.clone()).is_ok());
}
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
let (public, private) = X25519KeyPair::gen(rng);
Ok(X25519KeyPair {
pk: Key(public),
sk: Key(private),
})
}
#[test]
fn test_ristretto_pub_from_priv(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&KeyPair::<RistrettoPoint>::public_from_private(sk), pk);
}
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())
}
#[test]
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
if !point.is_torsion_free() {
Err(InternalPakeError::SubGroupError)
} else {
Ok(key)
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
prop_assert_eq!(dh1, dh2);
}
#[test]
fn test_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
let kp2_private_bytes = kp2.private().to_vec();
prop_assert_eq!(sk_bytes, kp2_private_bytes);
}
}
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
let mut pk_data = [0; 32];
pk_data.copy_from_slice(&pk.0[..]);
let mut sk_data = [0; 32];
sk_data.copy_from_slice(&sk.0[..]);
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
}
}
/// A custom, minimalistic Key pair struct built on Key, aimed at reproducing the behavior of libsignal's keypairs
#[derive(PartialEq)]
pub struct SignalKeyPair {
pk: Key,
sk: Key,
}
impl SignalKeyPair {
fn clamp_scalar(mut scalar: [u8; 32]) -> ::curve25519_dalek::scalar::Scalar {
scalar[0] &= 248;
scalar[31] &= 127;
scalar[31] |= 64;
::curve25519_dalek::scalar::Scalar::from_bits(scalar)
}
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
let mut bits = [0u8; 32];
rng.fill_bytes(&mut bits);
// It's proper to sanitize the scalar here, and reproduces x25519::StaticSecret::new
let sk = SignalKeyPair::clamp_scalar(bits);
let pk = ::curve25519_dalek::constants::X25519_BASEPOINT * sk;
(pk.as_bytes().to_vec(), sk.as_bytes().to_vec())
}
}
impl KeyPair for SignalKeyPair {
type Repr = Key;
fn public(&self) -> &Self::Repr {
&self.pk
}
fn private(&self) -> &Self::Repr {
&self.sk
}
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
Ok(SignalKeyPair {
pk: public,
sk: private,
})
}
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
let (public, private) = SignalKeyPair::gen(rng);
Ok(SignalKeyPair {
pk: Key(public),
sk: Key(private),
})
}
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
let mut secret_data = [0u8; 32];
secret_data.copy_from_slice(&secret.0[..]);
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
}
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
if !point.is_torsion_free() {
Err(InternalPakeError::SubGroupError)
} else {
Ok(key)
}
}
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
let mut pk_data = [0; 32];
pk_data.copy_from_slice(&pk.0[..]);
let mut sk_data = [0; 32];
sk_data.copy_from_slice(&sk.0[..]);
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
}
}
+658 -234
View File
@@ -5,332 +5,756 @@
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
//!
//! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-03](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html),
//! 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 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 KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! type Hash = sha2::Sha512;
//! type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! }
//! ```
//! See [examples/simple_login.rs](https://github.com/novifinancial/opaque-ke/blob/master/examples/simple_login.rs)
//! for a working example of a simple password-based login using OPAQUE.
//!
//! 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`,
//! which can be enabled through the `slow-hash` feature.
//!
//! ## Setup
//! To setup the protocol, the server begins by generating a static keypair:
//! To set up the protocol, the server begins by generating a static keypair:
//! ```
//! # use opaque_ke::keypair::{KeyPair, X25519KeyPair, SizedBytes};
//! # use opaque_ke::errors::ProtocolError;
//! use rand_core::{OsRng, RngCore};
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! use rand::{rngs::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
//! used by the client during both registration and login, and the private component will be used by the server during login.
//!
//! ## Registration
//! The registration protocol between the client and server consists of four steps along with three messages, denoted
//! as `r1`, `r2`, and `r3`. Before registration begins, it is expected that the server's static public key, `server_kp.public()`,
//! has been transmitted to the client in an offline step. A successful execution of the registration protocol results in the
//! server producing a password file corresponding to the tuple combination of (password, pepper, server public key) provided by
//! The registration protocol between the client and server consists of four steps along with three messages:
//! [RegistrationRequest], [RegistrationResponse], and [RegistrationUpload]. A successful execution of the registration protocol results in the
//! server producing a password file corresponding to the password provided by
//! the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.
//!
//! 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`:
//! ### Client Registration Start
//! In the first step of registration, the client chooses as input a registration password. The client runs [ClientRegistration::start]
//! to produce a [ClientRegistrationStartResult], which consists of a [RegistrationRequest] to be sent to the server and
//! a [ClientRegistration] which must be persisted on the client for the final step of client 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 rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ServerRegistration,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! use opaque_ke::ClientRegistration;
//! use rand::{rngs::OsRng, RngCore};
//! let mut client_rng = OsRng;
//! let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! b"password",
//! Some(b"pepper"),
//! let client_registration_start_result = ClientRegistration::<Default>::start(
//! &mut client_rng,
//! b"password",
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r1` is sent to the server, and `client_state` must be persisted on the client for the final step of client
//! registration.
//!
//! 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`:
//! ### Server Registration Start
//! In the second step of registration, the server takes as input the instance of [RegistrationRequest] from the client, and
//! the server's public key `server_kp.public()`.
//! The server runs [ServerRegistration::start] to produce an a [ServerRegistrationStartResult], which consists of
//! a [RegistrationResponse] to be returned to the client and
//! a [ServerRegistration] which must be persisted on the server for the final step of server 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 rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! use opaque_ke::ServerRegistration;
//! let mut server_rng = OsRng;
//! let (r2, server_state) =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! r1,
//! &mut server_rng,
//! )?;
//! let server_kp = Default::generate_random_keypair(&mut server_rng);
//! let server_registration_start_result = ServerRegistration::<Default>::start(
//! &mut server_rng,
//! client_registration_start_result.message,
//! server_kp.public(),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r2` is returned to the client, and `server_state` must be persisted on the server for the final step of server
//! registration.
//!
//! 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`:
//! ### Client Registration Finish
//! In the third step of registration, the client takes as input
//! a [RegistrationResponse] from the server, and
//! a [ClientRegistration] from the first step of registration.
//! The client runs [ClientRegistration::finish] to produce a [ClientRegistrationFinishResult], which consists of a [RegistrationUpload]
//! to be sent to the server and an `export_key` field which can be used optionally as described in the [Export Key](#export-key) section.
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # 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 server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng,
//! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::default(),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r3` is sent to the server, and the client can optionally use `kd_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`:
//! ### Server Registration Finish
//! In the fourth step of registration, the server takes as input
//! a [RegistrationUpload] from the client, and
//! a [ServerRegistration] from the second step.
//! The server runs [ServerRegistration::finish] to produce a finalized [ServerRegistration].
//! At this point, the client can be considered as successfully registered, and the server can invoke
//! [ServerRegistration::serialize] to store the password file for use during the login protocol.
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # 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 password_file = server_state.finish(r3)?;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! let password_file = server_registration_start_result.state.finish(
//! client_registration_finish_result.message,
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! At this point, the client can be considered as successfully registered, and the server can store
//! `password_file.to_bytes()` for use during the login protocol.
//!
//!
//! ## Login
//! The login protocol between a client and server also consists of four steps along with three messages, denoted as
//! `l1`, `l2`, and `l3`. The server is expected to have access to the a password file corresponding to an output
//! of the registration phase. The login protocol will execute successfully only if the same tuple combination of
//! (password, pepper, server public key) is presented as was used in the registration phase that produced the
//! password file that the server is testing against.
//! The login protocol between a client and server also consists of four steps along with three messages:
//! [CredentialRequest], [CredentialResponse], [CredentialFinalization]. The server is expected to have access to the password file
//! corresponding to an output of the registration phase. The login protocol will execute successfully only if the same password
//! was used in the registration phase that produced the password file that the server is testing against.
//!
//! 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`:
//! ### Client Login Start
//! In the first step of login, the client chooses as input a login password.
//! The client runs [ClientLogin::start] to produce an output consisting of
//! a [CredentialRequest] to be sent to the server, and
//! a [ClientLogin] which must be persisted on the client for the final step of client 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 rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ServerRegistration, ServerLogin, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! use opaque_ke::{ClientLogin, ClientLoginStartParameters};
//! let mut client_rng = OsRng;
//! let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! let client_login_start_result = ClientLogin::<Default>::start(
//! &mut client_rng,
//! b"password",
//! Some(b"pepper"),
//! &mut client_rng,
//! ClientLoginStartParameters::default(),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `l1` is sent to the server, and `client_state` must be persisted on the client for the final step of client login.
//!
//! In the second step (server login start), the server takes as input the `l1` message from the client, the server's
//! private key `server_kp.private()`, along with a serialized version of the password file, `password_file_bytes`, and
//! runs `ServerLogin::start` to produce `l2`:
//! ### Server Login Start
//! In the second step of login, the server takes as input
//! a [CredentialRequest] from the client,
//! the server's private key `server_kp.private()`, and
//! the password file output from registration.
//! The server runs [ServerLogin::start] to produce an output consisting of
//! a [CredentialResponse] which is returned to the client, and
//! a [ServerLogin] which must be persisted on the server for the final step of 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 rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # 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 password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! use std::convert::TryFrom;
//! let password_file =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! &password_file_bytes[..],
//! )?;
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! let mut server_rng = OsRng;
//! let (l2, server_state) =
//! ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `l2` is returned to the client, and `server_state` must be persisted on the server for the final step of server login.
//!
//! 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`:
//! ```
//! # 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 rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::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 password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::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 (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(
//! l2,
//! &server_kp.public(),
//! &mut client_rng,
//! let server_login_start_result = ServerLogin::start(
//! &mut server_rng,
//! password_file,
//! &server_kp.private(),
//! client_login_start_result.message,
//! ServerLoginStartParameters::default(),
//! )?;
//! assert_eq!(kd_key_registration, kd_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`.
//!
//! 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`:
//! ### Client Login Finish
//! In the third step of login, the client takes as input a [CredentialResponse] from the server.
//! The client runs [ClientLogin::finish] and produces an output consisting of
//! a [CredentialFinalization] to be sent to the server to complete the protocol,
//! the `session_key` sequence of bytes which will match the server's session key upon a successful 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 rand_core::{OsRng, RngCore};
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # 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 password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! # ServerRegistration::<Default>::deserialize(
//! # &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(
//! # l2,
//! # &server_kp.public(),
//! # &mut client_rng,
//! # )?;
//! let server_shared_secret = server_state.finish(l3)?;
//! assert_eq!(client_shared_secret, server_shared_secret);
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
//! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! If the protocol completes successfully, then the server obtains a `server_shared_secret` which is guaranteed to
//! match `client_shared_secret`. Otherwise, on failure, the `finish` algorithm outputs the error `InvalidLoginError`.
//!
//! ### Server Login Finish
//! In the fourth step of login, the server takes as input a [CredentialFinalization] from the client and runs [ServerLogin::finish] to
//! produce an output consisting of the `session_key` sequence of bytes which will match the client's session key upon a successful login.
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
//! # let client_login_finish_result = client_login_start_result.state.finish(
//! # server_login_start_result.message,
//! # ClientLoginFinishParameters::default(),
//! # )?;
//! let server_login_finish_result = server_login_start_result.state.finish(
//! client_login_finish_result.message,
//! )?;
//!
//! assert_eq!(
//! client_login_finish_result.session_key,
//! server_login_finish_result.session_key,
//! );
//! # Ok::<(), ProtocolError>(())
//! ```
//! If the protocol completes successfully, then the server obtains a `server_login_finish_result.session_key` which is guaranteed to
//! match `client_login_finish_result.session_key` (see the [Session Key](#session-key) section).
//! Otherwise, on failure, the [ServerLogin::finish] algorithm outputs the error [InvalidLoginError](errors::PakeError::InvalidLoginError).
//!
//! # Advanced Usage
//!
//! This implementation offers support for several optional features of OPAQUE, described below. They are not critical to the
//! execution of the main protocol, but can provide additional security benefits which can be suitable for various applications that rely on
//! OPAQUE for authentication.
//!
//! ## Session Key
//!
//! Upon a successful completion of the OPAQUE protocol (the client runs login with the same password used during registration),
//! the client and server have access to a session key, which is a pseudorandomly distributed 32-byte string which only the client
//! and server know. Multiple login runs using the same password for the same client will produce different session keys, distributed
//! as uniformly random strings. Thus, the session key can be used to establish a secure channel between the client and server.
//!
//! The session key can be accessed from the `session_key` field of [ClientLoginFinishResult] and [ServerLoginFinishResult]. See
//! the combination of [Client Login Finish](#client-login-finish) and [Server Login Finish](#server-login-finish) for example usage.
//!
//! ## Checking Server Consistency
//!
//! A [ClientLoginFinishResult] contains the `server_s_pk` field, which is represents the static public key of the server that is established
//! during the setup phase. This can be used by the client to verify the authenticity of the server it engages with during the login phase. In particular,
//! the client can check that the static public key of the server supplied during registration matches this field during login.
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! // During setup, server generates its static keypair
//! let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//!
//! // During setup or registration, the server transmits its static public key to the client
//! let server_s_pk = server_kp.public(); // obtained from the server
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
//!
//! // And then later, during login...
//! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
//! )?;
//!
//! // Check that the server's static public key matches what was obtained during
//! // setup or registration
//! assert_eq!(
//! &client_login_finish_result.server_s_pk,
//! server_s_pk,
//! );
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! Note that without this check over the consistency of the server's static public key, a malicious actor could impersonate the registration server if it were able to copy the password
//! file output during registration! Therefore, it is recommended to perform the following check in the application layer if the client can obtain a copy of the server's static
//! public key beforehand.
//!
//!
//! ## Export Key
//!
//! The export key is a pseudorandomly distributed 32-byte string output by both the
//! [Client Registration Finish](#client-registration-finish) and [Client Login Finish](#client-login-finish) steps.
//! The same export key string will be output by both functions only if the exact same password is passed to [ClientRegistration::start] and [ClientLogin::start].
//!
//! The export key retains as much secrecy as the password itself, and is similarly derived through an evaluation of the slow hashing function. Hence, only the parties which
//! know the password the client uses during registration and login can recover this secret, as it is never exposed to the server. As a result, the export key
//! can be used (separately from the OPAQUE protocol) to provide confidentiality and integrity to other data which only the client should be able to process.
//! For instance, if the server is expected to maintain any client-side secrets which require a password to access, then this export key can be used to encrypt
//! these secrets so that they remain hidden from the server (see [examples/digital_locker.rs](https://github.com/novifinancial/opaque-ke/blob/master/examples/digital_locker.rs)
//! for a working example).
//!
//! You can access the export key from the `export_key` field of [ClientRegistrationFinishResult] and [ClientLoginFinishResult].
//!
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! // During registration...
//! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng,
//! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::default()
//! )?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
//!
//! // And then later, during login...
//! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
//! )?;
//!
//! assert_eq!(
//! client_registration_finish_result.export_key,
//! client_login_finish_result.export_key,
//! );
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! ## Custom Identifiers
//!
//! Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered "username" which is sent to the server to
//! identify the corresponding password file established during registration. The server may also have an identifier corresponding to an entity (e.g. facebook.com).
//! By default, neither of these public identifiers need to be supplied to the OPAQUE protocol.
//!
//! But, for applications that wish to cryptographically bind these identities to
//! the registered password file as well as the session key output by the login phase, these custom identifiers can be specified through
//! [ClientRegistrationFinishParameters::WithIdentifiers] in [Client Registration Finish](#client-registration-finish):
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng,
//! server_registration_start_result.message,
//! ClientRegistrationFinishParameters::WithIdentifiers(
//! b"username".to_vec(),
//! b"facebook.com".to_vec(),
//! ),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! The same identifiers must also be supplied using [ServerLoginStartParameters::WithIdentifiers] in [Server Login Start](#server-login-start):
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
//! # let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
//! &mut server_rng,
//! password_file,
//! &server_kp.private(),
//! client_login_start_result.message,
//! ServerLoginStartParameters::WithIdentifiers(
//! b"username".to_vec(),
//! b"facebook.com".to_vec(),
//! ),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! as well as [ClientLoginFinishParameters::WithIdentifiers] in [Client Login Finish](#client-login-finish):
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
//! # slow_hash::NoOpHash,
//! # };
//! # use opaque_ke::ciphersuite::CipherSuite;
//! # struct Default;
//! # impl CipherSuite for Default {
//! # type Group = curve25519_dalek::ristretto::RistrettoPoint;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
//! # type Hash = sha2::Sha512;
//! # type SlowHash = opaque_ke::slow_hash::NoOpHash;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes[..],
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
//! let client_login_finish_result = client_login_start_result.state.finish(
//! server_login_start_result.message,
//! ClientLoginFinishParameters::WithIdentifiers(
//! b"username".to_vec(),
//! b"facebook.com".to_vec(),
//! ),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete
//! the protocol!
//!
//! ## Key Exchange Additional Data
//!
//! A key exchange protocol typically supports the passing of data between the two parties before the exchange is complete, so as to bind the integrity
//! and/or confidentiality of application-specific data to the security of the key exchange. During the login phase, the client and server can pass
//! additional data alongside the first two messages of the protocol, with confidential data being supported for the second message.
//!
//! The following three messages support passing of additional data:
//! - The first login message, where the client can populate [ClientLoginStartParameters::WithInfo] with plaintext additional data, and
//! the server can retrieve using the `plain_info` field of [ServerLoginStartResult].
//! - The second login message, where the server can populate [ServerLoginStartParameters::WithInfo] with confidential additional data,
//! and the client can retrieve using the `confidential_info` field of [ClientLoginFinishResult].
//!
//! For the second login message, the `WithInfoAndIdentifiers` variant can be used to specify these fields in addition to
//! [custom identifiers](#custom-identifiers), with the ordering of the fields as `WithInfoAndIdentifiers(confidential_info, username, server_name)`.
//!
//!
#![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;
// High-level API
mod opaque;
mod messages;
pub mod ciphersuite;
mod envelope;
pub mod hash;
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;
mod serialization;
#[cfg(test)]
mod tests;
// Exports
pub use rand;
pub use crate::messages::{
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload,
};
pub use crate::opaque::{ClientLogin, ClientRegistration, ServerLogin, ServerRegistration};
pub use crate::opaque::{
ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistrationFinishParameters,
ServerLoginStartParameters,
};
pub use crate::opaque::{
ClientLoginFinishResult, ClientLoginStartResult, ClientRegistrationFinishResult,
ClientRegistrationStartResult, ServerLoginFinishResult, ServerLoginStartResult,
ServerRegistrationStartResult,
};
+225
View File
@@ -0,0 +1,225 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
//! curve point
use crate::errors::{InternalPakeError, ProtocolError};
use crate::group::Group;
use crate::hash::Hash;
use crate::serialization::i2osp;
use curve25519_dalek::ristretto::RistrettoPoint;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// A subtrait of Group specifying how to hash a password into a point
pub trait GroupWithMapToCurve: Group {
/// The ciphersuite identifier as dictated by
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError>;
/// Generates the contextString parameter as defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
fn get_context_string(mode: u8) -> Result<Vec<u8>, ProtocolError> {
Ok([i2osp(mode as usize, 1)?, i2osp(Self::SUITE_ID, 2)?].concat())
}
}
impl GroupWithMapToCurve for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
// Implements the hash_to_ristretto255() function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError> {
let uniform_bytes =
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
Ok(<Self as Group>::hash_to_curve(
&GenericArray::clone_from_slice(&uniform_bytes[..]),
))
}
}
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
let additive = (x % y != 0) as usize;
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalPakeError> {
if x.len() != y.len() {
return Err(InternalPakeError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
}
// Corresponds to the expand_message_xmd() function defined in
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
pub(crate) fn expand_message_xmd<H: Hash>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, ProtocolError> {
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
let ell = div_ceil(len_in_bytes, b_in_bytes);
if ell > 255 {
return Err(InternalPakeError::HashToCurveError.into());
}
let dst_prime = [dst, &i2osp(dst.len(), 1)?].concat();
let z_pad = i2osp(0, r_in_bytes)?;
let l_i_b_str = i2osp(len_in_bytes, 2)?;
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1)?, &dst_prime].concat();
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1)?);
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
}
#[cfg(test)]
mod tests {
struct Params {
msg: &'static str,
len_in_bytes: usize,
uniform_bytes: &'static str,
}
#[test]
fn test_expand_message_xmd() {
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
let test_vectors: Vec<Params> = vec![
Params {
msg: "",
len_in_bytes: 0x20,
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
92181df928fca88",
},
Params {
msg: "abc",
len_in_bytes: 0x20,
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
5a1151775fe4da7",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x20,
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x20,
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
1716b1b964e1c642",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x20,
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
350db46f429b771b",
},
Params {
msg: "",
len_in_bytes: 0x80,
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
},
Params {
msg: "abc",
len_in_bytes: 0x80,
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x80,
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x80,
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x80,
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
}
}
+317
View File
@@ -0,0 +1,317 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Contains the messages used for OPAQUE
use crate::{
ciphersuite::CipherSuite,
envelope::Envelope,
errors::{
utils::{check_slice_size, check_slice_size_atleast},
PakeError, ProtocolError,
},
group::Group,
key_exchange::traits::{KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytesExt},
};
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use std::convert::TryFrom;
// Messages
// =========
/// The message sent by the client to the server, to initiate registration
pub struct RegistrationRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::Group,
}
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes
#[cfg(test)]
pub fn get_alpha_for_testing(&self) -> CS::Group {
self.alpha
}
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
fn clone(&self) -> Self {
Self { alpha: self.alpha }
}
}
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.alpha.to_arr().to_vec()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice = check_slice_size(&input, elem_len, "first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(checked_slice);
let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
Ok(Self { alpha })
}
}
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) beta: CS::Group,
/// Server's static public key
pub(crate) server_s_pk: Vec<u8>,
}
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[self.beta.to_arr().to_vec(), self.server_s_pk.clone()].concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size(&input, elem_len + key_len, "registration_response_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let beta = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
// Ensure that public key is valid
let server_s_pk =
KeyPair::<CS::Group>::check_public_key(Key::from_bytes(&checked_slice[elem_len..])?)?;
Ok(Self {
beta,
server_s_pk: server_s_pk.to_arr().to_vec(),
})
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the reflection
/// error case
pub fn set_beta_for_testing(&self, new_beta: CS::Group) -> Self {
Self {
beta: new_beta,
server_s_pk: self.server_s_pk.clone(),
}
}
}
/// The final message from the client, containing sealed cryptographic
/// identifiers
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
pub(crate) envelope: Envelope<CS::Hash>,
/// The user's public key
pub(crate) client_s_pk: Key,
}
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
self.client_s_pk.to_arr().to_vec(),
self.envelope.serialize(),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice = check_slice_size_atleast(&input, key_len, "registration_upload_bytes")?;
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[key_len..])?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.into());
}
Ok(Self {
envelope,
client_s_pk: KeyPair::<CS::Group>::check_public_key(Key::from_bytes(
&checked_slice[..key_len],
)?)?,
})
}
}
/// The message sent by the user to the server, to initiate registration
pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::Group,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message,
}
impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
let mut credential_request: Vec<u8> = Vec::new();
credential_request.extend_from_slice(&self.alpha.to_arr());
credential_request.extend_from_slice(&self.ke1_message.to_bytes()?);
Ok(credential_request)
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice =
check_slice_size_atleast(&input, elem_len, "login_first_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(
&checked_slice[elem_len..],
)?;
Ok(Self { alpha, ke1_message })
}
/// Only used for testing purposes
#[cfg(test)]
pub fn get_alpha_for_testing(&self) -> CS::Group {
self.alpha
}
}
/// The answer sent by the server to the user, upon reception of the
/// login attempt
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) beta: CS::Group,
pub(crate) server_s_pk: Key,
/// the user's sealed information,
pub(crate) envelope: Envelope<CS::Hash>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
}
impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
Ok([
Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope),
self.ke2_message.to_bytes()?,
]
.concat())
}
pub(crate) fn serialize_without_ke(
beta: &CS::Group,
server_s_pk: &Key,
envelope: &Envelope<CS::Hash>,
) -> Vec<u8> {
[
&beta.to_arr(),
&server_s_pk.to_arr()[..],
&envelope.to_bytes(),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let key_len = <Key as SizedBytes>::Len::to_usize();
let checked_slice =
check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let beta_bytes = &checked_slice[..elem_len];
let arr = GenericArray::from_slice(beta_bytes);
let beta = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
let (envelope, remainder) =
Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len + key_len..])?;
let ke2_message_size = CS::KeyExchange::ke2_message_size();
let checked_remainder =
check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
let ke2_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::try_from(
&checked_remainder,
)?;
Ok(Self {
beta,
server_s_pk,
envelope,
ke2_message,
})
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the reflection
/// error case
pub fn set_beta_for_testing(&self, new_beta: CS::Group) -> Self {
Self {
beta: new_beta,
server_s_pk: self.server_s_pk.clone(),
envelope: self.envelope.clone(),
ke2_message: self.ke2_message.clone(),
}
}
}
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message,
}
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
self.ke3_message.to_bytes()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let ke3_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(input)?;
Ok(Self { ke3_message })
}
}
+709 -653
View File
File diff suppressed because it is too large Load Diff
+116 -77
View File
@@ -3,108 +3,151 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{errors::InternalPakeError, group::Group};
use generic_array::{typenum::U64, GenericArray};
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use crate::{
errors::ProtocolError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
serialization::serialize,
};
use digest::Digest;
use generic_array::GenericArray;
use rand::{CryptoRng, RngCore};
// 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,
/// Used to store the OPRF input and blinding factor
pub struct Token<Grp: Group> {
pub(crate) data: Vec<u8>,
pub(crate) blind: Grp::Scalar,
}
static STR_VOPRF: &[u8] = b"VOPRF06-HashToGroup-";
static STR_VOPRF_FINALIZE: &[u8] = b"VOPRF06-Finalize-";
static MODE_BASE: u8 = 0x00;
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
/// an HKDF computation.
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: Group<UniformBytesLen = U64>>(
pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
pepper: Option<&[u8]>,
blinding_factor_rng: &mut R,
) -> Result<OprfClientBytes<G>, InternalPakeError> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let blinding_factor = G::random_scalar(blinding_factor_rng);
let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor;
Ok(OprfClientBytes {
alpha,
blinding_factor,
})
) -> Result<(Token<G>, G), ProtocolError> {
// Choose a random scalar that must be non-zero
let blind = G::random_nonzero_scalar(blinding_factor_rng);
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)?].concat();
let mapped_point = G::map_to_curve::<H>(input, &dst)?;
let blind_token = mapped_point * &blind;
Ok((
Token {
data: input.to_vec(),
blind,
},
blind_token,
))
}
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the server (who holds the OPRF key) to the client.
pub(crate) fn generate_oprf2<G: Group>(
point: G,
oprf_key: &G::Scalar,
) -> Result<G, InternalPakeError> {
Ok(point * oprf_key)
pub(crate) fn evaluate<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
point * oprf_key
}
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
/// the client unblinds the server's message.
pub(crate) fn generate_oprf3<G: Group>(
pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
point: G,
blinding_factor: &G::Scalar,
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, InternalPakeError> {
let unblinded = point * &G::scalar_invert(&blinding_factor);
let ikm: Vec<u8> = [&unblinded.to_bytes(), input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
Ok(prk)
blind: &G::Scalar,
evaluated_element: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
let unblinded_element = evaluated_element * &G::scalar_invert(blind);
finalize_after_unblind::<G, H>(input, unblinded_element)
}
// Tests
// =====
fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
unblinded_element: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)?].concat();
let hash_input = [
serialize(input, 2)?,
serialize(&unblinded_element.to_arr().to_vec(), 2)?,
serialize(&finalize_dst, 2)?,
]
.concat();
Ok(<H as Digest>::digest(&hash_input))
}
////////////////////////
// Benchmarking shims //
////////////////////////
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), ProtocolError> {
blind::<R, G, H>(input, blinding_factor_rng)
}
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
evaluate(point, oprf_key)
}
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn finalize_shim<G: GroupWithMapToCurve, H: Hash>(
token: &Token<G>,
point: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
finalize::<G, H>(&token.data, &token.blind, point)
}
///////////
// Tests //
// ===== //
///////////
#[cfg(test)]
mod tests {
use super::*;
use crate::group::Group;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::{arr, arr_impl, GenericArray};
use hkdf::Hkdf;
use rand_core::OsRng;
use generic_array::{arr, GenericArray};
use rand::rngs::OsRng;
use sha2::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));
fn prf(input: &[u8], oprf_key: &[u8; 32]) -> GenericArray<u8, <Sha512 as Digest>::OutputSize> {
let dst = [
STR_VOPRF,
&RistrettoPoint::get_context_string(MODE_BASE).unwrap(),
]
.concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(input, &dst).unwrap();
let scalar =
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar;
let ikm: Vec<u8> = [res.to_bytes().as_slice(), &input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
prk
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res).unwrap()
}
#[test]
fn oprf_retrieval() -> Result<(), InternalPakeError> {
fn oprf_retrieval() {
let input = b"hunter2";
let mut rng = OsRng;
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
let salt_bytes = arr![
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng).unwrap();
let oprf_key_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes)?;
let beta = generate_oprf2::<RistrettoPoint>(alpha, &salt)?;
let res = generate_oprf3::<RistrettoPoint>(input, beta, &blinding_factor)?;
let res2 = prf(&input[..], &salt.as_bytes());
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes).unwrap();
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
let res =
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta).unwrap();
let res2 = prf(&input[..], &oprf_key.as_bytes());
assert_eq!(res, res2);
Ok(())
}
#[test]
@@ -112,22 +155,18 @@ mod tests {
let mut rng = OsRng;
let mut input = vec![0u8; 64];
rng.fill_bytes(&mut input);
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
let res = generate_oprf3::<RistrettoPoint>(&input, alpha, &blinding_factor).unwrap();
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
let res =
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha).unwrap();
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let mut curve_input: Vec<u8> = Vec::new();
curve_input.extend_from_slice(&hashed_input);
curve_input.extend_from_slice(&[0u8; 32]);
let point = RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&curve_input);
let mut ikm: Vec<u8> = Vec::new();
ikm.extend_from_slice(&point.to_bytes());
ikm.extend_from_slice(&input);
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
let dst = [
STR_VOPRF,
&RistrettoPoint::get_context_string(MODE_BASE).unwrap(),
]
.concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point).unwrap();
assert_eq!(res, prk);
assert_eq!(res, res2);
}
}
-202
View File
@@ -1,202 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::errors::{utils::check_slice_size, InternalPakeError, PakeError};
use aead::{Aead, NewAead};
use generic_array::{typenum::Unsigned, GenericArray};
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
/// This trait encapsulates an encryption scheme that satisfies random-key robustness (RKR), which is implemented
/// through encrypt-then-HMAC -- see Section 3.1.1 of
/// https://www.ietf.org/id/draft-krawczyk-cfrg-opaque-03.txt
/// We require an Aead implementation with a 32-bit key size, since we
/// will derive the symmetric key from pw using Sha256
pub trait RKRCipher: Sized {
/// The requirement of KeySize = U32 is so that we can use a 32-bit hash
/// for key derivation form the user's password
type AEAD: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead;
// Required members
fn new(
aead_output: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
) -> Self;
fn aead_output(&self) -> &Vec<u8>;
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize>;
fn nonce(&self) -> &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>;
fn to_bytes(&self) -> Vec<u8>;
// Provided members for enc / dec
fn key_len() -> usize {
<Self::AEAD as NewAead>::KeySize::to_usize()
}
fn nonce_size() -> usize {
<Self::AEAD as Aead>::NonceSize::to_usize()
}
fn hmac_size() -> usize {
<Sha256 as Digest>::OutputSize::to_usize()
}
/// This estimates the size of the ciphertext once we encode —very specifically—
/// the payload we have planned for the protocol's env_u
fn ciphertest_size() -> usize {
Self::key_len() + <Self::AEAD as Aead>::TagSize::to_usize() + Self::hmac_size()
}
fn rkr_with_nonce_size() -> usize {
Self::ciphertest_size() + Self::nonce_size()
}
/// The format of the output ciphertext here is:
/// encryption_output | tag | hmac | nonce
/// variable length | AEAD_TAG_SIZE bytes | HMAC_SIZE bytes | NONCE_SIZE bytes
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes = check_slice_size(&bytes[..], Self::rkr_with_nonce_size(), "bytes")?;
let nonce_start = bytes.len() - Self::nonce_size();
let hmac_start = nonce_start - Self::hmac_size();
Ok(<Self as RKRCipher>::new(
bytes[..hmac_start].to_vec(),
GenericArray::from_slice(&checked_bytes[hmac_start..nonce_start]),
GenericArray::from_slice(&checked_bytes[nonce_start..]),
))
}
/// Encrypt with AEAD. Note that this encryption scheme needs to satisfy "random-key robustness" (RKR).
fn encrypt<R: RngCore + CryptoRng>(
encryption_key: &[u8],
hmac_key: &[u8],
plaintext: &[u8],
aad: &[u8],
rng: &mut R,
) -> Result<Self, PakeError> {
let mut nonce = vec![0u8; Self::nonce_size()];
rng.fill_bytes(&mut nonce);
let gen_nonce = GenericArray::from_slice(&nonce[..]);
let ciphertext = <Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key))
.encrypt(
GenericArray::from_slice(&nonce),
aead::Payload {
msg: &plaintext,
aad: &aad,
},
)
.map_err(|_| PakeError::EncryptionError)?;
let mut mac =
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
mac.input(&ciphertext);
Ok(<Self as RKRCipher>::new(
ciphertext,
&mac.result().code(),
gen_nonce,
))
}
fn decrypt(
&self,
encryption_key: &[u8],
hmac_key: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, PakeError> {
let mut mac =
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
mac.input(self.aead_output());
if mac.verify(self.hmac()).is_err() {
return Err(PakeError::DecryptionHmacError);
}
Aead::decrypt(
&<Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key)),
self.nonce(),
aead::Payload {
msg: self.aead_output(),
aad: &aad,
},
)
.map_err(|_| PakeError::DecryptionError)
}
}
/// This struct is a straightforward instantiation of the trait separating the
/// three components in Vecs
pub struct RKRCiphertext<T> {
aead_choice: std::marker::PhantomData<T>,
aead_output: Vec<u8>,
hmac: Vec<u8>,
nonce: Vec<u8>,
}
impl<T: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead> RKRCipher for RKRCiphertext<T> {
type AEAD = T;
fn new(
aead_output: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
) -> Self {
Self {
aead_choice: std::marker::PhantomData,
aead_output,
hmac: hmac.to_vec(),
nonce: nonce.to_vec(),
}
}
fn aead_output(&self) -> &Vec<u8> {
&self.aead_output
}
fn to_bytes(&self) -> Vec<u8> {
[&self.aead_output[..], &self.hmac[..], &self.nonce[..]].concat()
}
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize> {
GenericArray::from_slice(&self.hmac[..])
}
fn nonce(&self) -> &GenericArray<u8, <T as Aead>::NonceSize> {
GenericArray::from_slice(&self.nonce[..])
}
}
#[cfg(test)]
mod tests {
use super::*;
use chacha20poly1305::ChaCha20Poly1305;
use rand_core::OsRng;
#[test]
fn encrypt_and_decrypt() {
let mut rng = OsRng;
let mut encryption_key = [0u8; 32];
rng.fill_bytes(&mut encryption_key);
let mut hmac_key = [0u8; 32];
rng.fill_bytes(&mut hmac_key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
&encryption_key,
&hmac_key,
&msg,
b"",
&mut rng,
)
.unwrap();
let decrypted = ciphertext.decrypt(&encryption_key, &hmac_key, b"").unwrap();
assert_eq!(&msg.to_vec(), &decrypted);
}
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::errors::PakeError;
// Corresponds to the I2OSP() function from RFC8017
pub(crate) fn i2osp(input: usize, length: usize) -> Result<Vec<u8>, PakeError> {
let sizeof_usize = std::mem::size_of::<usize>();
// Check if input >= 256^length
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
return Err(PakeError::SerializationError);
}
if length <= sizeof_usize {
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
}
let mut output = vec![0u8; length];
output.splice(
length - sizeof_usize..length,
input.to_be_bytes().iter().cloned(),
);
Ok(output)
}
// Corresponds to the OS2IP() function from RFC8017
pub(crate) fn os2ip(input: &[u8]) -> Result<usize, PakeError> {
if input.len() > std::mem::size_of::<usize>() {
return Err(PakeError::SerializationError);
}
let mut output_array = [0u8; std::mem::size_of::<usize>()];
output_array[std::mem::size_of::<usize>() - input.len()..].copy_from_slice(input);
Ok(usize::from_be_bytes(output_array))
}
// Computes I2OSP(len(input), max_bytes) || input
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result<Vec<u8>, PakeError> {
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
}
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
// (input, remainder)
pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec<u8>, Vec<u8>), PakeError> {
if size_bytes > std::mem::size_of::<usize>() || input.len() < size_bytes {
return Err(PakeError::SerializationError);
}
let size = os2ip(&input[..size_bytes])?;
if size_bytes + size > input.len() {
return Err(PakeError::SerializationError);
}
Ok((
input[size_bytes..size_bytes + size].to_vec(),
input[size_bytes + size..].to_vec(),
))
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod unit_tests {
use super::*;
// Test the error condition for I2OSP
#[test]
fn test_i2osp_err_check() {
assert!(i2osp(0, 1).is_ok());
assert!(i2osp(255, 1).is_ok());
assert!(i2osp(256, 1).is_err());
assert!(i2osp(257, 1).is_err());
assert!(i2osp(256 * 256 - 1, 2).is_ok());
assert!(i2osp(256 * 256, 2).is_err());
assert!(i2osp(256 * 256 + 1, 2).is_err());
}
}
+457
View File
@@ -0,0 +1,457 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
envelope::{Envelope, InnerEnvelopeMode},
errors::*,
group::Group,
key_exchange::{
traits::{KeyExchange, ToBytes},
tripledh::{NonceLen, TripleDH},
},
opaque::*,
serialization::{i2osp, os2ip, serialize},
*,
};
use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes;
use proptest::{collection::vec, prelude::*};
use rand::{rngs::OsRng, RngCore};
use sha2::Digest;
use std::convert::TryFrom;
struct Default;
impl CipherSuite for Default {
type Group = RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = crate::slow_hash::NoOpHash;
}
const MAX_INFO_LENGTH: usize = 10;
const MAC_SIZE: usize = 64; // Because of SHA512
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];
let mut hasher = sha2::Sha512::new();
hasher.update(&random_bits[..]);
bits.copy_from_slice(&hasher.finalize());
RistrettoPoint::from_uniform_bytes(&bits)
}
#[test]
fn client_registration_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let elem = <RistrettoPoint as Group>::base_point() * sc;
// serialization order: scalar, password, group element
let bytes: Vec<u8> = [&elem.to_arr(), &sc.as_bytes()[..], &pw[..]].concat();
let reg = ClientRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn server_registration_roundtrip() {
// If we don't have envelope and client_pk, the server registration just
// contains the prf key
let mut rng = OsRng;
let oprf_key = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(oprf_key.as_bytes());
let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, oprf_bytes);
let mut ciphertext = [0u8; 32];
rng.fill_bytes(&mut ciphertext);
// Construct a mock envelope
let mut mock_envelope_bytes = Vec::new();
mock_envelope_bytes.extend_from_slice(&[1; 1]); // mode = 1
mock_envelope_bytes.extend_from_slice(&vec![0; NonceLen::to_usize()]); // empty nonce
mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
let mock_client_kp = Default::generate_random_keypair(&mut rng);
// serialization order: oprf_key, public key, envelope
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(oprf_key.as_bytes());
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
bytes.extend_from_slice(&mock_envelope_bytes);
let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn registration_request_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_arr().to_vec();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap();
let r1_bytes = r1.serialize();
assert_eq!(input, r1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(
match RegistrationRequest::<Default>::deserialize(identity_bytes.as_slice()) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
}
);
}
#[test]
fn registration_response_roundtrip() {
let pt = random_ristretto_point();
let beta_bytes = pt.to_arr();
let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng);
let pubkey_bytes = skp.public().to_arr();
let mut input = Vec::new();
input.extend_from_slice(beta_bytes.as_slice());
input.extend_from_slice(&pubkey_bytes.as_slice());
let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap();
let r2_bytes = r2.serialize();
assert_eq!(input, r2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match RegistrationResponse::<Default>::deserialize(
&[identity_bytes, pubkey_bytes.to_vec()].concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
}
#[test]
fn registration_upload_roundtrip() {
let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng);
let pubkey_bytes = skp.public().to_arr();
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
&mut rng,
&key,
&msg,
&pubkey_bytes,
InnerEnvelopeMode::Base,
)
.unwrap();
let envelope_bytes = envelope.serialize();
let mut input = Vec::new();
input.extend_from_slice(&pubkey_bytes[..]);
input.extend_from_slice(&envelope_bytes);
let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap();
let r3_bytes = r3.serialize();
assert_eq!(input, r3_bytes);
}
#[test]
fn credential_request_roundtrip() {
let mut rng = OsRng;
let alpha = random_ristretto_point();
let alpha_bytes = alpha.to_arr().to_vec();
let client_e_kp = Default::generate_random_keypair(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce);
let mut info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut info);
let ke1m: Vec<u8> = [
&client_nonce[..],
&serialize(&info.to_vec(), 2).unwrap(),
&client_e_kp.public(),
]
.concat();
let mut input = Vec::new();
input.extend_from_slice(&alpha_bytes);
input.extend_from_slice(&ke1m[..]);
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap();
let l1_bytes = l1.serialize().unwrap();
assert_eq!(input, l1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialRequest::<Default>::deserialize(
&[identity_bytes, ke1m.to_vec()].concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
}
#[test]
fn credential_response_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_arr().to_vec();
let mut rng = OsRng;
let skp = Default::generate_random_keypair(&mut rng);
let pubkey_bytes = skp.public().to_arr();
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
&mut rng,
&key,
&msg,
&pubkey_bytes,
InnerEnvelopeMode::Base,
)
.unwrap();
let server_e_kp = Default::generate_random_keypair(&mut rng);
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce);
let mut e_info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut e_info);
let ke2m: Vec<u8> = [
&server_nonce[..],
&server_e_kp.public(),
&serialize(&e_info.to_vec(), 2).unwrap(),
&mac[..],
]
.concat();
let serialized_envelope = envelope.serialize();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
input.extend_from_slice(&pubkey_bytes.as_slice());
input.extend_from_slice(&serialized_envelope);
input.extend_from_slice(&ke2m[..]);
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
let l2_bytes = l2.serialize().unwrap();
assert_eq!(input, l2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialResponse::<Default>::deserialize(
&[
identity_bytes,
pubkey_bytes.to_vec(),
serialized_envelope,
ke2m.to_vec()
]
.concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
}
#[test]
fn login_third_message_roundtrip() {
let mut rng = OsRng;
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
let input: Vec<u8> = [&mac[..]].concat();
let l3 = CredentialFinalization::<Default>::deserialize(&input).unwrap();
let l3_bytes = l3.serialize().unwrap();
assert_eq!(input, l3_bytes);
}
#[test]
fn client_login_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let client_e_kp = Default::generate_random_keypair(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce);
let serialized_credential_request = b"serialized credential_request".to_vec();
let l1_data = [client_e_kp.private().to_arr().to_vec(), client_nonce].concat();
// serialization order: scalar, credential_request, ke1_state, password
let bytes: Vec<u8> = [
&sc.as_bytes()[..],
&serialize(&serialized_credential_request, 2).unwrap(),
&serialize(&l1_data, 2).unwrap(),
&pw[..],
]
.concat();
let reg = ClientLogin::<Default>::deserialize(&bytes[..]).unwrap();
let reg_bytes = reg.serialize().unwrap();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn ke1_message_roundtrip() {
let mut rng = OsRng;
let client_e_kp = Default::generate_random_keypair(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce);
let mut info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut info);
let ke1m: Vec<u8> = [
&client_nonce[..],
&serialize(&info.to_vec(), 2).unwrap(),
&client_e_kp.public(),
]
.concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::try_from(&ke1m[..])
.unwrap();
let reg_bytes = reg.to_bytes().unwrap();
assert_eq!(reg_bytes, ke1m);
}
#[test]
fn ke2_message_roundtrip() {
let mut rng = OsRng;
let server_e_kp = Default::generate_random_keypair(&mut rng);
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce);
let mut e_info = [0u8; MAX_INFO_LENGTH];
rng.fill_bytes(&mut e_info);
let ke2m: Vec<u8> = [
&server_nonce[..],
&server_e_kp.public(),
&serialize(&e_info.to_vec(), 2).unwrap(),
&mac[..],
]
.concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::try_from(&ke2m[..])
.unwrap();
let reg_bytes = reg.to_bytes().unwrap();
assert_eq!(reg_bytes, ke2m);
}
#[test]
fn ke3_message_roundtrip() {
let mut rng = OsRng;
let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac);
let ke3m: Vec<u8> = [&mac[..]].concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::try_from(&ke3m[..])
.unwrap();
let reg_bytes = reg.to_bytes().unwrap();
assert_eq!(reg_bytes, ke3m);
}
proptest! {
#[test]
fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..std::mem::size_of::<usize>())) {
assert_eq!(i2osp(os2ip(&bytes)?, bytes.len())?, bytes);
}
#[test]
fn test_nocrash_registration_request(bytes in vec(any::<u8>(), 0..200)) {
RegistrationRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_response(bytes in vec(any::<u8>(), 0..200)) {
RegistrationResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_registration_upload(bytes in vec(any::<u8>(), 0..200)) {
RegistrationUpload::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_request(bytes in vec(any::<u8>(), 0..500)) {
CredentialRequest::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_response(bytes in vec(any::<u8>(), 0..500)) {
CredentialResponse::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_credential_finalization(bytes in vec(any::<u8>(), 0..500)) {
CredentialFinalization::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
ClientRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
ServerRegistration::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
ClientLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
ServerLogin::<Default>::deserialize(&bytes[..]).map_or(true, |_| true);
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Trait specifying a slow hashing function
use crate::{errors::InternalPakeError, hash::Hash};
use digest::Digest;
#[cfg(feature = "slow-hash")]
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// Used for the slow hashing function in OPAQUE
pub trait SlowHash<D: Hash> {
/// Computes the slow hashing function
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError>;
}
/// A no-op hash which simply returns its input
pub struct NoOpHash;
impl<D: Hash> SlowHash<D> for NoOpHash {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
Ok(input.to_vec())
}
}
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_LOG_N: u8 = 15u8;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_R: u32 = 8u32;
#[cfg(feature = "slow-hash")]
const DEFAULT_SCRYPT_P: u32 = 1u32;
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
let params =
scrypt::ScryptParams::new(DEFAULT_SCRYPT_LOG_N, DEFAULT_SCRYPT_R, DEFAULT_SCRYPT_P)
.map_err(|_| InternalPakeError::SlowHashError)?;
let mut output = vec![0u8; <D as Digest>::OutputSize::to_usize()];
scrypt::scrypt(&input, &[], &params, &mut output)
.map_err(|_| InternalPakeError::SlowHashError)?;
Ok(output)
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,7 +3,7 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use rand_core::{CryptoRng, Error, RngCore};
use rand::{CryptoRng, Error, RngCore};
use std::cmp::min;
/// A simple implementation of `RngCore` for testing purposes.
+3 -2
View File
@@ -3,6 +3,7 @@
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
mod full_test;
pub mod mock_rng;
mod opaque_ke_test;
mod serialization;
mod opaque_test_vectors;
mod voprf_test_vectors;
-577
View File
@@ -1,577 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::*,
group::Group,
key_exchange::NONCE_LEN,
keypair::{Key, KeyPair, SignalKeyPair},
opaque::*,
tests::mock_rng::CycleRng,
};
use aes_gcm::Aes256Gcm;
use curve25519_dalek::edwards::EdwardsPoint;
use rand_core::{OsRng, RngCore};
use serde_json::Value;
use std::convert::TryFrom;
// Tests
// =====
pub struct TestVectorParameters {
pub client_s_pk: Vec<u8>,
pub client_s_sk: Vec<u8>,
pub client_e_pk: Vec<u8>,
pub client_e_sk: Vec<u8>,
pub server_s_pk: Vec<u8>,
pub server_s_sk: Vec<u8>,
pub server_e_pk: Vec<u8>,
pub server_e_sk: Vec<u8>,
pub password: Vec<u8>,
pub blinding_factor_raw: Vec<u8>,
pub blinding_factor: Vec<u8>,
pub pepper: Vec<u8>,
pub oprf_key: Vec<u8>,
pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
pub r1: Vec<u8>,
pub r2: Vec<u8>,
pub r3: Vec<u8>,
pub l1: Vec<u8>,
pub l2: Vec<u8>,
pub l3: Vec<u8>,
client_registration_state: Vec<u8>,
server_registration_state: Vec<u8>,
client_login_state: Vec<u8>,
server_login_state: Vec<u8>,
pub password_file: Vec<u8>,
pub opaque_key: Vec<u8>,
pub shared_secret: Vec<u8>,
}
static TEST_VECTOR: &str = r#"
{
"client_s_pk": "f7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
"client_s_sk": "601ed276a42ec5795b3471f1a64e312f192e17ff252ce6053c8ecaf210138273",
"client_e_pk": "57260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
"client_e_sk": "e89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea68",
"server_s_pk": "a2b4e12d0621ebfb2631e00f5c872ab749e1a33915f16fb11203658b2189cc5e",
"server_s_sk": "90b6ca2ea8a37306060c7cd0998d4cdae59e972af7760312f7cf77099e78f940",
"server_e_pk": "64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073",
"server_e_sk": "883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f",
"password": "70617373776f7264",
"blinding_factor_raw": "b85e0df2ad0495771edf09a04b1073045e6472e2f86a41e9bab3143ebfb8eb08a3462503eb3750bf006dc82c93b37e07cdf3768018c22b431cf5146a9caeda1c",
"blinding_factor": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f",
"pepper": "706570706572",
"oprf_key": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
"envelope_nonce": "c87e44792a9dfd8858db676e",
"client_nonce": "1f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23",
"server_nonce": "d448cb1f58c38605fc29069ac688ec9c667c99d0316b38cd1b2609c1bc14aa90",
"r1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd897",
"r2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb",
"r3": "374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676ef7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
"l1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd8971f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a2357260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
"l2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073d81a1104fbd599ef56228bdbe9bf7be4a38ae907a8717ca0883b9d69b2efc529",
"l3": "a01332643e8aa7113f6f160205a9b3bd0705f3b33d8e4ea8eab9eae6685a6adb",
"client_registration_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f70617373776f7264",
"client_login_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60fe89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea681f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23dd1a7c2b4e9f9be94bd36f3b6c7f23aa9f1e6b3fda9030412a918d1288b4af1970617373776f7264",
"server_registration_state": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
"server_login_state": "809f95143f8f7fc1d0b42f578a83f714f58cfd96d9499aacee730ad296b37b19c18c903396e85da607d02542d4d07456e5357ff2e2eade3aaa42e532d4e9364f66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d",
"password_file": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0bf7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e",
"opaque_key": "682f2868a3e1460fed5a16767bd8778c33b4aecac6607270f848aa61c95a1a68",
"shared_secret": "66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d"
}
"#;
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
TestVectorParameters {
client_s_pk: decode(&values, "client_s_pk").unwrap(),
client_s_sk: decode(&values, "client_s_sk").unwrap(),
client_e_pk: decode(&values, "client_e_pk").unwrap(),
client_e_sk: decode(&values, "client_e_sk").unwrap(),
server_s_pk: decode(&values, "server_s_pk").unwrap(),
server_s_sk: decode(&values, "server_s_sk").unwrap(),
server_e_pk: decode(&values, "server_e_pk").unwrap(),
server_e_sk: decode(&values, "server_e_sk").unwrap(),
password: decode(&values, "password").unwrap(),
blinding_factor_raw: decode(&values, "blinding_factor_raw").unwrap(),
blinding_factor: decode(&values, "blinding_factor").unwrap(),
pepper: decode(&values, "pepper").unwrap(),
oprf_key: decode(&values, "oprf_key").unwrap(),
envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
client_nonce: decode(&values, "client_nonce").unwrap(),
server_nonce: decode(&values, "server_nonce").unwrap(),
r1: decode(&values, "r1").unwrap(),
r2: decode(&values, "r2").unwrap(),
r3: decode(&values, "r3").unwrap(),
l1: decode(&values, "l1").unwrap(),
l2: decode(&values, "l2").unwrap(),
l3: decode(&values, "l3").unwrap(),
client_registration_state: decode(&values, "client_registration_state").unwrap(),
client_login_state: decode(&values, "client_login_state").unwrap(),
server_registration_state: decode(&values, "server_registration_state").unwrap(),
server_login_state: decode(&values, "server_login_state").unwrap(),
password_file: decode(&values, "password_file").unwrap(),
opaque_key: decode(&values, "opaque_key").unwrap(),
shared_secret: decode(&values, "shared_secret").unwrap(),
}
}
fn stringify_test_vectors(p: &TestVectorParameters) -> String {
let mut s = String::new();
s.push_str("{\n");
s.push_str(format!("\"client_s_pk\": \"{}\",\n", hex::encode(&p.client_s_pk)).as_str());
s.push_str(format!("\"client_s_sk\": \"{}\",\n", hex::encode(&p.client_s_sk)).as_str());
s.push_str(format!("\"client_e_pk\": \"{}\",\n", hex::encode(&p.client_e_pk)).as_str());
s.push_str(format!("\"client_e_sk\": \"{}\",\n", hex::encode(&p.client_e_sk)).as_str());
s.push_str(format!("\"server_s_pk\": \"{}\",\n", hex::encode(&p.server_s_pk)).as_str());
s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
s.push_str(
format!(
"\"blinding_factor_raw\": \"{}\",\n",
hex::encode(&p.blinding_factor_raw)
)
.as_str(),
);
s.push_str(
format!(
"\"blinding_factor\": \"{}\",\n",
hex::encode(&p.blinding_factor)
)
.as_str(),
);
s.push_str(format!("\"pepper\": \"{}\",\n", hex::encode(&p.pepper)).as_str());
s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
s.push_str(
format!(
"\"envelope_nonce\": \"{}\",\n",
hex::encode(&p.envelope_nonce)
)
.as_str(),
);
s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
s.push_str(format!("\"r1\": \"{}\",\n", hex::encode(&p.r1)).as_str());
s.push_str(format!("\"r2\": \"{}\",\n", hex::encode(&p.r2)).as_str());
s.push_str(format!("\"r3\": \"{}\",\n", hex::encode(&p.r3)).as_str());
s.push_str(format!("\"l1\": \"{}\",\n", hex::encode(&p.l1)).as_str());
s.push_str(format!("\"l2\": \"{}\",\n", hex::encode(&p.l2)).as_str());
s.push_str(format!("\"l3\": \"{}\",\n", hex::encode(&p.l3)).as_str());
s.push_str(
format!(
"\"client_registration_state\": \"{}\",\n",
hex::encode(&p.client_registration_state)
)
.as_str(),
);
s.push_str(
format!(
"\"client_login_state\": \"{}\",\n",
hex::encode(&p.client_login_state)
)
.as_str(),
);
s.push_str(
format!(
"\"server_registration_state\": \"{}\",\n",
hex::encode(&p.server_registration_state)
)
.as_str(),
);
s.push_str(
format!(
"\"server_login_state\": \"{}\",\n",
hex::encode(&p.server_login_state)
)
.as_str(),
);
s.push_str(
format!(
"\"password_file\": \"{}\",\n",
hex::encode(&p.password_file)
)
.as_str(),
);
s.push_str(format!("\"opaque_key\": \"{}\",\n", hex::encode(&p.opaque_key)).as_str());
s.push_str(format!("\"shared_secret\": \"{}\"\n", hex::encode(&p.shared_secret)).as_str());
s.push_str("}\n");
s
}
fn generate_parameters() -> TestVectorParameters {
let mut rng = OsRng;
// Inputs
let server_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let server_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let client_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let client_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let password = b"password";
let pepper = b"pepper";
let mut blinding_factor_raw = [0u8; 64];
rng.fill_bytes(&mut blinding_factor_raw);
let mut oprf_key_raw = [0u8; 32];
rng.fill_bytes(&mut oprf_key_raw);
let mut envelope_nonce = [0u8; 12];
rng.fill_bytes(&mut envelope_nonce);
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let mut server_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut server_nonce);
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
password,
Some(pepper),
&mut blinding_factor_registration_rng,
)
.unwrap();
let r1_bytes = r1.to_bytes().to_vec();
let blinding_factor_bytes = client_registration.blinding_factor.to_bytes();
let client_registration_state = client_registration.to_bytes().to_vec();
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
let (r2, server_registration) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(r1, &mut oprf_key_rng)
.unwrap();
let r2_bytes = r2.to_bytes().to_vec();
let oprf_key = server_registration.oprf_key;
let oprf_key_bytes = EdwardsPoint::scalar_as_bytes(&oprf_key);
let server_registration_state = server_registration.to_bytes().to_vec();
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private());
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let (r3, opaque_key_registration) = client_registration
.finish::<_, SignalKeyPair>(r2, server_s_kp.public(), &mut finish_registration_rng)
.unwrap();
let r3_bytes = r3.to_bytes().to_vec();
let password_file = server_registration.finish(r3).unwrap();
let password_file_bytes = password_file.to_bytes();
let mut client_login_start: Vec<u8> = Vec::new();
client_login_start.extend_from_slice(&blinding_factor_raw);
client_login_start.extend_from_slice(&client_e_kp.private());
client_login_start.extend_from_slice(&client_nonce);
let mut client_login_start_rng = CycleRng::new(client_login_start);
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
password,
Some(pepper),
&mut client_login_start_rng,
)
.unwrap();
let l1_bytes = l1.to_bytes().to_vec();
let client_login_state = client_login.to_bytes().to_vec();
let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_vec());
let (l2, server_login) = ServerLogin::start(
password_file,
server_s_kp.private(),
l1,
&mut server_e_sk_rng,
)
.unwrap();
let l2_bytes = l2.to_bytes().to_vec();
let server_login_state = server_login.to_bytes().to_vec();
let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_vec());
let (l3, client_shared_secret, _opaque_key_login) = client_login
.finish(l2, server_s_kp.public(), &mut client_e_sk_rng)
.unwrap();
let l3_bytes = l3.to_bytes().to_vec();
TestVectorParameters {
client_s_pk: client_s_kp.public().to_vec(),
client_s_sk: client_s_kp.private().to_vec(),
client_e_pk: client_e_kp.public().to_vec(),
client_e_sk: client_e_kp.private().to_vec(),
server_s_pk: server_s_kp.public().to_vec(),
server_s_sk: server_s_kp.private().to_vec(),
server_e_pk: server_e_kp.public().to_vec(),
server_e_sk: server_e_kp.private().to_vec(),
password: password.to_vec(),
blinding_factor_raw: blinding_factor_raw.to_vec(),
blinding_factor: blinding_factor_bytes.to_vec(),
pepper: pepper.to_vec(),
oprf_key: oprf_key_bytes.to_vec(),
envelope_nonce: envelope_nonce.to_vec(),
client_nonce: client_nonce.to_vec(),
server_nonce: server_nonce.to_vec(),
r1: r1_bytes,
r2: r2_bytes,
r3: r3_bytes,
l1: l1_bytes,
l2: l2_bytes,
l3: l3_bytes,
password_file: password_file_bytes,
client_registration_state,
server_registration_state,
client_login_state,
server_login_state,
shared_secret: client_shared_secret,
opaque_key: opaque_key_registration.to_vec(),
}
}
#[test]
fn generate_test_vectors() {
let parameters = generate_parameters();
println!("{}", stringify_test_vectors(&parameters));
}
#[test]
fn test_r1() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
&parameters.password,
Some(&parameters.pepper),
&mut blinding_factor_rng,
)
.unwrap();
assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.to_bytes()));
assert_eq!(
hex::encode(&parameters.client_registration_state),
hex::encode(client_registration.to_bytes())
);
Ok(())
}
#[test]
fn test_r2() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let (r2, server_registration) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
RegisterFirstMessage::try_from(&parameters.r1[..]).unwrap(),
&mut oprf_key_rng,
)
.unwrap();
assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
assert_eq!(
hex::encode(&parameters.server_registration_state),
hex::encode(server_registration.to_bytes())
);
Ok(())
}
#[test]
fn test_r3() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let client_s_sk_and_nonce: Vec<u8> =
[parameters.client_s_sk, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let (r3, opaque_key_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::try_from(
&parameters.client_registration_state[..],
)
.unwrap()
.finish::<CycleRng, SignalKeyPair>(
RegisterSecondMessage::try_from(&parameters.r2[..]).unwrap(),
&Key::try_from(parameters.server_s_pk).unwrap(),
&mut finish_registration_rng,
)
.unwrap();
assert_eq!(hex::encode(parameters.r3), hex::encode(r3.to_bytes()));
assert_eq!(
hex::encode(parameters.opaque_key),
hex::encode(opaque_key_registration.to_vec())
);
Ok(())
}
#[test]
fn test_password_file() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let server_registration =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
&parameters.server_registration_state[..],
)
.unwrap();
let password_file = server_registration
.finish(RegisterThirdMessage::try_from(&parameters.r3[..]).unwrap())
.unwrap();
assert_eq!(
hex::encode(parameters.password_file),
hex::encode(password_file.to_bytes())
);
Ok(())
}
#[test]
fn test_l1() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let client_login_start = [
parameters.blinding_factor_raw,
parameters.client_e_sk,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
&parameters.password,
Some(&parameters.pepper),
&mut client_login_start_rng,
)
.unwrap();
assert_eq!(hex::encode(&parameters.l1), hex::encode(l1.to_bytes()));
assert_eq!(
hex::encode(&parameters.client_login_state),
hex::encode(client_login.to_bytes())
);
Ok(())
}
#[test]
fn test_l2() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
let (l2, server_login) = ServerLogin::start::<_, Aes256Gcm, _, SignalKeyPair>(
ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
&Key::try_from(parameters.server_s_sk).unwrap(),
LoginFirstMessage::<EdwardsPoint>::try_from(&parameters.l1[..]).unwrap(),
&mut server_e_sk_rng,
)
.unwrap();
assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.to_bytes()));
assert_eq!(
hex::encode(&parameters.server_login_state),
hex::encode(server_login.to_bytes())
);
Ok(())
}
#[test]
fn test_l3() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
let (l3, shared_secret, opaque_key_login) =
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
&parameters.client_login_state[..],
)
.unwrap()
.finish(
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(&parameters.l2[..]).unwrap(),
&Key::try_from(parameters.server_s_pk)?,
&mut client_e_sk_rng,
)
.unwrap();
assert_eq!(
hex::encode(&parameters.shared_secret),
hex::encode(&shared_secret)
);
assert_eq!(hex::encode(&parameters.l3), hex::encode(l3.to_bytes()));
assert_eq!(
hex::encode(&parameters.opaque_key),
hex::encode(opaque_key_login)
);
Ok(())
}
#[test]
fn test_server_login_finish() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let shared_secret = ServerLogin::try_from(&parameters.server_login_state[..])
.unwrap()
.finish(LoginThirdMessage::try_from(&parameters.l3[..])?)
.unwrap();
assert_eq!(
hex::encode(parameters.shared_secret),
hex::encode(shared_secret)
);
Ok(())
}
fn test_complete_flow(
registration_password: &[u8],
login_password: &[u8],
) -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_kp = SignalKeyPair::generate_random(&mut server_rng)?;
let (register_m1, client_state) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
registration_password,
None,
&mut client_rng,
)?;
let (register_m2, server_state) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
register_m1,
&mut server_rng,
)?;
let (register_m3, registration_opaque_key) =
client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
let p_file = server_state.finish(register_m3)?;
let (login_m1, client_login_state) =
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
login_password,
None,
&mut client_rng,
)?;
let (login_m2, server_login_state) =
ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
let client_login_result =
client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng);
if hex::encode(registration_password) == hex::encode(login_password) {
let (login_m3, client_shared_secret, login_opaque_key) = client_login_result?;
let server_shared_secret = server_login_state.finish(login_m3)?;
assert_eq!(
hex::encode(server_shared_secret),
hex::encode(client_shared_secret)
);
assert_eq!(
hex::encode(registration_opaque_key),
hex::encode(login_opaque_key)
);
} else {
let res = match client_login_result {
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
_ => false,
};
assert!(res);
}
Ok(())
}
#[test]
fn test_complete_flow_success() -> Result<(), ProtocolError> {
test_complete_flow(b"good password", b"good password")
}
#[test]
fn test_complete_flow_fail() -> Result<(), ProtocolError> {
test_complete_flow(b"good password", b"bad password")
}
+583
View File
@@ -0,0 +1,583 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
ciphersuite::CipherSuite,
errors::*,
key_exchange::tripledh::TripleDH,
keypair::{Key, SizedBytesExt},
opaque::*,
slow_hash::NoOpHash,
tests::mock_rng::CycleRng,
*,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use serde_json::Value;
// Tests
// =====
struct Ristretto255Sha512NoSlowHash;
impl CipherSuite for Ristretto255Sha512NoSlowHash {
type Group = RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
#[derive(PartialEq)]
pub enum EnvelopeMode {
Base,
CustomIdentifier,
}
#[allow(non_snake_case)]
pub struct TestVectorParameters {
pub envelope_mode: EnvelopeMode,
pub client_public_key: Vec<u8>,
pub client_private_key: Vec<u8>,
pub client_keyshare: Vec<u8>,
pub client_private_keyshare: Vec<u8>,
pub server_public_key: Vec<u8>,
pub server_private_key: Vec<u8>,
pub server_keyshare: Vec<u8>,
pub server_private_keyshare: Vec<u8>,
pub client_identity: Vec<u8>,
pub server_identity: Vec<u8>,
pub password: Vec<u8>,
pub blind_registration: Vec<u8>,
pub oprf_key: Vec<u8>,
pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
pub client_info: Vec<u8>,
pub server_info: Vec<u8>,
pub registration_request: Vec<u8>,
pub registration_response: Vec<u8>,
pub registration_upload: Vec<u8>,
pub KE1: Vec<u8>,
pub blind_login: Vec<u8>,
pub KE2: Vec<u8>,
pub KE3: Vec<u8>,
pub export_key: Vec<u8>,
pub session_key: Vec<u8>,
}
// Pulled from "OPAQUE-3DH Test Vector 1" and "OPAQUE-3DH Test Vector 6"
// of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
static TEST_VECTORS: &[&str] = &[
r#"
OPRF: 0001
Hash: SHA512
SlowHash: Identity
EnvelopeMode: 01
Group: ristretto255
Nh: 64
Npk: 32
Nsk: 32
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da54
20d3eebcd2b
client_private_key: 8bcb0b70dac18de24eef12e737d6b28724d3e37774e0b092f
9f70b255defaf04
client_public_key: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa1905
df6ea129343b09
server_private_key: f3a0829898a89239dce29ccc98ec8b449a34b255ba1e6f944
829d18e0d589b0f
server_public_key: 66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb74
9c22c762389c3c
client_info: 68656c6c6f20626f62
server_info: 6772656574696e677320616c696365
server_nonce: 98b8081059f60ffed9336f026fd8e124737205ac73f5348ae5bebdb
49456c70f
client_nonce: 58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e
36427ca44
server_keyshare: 5214e3ddc73db786480b79fa2da787f2080b82cbe922c2a9592b
44597d9a702e
client_keyshare: a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
b98265a5e651
server_private_keyshare: c4d002aa4cfcf281657cf36fe562bc60d9133e0e72a7
4432f685b2b6a4b42a0c
client_private_keyshare: de2e98f422bf7b99be19f7da7cac62f1599d35a225ec
6340149a0aaff3102003
blind_registration: 7e5bcbf82a46109ee0d24e9bcab41fc830a6ce8b82fc1e921
3a043b743b95800
blind_login: c4d5a15f0d5ffc354e340454ec779f575e4573a3886ab5e57e4da298
4bdd5306
oprf_key: 080d0a4d352de92672ab709b1ae1888cb48dfabc2d6ca5b914b335512fe
70508
auth_key: 7bb7f2b831ee30d3e5cc4012c8f721a4d8f9dd494932d53776e043df9bd
2aa284025b8b006fd8449536446ff50698f46c73fccb53f20d80898f185307d1d39e5
prk: b0aefddbb21d1b97bc40c07b172e0bf172ec740de4f6274f69d46350a447e9b1
b3fb1e4cefc7d8e393ff58a5c45c74d0615ee0eecde116f3d4e744142eb2ee89
pseudorandom_pad: 36a828b3b57bf242c4c47ccd9cb84e5b3cefaffe09629c6b94d
eba0ccec5fa39
envelope: 01cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3
eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a55
3d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e
906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
handshake_secret: 2b041dcf12ac9b75dded88f891c25d76746ce9e2c1a43118ac4
aa5721cdc1bc2f0691e6c012a1ea9eb95ab4899b3e7058d37fe9546c46b0511877e40
f55aac6c
handshake_encrypt_key: ceef10f15d869a4cea8174fa98d0d96c7aaf8602d006fe
0c5274a40173db76cac820138c5890bb63fb974d1e3e925850cc2464e2c10f0a9a776
9a45e80889b1e
server_mac_key: f8fd7fdc349b5ae1339515e05912c89a795f561a117cdc84d8d8b
5f05b05751abfb87fa01c799c5d367244d1e32eab67ff926833c6025c556acffa4af1
f3871a
client_mac_key: 92a30cc82c374c06895aa07e81f0cf5f25309a24b595faefcd225
1f9219b47e47d17da4fe8b572dedefa350ed365f87b217973e90d0b647a2ccf1d796a
8970f6
registration_request: ec9027daa5e9a901d641286a7ded51364142936ac7636e1
42e3f4368b4bd8124
registration_response: 8867d7c8c2c576a6322d49d46078ea32f479aed917c70a
636d3ada4397ea1c0e66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749
c22c762389c3c
registration_upload: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa19
05df6ea129343b0901cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da
5420d3eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129
932a553d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3cc
d8699e906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
KE1: e06a32011e1b1704eb686b263e5d132fff4e9f6429cd93b98db107485006792c
58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e36427ca4400096
8656c6c6f20626f62a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
b98265a5e651
KE2: 66f6b5fa1a4eb6bd7a0c93ed2639a31cba0d02e2df744003641d5a30a4a12364
66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749c22c762389c3c01cc7
abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3eebcd2bbd6323c3
6fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a553d469ffa9999fcb
d37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e906027842694b62
93b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e98b8081059f60ffed9336f0
26fd8e124737205ac73f5348ae5bebdb49456c70f5214e3ddc73db786480b79fa2da7
87f2080b82cbe922c2a9592b44597d9a702e000f72f38a0945819089c44c86820c51d
89cc35f77df03d330101bbed3b2f69066112f32529bdda0998657350fc9f8da4cde73
408ad931f4c2ea6237ccae4696483388b174f50cf96d439139b0f8680c3b
KE3: 9f0e4f73455ca9fe06bb52ad02670b09be5a03db11a73be4422f19963be082b0
eb55871022e8d1d87adc3ab50de7c738058eb659866d091648f2fed12e23fd53
export_key: 66c0b72aa829f13a166fb1a1168f1e26023921f0eed1126def4f81ba0
4924ad6012e42b63656ec199ba27670d1e7f23dc0a927714edc140134dde5a5d2063d
fc
session_key: 951c2bb1b876725fa7d3829db791dddd406a688507b47e24101bd0cc
5d071760b6fba59e8758a6ea6d7e5f51a715b49a47c50fee9a7c8a0451243c3ee837f
d30
"#,
r#"
OPRF: 0001
Hash: SHA512
SlowHash: Identity
EnvelopeMode: 02
Group: ristretto255
Nh: 64
Npk: 32
Nsk: 32
client_identity: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613b2
e16a6dde6b05
server_identity: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8e
de8d6d272c65
password: 436f7272656374486f72736542617474657279537461706c65
envelope_nonce: f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb241809
0438877888c
client_private_key: dc70a99bbabf1ebe98b192e93cedceb9c0164e95b891bd8bc
81721b83d66b00b
client_public_key: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613
b2e16a6dde6b05
server_private_key: 709687a36c94592ab76579f42ce1be6961f0700496e71df80
6ebd5320554720d
server_public_key: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c
8ede8d6d272c65
client_info: 68656c6c6f20626f62
server_info: 6772656574696e677320616c696365
server_nonce: ef49d83cef5f1411ea30abb82b08bd85423aadb86e2c19df5930b3c
8498b9f97
client_nonce: 4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d6
77165cd3b
server_keyshare: 96a9587e233e67f2397f10fec6355b68102534f1f1b115b4ddf7
485840efcd7c
client_keyshare: 54f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
e5033632611a
server_private_keyshare: 6650d64df70618a878504ce73dcca27b1af125c67e48
1e7bd49d0b24709b200f
client_private_keyshare: ebb01c59f99bc955df622548e247f7ef180732909ff3
c5f87ff8c7867b8be704
blind_registration: 308f1d3fa1fea402f3c90b04601274050a3c6f467387c2f48
878823949b0e109
blind_login: 141e21373228a44b09d4c00da9a6bbaf9a5e54a1687c07f327833643
4245510b
oprf_key: b7126967aa0cb69c311b71343843ea041bae30e2bde41b548b8fbd8bced
97604
auth_key: 9f761a5a56a74269b382403aeba47c1d24b9e200e1839efcb616fb280da
1b6ba7bd71d6455dd3cd979c545608cfbfc1c4e9ba677e1d40848054a00696c4b2589
prk: 30bc3f37a757890ac17ce46043f3c5ed30c96fb8743205e77e84dc167d98e114
6093150ff7b4d002f793bfe717e88d174ed2669abdd9e96af473a7ac82973b0a
pseudorandom_pad: b909f990c94b9c5949a2b8d0874d602f846b7981b331fd79978
b530cc46c8670
envelope: 02f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438
877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a36
7bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc
439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
handshake_secret: ff89f264f8c3974238f4c8d736af7b0a55f2e4edc487cbf3e5c
7b4bbf21acd7c1d28354c2c8555fba57c4d4b1fbb4b772bfdf909881f67dd517cc9f4
f6ebaeac
handshake_encrypt_key: 3071b181f639062cf70b74d0ffe5ec8fa695da13cd2f00
e74b8b7ef348ae7a5df9c3a32c9f7aeaad5a28379712cf849b9707e221dce124abfad
d0225a8e8e045
server_mac_key: 7c37b344d189cbbeff80bbb4b78e2703d1a80dc28239923094287
62f7ce2a93b11f6e85dd45c02809afe8583d4aad6377e72788773af92eef33c690692
20ae76
client_mac_key: 2c4aff12ba7aa911a51f9e5b7a7c01439d854c97e4b8ec842a9db
d78345760328fd5a72424e49e25ec8fe1b6d9d42f774516f400948bd5a105d995d000
2fc83b
registration_request: 3c8b89966e261a5aaf7aeb6dcdd94c87ce311bf197221b8
7ef44632d58f18a05
registration_response: caf9243d7ef3e267815632bf79c85a27a23f218a438815
2a523f6a310949807beae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8
ede8d6d272c65
registration_upload: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc6
13b2e16a6dde6b0502f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418
090438877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4
f90a367bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d
2566cc439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
KE1: 8261a1efd78bea73faf256a23c200d729259886530fa43b875c1ca124b09bc7e
4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d677165cd3b00096
8656c6c6f20626f6254f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
e5033632611a
KE2: fa1f33a43a03123ebe35345ef93aa23b57ea8bfbee7022b05a179d60768ba02e
eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8ede8d6d272c6502f41
e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438877888c6579500b
73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a367bdb425fa1dd4c4
9e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc439c83030464eff
ecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282ef49d83cef5f1411ea30abb
82b08bd85423aadb86e2c19df5930b3c8498b9f9796a9587e233e67f2397f10fec635
5b68102534f1f1b115b4ddf7485840efcd7c000f7ebe71d4ab326006a3aeca802435d
c995a38ac6662221f974cb920992d82b8ef8d147c77e29b628a82b5ccb01ea2f7bb60
af94cd1860e1bd974a11a1c9bd827789f663c4758eb71058c244138de0c2
KE3: d81f93397cdba85a43993d4d9afbdc67f147adfa2b223213b19692cb820eef48
5073eda4c8236b2f47702404ad60d9a875d189626fc7b7cc861825385470ae54
export_key: 03192555940b5b42e64e6200bf55cc701f1bace3d402a2f8d83977843
51a1e3fa1f07a471b783b208acb1d92be47903b6fa3a0df9f4d4b7956ee4f431e2950
f6
session_key: 58a7fa98bf3b7b52da21406abfb11d98734354edd47d7b32462c0513
f0617c89824ea6031d4147a86fc9f6c6837ce640c12fb937d764f296d1a9421ad1b2a
5d5
"#,
];
macro_rules! parse {
( $v:ident, $s:expr ) => {
match decode(&$v, $s) {
Some(x) => x,
None => vec![],
}
};
}
macro_rules! rfc_to_params {
( $v:ident ) => {
$v.iter()
.map(|x| populate_test_vectors(&serde_json::from_str(rfc_to_json(x).as_str()).unwrap()))
.collect::<Vec<TestVectorParameters>>()
};
}
fn rfc_to_json(input: &str) -> String {
let mut json = vec![];
for line in input.lines() {
// If line contains colon, then
if line.contains(":") {
if json.len() > 0 {
// Adding closing quote for previous line, comma, and newline
json.push("\",\n".to_string());
}
let mut iter = line.split(":");
let key = iter.next().unwrap().split_whitespace().next().unwrap();
let val = iter.next().unwrap().split_whitespace().next().unwrap();
json.push(format!(" \"{}\": \"{}", key, val));
} else {
let s = line.trim().to_string();
if s.len() > 0 {
json.push(s);
}
}
}
format!("{{\n{}\"\n}}", json.join(""))
}
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
TestVectorParameters {
envelope_mode: match values["EnvelopeMode"].as_str() {
Some("01") => EnvelopeMode::Base,
Some("02") => EnvelopeMode::CustomIdentifier,
_ => panic!("Could not match envelope mode"),
},
client_public_key: parse!(values, "client_public_key"),
client_private_key: parse!(values, "client_private_key"),
client_keyshare: parse!(values, "client_keyshare"),
client_private_keyshare: parse!(values, "client_private_keyshare"),
server_public_key: parse!(values, "server_public_key"),
server_private_key: parse!(values, "server_private_key"),
server_keyshare: parse!(values, "server_keyshare"),
server_private_keyshare: parse!(values, "server_private_keyshare"),
client_identity: parse!(values, "client_identity"),
server_identity: parse!(values, "server_identity"),
password: parse!(values, "password"),
blind_registration: parse!(values, "blind_registration"),
oprf_key: parse!(values, "oprf_key"),
envelope_nonce: parse!(values, "envelope_nonce"),
client_nonce: parse!(values, "client_nonce"),
server_nonce: parse!(values, "server_nonce"),
client_info: parse!(values, "client_info"),
server_info: parse!(values, "server_info"),
registration_request: parse!(values, "registration_request"),
registration_response: parse!(values, "registration_response"),
registration_upload: parse!(values, "registration_upload"),
KE1: parse!(values, "KE1"),
KE2: parse!(values, "KE2"),
KE3: parse!(values, "KE3"),
blind_login: parse!(values, "blind_login"),
export_key: parse!(values, "export_key"),
session_key: parse!(values, "session_key"),
}
}
fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key.clone());
let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
)?;
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
Ok(password_file.serialize())
}
#[test]
fn test_registration_request() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut rng,
&parameters.password,
)?;
assert_eq!(
hex::encode(&parameters.registration_request),
hex::encode(client_registration_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_registration_response() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let server_registration_start_result =
ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
&Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
)?;
assert_eq!(
hex::encode(parameters.registration_response),
hex::encode(server_registration_start_result.message.serialize())
);
}
Ok(())
}
#[test]
fn test_registration_upload() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
let client_registration_start_result =
ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
&mut rng,
&parameters.password,
)?;
let sk_u_and_nonce: Vec<u8> =
[parameters.client_private_key, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
let result = client_registration_start_result.state.finish(
&mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ClientRegistrationFinishParameters::WithIdentifiers(
parameters.client_identity,
parameters.server_identity,
)
} else {
ClientRegistrationFinishParameters::default()
},
)?;
assert_eq!(
hex::encode(parameters.registration_upload),
hex::encode(result.message.serialize())
);
assert_eq!(
hex::encode(parameters.export_key),
hex::encode(result.export_key.to_vec())
);
}
Ok(())
}
#[test]
fn test_ke1() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let client_login_start = [
parameters.blind_login,
parameters.client_private_keyshare,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?;
assert_eq!(
hex::encode(&parameters.KE1),
hex::encode(client_login_start_result.message.serialize()?)
);
}
Ok(())
}
#[test]
fn test_ke2() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let password_file_bytes = get_password_file_bytes(&parameters)?;
let mut server_private_keyshare_and_nonce_rng =
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
&Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.server_info.to_vec(),
parameters.client_identity,
parameters.server_identity,
)
} else {
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
},
)?;
assert_eq!(
hex::encode(&parameters.client_info),
hex::encode(server_login_start_result.plain_info),
);
assert_eq!(
hex::encode(&parameters.KE2),
hex::encode(server_login_start_result.message.serialize()?)
);
}
Ok(())
}
#[test]
fn test_ke3() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let client_login_start = [
parameters.blind_login,
parameters.client_private_keyshare,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
ClientLoginStartParameters::WithInfo(parameters.client_info),
)?;
let client_login_finish_result = client_login_start_result.state.finish(
CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?,
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ClientLoginFinishParameters::WithIdentifiers(
parameters.client_identity,
parameters.server_identity,
)
} else {
ClientLoginFinishParameters::default()
},
)?;
assert_eq!(
hex::encode(&parameters.server_info),
hex::encode(&client_login_finish_result.confidential_info)
);
assert_eq!(
hex::encode(&parameters.session_key),
hex::encode(&client_login_finish_result.session_key)
);
assert_eq!(
hex::encode(&parameters.KE3),
hex::encode(client_login_finish_result.message.serialize()?)
);
assert_eq!(
hex::encode(&parameters.export_key),
hex::encode(client_login_finish_result.export_key)
);
}
Ok(())
}
#[test]
fn test_server_login_finish() -> Result<(), ProtocolError> {
for parameters in rfc_to_params!(TEST_VECTORS) {
let password_file_bytes = get_password_file_bytes(&parameters)?;
let mut server_private_keyshare_and_nonce_rng =
CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
&mut server_private_keyshare_and_nonce_rng,
ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
&Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
.unwrap(),
if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.server_info.to_vec(),
parameters.client_identity,
parameters.server_identity,
)
} else {
ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
},
)?;
let server_login_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&parameters.KE3[..])?)?;
assert_eq!(
hex::encode(parameters.session_key),
hex::encode(&server_login_result.session_key)
);
}
Ok(())
}
-124
View File
@@ -1,124 +0,0 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
group::Group,
keypair::{KeyPair, SignalKeyPair, SizedBytes},
opaque::*,
rkr_encryption::{RKRCipher as _, RKRCiphertext},
};
use curve25519_dalek::ristretto::RistrettoPoint;
use chacha20poly1305::ChaCha20Poly1305;
use rand_core::{OsRng, RngCore};
use std::convert::TryFrom;
fn random_ristretto_point() -> RistrettoPoint {
let mut rng = OsRng;
let mut bits = [0u8; 64];
rng.fill_bytes(&mut bits);
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
}
#[test]
fn client_registration_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
// serialization order: scalar, password
let mut bytes: Vec<u8> = vec![];
bytes.extend_from_slice(sc.as_bytes());
bytes.extend_from_slice(pw);
let reg = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn server_registration_roundtrip() {
// If we don't have envelope and client_pk, the server registration just
// contains the prf key
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(sc.as_bytes());
let reg = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(
&oprf_bytes[..],
)
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, oprf_bytes);
// If we do have envelope and client pk, the server registration contains
// the whole kit
let rkr_size = RKRCiphertext::<ChaCha20Poly1305>::rkr_with_nonce_size();
let mut mock_rkr_bytes = vec![0u8; rkr_size];
rng.fill_bytes(&mut mock_rkr_bytes);
println!("{}", mock_rkr_bytes.len());
let mock_client_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
// serialization order: scalar, public key, envelope
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(sc.as_bytes());
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
bytes.extend_from_slice(&mock_rkr_bytes);
let reg =
ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(&bytes[..])
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn register_first_message_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_bytes();
let r1 = RegisterFirstMessage::<RistrettoPoint>::try_from(pt_bytes.as_slice()).unwrap();
let r1_bytes = r1.to_bytes();
assert_eq!(pt_bytes, r1_bytes);
}
#[test]
fn register_second_message_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_bytes();
let message = pt_bytes.to_vec();
let r2 = RegisterSecondMessage::<RistrettoPoint>::try_from(&message[..]).unwrap();
let r2_bytes = r2.to_bytes();
assert_eq!(message, r2_bytes);
}
#[test]
fn register_third_message_roundtrip() {
let mut rng = OsRng;
let skp = SignalKeyPair::generate_random(&mut rng).unwrap();
let pubkey_bytes = skp.public().to_arr();
let mut encryption_key = [0u8; 32];
rng.fill_bytes(&mut encryption_key);
let mut hmac_key = [0u8; 32];
rng.fill_bytes(&mut hmac_key);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
&encryption_key,
&hmac_key,
&msg,
&pubkey_bytes,
&mut rng,
)
.unwrap();
let mut message = Vec::new();
message.extend_from_slice(&ciphertext.to_bytes());
message.extend_from_slice(&pubkey_bytes);
let r3 =
RegisterThirdMessage::<ChaCha20Poly1305, SignalKeyPair>::try_from(&message[..]).unwrap();
let r3_bytes = r3.to_bytes();
assert_eq!(message, r3_bytes);
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::tests::mock_rng::CycleRng;
use crate::{errors::*, group::Group, oprf};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::GenericArray;
use serde_json::Value;
use sha2::Sha512;
struct VOPRFTestVectorParameters {
sksm: Vec<u8>,
input: Vec<u8>,
blind: Vec<u8>,
blinded_element: Vec<u8>,
evaluation_element: Vec<u8>,
output: Vec<u8>,
}
// Taken from https://github.com/cfrg/draft-irtf-cfrg-voprf/blob/master/draft-irtf-cfrg-voprf.md
// in base mode
static OPRF_RISTRETTO255_SHA512: &'static [&str] = &[
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "00",
"blind": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"blinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
"evaluation_element": "fc6c2b854553bf1ed6674072ed0bde1a9911e02b4bd64aa02cfb428f30251e77",
"output": "d8ed12382086c74564ae19b7a2b5ed9bdc52656d1fc151faaae51aaba86291e8df0b2143a92f24d44d5efd0892e2e26721d27d88745343493634a66d3a925e3a"
}
"#,
r#"
{
"sksm": "758cbac0e1eb4265d80f6e6489d9a74d788f7ddeda67d7fb3c08b08f44bda30a",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
"blinded_element": "28a5e797b710f76d20a52507145fbf320a574ec2c8ab0e33e65dd2c277d0ee56",
"evaluation_element": "345e140b707257ae83d4911f7ead3177891e7a62c54097732802c4c7a98ab25a",
"output": "4d5f4221b5ebfd4d1a9dd54830e1ed0bce5a8f30a792723a6fddfe6cfe9f86bb1d95a3725818aeb725eb0b1b52e01ee9a72f47042372ef66c307770054d674fc"
}
"#,
];
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
VOPRFTestVectorParameters {
sksm: decode(&values, "sksm").unwrap(),
input: decode(&values, "input").unwrap(),
blind: decode(&values, "blind").unwrap(),
blinded_element: decode(&values, "blinded_element").unwrap(),
evaluation_element: decode(&values, "evaluation_element").unwrap(),
output: decode(&values, "output").unwrap(),
}
}
// Tests input -> blind, blinded_element
#[test]
fn test_blind() -> Result<(), ProtocolError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let mut rng = CycleRng::new(parameters.blind.to_vec());
let (token, blinded_element) =
oprf::blind::<_, RistrettoPoint, Sha512>(&parameters.input, &mut rng)?;
assert_eq!(
&parameters.blind,
&RistrettoPoint::scalar_as_bytes(&token.blind).to_vec()
);
assert_eq!(
&parameters.blinded_element,
&blinded_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests sksm, blinded_element -> evaluation_element
#[test]
fn test_evaluate() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let evaluation_element = oprf::evaluate::<RistrettoPoint>(
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.blinded_element,
))
.unwrap(),
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&parameters.sksm)).unwrap(),
);
assert_eq!(
&parameters.evaluation_element,
&evaluation_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests input, blind, evaluation_element -> output
#[test]
fn test_finalize() -> Result<(), ProtocolError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let output = oprf::finalize::<RistrettoPoint, Sha512>(
&parameters.input,
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&parameters.blind))?,
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.evaluation_element,
))?,
)?;
assert_eq!(&parameters.output, &output.to_vec());
}
Ok(())
}