Add BouncyCastle JSSE ML-KEM integration test; make ACCP ML-KEM usable by JSSE - #561
Draft
WillChilds-Klein wants to merge 1 commit into
Draft
Conversation
…e by JSSE
Adds an end-to-end test proving BouncyCastle's JSSE provider (BCJSSE) can
source ML-KEM from ACCP over the standard JCA during a real TLS 1.3
handshake, when ACCP is installed as the top provider. A RecordingProvider
wrapping ACCP observes the KEM / KeyPairGenerator lookups BCJSSE routes
through JEP-452's javax.crypto.KEM API.
Wiring up that test surfaced two interop gaps where ACCP's ML-KEM SPIs were
stricter than the standard JCA contract callers rely on:
- MlKemGen.initialize(AlgorithmParameterSpec, SecureRandom) unconditionally
threw UnsupportedOperationException. JSSE stacks call
KeyPairGenerator.initialize(new NamedParameterSpec("ML-KEM-768"), random)
before generateKeyPair(). Now accepts a NamedParameterSpec matching the
SPI's bound parameter set (mirroring XDHGen's reflective, JDK8-safe
pattern) and rejects mismatches so the JCA can fail over.
- MlKemSpi.engineNewEncapsulator/Decapsulator rejected a null
AlgorithmParameterSpec and a non-null SecureRandom. The javax.crypto.KEM
convenience overloads (KEM.newEncapsulator(pk, random),
KEM.newDecapsulator(pk)) always pass exactly those. Null spec now defaults
to the key's parameter set; a supplied SecureRandom is accepted and
ignored (AWS-LC uses its own DRBG).
Test uses an RSA leaf certificate (an EC leaf trips BCJSSE server
credential selection here) purely to authenticate the handshake, and takes
the standard provider-mutation test locks (SAME_THREAD + RESOURCE_GLOBAL /
RESOURCE_PROVIDER) so it does not race other provider-touching tests.
Bumps the test-scoped BouncyCastle dependencies to 1.85 and adds bctls
(KEMSpiUtil, the delegation path, first shipped in 1.84). The test skips
unless javax.crypto.KEMSpi is present (JDK 21+ or KEM-backported JDK 17),
bctls carries KEMSpiUtil, and ACCP was built with ML-KEM enabled.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #561 +/- ##
============================================
- Coverage 78.55% 78.46% -0.10%
- Complexity 1217 1220 +3
============================================
Files 127 127
Lines 7759 7781 +22
Branches 982 985 +3
============================================
+ Hits 6095 6105 +10
- Misses 1133 1140 +7
- Partials 531 536 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
WillChilds-Klein
commented
Aug 7, 2026
| try { | ||
| clazz = Class.forName("java.security.spec.NamedParameterSpec"); | ||
| } catch (final ClassNotFoundException e) { | ||
| // JDK 10 or older; getNamedParameter will reject all specs, which is correct there. |
Contributor
Author
There was a problem hiding this comment.
do a check on JDK version and rethrow if runtime version is >10
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.
What
Adds an end-to-end integration test proving that BouncyCastle's JSSE provider (BCJSSE) can source ML-KEM from ACCP over the standard JCA during a real TLS 1.3 handshake, when ACCP is installed as the top provider. Wiring up that test surfaced two spots where ACCP's ML-KEM SPIs were stricter than the standard JCA contract that JSSE callers rely on, which this PR also fixes.
Why
BCJSSE, left in its default (unpinned) configuration, resolves ML-KEM via JEP-452's
javax.crypto.KEMAPI with no explicit provider, so normal JCA precedence applies and a foreign top provider can serve the request. This test is a positive-signal, cross-JDK probe that ACCP actually gets used on that path -- aRecordingProviderwrapping ACCP observes theKEM/KeyPairGenerator/KeyFactorylookups a genuine handshake drives.Changes
ACCP interop fixes (
src/)MlKemGen.initialize(AlgorithmParameterSpec, SecureRandom)previously threwUnsupportedOperationExceptionunconditionally. JSSE stacks (and ordinary callers) invokeKeyPairGenerator.initialize(new NamedParameterSpec("ML-KEM-768"), random)beforegenerateKeyPair(). It now accepts aNamedParameterSpecmatching the SPI's bound parameter set -- mirroring the existing reflective, JDK8-target-safe pattern inXDHGen-- and rejects a mismatch withInvalidAlgorithmParameterExceptionso the JCA can fail over.MlKemSpi.engineNewEncapsulator/Decapsulatorrejected anullAlgorithmParameterSpecand a non-nullSecureRandom. Thejavax.crypto.KEMconvenience overloads (KEM.newEncapsulator(pk, random),KEM.newDecapsulator(pk)) always pass exactly those. A null spec now defaults to the key's parameter set (each ACCP ML-KEM key already carries it); a suppliedSecureRandomis accepted and ignored (AWS-LC draws from its own DRBG). A non-null spec is still validated against the key's parameter set.Test (
tst/.../jdk17plus/BouncyCastleJsseMlKemIntegrationTest.java)MLKEM512/768/1024groups and the hybridX25519MLKEM768group, asserting ACCP served the ML-KEM lookups. Includes a negative control (ACCP absent -> no lookups observed).SAME_THREAD+RESOURCE_GLOBAL/RESOURCE_PROVIDER) so it does not race other provider-touching tests.javax.crypto.KEMSpipresent (JDK 21+ or KEM-backported JDK 17), bctls carriesKEMSpiUtil(BC >= 1.84), and ACCP was built with ML-KEM enabled (-DTARGET_JDK_VERSION=17+).Build (
build.gradle)1.81 -> 1.85and addsbctls-jdk18on(which carriesKEMSpiUtil, the delegation path first shipped in 1.84).Testing
-DTARGET_JDK_VERSION=21(pure 512/768/1024, hybrid, parametric set, negative control).MlKemTest(29),MlKemUtilsTest(4),EvpKeyFactoryTest(143) all pass -- the SPI relaxations preserve existing behavior (mismatched-parameter-set specs are still rejected)../gradlew build(spotless + static analysis) is green.Notes for reviewers
src/behavior changes are the substantive part and deserve the closest look -- particularly whether tolerating (and ignoring) a caller-suppliedSecureRandomis the desired contract, versus documenting ACCP's DRBG-only stance some other way.