Flow 2 – RSA-2048 Stream Sign / Verify (Detached JWS)
This flow walks through a streamed digital-signature round-trip with the AnkaSecure API:
- Resolve the pre-provisioned classical RSA-2048 key-pair.
- Stream-sign a document – server returns a detached JWS: the flattened
{"protected","signature"}JSON with RFC 7797b64: falseandcrit: ["b64"]in the protected header,algPS256(RSASSA-PSS). - Stream-verify the signature against the same payload.
Key points
- Uses the streaming helpers signFileStream / verifySignatureStream, so you can sign or verify multi-gigabyte artefacts without ever loading them fully into RAM.
- Produces a detached JWS (flattened JWS JSON,
b64: false) — the payload stays as raw binary while the header and signature live in a tiny JSON part (no Base64 bloat, easy to cache or checksum independently).- Surfaces rich lifecycle telemetry in HTTP headers (key requested, rotations, soft-limit warnings) ready for dashboards, logs or automated compliance hooks.
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
- Release pipelines that still rely on classical RSA trust anchors — e.g. package repos, container registries or firmware images that must remain backwards-compatible with existing RSA-2048 verification tooling.
- Very large files — think multi-GB log bundles or VM snapshots where holding the whole payload in memory is infeasible but you still want a single authoritative signature.
- Bandwidth-sensitive or append-only storage — detached JWS keeps the payload untouched, so you can stream directly to S3/GCS/CDNs and append signatures later without rewriting the object.
Dependency — the example uses the common helper class
co.ankatech.ankasecure.sdk.examples.ExampleUtil.
If you haven’t copied that file yet, grab it from example-util.md and place it next to the scenario sources.
Complete Java implementation
src/main/java/co/ankatech/ankasecure/sdk/examples/ExampleScenario2.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.model.SignResult;
import co.ankatech.ankasecure.sdk.model.VerifySignatureResult;
import co.ankatech.ankasecure.sdk.util.FileIO;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Properties;
import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;
/**
* Scenario 2 — RSA-2048 <em>Streaming</em> Sign / Verify.
*
* <p>This walkthrough illustrates a detached-JWS workflow on a
* <strong>pre-provisioned</strong> key (the SDK is data-plane-only — it does not
* create keys):</p>
* <ol>
* <li><strong>Key resolution</strong> — resolve a pre-provisioned
* RSA-2048 key from the demo playground.</li>
* <li><strong>Streaming sign</strong> — the SDK uploads the file in chunks
* and receives a <em>detached JWS</em>: flattened JWS JSON
* {@code {"protected", "signature"}} with RFC 7797 {@code "b64": false},
* carrying no {@code payload} member.</li>
* <li><strong>Streaming verify</strong> — the same detached signature is
* verified against the binary payload with constant memory.</li>
* </ol>
*
* <p><b>Rule of thumb</b>:</p>
* <ul>
* <li><em>Non-streaming</em> → compact JWS / compact JWE</li>
* <li><em>Streaming</em> → detached JWS / detached JWE</li>
* </ul>
*
* <p>All artefacts are written under <kbd>temp_files/</kbd>.</p>
*
* <p><b>Implementation notes (Java 21+):</b></p>
* <ul>
* <li>File handling relies on the {@link java.nio.file.Path} API.</li>
* <li>UTF-8 is enforced explicitly for deterministic encoding.</li>
* <li>Directory creation delegates to {@link ExampleUtil#ensureTempDir(java.nio.file.Path)}.</li>
* </ul>
*
* <p><b>Thread-safety:</b> the class is stateless and immutable; all variables
* are confined to the current thread.</p>
*
* @see ExampleUtil
* @see ExamplePlaygroundKeys
* @see AuthenticatedSdk
*/
public final class ExampleScenario2 {
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario2() { }
/**
* Runs the RSA-2048 streaming sign/verify 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(final String[] args) {
System.out.println("===== SCENARIO 2 START =====");
System.out.println("""
Purpose :
* RSA-2048 streaming signature data-plane round-trip
* Demonstrates detached-JWS pipeline (sign -> verify) on a
pre-provisioned playground key
Steps :
1) Resolve pre-provisioned RSA-2048 key
2) Create sample payload
3) Sign payload (detached JWS)
4) Verify signature
5) Report result
--------------------------------------------------------------""");
try {
prepareWorkingDir();
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
ExamplePlaygroundKeys playground = ExamplePlaygroundKeys.from(props, sdk);
runScenario(sdk, playground);
} catch (Exception ex) {
fatal("Scenario 2 failed", ex);
}
System.out.println("===== SCENARIO 2 END =====");
}
/**
* Executes the 4-step RSA-2048 streaming sign/verify scenario on a
* pre-provisioned key.
*
* <ol>
* <li>Create sample document.</li>
* <li>Resolve a pre-provisioned RSA-2048 signing key.</li>
* <li>Stream-sign to detached JWS.</li>
* <li>Stream-verify signature and report result.</li>
* </ol>
*
* @param sdk an authenticated {@link AuthenticatedSdk} instance; must not be {@code null}
* @param playground the pre-provisioned playground key resolver; must not be {@code null}
* @throws Exception if any operation fails
*/
private static void runScenario(final AuthenticatedSdk sdk,
final ExamplePlaygroundKeys playground) throws Exception {
/* 1 -- create sample document --------------------------------------- */
Path doc = TEMP_DIR.resolve("scenario2_doc.txt");
FileIO.writeUtf8(doc,
"Scenario-2 - RSA-2048 detached-JWS streaming sign & verify.");
System.out.println("[1] Document ready -> " + doc.toAbsolutePath());
/* 2 -- resolve pre-provisioned key (data-plane SDK: no key creation) */
final String kid = playground.kidForAlgorithmAndOp("RSA-2048", "sign");
System.out.println("[2] Using pre-provisioned key -> kid = " + kid);
/* 3 -- streaming sign (detached JWS) -------------------------------- */
Path sig = TEMP_DIR.resolve("scenario2.sig");
SignResult signMeta = sdk.signFileStream(kid, doc, sig);
System.out.println("[3] File signed -> " + sig.toAbsolutePath());
printSignMeta(signMeta);
/* 4 -- streaming verify --------------------------------------------- */
VerifySignatureResult verifyMeta = sdk.verifySignatureStream(doc, sig);
System.out.println(MessageFormat.format(
"[4] Signature valid : {0}", verifyMeta.isValid()));
printVerifyMeta(verifyMeta);
System.out.println(verifyMeta.isValid()
? "SUCCESS - signature verified."
: "FAILURE - signature verification failed.");
}
}
How to run
Console milestones:
-
RSA-2048 key resolution from the playground
-
Detached-JWS stream-sign →
scenario2.sig -
Streaming verify → SUCCESS confirmation