Flow 18 --- In-Place Key Upsize & Lifetime Extension (JSON Merge-Patch)
This scenario walks through a business-centric upgrade of an existing AES-256 key without any downtime or data migration:
-
Generate an AES-256 key with pilot limits
softUsageLimit = 5
,maxUsageLimit = 10
,- expires in ≈ 2 months.
-
Export baseline metadata →
temp_files/scenario18_before.json
. -
Apply an RFC 7396 JSON Merge-Patch that upsizes the limits to
50 / 100
and extends the expiry to ≈ 18 months. -
Export post-patch metadata →
temp_files/scenario18_after.json
and confirm the changes.
Key points
Zero data migration --- the key is modified in place with
patchKey(...)
.Uses AES-256 to illustrate symmetric-key governance.
Shows how to craft the patch with the fluent
PatchKeySpec.Builder
.Employs a custom Gson adapter so
ZonedDateTime
remains ISO-8601-friendly.All artefacts are stored under
temp_files/
.
When to use it
-
Pilot-to-production cut-overs --- lift restrictive usage caps once a feature leaves beta.
-
Usage spikes --- raise ceilings on-the-fly when business growth exceeds original forecasts.
-
Key-lifecycle audits --- prove you can extend validity without re-encrypting existing data.
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/ExampleScenario18.java
/* *****************************************************************************
* FILE: ExampleScenario18.java
* Copyright © 2025 Anka Technologies.
* SPDX-License-Identifier: MIT
* ---------------------------------------------------------------------------
* Scenario 18 – Dynamic Key-Limit Upsize (Business-Centric)
* ---------------------------------------------------------------------------
* Demonstrates how to:
* * Provision an AES-256 key with pilot limits (soft = 5, max = 10, expiry ≈ 2 months).
* * Export baseline metadata to JSON.
* * Apply an in-place RFC 7396 JSON Merge-Patch to raise limits (soft = 50,
* max = 100) and extend expiry to ≈ 18 months.
* * Export post-patch metadata to JSON and compare.
*
* All transient artefacts are written under <temp_files/>.
* ****************************************************************************/
package co.ankatech.ankasecure.sdk.examples;
import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.model.ExportedKeySpec;
import co.ankatech.ankasecure.sdk.model.GenerateKeySpec;
import co.ankatech.ankasecure.sdk.model.PatchKeySpec;
import com.google.gson.*;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Properties;
/**
* <h2>Scenario 18 – Dynamic Key-Limit Upsize</h2>
*
* <p>This scenario shows a full lifecycle where an AES-256 symmetric key is
* created with conservative “pilot” limits, then upsized in place via
* <em>JSON Merge-Patch</em>:</p>
* <ol>
* <li>Generate the key with <code>softUsageLimit = 5</code>,
* <code>maxUsageLimit = 10</code>, and an expiry ≈ 2 months.</li>
* <li>Export the baseline metadata.</li>
* <li>Patch the key to <code>softUsageLimit = 50</code>,
* <code>maxUsageLimit = 100</code>, and extend expiry to ≈ 18 months.</li>
* <li>Export the updated metadata and verify the changes.</li>
* </ol>
*
* @author ANKATech – Security Engineering
*/
public final class ExampleScenario18 {
/** Working directory for artefacts. */
private static final Path TEMP_DIR = Path.of("temp_files");
/* ====================================================================== */
/** Entry-point. */
public static void main(final String[] args) {
System.out.println("===== SCENARIO 18 START =====");
System.out.println("""
Purpose :
* Provision AES-256 with pilot limits and short expiry.
* Export baseline metadata.
* Merge-patch to upsize limits and extend expiry.
* Export post-patch metadata.
Steps :
1) Generate AES-256 key (soft=5, max=10, expiry≈2 mo)
2) Export baseline JSON
3) Apply JSON Merge-Patch (soft=50, max=100, expiry≈18 mo)
4) Export patched JSON
--------------------------------------------------------------""");
try {
ensureTempDir(TEMP_DIR);
Properties props = ExampleUtil.loadProperties();
AnkaSecureSdk sdk = ExampleUtil.authenticate(props);
runScenario(sdk);
} catch (Exception ex) {
fatal("Scenario 18 failed", ex);
}
System.out.println("===== SCENARIO 18 END =====");
}
/* ====================================================================== */
/** Executes the scenario logic. */
private static void runScenario(final AnkaSecureSdk sdk) throws Exception {
/* 1 ── generate AES-256 key with pilot limits ------------------- */
String kid = "sc18_aes256_" + System.currentTimeMillis();
ZonedDateTime pilotExpiry = ZonedDateTime.now(ZoneOffset.UTC).plusMonths(2);
GenerateKeySpec gen = new GenerateKeySpec()
.setKid(kid)
.setKty("oct")
.setAlg("AES-256")
.setExportable(false)
.setSoftUsageLimit(5)
.setMaxUsageLimit(10)
.setExpiresAt(pilotExpiry);
sdk.generateKey(gen);
System.out.println("[1] Pilot key created -> kid = " + kid);
/* 2 ── export baseline metadata -------------------------------- */
ExportedKeySpec before = sdk.exportKey(kid);
Path beforeJson = TEMP_DIR.resolve("scenario18_before.json");
saveJson(before, beforeJson);
System.out.println("[2] Baseline metadata saved -> " + beforeJson.toAbsolutePath());
printMeta("Baseline", before);
/* 3 ── build & apply JSON-Merge-Patch --------------------------- */
ZonedDateTime newExpiry = ZonedDateTime.now(ZoneOffset.UTC).plusMonths(18);
PatchKeySpec patch = new PatchKeySpec.Builder()
.softUsageLimit(50)
.maxUsageLimit(100)
.expiresAt(newExpiry.toInstant())
.build();
sdk.patchKey(kid, patch);
System.out.println("[3] Merge-patch applied");
/* 4 ── export patched metadata --------------------------------- */
ExportedKeySpec after = sdk.exportKey(kid);
Path afterJson = TEMP_DIR.resolve("scenario18_after.json");
saveJson(after, afterJson);
System.out.println("[4] Post-patch metadata saved -> " + afterJson.toAbsolutePath());
printMeta("After Patch", after);
}
/* ====================================================================== */
/** Serialises {@code spec} as pretty-printed JSON into {@code target}. */
private static void saveJson(final ExportedKeySpec spec, final Path target) {
try (var writer = Files.newBufferedWriter(target, StandardCharsets.UTF_8)) {
gson().toJson(spec, writer);
} catch (IOException e) {
fatal("Unable to write " + target.getFileName(), e);
}
}
/** Builds a configured {@link Gson} instance with ZonedDateTime support. */
private static Gson gson() {
return new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter(
ZonedDateTime.class,
(JsonSerializer<ZonedDateTime>) (src, t, ctx) ->
new JsonPrimitive(src.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)))
.registerTypeAdapter(
ZonedDateTime.class,
(JsonDeserializer<ZonedDateTime>) (el, t, ctx) ->
ZonedDateTime.parse(
el.getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME))
.create();
}
/* ====================================================================== */
/** Human-readable dump of key metadata. */
private static void printMeta(final String label, final ExportedKeySpec spec) {
System.out.println("----- " + label + " Metadata -----");
System.out.println("kid : " + spec.getKid());
System.out.println("kty : " + spec.getKty());
System.out.println("alg : " + spec.getAlg());
System.out.println("softUsageLimit : " + spec.getSoftUsageLimit());
System.out.println("maxUsageLimit : " + spec.getMaxUsageLimit());
System.out.println("expiresAt : " + spec.getExpiresAt());
System.out.println("status : " + spec.getStatus());
System.out.println("---------------------------------------------");
}
/* ====================================================================== */
/** Ensures {@code dir} exists. */
private static void ensureTempDir(final Path dir) throws IOException {
if (!Files.exists(dir)) {
Files.createDirectories(dir);
}
}
/** Logs an unrecoverable error and terminates. */
private static void fatal(final String msg, final Throwable t) {
System.err.println(msg);
if (t != null) {
t.printStackTrace(System.err);
}
System.exit(1);
}
/**
* Private constructor prevents instantiation.
*/
private ExampleScenario18() {
/* utility class – no instantiation */
}
}
How to run
Console milestones
-
AES-256 key created with pilot limits
-
Baseline metadata export →
scenario18_before.json
-
JSON Merge-Patch applied (limits + expiry upsized)
-
Post-patch metadata export →
scenario18_after.json
-
Printed diff confirms new limits and extended expiry
Where next?
© 2025 Anka Technologies. All rights reserved.