Rework SecretKey API to facilitate async#371
Merged
kevinlewi merged 2 commits intofacebook:mainfrom Apr 22, 2025
Merged
Conversation
84a0a3d to
885900a
Compare
kevinlewi
reviewed
Apr 21, 2025
Contributor
kevinlewi
left a comment
There was a problem hiding this comment.
Overall looks good, and thank you for putting together the description -- that was much appreciated when navigating the changes in this PR. :)
163ba69 to
bec37e7
Compare
Contributor
Author
|
Removed both dependency patches now that a release version is available. |
kevinlewi
approved these changes
Apr 21, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I apologize in advance to squash so many unrelated changes into a single commit and PR!
Let me know if you want me to split this up into multiple commits or PRs and I'm happy to put in the work!
The
SecretKeytrait has one big limitation: it doesn't support async operations. This became especially important with the advent of cloud HSMs, where operations can even happen over the Internet between different services.The intuitive solution would have been to introduce another trait:
SecretKeyAsync, or just add another method toSecretKeyand users can just slapunimplemented!()in the synchronous method. We can still do this with actually some minor changes, but I instead opted for a different solution: removing theSecretKeytrait entirely.Instead, there is now a different API flow available for remote key users:
ServerLogin::builder() -> ServerLoginBuilderServerLoginBuilder::private_key()to get a handle to the users secret key.ServerLoginBuilder::data()to get the clients ephemeral public key.ServerLoginBuilder::build() -> ServerLoginsupplying the computed shared secret.The normal API flow stays the same with
ServerLogin::start().One more point to add here: until now the Diffie-Hellman operation was quite tightly coupled with the rest of the API instead of being limited to the key exchange. E.g.
GroupandSecretKeyimplementations still have to provide it. This wasn't a problem until now because we only support the 3DH key exchange protocol.However, I intend to add support for SIGMA-I next. The big reason is that apart from AWS I don't know of any cloud HSM that supports ECDH, instead they only support signing, which SIGMA-I would facilitate. Therefor it was important for me to finally make this separation to allow us to add new key exchange methods without forcing users to slap
unimplemented!()everywhere and to retain compile-time checks.The crux here being is that there is no way to enforce a trait implementation on a generic that isn't part of the trait enforcing it. So
TripleDhcan't enforce a trait (e.g.DiffieHellman) onS(SecretKey), becauseSisn't a generic on eitherKeyExchangeorTripleDh. Making it a generic on either one would forceCipherSuites to be bound to a specific typeSand limit its flexibility.Instead, we can now add associated type to
KeyExchangethat determine the type users have to be able extract and perform computation on dynamically throughServerLoginBuilder::data(). E.g.TripleDhwill return the clients ephemeral public key to compute the shared secret with DH and SIGMA-I will return data to sign with the server secret key.This might have been solvable with a trait if Rust supported associated traits.
But I don't want to somehow undersell the new API. While its usage is only slightly more verbose then letting the user implement a trait, it is much more flexible. The main inspiration was Rustl's
AcceptorAPI, which was an answer to a similar problem.Major Changes
DiffieHellman, which is required forCipherSuite::KeGroup::SkbyTripleDh. This splits off the originalKeGroup::diffie_hellman()function into its own trait which is required from anyKeGroupwanting to be compatible withTripleDh.KeyPairwith a remote key in practice was quite difficult, requiring to operate the HSM from inside the trait method without being able to pass in an e.g. HSM connection. In practice, the public key was stored as part of the secret key to return when required, duplicating the data.KeyPair::new(),PrivateKey::deserialize()andPrivateKey::public_key()replacesKeyPair::from_private_key_slice()andKeyPair::from_private_key().PrivateKeySerialization, to facilitateKeyPair::de/serialize()andServerSetup::de/serialize().ProtocolError::Customis now only present inPrivateKeySerialization, making error types more compatible with each other.Internal Changes
KeyExchangeto facilitate the builder API:KE2Builderholds the data in the builder required by theKeyExchangeto finish buildingKE2Message.KE2BuilderDatais the data the user is required to process returned byServerLoginBuilder::data(). For 3DH this is the clients ephemeral public key for the DH key exchange.KE2BuilderInputis the data the user is required to supply toServerLoginBuilder::build(). For 3DH this is the shared secret computed by the user with the DH key exchange.KeyExchange::generate_ke2()into multiple parts:ke2_builder()creates theKE2Builder. Holds all the computation results forKE2Messageapart from anything requiring the servers secret key.generate_ke2_input()computes theKE2BuilderInputin the regular API flow when the secret key is available.build_ke2()finally generates theKE2Messagewith theKE2BuilderInput.Minor Changes
PublicKey::to_group_type(), otherwise real HSM implementations had to de/serialize receivedPublicKeys to actually work with the data.ServerSetup::oprf_seedinZeroizing.CipherSuite::KeGroupnow requires'static. This was needed to add a lifetime toKeyExchange::KE2BuilderData.InternalError::CustomandInternalError::SizeErrortoProtocolError, as they aren't internal errors.InternalError::InvalidByteSequenceandInternalError::PointError, as they are unused.This is currently based on RustCrypto/formats#1765 and ModProg/derive-where#109. So we might want to wait until they release or I can updateCargo.tomlin a follow-up PR.Both got a new release.