Skip to content

Rework SecretKey API to facilitate async#371

Merged
kevinlewi merged 2 commits intofacebook:mainfrom
khonsulabs:remote-key
Apr 22, 2025
Merged

Rework SecretKey API to facilitate async#371
kevinlewi merged 2 commits intofacebook:mainfrom
khonsulabs:remote-key

Conversation

@daxpedda
Copy link
Copy Markdown
Contributor

@daxpedda daxpedda commented Apr 18, 2025

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 SecretKey trait 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 to SecretKey and users can just slap unimplemented!() in the synchronous method. We can still do this with actually some minor changes, but I instead opted for a different solution: removing the SecretKey trait entirely.

Instead, there is now a different API flow available for remote key users:

  • ServerLogin::builder() -> ServerLoginBuilder
  • ServerLoginBuilder::private_key() to get a handle to the users secret key.
  • ServerLoginBuilder::data() to get the clients ephemeral public key.
  • At this point, the user can do their HSM operations any way they want without being limited by our API.
  • ServerLoginBuilder::build() -> ServerLogin supplying 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. Group and SecretKey implementations 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 TripleDh can't enforce a trait (e.g. DiffieHellman) on S (SecretKey), because S isn't a generic on either KeyExchange or TripleDh. Making it a generic on either one would force CipherSuites to be bound to a specific type S and limit its flexibility.

Instead, we can now add associated type to KeyExchange that determine the type users have to be able extract and perform computation on dynamically through ServerLoginBuilder::data(). E.g. TripleDh will 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 Acceptor API, which was an answer to a similar problem.


Major Changes

  • A new test suite actually testing against SoftHSM to verify our API makes sense and works in practice.
  • A new trait DiffieHellman, which is required for CipherSuite::KeGroup::Sk by TripleDh. This splits off the original KeGroup::diffie_hellman() function into its own trait which is required from any KeGroup wanting to be compatible with TripleDh.
  • Building KeyPair with 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() and PrivateKey::public_key() replaces KeyPair::from_private_key_slice() and KeyPair::from_private_key().
    • Introduce PrivateKeySerialization, to facilitate KeyPair::de/serialize() and ServerSetup::de/serialize().
  • ProtocolError::Custom is now only present in PrivateKeySerialization, making error types more compatible with each other.

Internal Changes

  • Introduce associated types to KeyExchange to facilitate the builder API:
    • KE2Builder holds the data in the builder required by the KeyExchange to finish building KE2Message.
    • KE2BuilderData is the data the user is required to process returned by ServerLoginBuilder::data(). For 3DH this is the clients ephemeral public key for the DH key exchange.
    • KE2BuilderInput is the data the user is required to supply to ServerLoginBuilder::build(). For 3DH this is the shared secret computed by the user with the DH key exchange.
  • Split KeyExchange::generate_ke2() into multiple parts:
    • ke2_builder() creates the KE2Builder. Holds all the computation results for KE2Message apart from anything requiring the servers secret key.
    • generate_ke2_input() computes the KE2BuilderInput in the regular API flow when the secret key is available.
    • build_ke2() finally generates the KE2Message with the KE2BuilderInput.

Minor Changes

  • Add PublicKey::to_group_type(), otherwise real HSM implementations had to de/serialize received PublicKeys to actually work with the data.
  • Wrap ServerSetup::oprf_seed in Zeroizing.
  • CipherSuite::KeGroup now requires 'static. This was needed to add a lifetime to KeyExchange::KE2BuilderData.
  • Move InternalError::Custom and InternalError::SizeError to ProtocolError, as they aren't internal errors.
  • Remove InternalError::InvalidByteSequence and InternalError::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 update Cargo.toml in a follow-up PR.
Both got a new release.

@daxpedda daxpedda marked this pull request as ready for review April 18, 2025 01:09
@daxpedda daxpedda force-pushed the remote-key branch 4 times, most recently from 84a0a3d to 885900a Compare April 19, 2025 14:53
Copy link
Copy Markdown
Contributor

@kevinlewi kevinlewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, and thank you for putting together the description -- that was much appreciated when navigating the changes in this PR. :)

@daxpedda daxpedda force-pushed the remote-key branch 2 times, most recently from 163ba69 to bec37e7 Compare April 21, 2025 11:09
@daxpedda daxpedda requested a review from kevinlewi April 21, 2025 11:24
@daxpedda
Copy link
Copy Markdown
Contributor Author

Removed both dependency patches now that a release version is available.

@kevinlewi kevinlewi merged commit d324584 into facebook:main Apr 22, 2025
57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants