Flow 16 --- Detached-JWS Stream Verification
This scenario walks through a constant-memory verification of a detached JWS created from a multi-megabyte payload:
-
Resolve the pre-provisioned XMSS key-pair (
XMSS-SHA2_10_256) that can sign and verify. -
Create a 1 MB dummy payload.
-
Sign that file as a detached JWS (the flattened
{"protected","signature"}JSON, payload omitted, with RFC 7797b64: falseandcrit: ["b64"]in the protected header) viasignFileStream. -
Verify the detached signature in streaming mode with
verifySignatureStream. -
Print rich server metadata and confirm the signature's validity.
Key points
Uses tree-based XMSS signatures --- quantum-resistant and stateful: each signature spends one of the key's 1,024 one-time indices, committed before the signature is returned. The playground key is shared by everyone running the examples: a
STATEFUL_KEY_BUSYrefusal is retried once after the server'sRetry-After, and aSTATEFUL_KEY_EXHAUSTEDrefusal stops the scenario with what to do, because the only remedy is rotating the key (stateful signing keys).Detached-JWS keeps large payloads out of the token; the signature file stays tiny.
Streaming helpers (
signFileStream,verifySignatureStream) hold only a small buffer in RAM --- ideal for 100 MiB+ artefacts.Produces repeatable 1 MB test data under
temp_files/, then cleans itself up automatically.
Prerequisites
The flow examples are data-plane only. They never create, rotate, or delete keys — they resolve pre-provisioned playground keys and operate on them. Before running any flow example you must:
- Provision a fresh
demo-cliplayground. Use theankasecure-demo-provisioningtool to provision thedemo-cliplayground. This seeds the cryptographic keys the examples operate on and thecli-reference@demo-cliactor that holds capability grants on them. - Use the emitted
cli.properties. The provisioning tool writes acli.propertiesfile that carries theankasecure.demo.kidscatalogue line — the comma-separated list of provisioned key ids, in YAML file order. The examples load this file to discover which keys exist. - Authenticate as
cli-reference@demo-cli. Authenticate the SDK as thecli-reference@demo-cliactor — the all-operations actor of thedemo-cliplayground — so the resolved keys carry the capability grants each operation needs.
Key selection in the examples is by algorithm or by exact name:
kidForAlgorithm(alg)returns a pre-provisioned key id matching the requested algorithm — used by the single-key operation flows.requireKid(name)asserts that an exact, named cross-kid endpoint is present in theankasecure.demo.kidscatalogue — used by the cross-kid REENCRYPT / RESIGN flows that must operate on a specific granted source/target pair.
If cli.properties is missing the ankasecure.demo.kids line, the examples fail fast with a clear message instructing you to (re-)provision the demo-cli playground — they will not silently fall back.
PKCS#7 / CMS examples (Flow 19 & Flow 20)
The PKCS#7 interop examples operate on a packaged, non-sensitive CMS EnvelopedData fixture — you do not supply a PKCS#7 file:
- Fixture:
src/main/resources/pkcs7/enveloped-data-sample.p7m, loaded from the classpath byPkcs7ExampleFixture. It is a single-recipient, zero-signer EnvelopedData artifact from the QA test signer; it carries no private key and no secret. - Flow 19 (analysis) needs no key. Structural analysis (
analyzePkcs7/analyzePkcs7Stream) inspects the CMS envelope metadata only, so it runs with no decryption key and no extra configuration. - Flow 20 (conversion) needs a pre-provisioned recipient key. Converting EnvelopedData to JWE requires the platform to decrypt the envelope, so it needs the recipient's private key referenced by the
pkcs7.decryptionKidproperty incli.properties. That key is matched by issuer DN + serial number and is imported by a control-plane step (outside the data-plane SDK); until it is provisioned, Flow 20's conversion calls fail at the server while Flow 19 still runs. The calling actor also needs the in-placeREENCRYPTgrant on that kid, requested with the kid twice (assetKids: [kid, kid]) and stored as[kid]: a conversion is authorized byREENCRYPT/RESIGNgrants, never byDECRYPT, and no wildcardREENCRYPTgrant exists. - Data-plane only. Like every flow example, these never import keystores or perform any key-lifecycle operation.
When to use it
-
High-volume log pipelines that need to stamp gigabytes with a quantum-safe signature without exhausting memory.
-
Content-distribution networks where the asset (video, image, dataset) travels separately from its JWS header & signature.
-
Compliance or audit trails that demand verifiable integrity proofs while keeping tokens lightweight.
Shared helper – this code imports the utility class from
example-util.md (configuration, authentication, JSON).
Complete Java implementation
src/main/java/co/ankatech/ankasecure/sdk/examples/ExampleScenario16.java
/*
* Copyright 2025 ANKATech Solutions Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package co.ankatech.ankasecure.sdk.examples;
import co.ankatech.ankasecure.sdk.AuthenticatedSdk;
import co.ankatech.ankasecure.sdk.exception.AnkaSecureSdkException;
import co.ankatech.ankasecure.sdk.exception.SdkErrorCode;
import co.ankatech.ankasecure.sdk.model.SignResult;
import co.ankatech.ankasecure.sdk.model.VerifySignatureResult;
import co.ankatech.ankasecure.sdk.util.FileIO;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;
/**
* Scenario 16 — XMSS Streaming Detached JWS Verification.
*
* <p>End-to-end, file-oriented demonstration of streaming detached JWS
* verification with a pre-provisioned XMSS key (the SDK is data-plane-only —
* it does not create keys).</p>
*
* <p><strong>Every run spends one signature of a finite key.</strong> XMSS is a stateful
* hash-based scheme: {@code XMSS-SHA2_10_256} holds 2<sup>10</sup> = 1024 one-time
* signatures and the playground key is shared by everyone running the examples. When they are all
* used, the sign step is refused with
* {@link SdkErrorCode#STATEFUL_KEY_EXHAUSTED}; the scenario then names the key and stops, because
* the only remedy is rotating it, which is a control-plane operation this data-plane SDK does not
* offer. A {@link SdkErrorCode#STATEFUL_KEY_BUSY} refusal (another run is reserving the key's next
* signature at the same moment) is retried once after the server's {@code Retry-After}.</p>
*
* @see ExampleUtil
* @see ExamplePlaygroundKeys
* @see AuthenticatedSdk
*/
public final class ExampleScenario16 {
private static final Path LARGE_PAYLOAD = TEMP_DIR.resolve("scenario16_payload.bin");
private static final Path DETACHED_SIG = TEMP_DIR.resolve("scenario16_payload.sig");
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario16() { }
/**
* Runs the XMSS streaming detached JWS verification scenario.
*
* <p>Loads CLI properties, authenticates against ANKASecure©,
* and delegates to the scenario logic. On any unrecoverable error
* the JVM terminates via
* {@link ExampleUtil#fatal(String, Throwable)}.</p>
*
* @param args command-line arguments (ignored)
*/
public static void main(String[] args) {
System.out.println("===== SCENARIO 16 START =====");
System.out.println("""
Purpose :
* Resolve a pre-provisioned XMSS sign/verify key.
* Create and sign a 1 MB payload as a detached JWS.
* Verify the detached signature in streaming mode.
Steps :
1) Resolve pre-provisioned XMSS key
2) Create dummy payload
3) Sign payload (detached JWS)
4) Verify signature stream
--------------------------------------------------------------""");
try {
prepareWorkingDir();
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
ExamplePlaygroundKeys playground = ExamplePlaygroundKeys.from(props, sdk);
runScenario(sdk, playground);
} catch (Exception ex) {
fatal("Scenario 16 failed", ex);
}
System.out.println("===== SCENARIO 16 END =====");
}
/* ====================================================================== */
private static void runScenario(AuthenticatedSdk sdk,
ExamplePlaygroundKeys playground) throws Exception {
/* 1 – resolve pre-provisioned XMSS key (data-plane SDK: no key creation) */
final String signKid = playground.kidForAlgorithm("XMSS-SHA2_10_256");
System.out.println("[1] Using pre-provisioned key -> kid = " + signKid);
/* 2 – 1 MB dummy payload ------------------------------------ */
byte[] blob = new byte[1 * 1024 * 1024]; // zero-filled buffer
FileIO.writeBytes(LARGE_PAYLOAD, blob);
System.out.println("[2] Payload created -> " + LARGE_PAYLOAD);
/* 3 – detached-JWS streaming sign ----------------------------- */
SignResult signMeta = signHonouringStatefulRefusals(sdk, signKid);
System.out.println("[3] Detached JWS written -> " + DETACHED_SIG);
printSignMeta(signMeta);
/* 4 – detached-JWS streaming verify --------------------------- */
VerifySignatureResult verifyMeta =
sdk.verifySignatureStream(LARGE_PAYLOAD, DETACHED_SIG);
System.out.println("[4] Signature valid? -> " + verifyMeta.isValid());
printVerifyMeta(verifyMeta);
}
/**
* Signs the payload, handling the two refusals a stateful key adds.
*
* <p>{@link SdkErrorCode#STATEFUL_KEY_BUSY} is transient: wait the server's
* {@code Retry-After} (one second when absent) and try once more.
* {@link SdkErrorCode#STATEFUL_KEY_EXHAUSTED} is permanent for this key: print what to do and
* rethrow, so {@link ExampleUtil#fatal(String, Throwable)} reports the run as failed.</p>
*
* @param sdk the authenticated SDK
* @param signKid the XMSS key to sign with
* @return the signing result
* @throws Exception when the sign fails for any other reason, or the key is exhausted
*/
private static SignResult signHonouringStatefulRefusals(AuthenticatedSdk sdk, String signKid)
throws Exception {
try {
return sdk.signFileStream(signKid, LARGE_PAYLOAD, DETACHED_SIG);
} catch (AnkaSecureSdkException refused) {
if (refused.getErrorCode() == SdkErrorCode.STATEFUL_KEY_BUSY) {
// The SDK bounds the hint at AnkaSecureSdkException.MAX_RETRY_AFTER_SECONDS (one
// hour); sleeping in SECONDS keeps the wait free of any millisecond arithmetic.
Long seconds = refused.getRetryAfterSeconds();
long wait = (seconds == null) ? 1L : seconds;
System.out.println("[3] Key " + signKid + " is busy reserving its next signature;"
+ " retrying in " + wait + " s");
TimeUnit.SECONDS.sleep(wait);
return sdk.signFileStream(signKid, LARGE_PAYLOAD, DETACHED_SIG);
}
if (refused.getErrorCode() == SdkErrorCode.STATEFUL_KEY_EXHAUSTED) {
System.out.println("[3] Key " + signKid + " has used all of its one-time signatures."
+ " Rotate it with the Key Management SDK (or its CLI, ankasecure-km);"
+ " the signatures it already produced still verify.");
}
throw refused;
}
}
}
How to run
Console milestones
-
XMSS key resolved from the playground
-
1 MB payload written to
scenario16_payload.bin -
Detached-JWS signature file created
-
Signature verified valid in streaming mode
-
Sign & verify metadata (key IDs, algorithm, warnings)
Where next?
© 2025 ANKATech Solutions Inc. All rights reserved.