Skip to content

Comments

[KeyManager] Add HPKE and DHKEM crypto primitives#643

Merged
NilanjanDaw merged 16 commits intogoogle:mainfrom
NilanjanDaw:add-crypto-libs
Feb 13, 2026
Merged

[KeyManager] Add HPKE and DHKEM crypto primitives#643
NilanjanDaw merged 16 commits intogoogle:mainfrom
NilanjanDaw:add-crypto-libs

Conversation

@NilanjanDaw
Copy link
Collaborator

Introduces a crypto module in km_common to handle cryptographic operations backed by BoringSSL (bssl-crypto).

Key Contributions:

  1. HPKE Decryption (decrypt):

    • Implements decrypt using bssl_crypto::hpke for "single-shot" Hybrid Public Key Encryption (HPKE) opening.
    • Supports the standard HPKE suite: DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, and AES-256-GCM.
    • Maps internal HpkeAlgorithm types to BoringSSL parameters.
  2. DHKEM Decapsulation (decaps):

    • Implements a standalone decaps function for DHKEM(X25519, HKDF-SHA256).
    • Manually constructs the KEM context (suite ID, labeled IKM/Info) compliant with RFC 9180 to derive the shared secret directly from an encapsulated key and private key.
    • Validates implementation against test vectors accounting for BoringSSL's internal private key clamping.
  3. Dependencies & Error Handling:

    • Adds bssl-crypto for underlying crypto operations.
    • Uses thiserror for structured error definitions (KeyLenMismatch, DecapsError, HpkeDecryptionError).


// Extract eae_prk
// labeled_ikm = "HPKE-v1" || suite_id || "eae_prk" || shared_key
let labeled_ikm = [b"HPKE-v1".as_slice(), &suite_id, b"eae_prk", &shared_key].concat();
Copy link
Collaborator

Choose a reason for hiding this comment

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

.concat creates a heap. and clear_stack_on_return won't clear that heap i think.

can you wrap this in Zeroizing::new(?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It may be a good idea to wrap all the private types with structures that ZeroizeOnDrop.

  1. It simplifies error handling (Early returns without zeroizing sensitive data).
  2. As the code shifts around, critical data will still be zeroized.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I have added a ZeroizeOnDrop to the PrivateKey wrapper. Additionally wrapped the labeled_ikm and labeled_info with Zeroizing::new()

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah I'm sorry, I didn't realize that when I suggested moving to .concat(), that would lead to additional heap issues.

I see you have an implementation of labeled_extract() in #649 that's earmarked for tests, but makes it clear when a Vec is being created. Could consider adopting that pattern here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved the labeled_extract and labeled_expand to separate functions with SecretBox based storage. This should make it easier to handle the secret material clean-up.

@NilanjanDaw
Copy link
Collaborator Author

/gcbrun

Vault for secure key storage

Implements a secure Vault struct in keymanager/common
that stores sensitive key material in a memory-backed
file created via memfd_create.

Key Changes:

- Sealed Storage: Uses memfd_create with `MFD_CLOEXEC`
and `MFD_NOEXEC_SEAL` to create an anonymous file in RAM.
- Immutability: Applies `F_SEAL_GROW`, `F_SEAL_SHRINK`,
and `F_SEAL_SEAL` using fcntl to prevent modification
of the file's content or size after initialization.

- Secure Cleanup: Implements ZeroizeOnDrop
(via the zeroize crate) to ensure the memory map is
cleared when the Vault is dropped.

- Restricted Access: The underlying file descriptor
is dropped immediately after mapping (except in test builds),
ensuring no direct file access remains.
`km_common`

Introduces a `crypto` module in `km_common` to handle cryptographic
operations backed by BoringSSL (`bssl-crypto`).

Key Contributions:
1. HPKE Decryption (`decrypt`):
   - Implements `decrypt` using `bssl_crypto::hpke` for "single-shot"
     Hybrid Public Key Encryption (HPKE) opening.
   - Supports the standard HPKE suite: DHKEM(X25519, HKDF-SHA256),
     HKDF-SHA256, and AES-256-GCM.
   - Maps internal `HpkeAlgorithm` types to BoringSSL parameters.

2. DHKEM Decapsulation (`decaps`):
   - Implements a standalone `decaps` function for DHKEM(X25519, HKDF-SHA256).
   - Manually constructs the KEM context (suite ID, labeled IKM/Info)
     compliant with RFC 9180 to derive the shared secret directly from
     an encapsulated key and private key.
   - Validates implementation against test vectors accounting for
     BoringSSL's internal private key clamping.

3. Dependencies & Error Handling:
   - Adds `bssl-crypto` for underlying crypto operations.
   - Uses `thiserror` for structured error definitions (`KeyLenMismatch`,
     `DecapsError`, `HpkeDecryptionError`).
- Refactor PublicKey and PrivateKey to use an enum-wrapped trait pattern
- Use fixed-size [u8; 32] arrays for X25519 keys to improve memory hygiene
- Implement Zeroize and ZeroizeOnDrop for X25519PrivateKey
- Enhance decaps_internal with Zeroizing wrappers for sensitive intermediates
- Add RFC 9180 Section 4.1 documentation for DHKEM decapsulation
- Improve test coverage for X25519 clamped vector compatibility
- Added `test_labeled_extract_and_expand` in `x25519.rs` to verify the functionality of the new helper functions.
- Ensures correct output length and that different inputs produce different outputs.
const CLEAR_STACK_PAGES: usize = 2;

/// A trait for public keys with algorithm-specific implementations.
pub(crate) trait PublicKeyOps: Send + Sync {
Copy link
Collaborator

Choose a reason for hiding this comment

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

These traits assume that every public key can do HPKE sealing and every private key can do both KEM decaps and HPKE open. If there were a future use-case for an ECDSA or ML-DSA key, it would be hard to represent those in this type system.

Would it make sense to have individual traits for each of these capabilities? Leaves room in the future for Sign and Verify traits.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That makes sense. Let me do this in a follow up PR. Filed Issue #660 to track the change.

@NilanjanDaw
Copy link
Collaborator Author

/gcbrun

@NilanjanDaw
Copy link
Collaborator Author

/gcbrun

@NilanjanDaw NilanjanDaw merged commit 3b011bd into google:main Feb 13, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants