Flow 6 – ML-DSA-87 Sign/Verify (Non-Streaming)
This Flow 6 example demonstrates:
- Resolving the pre-provisioned ML-DSA-87 key (post-quantum signature).
- Signing a file in non-streaming mode.
- Verifying the signature, also in non-streaming mode.
Key Points
- Focuses on signature operations (no encryption here).
- Uses ML-DSA-87, a post-quantum algorithm for digital signatures.
- Non-streaming sign/verify methods:signFile(...)/verifySignature(...).
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
- Human-readable proofs – the compact-JWS string is plain text, so it can be pasted into Git commits, SBOMs or e-mails and later verified with nothing more than the exported public key.
- Long-lived authenticity – ML-DSA-87 is a post-quantum algorithm, making these signatures safe even after large-scale quantum computers undermine today’s RSA/ECC certificates.
- Air-gapped or offline review – because the whole payload is carried in one token, auditors can validate the signature on a disconnected workstation without needing to stream data from the server.
Shared helper – this code imports the utility class from
example-util.md (configuration, authentication, JSON).
Full Java Code
Below is the complete Java code for Flow 6. Copy and paste it into your IDE or project (e.g., co/ankatech/ankasecure/sdk/examples/ExampleScenario6.java) and run the main method.
/*
* 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 6 — ML-DSA-87 Bulk Helpers (Compact JWS).
*
* <p>This scenario signs and verifies a document with <strong>non-streaming</strong>
* helpers that operate on RFC 7515 <em>Compact JWS</em> tokens:</p>
* <ol>
* <li>Resolve a pre-provisioned <code>ML-DSA-87</code> key from the demo
* playground (the SDK is data-plane-only — it does not create keys).</li>
* <li>Create a Compact JWS on disk via {@link AuthenticatedSdk#signFileCompact(String, Path, Path)}.</li>
* <li>Verify it via {@link AuthenticatedSdk#verifySignature(Path)}.</li>
* <li>Print server metadata and validate integrity.</li>
* </ol>
*
* <p><b>Implementation notes (Java 21+):</b></p>
* <ul>
* <li>Filesystem interactions rely on the {@link java.nio.file.Path} API.</li>
* <li>UTF-8 is enforced explicitly for deterministic encoding.</li>
* <li>Temporary artefacts reside under <kbd>temp_files/</kbd>.</li>
* </ul>
*
* <p><b>Thread-safety:</b> this class is stateless and immutable.</p>
*
* @see ExampleUtil
* @see ExamplePlaygroundKeys
* @see AuthenticatedSdk
*/
public final class ExampleScenario6 {
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario6() { }
/**
* Runs the ML-DSA-87 compact JWS 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 6 START =====");
System.out.println("""
Purpose :
* ML-DSA-87 compact-JWS sign / verify helpers
* Non-streaming - token handled as text
* Operates on a pre-provisioned playground key
Steps :
1) Resolve pre-provisioned ML-DSA-87 key
2) Sign payload (compact JWS)
3) Verify signature
4) Validate integrity
--------------------------------------------------------------""");
try {
prepareWorkingDir();
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
ExamplePlaygroundKeys playground = ExamplePlaygroundKeys.from(props, sdk);
runScenario(sdk, playground);
} catch (Exception ex) {
fatal("Scenario 6 failed", ex);
}
System.out.println("===== SCENARIO 6 END =====");
}
/**
* Executes the ML-DSA-87 sign/verify scenario on a pre-provisioned key.
*
* <ol>
* <li>Prepare document file.</li>
* <li>Resolve a pre-provisioned ML-DSA-87 signing key.</li>
* <li>Sign document (compact JWS).</li>
* <li>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 step fails
*/
private static void runScenario(final AuthenticatedSdk sdk,
final ExamplePlaygroundKeys playground) throws Exception {
/* 1 -- prepare document --------------------------------------------- */
Path doc = TEMP_DIR.resolve("scenario6_doc.txt");
FileIO.writeUtf8(doc,
"Scenario-6 - ML-DSA-87 compact-JWS sign / verify demo.");
System.out.println("[1] Document ready -> " + doc.toAbsolutePath());
/* 2 -- resolve pre-provisioned key (data-plane SDK: no key creation) */
final String kid = playground.kidForAlgorithm("ML-DSA-87");
System.out.println("[2] Using pre-provisioned key -> kid = " + kid);
/* 3 -- sign (compact JWS) ------------------------------------------- */
Path jwsFile = TEMP_DIR.resolve("scenario6.jws");
SignResult signMeta = sdk.signFileCompact(kid, doc, jwsFile);
System.out.println("[3] JWS written -> " + jwsFile.toAbsolutePath());
printSignMeta(signMeta);
/* 4 -- verify ------------------------------------------------------- */
VerifySignatureResult verMeta = sdk.verifySignature(jwsFile);
System.out.println(MessageFormat.format(
"[4] Signature valid? : {0}", verMeta.isValid()));
printVerifyMeta(verMeta);
System.out.println(verMeta.isValid()
? "[5] SUCCESS - signature verified."
: "[5] FAILURE - signature invalid.");
}
}
How to Run
- Copy this code to a Java file in your project (e.g.,
co/ankatech/ankasecure/sdk/examples/ExampleScenario6.java). - Compile (Maven, Gradle, or
javac). - Ensure
cli.propertiesis accessible (file or-Dcli.config). -
Run the
mainmethod, for example:java co.ankatech.ankasecure.sdk.examples.ExampleScenario6
Look for output indicating:
- ML-DSA-87 key resolution from the playground
- Non-streaming file signature
- Signature verification success/failure
Related Flows
- Flow 5 -- ML-KEM-512 Non-Streaming Encrypt / Decrypt (Compact JWE)
- Flow 7 -- AES-256 Compact-JWE Encrypt / Decrypt (Non-Streaming)
- Integration Examples Overview
© 2025 AnkaTech. All rights reserved.