[
https://issues.apache.org/jira/browse/CAMEL-25800?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18128663#comment-18128663
]
Andrea Cosentino commented on CAMEL-25800:
------------------------------------------
Correction to the original description, after verifying against the
BouncyCastle 1.86 jar and an actual round-trip test.
*X448 is not affected.* {{BCXDHPublicKey.getAlgorithm()}} returns the concrete
name ({{X25519}} / {{X448}}) unless the {{org.bouncycastle.emulate.oracle}}
system property is set, so the existing {{X448}} branch of
{{generateEphemeralKeyPair}} is reachable and works. A parameterised round-trip
test confirms it: with the unpatched code, {{secp256r1}} and {{X448}} pass
while {{secp384r1}} and {{secp521r1}} fail.
What is actually broken:
* *EC curves other than P-256* - confirmed. {{getAlgorithm()}} returns a bare
{{EC}} for every curve, so the ephemeral key is always secp256r1 and the
agreement fails on mismatched domain parameters.
* *X448 only when the key reports the generic {{XDH}}* - a latent narrower
case. That happens under Oracle emulation, or with a JDK {{XECPublicKey}} from
SunEC, where {{"XDH".equals(algorithm)}} maps unconditionally to X25519.
The summary has been amended accordingly. The fix derives the group from the
recipient key itself ({{ECPublicKey.getParams()}}, or
{{XECPublicKey.getParams()}} when present) and rejects an {{XDH}} key whose
group cannot be determined, rather than silently picking one.
> camel-pqc - the hybrid KEM always uses P-256, so ECDH_P384 and ECDH_P521
> cannot work
> ------------------------------------------------------------------------------------
>
> Key: CAMEL-25800
> URL: https://issues.apache.org/jira/browse/CAMEL-25800
> Project: Camel
> Issue Type: Bug
> Components: camel-pqc
> Reporter: Andrea Cosentino
> Assignee: Andrea Cosentino
> Priority: Major
>
> {{HybridKEM.encapsulate()}} derives the ephemeral classical key from the
> *key's own* algorithm string rather than from the configured
> {{classicalKEMAlgorithm}}, and hardcodes secp256r1 for every EC key.
> {{HybridKEM.java:287-305}}:
> {code:java}
> private static KeyPair generateEphemeralKeyPair(String algorithm) throws
> Exception {
> KeyPairGenerator kpg;
> if ("EC".equals(algorithm)) {
> kpg = KeyPairGenerator.getInstance("EC");
> // Default to P-256
> kpg.initialize(new ECGenParameterSpec("secp256r1"),
> SecureRandomHelper.getSecureRandom());
> } else if ("X25519".equals(algorithm) || "XDH".equals(algorithm)) {
> {code}
> {{classicalPublicKey.getAlgorithm()}} returns just {{"EC"}} for every curve,
> so the ephemeral key is always P-256. With a P-384 or P-521 static key,
> {{classicalKA.doPhase(classicalPublicKey, true)}} ({{HybridKEM.java:97}})
> fails on mismatched domain parameters. The {{X448}} branch exists at
> {{:298-299}} but nothing can select it, because the branch keys off the key's
> own algorithm string rather than the configuration.
> h3. The option is advertised
> {{PQCConfiguration.java:85}}:
> {code:java}
> @UriParam(enums = "ECDH_P256,ECDH_P384,ECDH_P521,X25519,X448", ...)
> {code}
> and {{src/main/docs/pqc-hybrid.adoc:120-123}} documents {{ECDH_P384}},
> {{ECDH_P521}} and {{X448}} as supported.
> h3. The enum already has everything needed, unused
> {{PQCClassicalKEMAlgorithms.getCurveSpec()}}, {{getSharedSecretSize()}} and
> {{isECDH()}} have *zero callers* anywhere in {{src/main}} — verified by grep.
> Only {{getKeyAlgorithm()}} is used, at {{PQCProducer.java:478-479}}. That is
> precisely why this went unnoticed.
> h3. Suggested fix
> Thread the configured {{PQCClassicalKEMAlgorithms}} into
> {{HybridKEM.encapsulate()}} and use {{getCurveSpec()}} to initialise the
> ephemeral generator. Alternatively derive the curve from the static key's
> {{ECParameterSpec}} instead of hardcoding one.
> h3. Related: the hybrid KDF silently downgrades on an unrecognised name
> {{HybridKEM.java:278-284}}:
> {code:java}
> private static HKDFBytesGenerator createHKDF(String algorithm) {
> return switch (algorithm) {
> case "HKDF-SHA256" -> new HKDFBytesGenerator(new SHA256Digest());
> case "HKDF-SHA384" -> new HKDFBytesGenerator(new SHA384Digest());
> case "HKDF-SHA512" -> new HKDFBytesGenerator(new SHA512Digest());
> default -> new HKDFBytesGenerator(new SHA256Digest());
> };
> {code}
> {{hybridKdfAlgorithm}} is a free-form {{String}}
> ({{PQCConfiguration.java:105}}) validated only by catalog metadata, never at
> runtime. A typo such as {{HKDF-SHA-512}} silently yields SHA-256 instead of
> failing. It should throw {{IllegalArgumentException}}.
> h3. Test gap
> No test anywhere in {{src/test}} references {{ECDH_P384}}, {{ECDH_P521}} or
> {{X448}} — only {{ECDH_P256}} ({{PQCHybridKEMECDHMLKEMTest.java:68}}) and
> {{X25519}} ({{PQCHybridKEMX25519MLKEMTest.java:68}}).
> ----
> _Reported by Claude Code (AI agent) on behalf of [~oscerd]. Every code
> reference was verified against commit {{7aae27315946}} on {{main}}._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)