[
https://issues.apache.org/jira/browse/CAMEL-25797?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25797:
--------------------------------
Fix Version/s: 4.24.0
> camel-pqc - the usage-count and PENDING_ROTATION key rotation signals never
> fire
> --------------------------------------------------------------------------------
>
> Key: CAMEL-25797
> URL: https://issues.apache.org/jira/browse/CAMEL-25797
> Project: Camel
> Issue Type: Bug
> Components: camel-pqc
> Reporter: Andrea Cosentino
> Assignee: Andrea Cosentino
> Priority: Major
> Fix For: 4.24.0
>
>
> {{KeyRotationScheduler}} documents three rotation signals: key age, usage
> count, and a {{PENDING_ROTATION}} status on the key metadata. Two of the
> three cannot trigger.
> h3. 1. {{usageCount}} is only ever advanced for stateful keys
> {{KeyMetadata.updateLastUsed()}} is the only thing that increments the
> counter — {{KeyMetadata.java:81-84}}:
> {code:java}
> public void updateLastUsed() {
> this.lastUsedAt = Instant.now();
> this.usageCount++;
> {code}
> It has exactly one production caller, {{PQCProducer.java:1185}}, inside
> {{persistStatefulKeyStateAfterSign}}, which returns early for anything that
> is not XMSS/XMSSMT/LMS/HSS — {{PQCProducer.java:1159-1162}}:
> {code:java}
> long remaining = getStatefulKeyRemaining(privateKey);
> if (remaining < 0) {
> // Not a stateful key
> return;
> }
> {code}
> So for ML-DSA, SLH-DSA, Falcon and every KEM key — the overwhelming majority
> — {{usageCount}} stays {{0}} forever, while all five managers gate rotation
> on it, e.g. {{InMemoryKeyLifecycleManager.java:158}}:
> {code:java}
> return maxUsage > 0 && keyMetadata.getUsageCount() >= maxUsage;
> {code}
> {{KeyRotationScheduler.setMaxKeyUsage}} and the documented
> {{camel.component.pqc.key-rotation-max-usage}} property
> ({{pqc-key-lifecycle.adoc:975}}, "rotate keys used more than 1,000,000
> times") are therefore inert.
> h3. 2. {{PENDING_ROTATION}} is filtered out before it is consulted
> {{KeyRotationScheduler.java:79}}:
> {code:java}
> private Predicate<KeyMetadata> keyFilter = metadata ->
> metadata.getStatus() == KeyMetadata.KeyStatus.ACTIVE;
> {code}
> {{KeyRotationScheduler.java:130-137}}:
> {code:java}
> for (KeyMetadata metadata : keys) {
> if (metadata == null || !keyFilter.test(metadata)) {
> continue;
> }
> String keyId = metadata.getKeyId();
> try {
> if (keyManager.needsRotation(keyId, maxKeyAge,
> maxKeyUsage)) {
> {code}
> {{KeyMetadata.needsRotation()}} ({{:130-132}}) does check the status:
> {code:java}
> public boolean needsRotation() {
> return status == KeyStatus.PENDING_ROTATION
> || (nextRotationAt != null &&
> Instant.now().isAfter(nextRotationAt));
> {code}
> but a key whose status an operator set to {{PENDING_ROTATION}} is no longer
> {{ACTIVE}}, so it is {{continue}}d at {{:132}} before {{needsRotation}} ever
> runs. Only {{nextRotationAt}} on an otherwise-ACTIVE key works today.
> The scheduler javadoc ({{:62-64}}) and {{pqc-key-lifecycle.adoc:929-931}}
> both advertise {{PENDING_ROTATION}} as a supported signal.
> h3. Suggested fix
> Increment {{usageCount}} on every successful sign/verify/KEM operation, not
> just the stateful path. Widen the default {{keyFilter}} to {{ACTIVE}} or
> {{PENDING_ROTATION}}, or test {{needsRotation()}} before applying the filter.
> h3. Test gap
> {{KeyRotationSchedulerTest}} increments {{usageCount}} by hand ({{:87}}),
> which hides that production never does, and never asserts that a
> {{PENDING_ROTATION}} key rotates.
> ----
> _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)