[
https://issues.apache.org/jira/browse/CAMEL-25796?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25796:
--------------------------------
Fix Version/s: 4.24.0
> camel-pqc - persist the stateful signature key index before the signature is
> emitted
> ------------------------------------------------------------------------------------
>
> Key: CAMEL-25796
> URL: https://issues.apache.org/jira/browse/CAMEL-25796
> Project: Camel
> Issue Type: Bug
> Components: camel-pqc
> Reporter: Andrea Cosentino
> Assignee: Andrea Cosentino
> Priority: Major
> Fix For: 4.24.0
>
>
> For the stateful hash-based signature schemes (LMS, HSS, XMSS, XMSSMT) each
> private key index may be used exactly once. {{camel-pqc}} advances the index
> by signing and persists the advanced key *afterwards*.
> {{PQCProducer.java:523-537}}:
> {code:java}
> private void signature(Exchange exchange)
> throws Exception {
> checkStatefulKeyBeforeSign();
> Signature signerForExchange = signerForExchange();
> byte[] signature;
> synchronized (signerForExchange) {
> signerForExchange.initSign(keyPair.getPrivate());
> updateSignatureFromBody(signerForExchange, exchange.getMessage());
> signature = signerForExchange.sign();
> }
> exchange.getMessage().setHeader(PQCConstants.SIGNATURE, signature);
> persistStatefulKeyStateAfterSign(exchange);
> }
> {code}
> {{sign()}} consumes the index inside the Bouncy Castle private key and the
> signature is published on the message. Only then is the advanced key written
> to the store. Any failure in between — a crash, a {{kill -9}}, an exception
> inside {{persistStatefulKeyStateAfterSign}} — leaves the emitted signature in
> the world while the persisted key still sits at the old index. On restart the
> same index is used again, which defeats the one-time-use property the whole
> scheme rests on. The same ordering applies to hybrid signing at
> {{PQCProducer.java:779}}.
> h3. Silent skips make the window wider
> {{persistStatefulKeyStateAfterSign}} ({{PQCProducer.java:1152-1190}}) returns
> without any log when there is no {{KeyLifecycleManager}}:
> {code:java}
> KeyLifecycleManager klm = getConfiguration().getKeyLifecycleManager();
> if (klm == null) {
> return;
> }
> {code}
> and again when the {{CamelPQCKeyId}} header is absent ({{:1170-1172}}). Only
> the {{metadata == null}} case warns. So a stateful route with no key id
> header advances the index on every exchange and persists nothing, silently.
> h3. Not atomic with the exhaustion check either
> {{checkStatefulKeyBeforeSign()}} runs at {{:525}}, outside the
> {{synchronized}} block, and the lock is on a *per-exchange* {{Signature}}
> instance. Two concurrent exchanges can both observe {{remaining == 1}}.
> h3. Suggested fix
> Reserve and persist the next index before signing, then sign. Where the
> backing store cannot do that atomically, at minimum persist before publishing
> the signature on the exchange, make the two silent {{return}} paths log a
> warning, and serialise the check-and-sign for stateful algorithms.
> h3. Test gap
> {{PQCStatefulKeyTrackingTest}} covers only sequential signing and two
> hand-constructed {{StatefulKeyState}} objects. Nothing covers crash-restart
> index resumption or concurrent signing.
> ----
> _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
(v10.3.26#10030026)