Skip to content

Example Utility Class

All integration flows import co.ankatech.ankasecure.sdk.examples.ExampleUtil for:

  • encrypted-credential authentication
  • property loading (cli.properties, -Dcli.config)
  • JSON pretty-printing with Java Time
  • AES-GCM helper functions
  • fatal-exit convenience

// FILE: ExampleUtil.java
package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.exception.AnkaSecureSdkException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.text.MessageFormat;
import java.util.*;

public final class ExampleUtil {

    private ExampleUtil() {/* utility */}

    /* ---------- JSON pretty printer ---------- */
    private static final ObjectMapper JSON = new ObjectMapper()
            .enable(SerializationFeature.INDENT_OUTPUT)
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .registerModule(new JavaTimeModule());

    /* ---------- CLI config loader ------------ */
    public static Properties loadProperties() {
        Properties p = new Properties();
        File local = new File("cli.properties");
        try {
            if (local.isFile()) {
                try (InputStream in = new FileInputStream(local)) {
                    p.load(in);
                    System.out.println("Loaded config from " + local.getAbsolutePath());
                    return p;
                }
            }
            String sys = System.getProperty("cli.config");
            if (sys != null && !sys.isBlank()) {
                File f = new File(sys);
                if (f.isFile()) {
                    try (InputStream in = new FileInputStream(f)) {
                        p.load(in);
                        System.out.println("Loaded config from " + f.getAbsolutePath());
                        return p;
                    }
                }
            }
            try (InputStream in = ExampleUtil.class.getResourceAsStream("/cli.properties")) {
                if (in == null) fatal("cli.properties not found; run `init` first.", null);
                p.load(in);
                System.out.println("Loaded config from classpath /cli.properties");
                return p;
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Failed to load configuration", e);
        }
    }

    /* ---------- Auth helpers ----------------- */
    public static AnkaSecureSdk authenticate(Properties props) {
        String uuid   = props.getProperty("client.uuid");
        String salt   = props.getProperty("client.salt");
        String idEnc  = props.getProperty("clientIdEnc");
        String scEnc  = props.getProperty("clientSecretEnc");

        if (uuid == null || salt == null || idEnc == null || scEnc == null) {
            fatal("CLI not initialised; run `init` first.", null);
        }

        try {
            byte[] key   = deriveKey(uuid, salt);
            String id    = decryptValue(idEnc,  key);
            String sec   = decryptValue(scEnc,  key);

            AnkaSecureSdk sdk = new AnkaSecureSdk(props);
            sdk.authenticateApplication(id, sec);
            System.out.println("Authenticated clientId=" + id);
            return sdk;
        } catch (AnkaSecureSdkException e) {
            fatal(MessageFormat.format("Auth failed HTTP={0}", e.getStatusCode()), e);
            return null; // never reached
        } catch (Exception e) {
            fatal("Credential decryption error", e);
            return null;
        }
    }

    /* ---------- JSON helper ------------------ */
    public static String toJson(Object o) {
        try { return JSON.writeValueAsString(o); }
        catch (IOException e) { throw new UncheckedIOException(e); }
    }

    /* ---------- AES-GCM derivation ------------ */
    private static byte[] deriveKey(String uuid, String saltHex) throws Exception {
        byte[] salt = hexToBytes(saltHex);
        PBEKeySpec spec = new PBEKeySpec(uuid.toCharArray(), salt, 150_000, 256);
        return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
                               .generateSecret(spec).getEncoded();
    }
    private static String decryptValue(String b64, byte[] key) throws Exception {
        byte[] blob = Base64.getDecoder().decode(b64);
        byte[] iv   = Arrays.copyOfRange(blob, 0, 12);
        byte[] ct   = Arrays.copyOfRange(blob, 12, blob.length);
        Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
        c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
               new GCMParameterSpec(128, iv));
        return new String(c.doFinal(ct), StandardCharsets.UTF_8);
    }
    private static byte[] hexToBytes(String h) {
        byte[] out = new byte[h.length() / 2];
        for (int i = 0; i < h.length(); i += 2) {
            out[i / 2] = (byte) ((Character.digit(h.charAt(i),16) << 4)
                               +  Character.digit(h.charAt(i+1),16));
        }
        return out;
    }

    /* ---------- misc ------------------------- */
    public static void ensureDir(Path p) {
        try { Files.createDirectories(p); }
        catch (IOException e) { fatal("Cannot create dir " + p, e); }
    }
    public static void fatal(String msg, Throwable t) {
        System.err.println(msg + (t!=null?": "+t.getMessage():""));
        if (t!=null) t.printStackTrace(System.err);
        System.exit(1);
    }
}
2 · Reference the utility once per flow At the top of every flow page add a short note:

> **Dependency** — this class imports `ExampleUtil`.  
> If you have not copied `ExampleUtil.java` yet, see [example_util.md](example_util.md).
No more duplicated helpers—each scenario remains a single, self-contained Java file that compiles as long as ExampleUtil.java sits in the same Maven/Gradle source tree.

3 · Compile everything together

src/
└─ main/
└─ java/
└─ co/ankatech/ankasecure/sdk/examples/
├─ ExampleUtil.java
├─ ExampleScenario1.java
├─ ExampleScenario2.java
└─ 
mvn -q compile exec:java
-Dexec.mainClass="co.ankatech.ankasecure.sdk.examples.ExampleScenario1"
All other flows run the same way—no code duplication, one shared utility.