147 Commits
Author SHA1 Message Date
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
38 changed files with 7262 additions and 2822 deletions
+161 -13
View File
@@ -1,8 +1,160 @@
name: Rust CI
on: [push]
on:
push:
branches:
- tls
- master
pull_request:
types: [opened, repoened, synchronize]
jobs:
combo:
name: test + Clippy + rustfmt
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
backend_feature:
- u64_backend
- u32_backend
name: test
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features ${{ matrix.backend_feature }}
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
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: nightly
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
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: nightly
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
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo bench --no-run
uses: actions-rs/cargo@v1
with:
command: bench
args: --features "bench" --no-run
clippy:
name: cargo clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install nightly toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
format:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
@@ -22,13 +174,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 }}
+32
View File
@@ -1,5 +1,37 @@
# Changelog
## 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
+1034 -245
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.3.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"
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" }
generic-bytes-derive = { version = "0.1.0" }
hkdf = "0.10.0"
hmac = "0.10.1"
rand = "0.8.3"
scrypt = { version = "0.5.0", optional = true }
subtle = { version = "2.3.0", default-features = false }
thiserror = "1.0.22"
zeroize = "1.1.1"
[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 = "7.0.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.3.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-02](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-02.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, unblind_and_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("unblind_and_finalize with Ristretto", move |b| {
b.iter(|| {
let _res = unblind_and_finalize_shim::<RistrettoPoint, Sha512>(&token, beta).unwrap();
})
});
}
criterion_group!(oprf_benches, oprf1, oprf2, oprf3);
criterion_main!(oprf_benches);
+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::convert::TryFrom;
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.to_bytes(),
}
}
// 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();
// Client sends credential_request_bytes to server
let password_file = ServerRegistration::<Default>::try_from(&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();
// 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();
// 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)
}
}
}
+234
View File
@@ -0,0 +1,234 @@
// 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::convert::TryFrom;
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.to_bytes()
}
// 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();
// Client sends credential_request_bytes to server
let password_file = ServerRegistration::<Default>::try_from(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();
// 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();
// 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)
}
}
+344
View File
@@ -0,0 +1,344 @@
// 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,
serialization::{serialize, tokenize},
};
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use hkdf::Hkdf;
use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
// 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)]
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),
}
}
}
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[..],
&serialize(&self.ciphertext, 2)[..],
]
.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 bytes = &input[1..];
if bytes.len() < NONCE_LEN {
return Err(ProtocolError::VerificationError(
PakeError::SerializationError,
));
}
let nonce = &bytes[..NONCE_LEN];
let (ciphertext, remainder) = tokenize(&bytes[NONCE_LEN..], 2)?;
Ok((
Self {
mode,
nonce: nonce.to_vec(),
ciphertext,
},
remainder,
))
}
}
/// This struct is an instantiation of the envelope as described in
/// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
///
/// Note that earlier versions of this specification described an
/// implementation of this envelope using an encryption scheme that
/// satisfied random-key robustness
/// (https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05#section-4).
/// The specification update has simplified this assumption by taking
/// an XOR-based approach without compromising on security, and to avoid
/// the confusion around the implementation of an RKR-secure encryption.
pub(crate) struct Envelope<D: Hash> {
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> {
/// The additional number of bytes added to the plaintext
pub(crate) fn additional_size() -> usize {
NONCE_LEN + <D as Digest>::OutputSize::to_usize()
}
fn hmac_key_size() -> usize {
<D as Digest>::OutputSize::to_usize()
}
fn 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>), InternalPakeError> {
let plaintext = serialize(&client_s_sk, 2);
let aad = construct_aad(server_s_pk, &optional_ids);
Self::seal_raw(rng, key, &plaintext, &aad, mode_from_ids(&optional_ids))
}
/// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
/// Note that a new nonce is sampled for each call to seal.
pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
rng: &mut R,
key: &[u8],
plaintext: &[u8],
aad: &[u8],
mode: InnerEnvelopeMode,
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
let mut nonce = vec![0u8; NONCE_LEN];
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>, InternalPakeError> {
// First, check that mode matches
if self.inner_envelope.mode != mode_from_ids(optional_ids) {
return Err(InternalPakeError::IncompatibleEnvelopeModeError);
}
let aad = construct_aad(server_s_pk, optional_ids);
let opened = self.open_raw(key, &aad)?;
let (client_s_sk, remainder) = tokenize(&opened.plaintext, 2)
.map_err(|_| InternalPakeError::UnexpectedEnvelopeContentsError)?;
if !remainder.is_empty() {
// Should not have anything else in plaintext
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
}
Ok(OpenedEnvelope {
client_s_sk,
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,
),
})
}
}
// Helper functions
fn construct_aad(server_s_pk: &[u8], optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> {
let ids = optional_ids
.iter()
.flat_map(|(l, r)| [serialize(l, 2), serialize(r, 2)].concat())
.collect();
[serialize(server_s_pk, 2), 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());
}
}
+83 -27
View File
@@ -4,55 +4,75 @@
// 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,
}
// This is meant to express future(ly) non-trivial ways of converting the
@@ -64,17 +84,20 @@ 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,
}
@@ -103,6 +126,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 +161,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)
}
}
+49 -72
View File
@@ -3,10 +3,13 @@
// 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,
};
@@ -14,9 +17,9 @@ 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;
@@ -24,10 +27,10 @@ use zeroize::Zeroize;
/// 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> {
/// 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>;
@@ -45,14 +48,22 @@ 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;
}
/// The implementation of such a subgroup for Ristretto
@@ -67,7 +78,20 @@ impl Group for RistrettoPoint {
Ok(Scalar::from_bytes_mod_order(bits))
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
#[cfg(not(test))]
{
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)]
{
let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes)
}
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
@@ -83,78 +107,31 @@ 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)
}
}
+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;
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::{PakeError, ProtocolError},
group::Group,
hash::Hash,
keypair::Key,
};
use rand::{CryptoRng, RngCore};
use std::convert::TryFrom;
pub trait KeyExchange<D: Hash, G: Group> {
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
fn generate_ke1<R: RngCore + CryptoRng>(
info: Vec<u8>,
rng: &mut R,
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke2<R: RngCore + CryptoRng>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
ke1_message: Self::KE1Message,
client_s_pk: Key,
server_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
e_info: Vec<u8>,
) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn generate_ke3(
l2_component: Vec<u8>,
ke2_message: Self::KE2Message,
ke1_state: &Self::KE1State,
serialized_credential_request: &[u8],
server_s_pk: Key,
client_s_sk: Key,
id_u: Vec<u8>,
id_s: Vec<u8>,
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError>;
fn ke2_message_size() -> usize;
}
pub trait ToBytes {
fn to_bytes(&self) -> Vec<u8>;
}
+557
View File
@@ -0,0 +1,557 @@
// 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},
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;
const KEY_LEN: usize = 32;
pub(crate) type NonceLen = U32;
static STR_3DH: &[u8] = b"3DH keys";
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_SERVER_ENC: &[u8] = b"handshake enc";
static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
static STR_SESSION_KEY: &[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: GenericArray<u8, NonceLen> = {
let mut client_nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce_bytes);
GenericArray::clone_from_slice(&client_nonce_bytes)
};
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: GenericArray<u8, NonceLen> = {
let mut server_nonce_bytes = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce_bytes);
GenericArray::clone_from_slice(&server_nonce_bytes)
};
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(),
},
&ke1_message.client_nonce,
&server_nonce,
&id_u,
&id_s,
)?;
// 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();
let transcript2: Vec<u8> = [
&serialized_credential_request[..],
&l2_bytes[..],
&server_nonce[..],
&server_e_kp.public().to_arr(),
&serialize(&ciphertext, 2),
]
.concat();
let mut hasher = D::new();
hasher.update(&transcript2);
let hashed_transcript_without_mac = hasher.finalize_reset();
let mut mac_hasher =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac_hasher.update(&hashed_transcript_without_mac);
let mac = mac_hasher.finalize().into_bytes();
hasher.update(&transcript2);
hasher.update(&mac);
let hashed_transcript = hasher.finalize();
Ok((
ke1_message.info,
Ke2State {
km3,
hashed_transcript,
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 (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,
},
&ke1_state.client_nonce,
&ke2_message.server_nonce,
&id_u,
&id_s,
)?;
let transcript: Vec<u8> = [
&serialized_credential_request,
&l2_component[..],
&ke2_message.to_bytes_without_mac(),
]
.concat();
let mut hasher = D::new();
hasher.update(&transcript);
let hashed_transcript_without_mac = hasher.finalize_reset();
let mut server_mac =
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.update(&hashed_transcript_without_mac);
if ke2_message.mac != server_mac.finalize().into_bytes() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
hasher.update(transcript);
hasher.update(ke2_message.mac.to_vec());
let hashed_transcript = hasher.finalize();
let mut client_mac =
Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&hashed_transcript);
// Compute decryption of e_info
let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
.map_err(|_| InternalPakeError::HkdfError)?;
let plaintext: Vec<u8> = encryption_pad
.iter()
.zip(ke2_message.e_info.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
Ok((
plaintext,
session_key.to_vec(),
Ke3Message {
mac: client_mac.finalize().into_bytes(),
},
))
}
#[allow(clippy::type_complexity)]
fn finish_ke(
ke3_message: Self::KE3Message,
ke2_state: &Self::KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.update(&ke2_state.hashed_transcript);
if ke3_message.mac != client_mac.finalize().into_bytes() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.session_key.to_vec())
}
fn ke2_message_size() -> usize {
NonceLen::to_usize() + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
}
}
/// The client state produced after the first key exchange message
#[derive(PartialEq, Eq)]
pub struct Ke1State {
client_e_sk: Key,
client_nonce: GenericArray<u8, NonceLen>,
}
/// The first key exchange message
#[derive(PartialEq, Eq)]
pub struct Ke1Message {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) info: Vec<u8>,
pub(crate) client_e_pk: Key,
}
impl TryFrom<&[u8]> for Ke1State {
type Error = PakeError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
),
})
}
}
impl ToBytes for Ke1State {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output
}
}
impl ToBytes for Ke1Message {
fn to_bytes(&self) -> Vec<u8> {
[
&self.client_nonce[..],
&serialize(&self.info, 2),
&self.client_e_pk.to_arr(),
]
.concat()
}
}
impl TryFrom<&[u8]> for Ke1Message {
type Error = PakeError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let nonce_len = NonceLen::to_usize();
let checked_nonce =
check_slice_size_atleast(ke1_message_bytes, nonce_len, "ke1_message nonce")?;
let (info, remainder) = tokenize(&checked_nonce[nonce_len..], 2)?;
let checked_client_e_pk = check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
info,
client_e_pk: Key::from_bytes(&checked_client_e_pk)?,
})
}
}
/// The server state produced after the second key exchange message
pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>,
}
/// The second key exchange message
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: Key,
e_info: Vec<u8>,
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat()
}
}
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
type Error = PakeError;
fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
let hash_len = HashLen::to_usize();
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
hashed_transcript: GenericArray::clone_from_slice(
&checked_bytes[hash_len..2 * hash_len],
),
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
})
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[&self.to_bytes_without_mac(), &self.mac[..]].concat()
}
}
impl<HashLen: ArrayLength<u8>> Ke2Message<HashLen> {
fn to_bytes_without_mac(&self) -> Vec<u8> {
[
&self.server_nonce[..],
&self.server_e_pk.to_arr(),
&serialize(&self.e_info, 2),
]
.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) -> Vec<u8> {
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,
client_nonce: &GenericArray<u8, NonceLen>,
server_nonce: &GenericArray<u8, NonceLen>,
id_u: &[u8],
id_s: &[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 info: Vec<u8> = [
STR_3DH,
&serialize(&client_nonce, 2),
&serialize(&server_nonce, 2),
&serialize(id_u, 2),
&serialize(id_s, 2),
]
.concat();
let extracted_ikm = Hkdf::<D>::new(None, &ikm);
let handshake_secret = derive_secrets::<D>(&extracted_ikm, &STR_HANDSHAKE_SECRET, &info)?;
let session_key = derive_secrets::<D>(&extracted_ikm, &STR_SESSION_KEY, &info)?;
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_SERVER_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();
hkdf_label.extend_from_slice(&length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
let mut opaque_label: Vec<u8> = Vec::new();
opaque_label.extend_from_slice(&STR_OPAQUE);
opaque_label.extend_from_slice(&label);
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1));
hkdf_label.extend_from_slice(&serialize(&context, 1));
hkdf.expand(&hkdf_label, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok(okm)
}
fn derive_secrets<D: Hash>(
hkdf: &Hkdf<D>,
label: &[u8],
transcript: &[u8],
) -> Result<Vec<u8>, ProtocolError> {
let hashed_transcript = D::digest(transcript);
hkdf_expand_label_extracted::<D>(
hkdf,
label,
&hashed_transcript,
<D as Digest>::OutputSize::to_usize(),
)
}
+121 -217
View File
@@ -5,97 +5,123 @@
//! 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};
use crate::errors::InternalPakeError;
use crate::group::Group;
use generic_array::{typenum::U32, GenericArray};
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
use generic_bytes_derive::TryFromForSizedBytes;
#[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 std::convert::TryFrom;
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>,
}
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;
pub fn private(&self) -> &Key {
&self.sk
}
/// A constructor that receives public and private key independently as
/// bytes
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError>;
pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
Ok(Self {
pk: public,
sk: private,
_g: PhantomData,
})
}
/// Generating a random key pair given a cryptographic rng
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_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);
Self::new(pk, sk)
}
}
/// 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, TryFromForSizedBytes)]
#[ErrorType = "::generic_bytes::TryFromSizedBytesError"]
#[repr(transparent)]
pub struct Key(Vec<u8>);
@@ -107,14 +133,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 +140,48 @@ 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 curve25519_dalek::ristretto::RistrettoPoint;
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())
}
}
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());
}
impl KeyPair for X25519KeyPair {
type Repr = Key;
#[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(&self) -> &Self::Repr {
&self.pk
}
#[test]
fn test_ristretto_dh(kp1 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy(),
kp2 in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
fn private(&self) -> &Self::Repr {
&self.sk
}
let dh1 = KeyPair::<RistrettoPoint>::diffie_hellman(kp1.public().clone(), kp2.private().clone())?;
let dh2 = KeyPair::<RistrettoPoint>::diffie_hellman(kp2.public().clone(), kp1.private().clone())?;
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
Ok(X25519KeyPair {
pk: public,
sk: private,
})
}
prop_assert_eq!(dh1, dh2);
}
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_private_key_slice(kp in KeyPair::<RistrettoPoint>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().to_vec();
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())
}
let kp2 = KeyPair::<RistrettoPoint>::from_private_key_slice(&sk_bytes)?;
let kp2_private_bytes = kp2.private().to_vec();
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
if !point.is_torsion_free() {
Err(InternalPakeError::SubGroupError)
} else {
Ok(key)
prop_assert_eq!(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()
}
}
+663 -232
View File
@@ -5,332 +5,763 @@
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
//!
//! Note: This implementation is in sync with [draft-irtf-cfrg-opaque-02](https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-02.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::to_bytes] 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)?.to_bytes();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! use std::convert::TryFrom;
//! let password_file =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! &password_file_bytes[..],
//! )?;
//! let password_file = ServerRegistration::<Default>::try_from(&password_file_bytes[..])?;
//! let mut server_rng = OsRng;
//! let (l2, server_state) =
//! ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! # 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)?.to_bytes();
//! # 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>::try_from(
//! # &password_file_bytes[..],
//! # )?;
//! # let (l2, server_state) =
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! # let (l3, client_shared_secret, kd_key) = client_state.finish(
//! # 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)?.to_bytes();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<Default>::try_from(
//! # &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)?.to_bytes();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<Default>::try_from(
//! # &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)?.to_bytes();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<Default>::try_from(
//! # &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)?.to_bytes();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! # use std::convert::TryFrom;
//! # let password_file = ServerRegistration::<Default>::try_from(&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)?.to_bytes();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # ClientLoginStartParameters::default(),
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<Default>::try_from(
//! # &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;
use crate::group::Group;
use crate::hash::Hash;
use crate::serialization::i2osp;
use curve25519_dalek::ristretto::RistrettoPoint;
use digest::{BlockInput, Digest};
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// A subtrait of Group specifying how to hash a password into a point
pub trait GroupWithMapToCurve: Group {
/// The ciphersuite identifier as dictated by
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
const SUITE_ID: usize;
/// transforms a password and domain separation tag (DST) into a curve point
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
/// Generates the contextString parameter as defined in
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
fn get_context_string(mode: u8) -> Vec<u8> {
[i2osp(mode as usize, 1), i2osp(Self::SUITE_ID, 2)].concat()
}
}
impl GroupWithMapToCurve for RistrettoPoint {
const SUITE_ID: usize = 0x0001;
// Implements the hash_to_ristretto255() function from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError> {
let uniform_bytes =
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
Ok(<Self as Group>::hash_to_curve(
&GenericArray::clone_from_slice(&uniform_bytes[..]),
))
}
}
// Computes ceil(x / y)
fn div_ceil(x: usize, y: usize) -> usize {
let additive = (x % y != 0) as usize;
x / y + additive
}
fn xor(x: &[u8], y: &[u8]) -> Result<Vec<u8>, InternalPakeError> {
if x.len() != y.len() {
return Err(InternalPakeError::HashToCurveError);
}
Ok(x.iter().zip(y).map(|(&x1, &x2)| x1 ^ x2).collect())
}
// Corresponds to the expand_message_xmd() function defined in
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
pub(crate) fn expand_message_xmd<H: Hash>(
msg: &[u8],
dst: &[u8],
len_in_bytes: usize,
) -> Result<Vec<u8>, InternalPakeError> {
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
let ell = div_ceil(len_in_bytes, b_in_bytes);
if ell > 255 {
return Err(InternalPakeError::HashToCurveError);
}
let dst_prime = [dst, &i2osp(dst.len(), 1)].concat();
let z_pad = i2osp(0, r_in_bytes);
let l_i_b_str = i2osp(len_in_bytes, 2);
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1), &dst_prime].concat();
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
let mut h = H::new();
h.update(&b[0]);
h.update(&i2osp(1, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[1]
let mut uniform_bytes: Vec<u8> = Vec::new();
uniform_bytes.extend_from_slice(&b[1]);
for i in 2..(ell + 1) {
h.update(xor(&b[0], &b[i - 1])?);
h.update(&i2osp(i, 1));
h.update(&dst_prime);
b.push(h.finalize_reset().to_vec()); // b[i]
uniform_bytes.extend_from_slice(&b[i]);
}
Ok(uniform_bytes[..len_in_bytes].to_vec())
}
#[cfg(test)]
mod tests {
struct Params {
msg: &'static str,
len_in_bytes: usize,
uniform_bytes: &'static str,
}
#[test]
fn test_expand_message_xmd() {
// Test vectors taken from Section K.1 of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
let test_vectors: Vec<Params> = vec![
Params {
msg: "",
len_in_bytes: 0x20,
uniform_bytes: "f659819a6473c1835b25ea59e3d38914c98b374f0970b7e4c\
92181df928fca88",
},
Params {
msg: "abc",
len_in_bytes: 0x20,
uniform_bytes: "1c38f7c211ef233367b2420d04798fa4698080a8901021a79\
5a1151775fe4da7",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x20,
uniform_bytes: "8f7e7b66791f0da0dbb5ec7c22ec637f79758c0a48170bfb7c4611bd304ece89",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x20,
uniform_bytes: "72d5aa5ec810370d1f0013c0df2f1d65699494ee2a39f72e\
1716b1b964e1c642",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x20,
uniform_bytes: "3b8e704fc48336aca4c2a12195b720882f2162a4b7b13a9c\
350db46f429b771b",
},
Params {
msg: "",
len_in_bytes: 0x80,
uniform_bytes: "8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8\
9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991\
e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02\
fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608\
61c0cde2005afc2c114042ee7b5848f5303f0611cf297f",
},
Params {
msg: "abc",
len_in_bytes: 0x80,
uniform_bytes: "fe994ec51bdaa821598047b3121c149b364b178606d5e72b\
fbb713933acc29c186f316baecf7ea22212f2496ef3f785a27e84a\
40d8b299cec56032763eceeff4c61bd1fe65ed81decafff4a31d01\
98619c0aa0c6c51fca15520789925e813dcfd318b542f879944127\
1f4db9ee3b8092a7a2e8d5b75b73e28fb1ab6b4573c192",
},
Params {
msg: "abcdef0123456789",
len_in_bytes: 0x80,
uniform_bytes: "c9ec7941811b1e19ce98e21db28d22259354d4d0643e3011\
75e2f474e030d32694e9dd5520dde93f3600d8edad94e5c3649030\
88a7228cc9eff685d7eaac50d5a5a8229d083b51de4ccc3733917f\
4b9535a819b445814890b7029b5de805bf62b33a4dc7e24acdf2c9\
24e9fe50d55a6b832c8c84c7f82474b34e48c6d43867be",
},
Params {
msg: "q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
qqqqqqqqqqqqqqqqqqqqqqqqq",
len_in_bytes: 0x80,
uniform_bytes: "48e256ddba722053ba462b2b93351fc966026e6d6db49318\
9798181c5f3feea377b5a6f1d8368d7453faef715f9aecb078cd40\
2cbd548c0e179c4ed1e4c7e5b048e0a39d31817b5b24f50db58bb3\
720fe96ba53db947842120a068816ac05c159bb5266c63658b4f00\
0cbf87b1209a225def8ef1dca917bcda79a1e42acd8069",
},
Params {
msg: "a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
len_in_bytes: 0x80,
uniform_bytes: "396962db47f749ec3b5042ce2452b619607f27fd3939ece2\
746a7614fb83a1d097f554df3927b084e55de92c7871430d6b95c2\
a13896d8a33bc48587b1f66d21b128a1a8240d5b0c26dfe795a1a8\
42a0807bb148b77c2ef82ed4b6c9f7fcb732e7f94466c8b51e52bf\
378fba044a31f5cb44583a892f5969dcd73b3fa128816e",
},
];
let dst = "QUUX-V01-CS02-with-expander";
for tv in test_vectors {
let uniform_bytes = super::expand_message_xmd::<sha2::Sha256>(
tv.msg.as_bytes(),
dst.as_bytes(),
tv.len_in_bytes,
)
.unwrap();
assert_eq!(tv.uniform_bytes, hex::encode(uniform_bytes));
}
}
}
+360
View File
@@ -0,0 +1,360 @@
// 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,
hash::Hash,
key_exchange::traits::{KeyExchange, ToBytes},
keypair::{Key, KeyPair, SizedBytesExt},
serialization::{serialize, tokenize},
};
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use std::convert::TryFrom;
use std::marker::PhantomData;
// Messages
// =========
/// The message sent by the client to the server, to initiate registration
pub struct RegistrationRequest<Grp> {
/// blinded password information
pub(crate) alpha: Grp,
}
impl<Grp: Group> TryFrom<&[u8]> for RegistrationRequest<Grp> {
type Error = ProtocolError;
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let elem_len = Grp::ElemLen::to_usize();
let checked_slice = check_slice_size(first_message_bytes, 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[checked_slice.len() - elem_len..]);
let alpha = Grp::from_element_slice(arr)?;
Ok(Self { alpha })
}
}
impl<Grp: Group> RegistrationRequest<Grp> {
/// Byte representation for the registration request
pub fn to_bytes(&self) -> Vec<u8> {
self.alpha.to_arr().to_vec()
}
/// 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 checked_slice =
check_slice_size(&input, Grp::ElemLen::to_usize(), "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 = Grp::from_element_slice(arr)?;
Ok(Self { alpha })
}
}
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegistrationResponse<Grp> {
/// The server's oprf output
pub(crate) beta: Grp,
/// Server's static public key
pub(crate) server_s_pk: Vec<u8>,
}
impl<Grp> TryFrom<&[u8]> for RegistrationResponse<Grp>
where
Grp: Group,
{
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let elem_len = Grp::ElemLen::to_usize();
let checked_slice = check_slice_size_atleast(bytes, elem_len, "second_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 beta = Grp::from_element_slice(arr)?;
// FIXME check public key bytes
let server_s_pk = checked_slice[elem_len..].to_vec();
Ok(Self { beta, server_s_pk })
}
}
impl<Grp> RegistrationResponse<Grp>
where
Grp: Group,
{
/// Byte representation for the registration response message. This does not
/// include the envelope credentials format
pub fn to_bytes(&self) -> Vec<u8> {
[&self.beta.to_arr().to_vec()[..], &self.server_s_pk[..]].concat()
}
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let mut registration_response: Vec<u8> = Vec::new();
registration_response.extend_from_slice(&self.beta.to_arr());
registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2));
registration_response
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let checked_slice =
check_slice_size_atleast(&input, Grp::ElemLen::to_usize(), "second_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..Grp::ElemLen::to_usize()]);
let beta = Grp::from_element_slice(arr)?;
let (server_s_pk, remainder) = tokenize(&checked_slice[Grp::ElemLen::to_usize()..], 2)?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.into());
}
Ok(Self { server_s_pk, beta })
}
}
/// The final message from the client, containing sealed cryptographic
/// identifiers
pub struct RegistrationUpload<D: Hash, G: Group> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
pub(crate) envelope: Envelope<D>,
/// The user's public key
pub(crate) client_s_pk: Key,
pub(crate) _g: PhantomData<G>,
}
impl<D: Hash, G: Group> TryFrom<&[u8]> for RegistrationUpload<D, G> {
type Error = ProtocolError;
fn try_from(third_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let key_len = <Key as SizedBytes>::Len::to_usize();
let envelope_size = key_len + Envelope::<D>::additional_size();
let checked_bytes = check_slice_size(
third_message_bytes,
envelope_size + key_len,
"third_message",
)?;
let unchecked_client_s_pk = Key::from_bytes(&checked_bytes[envelope_size..])?;
let client_s_pk = KeyPair::<G>::check_public_key(unchecked_client_s_pk)?;
Ok(Self {
envelope: Envelope::<D>::from_bytes(&checked_bytes[..envelope_size])?,
client_s_pk,
_g: PhantomData,
})
}
}
impl<D: Hash, G: Group> RegistrationUpload<D, G> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let mut message: Vec<u8> = Vec::new();
message.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2));
message.extend_from_slice(&self.envelope.serialize());
message
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let (client_s_pk, remainder) = tokenize(&input, 2)?;
let (envelope, remainder) = Envelope::<D>::deserialize(&remainder)?;
if !remainder.is_empty() {
return Err(PakeError::SerializationError.into());
}
Ok(Self {
envelope,
client_s_pk: KeyPair::<G>::check_public_key(Key::from_bytes(&client_s_pk)?)?,
_g: PhantomData,
})
}
}
/// 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> TryFrom<&[u8]> for CredentialRequest<CS> {
type Error = ProtocolError;
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
Self::deserialize(first_message_bytes)
}
}
impl<CS: CipherSuite> CredentialRequest<CS> {
/// byte representation for the login request
pub(crate) fn to_bytes(&self) -> Vec<u8> {
[&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat()
}
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let mut credential_request: Vec<u8> = Vec::new();
credential_request.extend_from_slice(&self.alpha.to_arr());
credential_request.extend_from_slice(&self.ke1_message.to_bytes());
credential_request
}
/// 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)?;
let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(
&checked_slice[elem_len..],
)?;
Ok(Self { alpha, ke1_message })
}
}
/// 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) -> Vec<u8> {
[
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(),
&serialize(&server_s_pk.to_arr().to_vec(), 2)[..],
&envelope.to_bytes(),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
Self::try_from(input)
}
}
impl<CS: CipherSuite> TryFrom<&[u8]> for CredentialResponse<CS> {
type Error = ProtocolError;
fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let checked_slice =
check_slice_size_atleast(second_message_bytes, elem_len, "login_second_message_bytes")?;
// Check that the message is actually containing an element of the
// correct subgroup
let beta_bytes = &checked_slice[..elem_len];
let arr = GenericArray::from_slice(beta_bytes);
let beta = CS::Group::from_element_slice(arr)?;
let (serialized_server_s_pk, remainder) = tokenize(&checked_slice[elem_len..], 2)?;
let sized_server_s_pk = check_slice_size(
&serialized_server_s_pk[..],
<Key as SizedBytes>::Len::to_usize(),
"server_s_pk in credential_response",
)?;
let unchecked_server_s_pk = Key::from_bytes(sized_server_s_pk)?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&remainder)?;
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,
})
}
}
/// 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> TryFrom<&[u8]> for CredentialFinalization<CS> {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let ke3_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE3Message::try_from(bytes)?;
Ok(Self { ke3_message })
}
}
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
self.ke3_message.to_bytes()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
Self::try_from(input)
}
/// byte representation for the login finalization
pub fn to_bytes(&self) -> Vec<u8> {
self.ke3_message.to_bytes()
}
}
+573 -608
View File
File diff suppressed because it is too large Load Diff
+109 -76
View File
@@ -3,106 +3,147 @@
// 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::InternalPakeError, 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), InternalPakeError> {
let blind = G::random_scalar(blinding_factor_rng);
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
let mapped_point = G::map_to_curve::<H>(input, &dst)?;
let blind_token = mapped_point * &blind;
Ok((
Token {
data: input.to_vec(),
blind,
},
blind_token,
))
}
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the server (who holds the OPRF key) to the client.
pub(crate) fn 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>(
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)
pub(crate) fn unblind<G: Group>(token: &Token<G>, point: G) -> Vec<u8> {
let unblinded = point * &G::scalar_invert(&token.blind);
unblinded.to_arr().to_vec()
}
// Tests
// =====
pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
token_data: &[u8],
issued_token: &[u8],
info: &[u8],
) -> GenericArray<u8, <H as Digest>::OutputSize> {
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
let hash_input = [
serialize(token_data, 2),
serialize(issued_token, 2),
serialize(info, 2),
serialize(&finalize_dst, 2),
]
.concat();
<H as Digest>::digest(&hash_input)
}
////////////////////////
// Benchmarking shims //
////////////////////////
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8],
blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), InternalPakeError> {
blind::<R, G, H>(input, blinding_factor_rng)
}
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
evaluate(point, oprf_key)
}
#[cfg(feature = "bench")]
#[doc(hidden)]
#[inline]
pub fn unblind_and_finalize_shim<G: GroupWithMapToCurve, H: Hash>(
token: &Token<G>,
point: G,
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
Ok(finalize::<G, H>(
&token.data,
&unblind::<G>(token, point),
b"",
))
}
///////////
// 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)].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::<RistrettoPoint, sha2::Sha512>(&input, &res.to_arr().to_vec(), b"")
}
#[test]
fn oprf_retrieval() -> Result<(), InternalPakeError> {
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)?;
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)?;
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
let res =
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &unblind(&token, beta), b"");
let res2 = prf(&input[..], &oprf_key.as_bytes());
assert_eq!(res, res2);
Ok(())
}
@@ -112,22 +153,14 @@ 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, &unblind(&token, alpha), b"");
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)].concat();
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
let res2 = finalize::<RistrettoPoint, sha2::Sha512>(&input, &point.to_arr().to_vec(), b"");
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);
}
}
+57
View File
@@ -0,0 +1,57 @@
// 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) -> Vec<u8> {
if length <= std::mem::size_of::<usize>() {
return (&input.to_be_bytes()[std::mem::size_of::<usize>() - length..]).to_vec();
}
let mut output = vec![0u8; length];
output.splice(
length - std::mem::size_of::<usize>()..length,
input.to_be_bytes().iter().cloned(),
);
output
}
// Corresponds to the OS2IP() function from RFC8017
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) -> Vec<u8> {
[&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;
+410
View File
@@ -0,0 +1,410 @@
// 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},
group::Group,
key_exchange::{
traits::{KeyExchange, ToBytes},
tripledh::{NonceLen, TripleDH},
},
opaque::*,
serialization::{i2osp, os2ip, serialize},
*,
};
use curve25519_dalek::ristretto::RistrettoPoint;
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_scalar(&mut rng);
// serialization order: scalar, password
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
let reg = ClientRegistration::<Default>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
#[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_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(oprf_key.as_bytes());
let reg = ServerRegistration::<Default>::try_from(&oprf_bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, oprf_bytes);
// If we do have envelope and client pk, the server registration contains
// the whole kit
// 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(&[0, 0]); // empty ciphertext
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>::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_arr().to_vec();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
let r1 = RegistrationRequest::<RistrettoPoint>::deserialize(input.as_slice()).unwrap();
let r1_bytes = r1.serialize();
assert_eq!(input, r1_bytes);
}
#[test]
fn register_second_message_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 pubkey_length: usize = pubkey_bytes.len();
let mut input = Vec::new();
input.extend_from_slice(beta_bytes.as_slice());
input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
input.extend_from_slice(&pubkey_bytes.as_slice());
let r2 = RegistrationResponse::<RistrettoPoint>::deserialize(input.as_slice()).unwrap();
let r2_bytes = r2.serialize();
assert_eq!(input, r2_bytes);
}
#[test]
fn register_third_message_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 pubkey_length: usize = pubkey_bytes.len();
let mut input = Vec::new();
input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
input.extend_from_slice(&pubkey_bytes[..]);
input.extend_from_slice(&envelope_bytes);
let r3 = RegistrationUpload::<sha2::Sha512, RistrettoPoint>::deserialize(&input[..]).unwrap();
let r3_bytes = r3.serialize();
assert_eq!(input, r3_bytes);
}
#[test]
fn login_first_message_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),
&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();
assert_eq!(input, l1_bytes);
}
#[test]
fn login_second_message_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 pubkey_length: usize = pubkey_bytes.len();
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),
&mac[..],
]
.concat();
let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice());
input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
input.extend_from_slice(&pubkey_bytes.as_slice());
input.extend_from_slice(&envelope.serialize());
input.extend_from_slice(&ke2m[..]);
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
let l2_bytes = l2.serialize();
assert_eq!(input, l2_bytes);
}
#[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();
assert_eq!(input, l3_bytes);
}
#[test]
fn client_login_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
let client_e_kp = Default::generate_random_keypair(&mut rng);
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),
&serialize(&l1_data, 2),
&pw[..],
]
.concat();
let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
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),
&client_e_kp.public(),
]
.concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::try_from(&ke1m[..])
.unwrap();
let reg_bytes = reg.to_bytes();
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),
&mac[..],
]
.concat();
let reg =
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::try_from(&ke2m[..])
.unwrap();
let reg_bytes = reg.to_bytes();
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();
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_register_first_message(bytes in vec(any::<u8>(), 0..200)) {
RegistrationRequest::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_register_second_message(bytes in vec(any::<u8>(), 0..200)) {
RegistrationResponse::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_register_third_message(bytes in vec(any::<u8>(), 0..200)) {
RegistrationUpload::<sha2::Sha512, RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_login_first_message(bytes in vec(any::<u8>(), 0..500)) {
CredentialRequest::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) {
CredentialResponse::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_login_third_message(bytes in vec(any::<u8>(), 0..500)) {
CredentialFinalization::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
ClientRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
ServerRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
ClientLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
#[test]
fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
ServerLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
}
}
+45
View File
@@ -0,0 +1,45 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Trait specifying a slow hashing function
use crate::{errors::InternalPakeError, hash::Hash};
use digest::Digest;
#[cfg(feature = "slow-hash")]
use generic_array::typenum::Unsigned;
use generic_array::GenericArray;
/// Used for the slow hashing function in OPAQUE
pub trait SlowHash<D: Hash> {
/// Computes the slow hashing function
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError>;
}
/// A no-op hash which simply returns its input
pub struct NoOpHash;
impl<D: Hash> SlowHash<D> for NoOpHash {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
Ok(input.to_vec())
}
}
#[cfg(feature = "slow-hash")]
impl<D: Hash> SlowHash<D> for scrypt::ScryptParams {
fn hash(
input: GenericArray<u8, <D as Digest>::OutputSize>,
) -> Result<Vec<u8>, InternalPakeError> {
let params =
scrypt::ScryptParams::new(15, 8, 1).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)
}
}
+710
View File
@@ -0,0 +1,710 @@
// 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::*,
group::Group,
key_exchange::tripledh::{NonceLen, TripleDH},
keypair::Key,
opaque::*,
slow_hash::NoOpHash,
tests::mock_rng::CycleRng,
*,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes;
use rand::{rngs::OsRng, RngCore};
use serde_json::Value;
use std::convert::TryFrom;
// Tests
// =====
struct RistrettoSha5123dhNoSlowHash;
impl CipherSuite for RistrettoSha5123dhNoSlowHash {
type Group = RistrettoPoint;
type KeyExchange = TripleDH;
type Hash = sha2::Sha512;
type SlowHash = NoOpHash;
}
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 id_u: Vec<u8>,
pub id_s: Vec<u8>,
pub password: Vec<u8>,
pub blinding_factor: Vec<u8>,
pub oprf_key: Vec<u8>,
pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
pub info1: Vec<u8>,
pub einfo2: Vec<u8>,
pub registration_request: Vec<u8>,
pub registration_response: Vec<u8>,
pub registration_upload: Vec<u8>,
pub credential_request: Vec<u8>,
pub credential_response: Vec<u8>,
pub credential_finalization: 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 export_key: Vec<u8>,
pub session_key: Vec<u8>,
}
static TEST_VECTOR: &str = r#"
{
"client_s_pk": "2c3247e1d7fcf2bef09a0c6e771c44fe922f36deb5726998c89f7816323bc373",
"client_s_sk": "d7dd5397912dc1dce64a26b8d24bee59b7929b15a6b9769ad6357913a13e7f09",
"client_e_pk": "3431e8f57ce0c1ae9da2b4f37a2f877a64b244a86bc1384ba70a9c39985b3729",
"client_e_sk": "8410c0520fd2a37775e54000d9abb5a84825d37649827d8f2721dd655cd64c0c",
"server_s_pk": "6a55f77ffb17741d5dfd404ef7079ef276cb2114a76f507c0ae7172847da647a",
"server_s_sk": "f4433f94dea3f4bf877ab760566efa36340e34d516dc4462b2ad5b9600157a06",
"server_e_pk": "32d56c7e3b882b9ac1a4e38d60456c4942ec6b6aceed91d069ead967a825df74",
"server_e_sk": "256f9cdddb2c4bed42866e37d5dff019b4a9584b7486e5683d52b56842d82b0d",
"id_u": "696455",
"id_s": "696453",
"password": "70617373776f7264",
"blinding_factor": "c0937fddc5b7a8cbb76301cbe588268d52a9efe6a205af21a0a12b1fadf3a008",
"oprf_key": "f386e8710c12c870a0ec74f09364811142050a5266ca53d36c11e369343a5e09",
"envelope_nonce": "2338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c93",
"client_nonce": "09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf",
"server_nonce": "5c11e9abe0d5d677dd1a66cd1c8e9baa1f1a44c7554ffe61978b100e657f79f4",
"info1": "696e666f31",
"einfo2": "65696e666f32",
"registration_request": "58e629f68bd59f1d5fcee531a5afeedb4e07d2c6aed98d15cab686d063f2f46e",
"registration_response": "a2227384ed34860b7d879d617f7daa802b6a878eaa5eab33669400f30ad0431d00206a55f77ffb17741d5dfd404ef7079ef276cb2114a76f507c0ae7172847da647a",
"registration_upload": "00202c3247e1d7fcf2bef09a0c6e771c44fe922f36deb5726998c89f7816323bc373022338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c930022576b449b09abeb68d5f6c56082b0c2f560c636bff4f0af3b44b6377045201b545598d319af3122041f06e3ab86371bff6c0727557c77d20efeb9a0b9f24573e3646735eeebed45e741d11d0120741af27b04c2716eb6bdfe950daff937cfcae8569e",
"credential_request": "58e629f68bd59f1d5fcee531a5afeedb4e07d2c6aed98d15cab686d063f2f46e09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf0005696e666f313431e8f57ce0c1ae9da2b4f37a2f877a64b244a86bc1384ba70a9c39985b3729",
"credential_response": "a2227384ed34860b7d879d617f7daa802b6a878eaa5eab33669400f30ad0431d00206a55f77ffb17741d5dfd404ef7079ef276cb2114a76f507c0ae7172847da647a022338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c930022576b449b09abeb68d5f6c56082b0c2f560c636bff4f0af3b44b6377045201b545598d319af3122041f06e3ab86371bff6c0727557c77d20efeb9a0b9f24573e3646735eeebed45e741d11d0120741af27b04c2716eb6bdfe950daff937cfcae8569e5c11e9abe0d5d677dd1a66cd1c8e9baa1f1a44c7554ffe61978b100e657f79f432d56c7e3b882b9ac1a4e38d60456c4942ec6b6aceed91d069ead967a825df740006a0c5112e94c86671fd294468658b8a78375f89fca9fb7c3ee1f63871360d5237e57da81719849a8dbccb974742333a1aed034d63d0bdba0beacfe30196912d561e99c1e79ccf",
"credential_finalization": "b1fad859f343f1a15df14bd7782db9ab6ee4493225a470152e53b3b7601dc954bc80d7b7dadfe70f743b1ab1552a054e7a9d192399ea42073a1d1c5839aabfc2",
"client_registration_state": "c0937fddc5b7a8cbb76301cbe588268d52a9efe6a205af21a0a12b1fadf3a00870617373776f7264",
"client_login_state": "c0937fddc5b7a8cbb76301cbe588268d52a9efe6a205af21a0a12b1fadf3a008006758e629f68bd59f1d5fcee531a5afeedb4e07d2c6aed98d15cab686d063f2f46e09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf0005696e666f313431e8f57ce0c1ae9da2b4f37a2f877a64b244a86bc1384ba70a9c39985b372900408410c0520fd2a37775e54000d9abb5a84825d37649827d8f2721dd655cd64c0c09f2721189a82d90e891d346aa9be462e003cc6b6824a55352da7df685eca5bf70617373776f7264",
"server_registration_state": "f386e8710c12c870a0ec74f09364811142050a5266ca53d36c11e369343a5e09",
"server_login_state": "761333eea593c396021c3930fb36cf97aad7c7ea00f1ff983e4df9ca002885017161427303fddb4508c9136a67612e01b673ed88b1de49ed6628d0a77e43c590595f53bf0b0766c165b876173c509efc982868d3860df5dad3f0753477ec9bdd103b71dab9540537f2b10964da82032a6339da6d663a409f4e8e5dea03e3653bb6202f265ed814df39f446486197e1c6091ec6b74200f18df5eef59813ff7f245d69b34e8843be496630921601ab784b1f3ccbfe2ff014ea132199c5c76deda7",
"password_file": "f386e8710c12c870a0ec74f09364811142050a5266ca53d36c11e369343a5e092c3247e1d7fcf2bef09a0c6e771c44fe922f36deb5726998c89f7816323bc373022338be69855ad2a280ab5ed67fb04cd96691841241dbb4873ca776adc60d1c930022576b449b09abeb68d5f6c56082b0c2f560c636bff4f0af3b44b6377045201b545598d319af3122041f06e3ab86371bff6c0727557c77d20efeb9a0b9f24573e3646735eeebed45e741d11d0120741af27b04c2716eb6bdfe950daff937cfcae8569e",
"export_key": "96b95891f06c7f02ab9c508f30e1a82ddea25e2f4fc4ffdafeb199ee65e24e418b2648a201e889eaf84301a1a8d0c3b7fea0ec8d4611686e6daee12430f60c05",
"session_key": "b6202f265ed814df39f446486197e1c6091ec6b74200f18df5eef59813ff7f245d69b34e8843be496630921601ab784b1f3ccbfe2ff014ea132199c5c76deda7"
}
"#;
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(),
id_u: decode(&values, "id_u").unwrap(),
id_s: decode(&values, "id_s").unwrap(),
password: decode(&values, "password").unwrap(),
blinding_factor: decode(&values, "blinding_factor").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(),
info1: decode(&values, "info1").unwrap(),
einfo2: decode(&values, "einfo2").unwrap(),
registration_request: decode(&values, "registration_request").unwrap(),
registration_response: decode(&values, "registration_response").unwrap(),
registration_upload: decode(&values, "registration_upload").unwrap(),
credential_request: decode(&values, "credential_request").unwrap(),
credential_response: decode(&values, "credential_response").unwrap(),
credential_finalization: decode(&values, "credential_finalization").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(),
export_key: decode(&values, "export_key").unwrap(),
session_key: decode(&values, "session_key").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!("\"id_u\": \"{}\",\n", hex::encode(&p.id_u)).as_str());
s.push_str(format!("\"id_s\": \"{}\",\n", hex::encode(&p.id_s)).as_str());
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
s.push_str(
format!(
"\"blinding_factor\": \"{}\",\n",
hex::encode(&p.blinding_factor)
)
.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!("\"info1\": \"{}\",\n", hex::encode(&p.info1)).as_str());
s.push_str(format!("\"einfo2\": \"{}\",\n", hex::encode(&p.einfo2)).as_str());
s.push_str(
format!(
"\"registration_request\": \"{}\",\n",
hex::encode(&p.registration_request)
)
.as_str(),
);
s.push_str(
format!(
"\"registration_response\": \"{}\",\n",
hex::encode(&p.registration_response)
)
.as_str(),
);
s.push_str(
format!(
"\"registration_upload\": \"{}\",\n",
hex::encode(&p.registration_upload)
)
.as_str(),
);
s.push_str(
format!(
"\"credential_request\": \"{}\",\n",
hex::encode(&p.credential_request)
)
.as_str(),
);
s.push_str(
format!(
"\"credential_response\": \"{}\",\n",
hex::encode(&p.credential_response)
)
.as_str(),
);
s.push_str(
format!(
"\"credential_finalization\": \"{}\",\n",
hex::encode(&p.credential_finalization)
)
.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!("\"export_key\": \"{}\",\n", hex::encode(&p.export_key)).as_str());
s.push_str(format!("\"session_key\": \"{}\"\n", hex::encode(&p.session_key)).as_str());
s.push_str("}\n");
s
}
fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
let mut rng = OsRng;
// Inputs
let server_s_kp = CS::generate_random_keypair(&mut rng);
let server_e_kp = CS::generate_random_keypair(&mut rng);
let client_s_kp = CS::generate_random_keypair(&mut rng);
let client_e_kp = CS::generate_random_keypair(&mut rng);
let id_u = b"idU";
let id_s = b"idS";
let password = b"password";
let mut oprf_key_raw = [0u8; 32];
rng.fill_bytes(&mut oprf_key_raw);
let mut envelope_nonce = [0u8; 32];
rng.fill_bytes(&mut envelope_nonce);
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut client_nonce);
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce);
let blinding_factor = CS::Group::random_scalar(&mut rng);
let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone();
let info1 = b"info1";
let einfo2 = b"einfo2";
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_bytes.to_vec());
let client_registration_start_result =
ClientRegistration::<CS>::start(&mut blinding_factor_registration_rng, password).unwrap();
let blinding_factor_bytes_returned =
CS::Group::scalar_as_bytes(&client_registration_start_result.state.token.blind).clone();
assert_eq!(
hex::encode(&blinding_factor_bytes),
hex::encode(&blinding_factor_bytes_returned)
);
let registration_request_bytes = client_registration_start_result
.message
.serialize()
.to_vec();
let client_registration_state = client_registration_start_result.state.to_bytes().to_vec();
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
let server_registration_start_result = ServerRegistration::<CS>::start(
&mut oprf_key_rng,
client_registration_start_result.message,
server_s_kp.public(),
)
.unwrap();
let registration_response_bytes = server_registration_start_result
.message
.serialize()
.to_vec();
let oprf_key_bytes =
CS::Group::scalar_as_bytes(&server_registration_start_result.state.oprf_key).clone();
let server_registration_state = server_registration_start_result.state.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().to_arr());
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let client_registration_finish_result = client_registration_start_result
.state
.finish(
&mut finish_registration_rng,
server_registration_start_result.message,
ClientRegistrationFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
)
.unwrap();
let registration_upload_bytes = client_registration_finish_result
.message
.serialize()
.to_vec();
let password_file = server_registration_start_result
.state
.finish(client_registration_finish_result.message)
.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_bytes);
client_login_start.extend_from_slice(&client_e_kp.private().to_arr());
client_login_start.extend_from_slice(&client_nonce);
let mut client_login_start_rng = CycleRng::new(client_login_start);
let client_login_start_result = ClientLogin::<CS>::start(
&mut client_login_start_rng,
password,
ClientLoginStartParameters::WithInfo(info1.to_vec()),
)
.unwrap();
let credential_request_bytes = client_login_start_result.message.serialize().to_vec();
let client_login_state = client_login_start_result.state.to_bytes().to_vec();
let mut server_e_sk_and_nonce_rng = CycleRng::new(
[
server_e_kp.private().to_arr().to_vec(),
server_nonce.to_vec(),
]
.concat(),
);
let server_login_start_result = ServerLogin::<CS>::start(
&mut server_e_sk_and_nonce_rng,
password_file,
server_s_kp.private(),
client_login_start_result.message,
ServerLoginStartParameters::WithInfoAndIdentifiers(
einfo2.to_vec(),
id_u.to_vec(),
id_s.to_vec(),
),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize().to_vec();
let server_login_state = server_login_start_result.state.to_bytes().to_vec();
let client_login_finish_result = client_login_start_result
.state
.finish(
server_login_start_result.message,
ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
)
.unwrap();
let credential_finalization_bytes = client_login_finish_result.message.to_bytes().to_vec();
TestVectorParameters {
client_s_pk: client_s_kp.public().to_arr().to_vec(),
client_s_sk: client_s_kp.private().to_arr().to_vec(),
client_e_pk: client_e_kp.public().to_arr().to_vec(),
client_e_sk: client_e_kp.private().to_arr().to_vec(),
server_s_pk: server_s_kp.public().to_arr().to_vec(),
server_s_sk: server_s_kp.private().to_arr().to_vec(),
server_e_pk: server_e_kp.public().to_arr().to_vec(),
server_e_sk: server_e_kp.private().to_arr().to_vec(),
id_u: id_u.to_vec(),
id_s: id_s.to_vec(),
password: password.to_vec(),
blinding_factor: blinding_factor_bytes.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(),
info1: info1.to_vec(),
einfo2: einfo2.to_vec(),
registration_request: registration_request_bytes,
registration_response: registration_response_bytes,
registration_upload: registration_upload_bytes,
credential_request: credential_request_bytes,
credential_response: credential_response_bytes,
credential_finalization: credential_finalization_bytes,
password_file: password_file_bytes,
client_registration_state,
server_registration_state,
client_login_state,
server_login_state,
session_key: client_login_finish_result.session_key,
export_key: client_registration_finish_result.export_key.to_vec(),
}
}
#[test]
fn generate_test_vectors() {
let parameters = generate_parameters::<RistrettoSha5123dhNoSlowHash>();
println!("{}", stringify_test_vectors(&parameters));
}
#[test]
fn test_registration_request() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut rng = CycleRng::new(parameters.blinding_factor.to_vec());
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(&mut rng, &parameters.password)?;
assert_eq!(
hex::encode(&parameters.registration_request),
hex::encode(client_registration_start_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.client_registration_state),
hex::encode(client_registration_start_result.state.to_bytes())
);
Ok(())
}
#[test]
fn test_registration_response() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut oprf_key_rng,
RegistrationRequest::deserialize(&parameters.registration_request[..])?,
&Key::try_from(&parameters.server_s_pk[..])?,
)?;
assert_eq!(
hex::encode(parameters.registration_response),
hex::encode(server_registration_start_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.server_registration_state),
hex::encode(server_registration_start_result.state.to_bytes())
);
Ok(())
}
#[test]
fn test_registration_upload() -> Result<(), ProtocolError> {
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 result = ClientRegistration::<RistrettoSha5123dhNoSlowHash>::try_from(
&parameters.client_registration_state[..],
)?
.finish(
&mut finish_registration_rng,
RegistrationResponse::deserialize(&parameters.registration_response[..])?,
ClientRegistrationFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
)?;
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_password_file() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let server_registration = ServerRegistration::<RistrettoSha5123dhNoSlowHash>::try_from(
&parameters.server_registration_state[..],
)?;
let password_file = server_registration.finish(RegistrationUpload::deserialize(
&parameters.registration_upload[..],
)?)?;
assert_eq!(
hex::encode(parameters.password_file),
hex::encode(password_file.to_bytes())
);
Ok(())
}
#[test]
fn test_credential_request() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let client_login_start_rng = [
parameters.blinding_factor,
parameters.client_e_sk,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start_rng);
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_login_start_rng,
&parameters.password,
ClientLoginStartParameters::WithInfo(parameters.info1),
)?;
assert_eq!(
hex::encode(&parameters.credential_request),
hex::encode(client_login_start_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.client_login_state),
hex::encode(client_login_start_result.state.to_bytes())
);
Ok(())
}
#[test]
fn test_credential_response() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut server_e_sk_and_nonce_rng =
CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat());
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_e_sk_and_nonce_rng,
ServerRegistration::try_from(&parameters.password_file[..])?,
&Key::try_from(&parameters.server_s_sk[..])?,
CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize(
&parameters.credential_request[..],
)?,
ServerLoginStartParameters::WithInfoAndIdentifiers(
parameters.einfo2.to_vec(),
parameters.id_u,
parameters.id_s,
),
)?;
assert_eq!(
hex::encode(&parameters.info1),
hex::encode(server_login_start_result.plain_info),
);
assert_eq!(
hex::encode(&parameters.credential_response),
hex::encode(server_login_start_result.message.serialize())
);
assert_eq!(
hex::encode(&parameters.server_login_state),
hex::encode(server_login_start_result.state.to_bytes())
);
Ok(())
}
#[test]
fn test_credential_finalization() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let client_login_finish_result =
ClientLogin::<RistrettoSha5123dhNoSlowHash>::try_from(&parameters.client_login_state[..])?
.finish(
CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize(
&parameters.credential_response[..],
)?,
ClientLoginFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
)?;
assert_eq!(
hex::encode(&parameters.einfo2),
hex::encode(&client_login_finish_result.confidential_info)
);
assert_eq!(
hex::encode(&parameters.server_s_pk),
hex::encode(&client_login_finish_result.server_s_pk.to_arr().to_vec())
);
assert_eq!(
hex::encode(&parameters.session_key),
hex::encode(&client_login_finish_result.session_key)
);
assert_eq!(
hex::encode(&parameters.credential_finalization),
hex::encode(client_login_finish_result.message.to_bytes())
);
assert_eq!(
hex::encode(&parameters.export_key),
hex::encode(client_login_finish_result.export_key)
);
Ok(())
}
#[test]
fn test_server_login_finish() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let server_login_result =
ServerLogin::<RistrettoSha5123dhNoSlowHash>::try_from(&parameters.server_login_state[..])?
.finish(CredentialFinalization::try_from(
&parameters.credential_finalization[..],
)?)?;
assert_eq!(
hex::encode(parameters.session_key),
hex::encode(server_login_result.session_key)
);
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 = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
registration_password,
)?;
let server_registration_start_result =
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::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 p_file = server_registration_start_result
.state
.finish(client_registration_finish_result.message)?;
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_rng,
login_password,
ClientLoginStartParameters::default(),
)?;
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
&mut server_rng,
p_file,
&server_kp.private(),
client_login_start_result.message,
ServerLoginStartParameters::default(),
)?;
let client_login_result = client_login_start_result.state.finish(
server_login_start_result.message,
ClientLoginFinishParameters::default(),
);
if hex::encode(registration_password) == hex::encode(login_password) {
let client_login_finish_result = client_login_result?;
let server_login_finish_result = server_login_start_result
.state
.finish(client_login_finish_result.message)?;
assert_eq!(
hex::encode(server_login_finish_result.session_key),
hex::encode(client_login_finish_result.session_key)
);
assert_eq!(
hex::encode(client_registration_finish_result.export_key),
hex::encode(client_login_finish_result.export_key)
);
} else {
let res = matches!(
client_login_result,
Err(ProtocolError::VerificationError(
PakeError::InvalidLoginError
))
);
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")
}
+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")
}
+574
View File
@@ -0,0 +1,574 @@
// 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, opaque::*,
slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
};
use curve25519_dalek::ristretto::RistrettoPoint;
use serde_json::Value;
use std::convert::TryFrom;
// 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>,
}
static TEST_VECTORS: &[&str] = &[
r#"
Group: ristretto255
EnvelopeMode: 01
OPRF: 0001
SlowHash: Identity
Hash: SHA512
server_nonce: a4997137a8fa0d4baf7052a499bf877057f9404e03c889d641a0d7c
807b6a518
oprf_key: 5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be03
7e50b
password: 436f7272656374486f72736542617474657279537461706c65
blind_login: ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63
e3263503
server_private_keyshare: 31587dff30b8001d9d43584decc22e358fa7f9d6e606
29fb1223081c3bae7103
client_nonce: 75a1ad27ab77578bc08b44c4318f09b31d53145c9ba3b42abf0ea08
a781277a6
server_info: 6772656574696e677320616c696365
client_info: 68656c6c6f20626f62
client_private_keyshare: fbbf4ad24119f08a35bf999f8ae0c779ed7b3e266bf3
3f793f6bf9ebf4578005
envelope_nonce: 6c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e037
585783444c0
server_keyshare: 82be40ef93bf7c6edd43d4ed9f52fa19827649b819de39c52a22
43e985b75d62
blind_registration: c604c785ada70d77a5256ae21767de8c3304115237d262134
f5e46e512cf8e03
client_public_key: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3ef
caba4f1c4ae861
client_private_key: f0f56cfb488649fe28691dd9aa5dc9ff4c0e6028075baa3c5
615398a2cb12304
client_keyshare: 484e47e31b3132f4ee512e41805a1690891111a7b885bc526198
22c14cabf360
server_public_key: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdc
eeeb935924a903
server_private_key: d63d709e3a739a128929a9f289ff263fdcbc457f2f47f7c43
ccafbbfee72290c
auth_key: 22198da4ad73b1d35cd8bb875e64ce1a9fc2edeb073d760e114d1d7a2f8
6d47411caf1787907e2ff96cd3190b14d101101d74cff234259d9f19a18f2cfe29d0b
server_mac_key: 9de28e2f7107afe266570934c033dd6a403fb2b09a9f1a1357a81
9fb072d25e6651626638d77bd7f0adf4b2b715d0a0bee2ac531fd0f7da699aba6e9ed
717466
envelope: 016c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e0375857
83444c00022e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92c73838c043
fc316deeb83deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23775aa54633
ed75665cd88f451e044cac1b282d890269476d1ac18ff9a4c0cb832e1143ffa7d447a
prk: 732303f65e76c39f30876aec31af1f5bcd8861626c922baa2e842209c7d5bf2e
024912db2d6cac00b1260c437d34ec588a664a55fc7a0a40251915d2b15d8ec9
client_mac_key: 1536168d48218d08dcbed3438e897d98eff566894240d8d136be0
4ba46b2c788883fd165ce7614c52a6a9c926bf55d249e6c29bc4a23d5a1775ad294a7
858922
pseudorandom_pad: e99274b5cd27d14aeeaf16e7aa8d7e7a03071d58229c5dc96d0
46ed57a761ddccdbc
handshake_encrypt_key: c646def7a8ca75282ac1a0ad15630834cba2772837d7a3
a26d00b36923e55b3f5d065679c715e4c799654f261c865f1a55bc94cabc29a6e2e72
296dfc494a984
handshake_secret: cc887e128c28064106d4101ac3de633e9096e170f2c4a9913d3
50f306274b1665a5b251f761672c12db4c403a615c22cee96adb3539fa62662a17f2e
18cd5fed
registration_response: 1867301bcc67bdf8e640b7d6edcbe2a65488446417b50d
30cdba66ccb379e57200205442a6f57333a332b4c6f07308f6fa846bde3ed27425820
cdceeeb935924a903
export_key: 6ca2c344763e5bc9e3d2bbfe3d982b826b709da597e28e85f9594ec54
2a20c697d55de277ccce1d1af7c48ab7fea1467ac1e3a99c71dcf6326a909d280bd2f
6f
registration_upload: 0020b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9a
a2c3efcaba4f1c4ae861016c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc5
56e037585783444c00022e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92
c73838c043fc316deeb83deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23
775aa54633ed75665cd88f451e044cac1b282d890269476d1ac18ff9a4c0cb832e114
3ffa7d447a
registration_request: 241b621c417c0705b5ea7a8b7cdd5039fd61e6b63effe2a
44418164c4d49003e
session_key: ee9f1ef224d498858f6c9b3a121016a38bad7816055c452b1c7edf3d
d439c42a4cc78cbd672e985a20910df14f8f1af4ce5793303ffe6954ff5f1a264e3fd
515
KE3: e771daf28bc5e8068dead67c3db19f9ad03ee919e52f6c7a6e79cf1085bd7448
1e76512c77f37762578eb2faff8fe98e4185ca2d01957216c556d33a6fba3028
KE2: e83812f06568d57b8cdfdcc90fe91454e21bd25dd2a1c32dd1599a2e4a4b6c35
00205442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdceeeb935924a9030
16c6bb9021b9833c788dd25994d3ce7f3811338744fd6dc556e037585783444c00022
e9b28440a1dc99cca7513e8eb754d427caf8515642b45a92c73838c043fc316deeb83
deface81589bc8e5c2c767b5ab79218834fbf4fa4f87cf23775aa54633ed75665cd88
f451e044cac1b282d890269476d1ac18ff9a4c0cb832e1143ffa7d447aa4997137a8f
a0d4baf7052a499bf877057f9404e03c889d641a0d7c807b6a51882be40ef93bf7c6e
dd43d4ed9f52fa19827649b819de39c52a2243e985b75d62000f13aed85ae30aee2f8
9ac5e1c5dd53609b890267ef2d765bb56000bc704c2ba4e4256107befee6655cdb084
64e6c75d8b251642e0d721b3fce38f245568b50e7f6c98825d54cc1a26d6eafadcc1d
344
KE1: b68e0e356f8490fa9c3bed952e16cc02db21eda686b3c484f3d9d912caa41f76
75a1ad27ab77578bc08b44c4318f09b31d53145c9ba3b42abf0ea08a781277a600096
8656c6c6f20626f62484e47e31b3132f4ee512e41805a1690891111a7b885bc526198
22c14cabf360
"#,
r#"
Group: ristretto255
EnvelopeMode: 02
OPRF: 0001
SlowHash: Identity
Hash: SHA512
server_nonce: 0f3a6da8b667bc7a383c987586bee749c5f2787691baca68757e78b
6128b0a0f
oprf_key: 89c61a42c8191a5ca41f2fe959843d333bcf43173b7de4c5c119e0e0d8b
0e707
password: 436f7272656374486f72736542617474657279537461706c65
blind_login: e6d0f1d89ad552e383d6c6f4e8598cc3037d6e274d22da3089e7afbd
4171ea02
server_private_keyshare: 70c944dcb7f4dddde168ecb48dd9488c62b6fc7e9bb4
2a16d291afca9dd25b07
client_nonce: 480917b09c6720680b4a7a0ba9f54b69d870f640a4a7994b47ad07d
1a95c984f
server_info: 6772656574696e677320616c696365
server_identity: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdcee
eb935924a903
client_info: 68656c6c6f20626f62
client_private_keyshare: a4ba6cb7e16ab76eccdb4c0b9261eedd426d7863f00b
fc4a0e09476d3121e70c
envelope_nonce: e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7dee
b8cfb42d13f
server_public_key: 5442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdc
eeeb935924a903
client_identity: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3efca
ba4f1c4ae861
blind_registration: 019cbd1d7420292528f8cdd62f339fdabb602f04a95dac9db
cec831b8c681a09
client_public_key: b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9aa2c3ef
caba4f1c4ae861
client_private_key: f0f56cfb488649fe28691dd9aa5dc9ff4c0e6028075baa3c5
615398a2cb12304
client_keyshare: 4ae7d50bb80cc8f5034d36c1c27edc30caca0983677a941bc0ac
e5e10b18300a
server_keyshare: a2e9e0809b270a1d5c8208f3498a3188538265e6a9e6b274cb38
c4c5d9b1792d
server_private_key: d63d709e3a739a128929a9f289ff263fdcbc457f2f47f7c43
ccafbbfee72290c
auth_key: 4c7c0ae950ad7e9c518266f953d4a01bd2232695f92fb028aa6c124996e
31a205f621305fd4997edf8a5fce04a51252ba8430227c134b81d7093a58e01c70752
server_mac_key: 974bde939ef1d30ba29f9ec2addcaff1eeb105a1f534e04113f9f
0b5c3b9a0797e3fa5f7006e63dfb2b0ce74d002bd1161767c361507bfb1fa0fed5063
1cdcb7
envelope: 02e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7deeb8cf
b42d13f00227ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26ecd566d7688
095f9478a721e5a592be9dd3bda76ed97421819aca4a30752813223d33bc7ea443be9
76754605116278c7fffda4de142674ed154f540bd3285080637eb4b929e1378336b11
prk: e3bb74ca5f88a95571578e921489e1b6119b438e4efce6e955ae9b6453f24aa4
e3a34fa22bc5f470cfc134ca0784a9cd7df64be46ff3b325fc19f4009979bf2e
client_mac_key: 1c55ecba64d98dc2db8f45faa72b8fdd63cad8677b8665cc575cf
8ad36ef38ec63c9dfc5552007573215983f55d8f5cfa1651da8417e3f27094dde5254
7f5b89
pseudorandom_pad: 7ef495b828a97c896f381824d3371ba012eb63c3f19bb535676
a3b63b18373255ba3
handshake_encrypt_key: 0852fb826109073be6e7c065b6e2d1f872c16f9977c177
cd2f945b26bb933fe6252f226f40483f7aa52f545f801d1e4430f30f80fd42070494e
a77fdcecd6595
handshake_secret: bfd3bfbe451520880975d0e568cbd3b5155b23c02de504fccb9
dd5a8195266cd94d49d040530b3d6b0a585d542eb24da708a2b6f6dc34dee4652d0c1
c62c4e59
registration_response: 088ac01ebf5700f0c96bc2988509343cb7e2dd6f0df820
d0fb807faa11a26f5600205442a6f57333a332b4c6f07308f6fa846bde3ed27425820
cdceeeb935924a903
export_key: 0effa605dc47ba4fe565c423b782b8b6697b26ee2ede7059b0e17510d
f8b11554ce053409671480a56ffbe77b91edc95205c213caeaf9dcb0841790ff834a0
09
registration_upload: 0020b2d1b10f3741a8efef3139f4d2889f2b11f776b79b9a
a2c3efcaba4f1c4ae86102e38fb444afe3df13ae05e6876d10eca7661196375518eb6
6d7deeb8cfb42d13f00227ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26e
cd566d7688095f9478a721e5a592be9dd3bda76ed97421819aca4a30752813223d33b
c7ea443be976754605116278c7fffda4de142674ed154f540bd3285080637eb4b929e
1378336b11
registration_request: c8d2e9ba503bf3f8821226653314427edb1ec8a3ecc94a5
dfbbe33d59d07b645
session_key: 8dc21ff264f2774de95955d35544ba314e92d07f4a3b32c89ead5e70
c83ac4c0221deadd34ed11d43fc4d3651aec612d696c63979c96bf1ddd1ee44da5d0c
d68
KE3: c178e45ed9b314653685cdbf5f7730e3e40f8652ceb9b10f47d1c784fdd75dd1
07c4ae3f7683de5a692359178c8f13f41a043fc1dcfc14b1fb7cb411514efc6c
KE2: 5079b16709b195b3b63257b419efb752bd0603170160fa72b828ce9ff9209c0c
00205442a6f57333a332b4c6f07308f6fa846bde3ed27425820cdceeeb935924a9030
2e38fb444afe3df13ae05e6876d10eca7661196375518eb66d7deeb8cfb42d13f0022
7ed4654d4452340f26c6304dceeeb1fddb142fcd91b3b26ecd566d7688095f9478a72
1e5a592be9dd3bda76ed97421819aca4a30752813223d33bc7ea443be976754605116
278c7fffda4de142674ed154f540bd3285080637eb4b929e1378336b110f3a6da8b66
7bc7a383c987586bee749c5f2787691baca68757e78b6128b0a0fa2e9e0809b270a1d
5c8208f3498a3188538265e6a9e6b274cb38c4c5d9b1792d000f5a1a0b34573bf728b
14f53485f3bf62fd91154dc9ca01b21945b2204f96adc87bd80e8283ecf3522b6d893
7527f2e9b9782a94fedab0fa304590d1d4f03d72fb1664fca1523a3be95f6c3a97d0a
2ed
KE1: 7024ca0d5423176294fbb9ca968d8ce3fc879a231f1ceef69e672c89e02ded59
480917b09c6720680b4a7a0ba9f54b69d870f640a4a7994b47ad07d1a95c984f00096
8656c6c6f20626f624ae7d50bb80cc8f5034d36c1c27edc30caca0983677a941bc0ac
e5e10b18300a
"#,
];
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::try_from(&parameters.server_public_key[..]).unwrap(),
)?;
let password_file = server_registration_start_result
.state
.finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
Ok(password_file.to_bytes())
}
#[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::try_from(&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::try_from(&password_file_bytes[..]).unwrap(),
&Key::try_from(&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.to_bytes())
);
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::try_from(&password_file_bytes[..]).unwrap(),
&Key::try_from(&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::try_from(&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);
}
+157
View File
@@ -0,0 +1,157 @@
// 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>,
unblinded_element: Vec<u8>,
info: 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": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"input": "00",
"blind": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
"blinded_element": "5cccd309ec729aebe398c53e19c0ab09c24a29f01036960bdad109852e7bdb44",
"evaluation_element": "86bd5eeabf29a87cb4a5c7207cb3ade5297e65f9b74c979bd3551891f4b21515",
"unblinded_element": "3c7f2d901c0d4f245503a186086fbdf5d8b4408432b25c5163e8b5a19c258348",
"info": "4f505246207465737420766563746f7273",
"output": "0bb570873cc0402ca38f1a2c395301f2a3627616e305f2bc54bb08c3f6ea9871eb71074e52e36b90778ba7c3e3429ef7170245c9e01647f3827fdef84d3ba930"
}
"#,
r#"
{
"sksm": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
"input": "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a",
"blind": "ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e3263503",
"blinded_element": "227d63ca69e93bd062193c1e97fff3d5ebf628f646009d77c4e22ba6429be154",
"evaluation_element": "063b91a12e7cbb98dfeb75d8a7eeb83aacf9fd6df7e0b4197466fb77a27fa631",
"unblinded_element": "804ec6774764ed50a0bbad0a5f477aa04df7323acab8f98ca6e468b7790bca4c",
"info": "4f505246207465737420766563746f7273",
"output": "af7cc264dbc96a6b898ba0fa33bfa9e1407bf1dcfbf8772204d470d4458b8f047806679dbfa251f656b906edf9fa638e268adf979bd0e2380a092047d61f9db9"
}
"#,
];
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(),
unblinded_element: decode(&values, "unblinded_element").unwrap(),
info: decode(&values, "info").unwrap(),
output: decode(&values, "output").unwrap(),
}
}
// Tests input -> blind, blinded_element
#[test]
fn test_blind() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let mut rng = CycleRng::new(parameters.blind.to_vec());
let (token, blinded_element) =
oprf::blind::<_, RistrettoPoint, Sha512>(&parameters.input, &mut rng)?;
assert_eq!(
&parameters.blind,
&RistrettoPoint::scalar_as_bytes(&token.blind).to_vec()
);
assert_eq!(
&parameters.blinded_element,
&blinded_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests sksm, blinded_element -> evaluation_element
#[test]
fn test_evaluate() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let evaluation_element = oprf::evaluate::<RistrettoPoint>(
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.blinded_element,
))
.unwrap(),
&RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&parameters.sksm)).unwrap(),
);
assert_eq!(
&parameters.evaluation_element,
&evaluation_element.to_arr().to_vec()
);
}
Ok(())
}
// Tests sksm, evaluation_element -> evaluation_element
#[test]
fn test_unblind() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let token = oprf::Token {
data: parameters.input,
blind: RistrettoPoint::from_scalar_slice(GenericArray::from_slice(
&parameters.blind[..],
))
.unwrap(),
};
let unblinded_element = oprf::unblind::<RistrettoPoint>(
&token,
RistrettoPoint::from_element_slice(GenericArray::from_slice(
&parameters.evaluation_element,
))
.unwrap(),
);
assert_eq!(&parameters.unblinded_element, &unblinded_element);
}
Ok(())
}
// Tests input, unblinded_element, info -> output
#[test]
fn test_finalize() -> Result<(), PakeError> {
for tv in OPRF_RISTRETTO255_SHA512 {
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
let output = oprf::finalize::<RistrettoPoint, Sha512>(
&parameters.input,
&parameters.unblinded_element,
&parameters.info,
);
assert_eq!(&parameters.output, &output.to_vec());
}
Ok(())
}