Skip to content

Convert non-BC ML-KEM keys in the legacy KEM code path - #2466

Draft
WillChilds-Klein wants to merge 1 commit into
bcgit:mainfrom
WillChilds-Klein:mlkem-legacy-kem-foreign-keys
Draft

WillChilds-Klein wants to merge 1 commit into
bcgit:mainfrom
WillChilds-Klein:mlkem-legacy-kem-foreign-keys

Conversation

@WillChilds-Klein

Copy link
Copy Markdown

Written by Claude Opus 5 (Claude Code) on my behalf.

Description

  • KemUtil looks up the ML-KEM key objects and the KeyGenerator from the JCA independently, so a provider ahead of BC can decode the peer's public key while BC still serves the encapsulation. MLKEMKeyGeneratorSpi casts to BCMLKEMPublicKey, so that split fails every handshake with internal_error(80) and no chained cause on the wire.
  • The legacy encapsulate and decapsulate now re-import a foreign key through the KeyGenerator's own provider. decodePublicKey already does the equivalent when the KeyFactory is not BC's.
  • Only the pre-JEP-452 path changes. Where SpiUtil.hasKEM() is true, javax.crypto.KEM accepts a foreign key already.
  • The conversion is guarded on the generator belonging to a BouncyCastleProvider and the key not already implementing MLKEMPublicKey/MLKEMPrivateKey, so an all-BC classpath runs the same code as before.
  • Both copies of KemUtil carry the fix, because the base copy is what runs when a repackaged jar leaves the multi-release overlay unreachable and SpiUtil.hasKEM() reports false on a KEM-capable JDK.

Testing / verification

  • Drove two BCJSSE peers through a TLS 1.3 handshake on a KEM-capable JDK 17 with a third-party ML-KEM provider ahead of BC, over X25519MLKEM768, MLKEM768 and MLKEM1024. Each aborts with internal_error(80) before this change and completes after it.
  • Covered both routes that reach the legacy path: a bcprov missing its versions/17 SpiUtil overlay next to a current bctls, and a fully flattened classpath where the base KemUtil runs.
  • Re-ran the same legacy path with BC serving every ML-KEM primitive, to confirm the guard leaves the existing case untouched.
  • The hybrid group exercises decapsulate as well, since the client generates its ephemeral keypair through the foreign provider and then extracts with BC.
  • Happy to attach the standalone reproducer, or to fold it into tls tests behind a stub provider if you would like it in the suite.

KemUtil resolves the key objects and the KeyGenerator from the JCA
separately, so a provider ahead of BC can decode a peer's ML-KEM public
key while BC still serves the encapsulation. MLKEMKeyGeneratorSpi casts
to BC's own key types, so that split fails every handshake with
internal_error(80).
@dghgit dghgit self-assigned this Sep 25, 2026
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.

2 participants