Andrea Cosentino created CAMEL-25803:
----------------------------------------
Summary: camel-pqc - the hybrid wire format v2 is documented but
not implemented
Key: CAMEL-25803
URL: https://issues.apache.org/jira/browse/CAMEL-25803
Project: Camel
Issue Type: Bug
Components: camel-pqc
Reporter: Andrea Cosentino
{{src/main/docs/pqc-hybrid.adoc}} documents a self-describing "wire format v2"
for hybrid signatures and encapsulations, in a dedicated hundred-line section.
The code implements only v1.
h3. What the documentation claims
{{pqc-hybrid.adoc:104}}:
{noformat}
Since Camel 4.19, hybrid signatures use wire format v2 which includes magic
bytes, version, and algorithm identifiers (see <<Wire Format v2 and Algorithm
Identification>>). The v1 format `[4 bytes: classical sig length][classical
sig][pqc sig]` is still accepted on input for backward compatibility.
{noformat}
The same sentence appears for encapsulations at {{:217}}, with the format laid
out under {{== Wire Format v2 and Algorithm Identification}} ({{:289}}) and
{{=== Wire Format v2 Layout}} ({{:364}}).
h3. What the code does
{{HybridSignature.java:73-74}}:
{code:java}
// Combine: [4 bytes length][classical sig][pqc sig]
return combineSignatures(classicalSignature, pqcSignature);
{code}
That is the v1 format. {{HybridKEM}} is the same. There are no magic bytes, no
version byte and no algorithm identifiers anywhere in either class.
h3. {{PQCAlgorithmId}} is public, documented and referenced by nothing
{{PQCAlgorithmId.java:36}} declares a public enum of about fifty constants
whose Javadoc says it is "used in the wire format header to make hybrid
cryptographic outputs self-describing". A repository-wide grep finds *zero*
references outside its own file — no production caller, no test, only the adoc.
It is untested public API surface.
It is also internally inconsistent with the algorithm enums, so
{{fromJcaName()}} returns {{UNKNOWN}} for the real JCE names.
{{PQCAlgorithmId.java:77}}:
{code:java}
FRODO(0x0406, "FRODO"),
{code}
versus {{PQCKeyEncapsulationAlgorithms.java:29}}:
{code:java}
FRODO("FrodoKEM", "BC"),
{code}
Same for {{KYBER}} ({{"KYBER"}} vs {{"ML-KEM"}}), {{DILITHIUM}} and
{{SPHINCSPLUS}}. The enum is also missing HSS and seven
{{PQCSymmetricAlgorithms}} constants, and carries a {{PICNIC}} constant that
exists in no other enum (Picnic was removed from Bouncy Castle).
h3. The documented API does not compile
{{pqc-hybrid.adoc:357}}:
{code:java}
PQCAlgorithmId classical = components.classicalAlgorithmId(); // e.g.
SHA256_WITH_ECDSA
{code}
{{HybridSignature.java:216}}:
{code:java}
public record HybridSignatureComponents(byte[] classicalSignature, byte[]
pqcSignature) {
{code}
There is no {{classicalAlgorithmId()}} accessor. A user following the published
snippet gets a compile error.
h3. Decision needed
Either implement v2 — magic bytes, version, algorithm ids, with v1 still
accepted on input as the docs already promise — and give
{{HybridSignatureComponents}} the documented accessors; or delete
{{PQCAlgorithmId}} and the entire v2 section of the documentation and describe
the v1 format that actually ships. Shipping a documented format that does not
exist is worse than either.
If v2 is implemented, it would also be the natural place to fix the related gap
in {{PQCDataFormat}} ({{:182-185}}), whose output carries neither the symmetric
algorithm nor the key length even though both can be overridden per message via
{{CamelPQCSymmetricAlgorithm}} — a mismatch currently surfaces as a misleading
"may have been tampered with" at {{:240-243}}.
h3. Note on the combiner itself
For the record, the hybrid construction is sound in kind and should not change
as part of this: both secrets are fed through HKDF ({{HybridKEM.java:260-266}},
not XOR and not single-secret), {{HybridSignature.verify}} correctly uses
{{&&}} ({{:118}}), and the v1 wire format is length-prefixed and bounds-checked
on parse. Two smaller hardening items worth folding in: the HKDF {{salt}} is
{{null}} and {{info}} is only the cipher name, where the established combiners
also bind the PQ ciphertext and the public keys; and {{info.getBytes()}} uses
the platform default charset inside a key derivation.
h3. Test gap
The only negative hybrid-signature test tampers with the data, which
invalidates *both* signatures — it would pass identically if the verification
operator were {{||}}. A test that forges exactly one half (valid classical
signature spliced onto a garbage PQC signature) is the highest-value missing
test in this area.
----
_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)