Flow 9 -- RSA-3072 ➜ ML-DSA-87 Compact-JWS Re-Sign (Non-Streaming)
This scenario shows how to upgrade an existing signature from a classical RSA scheme to a post-quantum ML-DSA-87 key without re-uploading the original data:
-
Resolve the pre-provisioned
RSA-3072key (oldKid). -
Sign a file → compact JWS.
-
Resolve the pre-provisioned
ML-DSA-87key (newKid). -
Re-sign the very same payload RSA ➜ ML-DSA-87 on the server.
-
Verify both the old and the new signature.
Key points
Uses the helper trio
signFileCompact → verifySignature → resignFileCompact(entire token handled in memory).Demonstrates a zero-plaintext signature migration---only the detached JWS leaves the service.
Produces rich telemetry for every step (keys requested, material versions, algorithm, warnings).
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
-
Long-term manifest hardening -- refresh existing RSA signatures in software bills of materials or firmware bundles so they remain trustworthy after quantum breakthroughs.
-
Regulatory migrations -- meet PQC deadlines without touching or re-hashing the original artefacts.
-
Hybrid release workflows -- keep legacy RSA signatures for backward compatibility while attaching a forward-secure ML-DSA token in a single API pass.
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/ExampleScenario9.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.ResignResult;
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.util.Properties;
import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;
/**
* Scenario 9 — RSA-3072 → ML-DSA-87 Bulk Re-sign (Compact JWS).
*
* <p>This scenario migrates a Compact JWS signature from a classical
* RSA-3072 key to a post-quantum ML-DSA-87 key without uploading the payload
* again.</p>
*
* <p>The SDK is data-plane-only: it does not create keys. This scenario operates
* on the <strong>pre-provisioned granted cross-kid pair</strong> of the
* demo playground — source {@code cli-resign2-source}
* ({@code RSA-3072}) and target {@code cli-resign2-target} ({@code ML-DSA-87}).
* The playground actor holds the cross-kid RESIGN grant on this pair
* plus the atomic sign/verify grants on each endpoint.</p>
*
* <ol>
* <li>Resolve the granted {@code RSA-3072} source and {@code ML-DSA-87} target kids.</li>
* <li>Sign a file under the source (compact JWS).</li>
* <li>Verify the RSA-based JWS.</li>
* <li>Re-sign the payload on the server (RSA → ML-DSA).</li>
* <li>Verify the new ML-DSA JWS.</li>
* </ol>
*
* <p><b>Implementation notes (Java 21+):</b></p>
* <ul>
* <li>Filesystem APIs rely on {@link java.nio.file.Path}.</li>
* <li>UTF-8 encoding is enforced explicitly for deterministic behaviour.</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 ExampleScenario9 {
private static final Path DATA_FILE = TEMP_DIR.resolve("scenario9_data.txt");
private static final Path OLD_JWS = TEMP_DIR.resolve("scenario9_rsa.jws");
private static final Path NEW_JWS = TEMP_DIR.resolve("scenario9_mldsa.jws");
/** No instantiation — this class only exposes {@link #main(String[])}. */
private ExampleScenario9() { }
/**
* Runs the RSA-3072 to ML-DSA-87 compact JWS re-sign 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 9 START =====");
System.out.println("""
Purpose :
* Re-sign a Compact JWS from RSA-3072 to ML-DSA-87 (non-streaming helpers)
* Uses the pre-provisioned granted cross-kid pair
(cli-resign2-source / cli-resign2-target)
* Demonstrates signFileCompact -> verifySignature
-> resignFileCompact -> verifySignature
Steps :
1) Resolve granted RSA-3072 source + ML-DSA-87 target kids
2) Sign payload (compact JWS)
3) Verify RSA-based JWS
4) Re-sign payload on server
5) Verify ML-DSA JWS
--------------------------------------------------------------""");
try {
prepareWorkingDir();
Properties props = loadProperties();
AuthenticatedSdk sdk = authenticate(props);
ExamplePlaygroundKeys playground = ExamplePlaygroundKeys.from(props, sdk);
runScenario(sdk, playground);
} catch (Exception ex) {
fatal("Scenario 9 failed", ex);
}
System.out.println("===== SCENARIO 9 END =====");
}
/**
* Executes the RSA-3072 to ML-DSA-87 re-sign scenario on the granted cross-kid pair.
*
* <ol>
* <li>Create sample data file.</li>
* <li>Resolve the granted RSA-3072 source and ML-DSA-87 target kids.</li>
* <li>Sign under the RSA-3072 source and verify.</li>
* <li>Re-sign payload on server (RSA → ML-DSA).</li>
* <li>Verify new ML-DSA JWS.</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 -- sample data -------------------------------------------------- */
FileIO.writeUtf8(
DATA_FILE,
"Scenario-9 - bulk re-sign RSA-3072 -> ML-DSA-87.");
System.out.println("[1] Data file -> " + DATA_FILE.toAbsolutePath());
/* 2 -- resolve granted cross-kid pair (data-plane SDK: no key creation) */
final String rsaKid = playground.requireKid("cli-resign2-source"); // RSA-3072
final String dsaKid = playground.requireKid("cli-resign2-target"); // ML-DSA-87
System.out.println("[2] Source kid (RSA-3072) -> " + rsaKid);
System.out.println("[2] Target kid (ML-DSA-87) -> " + dsaKid);
/* 3 -- sign under RSA-3072 source + verify -------------------------- */
SignResult rsaSign = sdk.signFileCompact(rsaKid, DATA_FILE, OLD_JWS);
System.out.println("[3] RSA-3072 JWS created -> " + OLD_JWS.toAbsolutePath());
printSignMeta("RSA-3072 SIGN", rsaSign);
VerifySignatureResult rsaVerify = sdk.verifySignature(OLD_JWS);
printVerifyMeta("RSA-3072 VERIFY", rsaVerify);
/* 4 -- re-sign payload to ML-DSA-87 target -------------------------- */
ResignResult reMeta = sdk.resignFileCompact(dsaKid, OLD_JWS, NEW_JWS);
System.out.println("[4] ML-DSA-87 JWS created -> " + NEW_JWS.toAbsolutePath());
printResignMeta(reMeta);
/* 5 -- verify new JWS ----------------------------------------------- */
VerifySignatureResult dsaVerify = sdk.verifySignature(NEW_JWS);
printVerifyMeta("ML-DSA-87 VERIFY", dsaVerify);
System.out.println(dsaVerify.isValid()
? "[6] SUCCESS - new JWS is valid."
: "[6] FAILURE - new JWS is INVALID.");
}
}
How to run
Console milestones
-
RSA-3072 key resolution from the playground & compact-JWS signature
-
Successful verification of the RSA signature
-
ML-DSA-87 key resolution from the playground
-
Server-side re-sign → new ML-DSA compact JWS
-
Verification of the new post-quantum signature → SUCCESS