Skip to content

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:

  1. Generate an AES-256 key with pilot limits

    • softUsageLimit = 5, maxUsageLimit = 10,
    • expires in ≈ 2 months.
  2. Export baseline metadata → temp_files/scenario18_before.json.

  3. Apply an RFC 7396 JSON Merge-Patch that upsizes the limits to 50 / 100 and extends the expiry to ≈ 18 months.

  4. 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&nbsp;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

mvn -q compile exec:java\
  -Dexec.mainClass="co.ankatech.ankasecure.sdk.examples.ExampleScenario18"

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.