Skip to content

Using the AnkaSecure Key Management SDK in Java

SDK Version: 3.4.0
Java Version: 17+
Artifact: co.ankatech:ankasecure-key-management-sdk:3.4.0

This guide covers the configuration, the sign-in and every operation group of the Key Management SDK. Read Prerequisites first: the workload, its key and its scopes must exist.


1. Installation

<dependency>
    <groupId>co.ankatech</groupId>
    <artifactId>ankasecure-key-management-sdk</artifactId>
    <version>3.4.0</version>
</dependency>
implementation 'co.ankatech:ankasecure-key-management-sdk:3.4.0'

For a build that cannot reach Maven Central, the JAR and its checksums are on the Downloads page.


2. Configuration

Properties props = new Properties();
props.setProperty("kmsdk.scheme", "https");
props.setProperty("kmsdk.host", "api.example.com");
props.setProperty("kmsdk.port", "443");

AnkaSecureKeyManagementSdk factory = new AnkaSecureKeyManagementSdk(props)
        .withCorrelationIdSupplier(() -> MDC.get("requestId"));   // optional: the X-Request-ID of each call

Every key and its default is listed in Prerequisites § Configuration. The factory is thread-safe and can produce several authenticated instances.


3. Signing in

authenticateWorkload(WorkloadCredential) is the only sign-in. It returns an AuthenticatedKeyManagementSdk, which is AutoCloseable: close it when the work is done.

Credential Factory method The workload proves itself with
Private key file WorkloadCredential.privateKeyJwt(workloadId, Path) a private_key_jwt assertion signed by the key in a PKCS#8 PEM or private JWK file (owner-only on POSIX)
Private key object WorkloadCredential.privateKeyJwt(workloadId, PrivateKey) the same, with a JCA key your product already holds
Federated token WorkloadCredential.federated(workloadId, Supplier<String>) a token an external issuer the workload is bound to minted for it
Federated token file WorkloadCredential.federatedTokenFile(workloadId, Path) the token in a file a platform refreshes (for example a projected service-account token)
Federated token command WorkloadCredential.federatedTokenCommand(workloadId, List<String>) the token a command prints

What happens on sign-in:

  1. The SDK reads the authorization server metadata and checks that it names the configured platform issuer (kmsdk.auth.issuer), lists ES256 DPoP proofs and publishes a token endpoint. A deployment that does not issue DPoP-bound workload tokens is refused here, before any credential is sent.
  2. It generates an EC P-256 key in memory for the instance, never written anywhere.
  3. It requests a token with grant_type=client_credentials, the workload id, the assertion (or the federated token) and a DPoP proof for the published token endpoint. No client_secret or password is ever sent.
  4. Every later request carries Authorization: DPoP <token> and a new proof bound to the request's method, path and token. The five-minute token is renewed within 60 seconds of its expiry.

The published token endpoint must be on the origin the SDK dials, or sign-in is refused. The proofs name that origin, and the assertion audience is that origin followed by /api. No request follows a redirect: a 3xx answer is an error naming the status.

A refused sign-in raises KeyManagementSdkException with getOAuthError(): invalid_client (the workload, its key or its federation binding was refused), invalid_dpop_proof, or invalid_request (the deployment does not issue workload tokens). A 503 carries getRetryAfter().

try (AuthenticatedKeyManagementSdk sdk = factory.authenticateWorkload(
        WorkloadCredential.federatedTokenFile(workloadId, Path.of("/var/run/secrets/tokens/ankasecure")))) {
    SessionInfo session = sdk.viewSession();   // workload, tenant, expiry, scopes; never the token
    System.out.println(session);
}

4. Keys — sdk.keys()

PageAdminKeyResponse page = sdk.keys().list(KeyListQuery.firstPage()
        .withStatus("active").withKty("ML-KEM").withSize(50));
AdminKeyResponse key = sdk.keys().get("payments-data-key");

sdk.keys().create(new OrchestrationKeyRequest()
        .kid("payments-data-key").kty("ML-KEM").algorithm("ML-KEM-768")
        .purpose(OrchestrationKeyRequest.PurposeEnum.ENCRYPT_DECRYPT));

sdk.keys().update("payments-data-key", new AdminKeyMergePatchRequest());   // JSON Merge Patch of the metadata
AdminKeyExportResponse publicPart = sdk.keys().export("payments-data-key");
List<SupportedAlgorithm> algorithms = sdk.keys().supportedAlgorithms(SupportedAlgorithmQuery.all());
Method Does
list(KeyListQuery) One page of the tenant's keys; the query carries every filter the endpoint accepts (status, origin, restricted, kty, conformance, page, size, sort)
get(kid) The key's metadata
create(OrchestrationKeyRequest) A new key generated by the platform; a Key ID that any key of the tenant ever held, destroyed keys included, is refused with 409 …/key-id-retired
update(kid, AdminKeyMergePatchRequest) Changes the key's metadata; a weakening change the quorum policy gates throws QuorumApprovalPendingException
importKey(KeyImportRequest) Imports one key (JWK)
importKeystore(Path, metadata, SecretChars) / analyzeKeystore(...) Imports, or analyses without importing, a PKCS#12 or JKS keystore; the password is sent as the raw bytes of its multipart part and never written to a file
export(kid) The public part of the key
lifecycleCauseVerdicts(kid) Which lifecycle causes the tenant's policy admits for the key's transitions
supportedAlgorithms(SupportedAlgorithmQuery) The algorithms the tenant may use
suspend, reactivate, revoke, archive The key's lifecycle transitions; revoke and suspend take a cause and a reason

5. Key versions — sdk.materials()

A key keeps one Key ID across rotations; each rotation adds a material version.

List<MaterialVersionResponse> versions = sdk.materials().list("payments-data-key");
MaterialVersionResponse v1 = sdk.materials().get("payments-data-key", 1);
sdk.materials().revoke("payments-data-key", 1, new MaterialRevokeRequest());

6. Rotation — sdk.rotation()

RotationAdmissibilityResponse check = sdk.rotation().admissibility("payments-data-key");
KeySummaryResponse rotated = sdk.rotation().rotate("payments-data-key", request, "rotation-2026-11-01");

KeyRotationScheduleView schedule = sdk.rotation().schedule("payments-data-key");
sdk.rotation().replaceSchedule("payments-data-key", scheduleRequest);
KeyRotationExecutionsResponse history = sdk.rotation().listExecutions("payments-data-key",
        RotationExecutionListQuery.firstPage());

The rotate overload with an idempotency key sends Idempotency-Key, so a retried call rotates once. listExecutions returns the key's automatic-rotation schedule and the executions of its rotation obligation only.


7. Destruction — sdk.destruction()

A workload never destroys key material. It requests the destruction, and people approve it.

QuorumRequestReceipt receipt = sdk.destruction().requestKey("old-archive-key",
        new MarkForDestructionRequest().coolingOffDays(30));
System.out.println(receipt.requestId() + " " + receipt.status());

List<KeyDestructionOrder> orders = sdk.destruction().orders("old-archive-key");
sdk.destruction().cancelKey("old-archive-key");   // only while the destruction is scheduled
Method Does
requestKey(kid, MarkForDestructionRequest) Opens the quorum request for an ARCHIVED key and returns its receipt
requestVersion(stableKid, version, MaterialMarkForDestructionRequest) The same for one RETIRED, REVOKED or PENDING_PRIMARY version
cancelKey(kid) / cancelVersion(stableKid, version) Cancels a scheduled destruction; every version returns to the status it had before the approval
orders(kid) What each approval scheduled and how it ended

The whole workflow — mandatory approval, cooling-off period, automatic execution, the Key ID that is never reused — is on the Key Destruction page.


8. Own quorum requests — sdk.quorumRequests()

QuorumRequestPageResponse mine = sdk.quorumRequests().list(QuorumRequestListQuery.firstPage());
QuorumRequestResponse request = sdk.quorumRequests().get(receipt.requestId());
sdk.quorumRequests().cancel(UUID.fromString(receipt.requestId()));   // withdraw before approval

An operation the tenant's quorum policy gates throws QuorumApprovalPendingException:

try {
    sdk.keys().update("payments-data-key", patch);
} catch (QuorumApprovalPendingException pending) {
    QuorumRequestReceipt r = pending.getReceipt();
    // r.requestId(), r.operation(), r.requiredApprovals(), r.expiresAt(), r.location()
}

Nothing changes until people approve the request in the Admin Console. The SDK has no approve or reject method.


9. Exchange governance

Group Accessor Typical use
Exchange contexts sdk.exchangeContexts() the context in which an application exchanges protected data; create, update, suspend, resume, close
Exchanges sdk.exchanges() a governed exchange: create a draft, validate it, preview and activate it, suspend, resume, revoke
Capability grants sdk.capabilityGrants() which actor may use which key for which capability; preview shows what a grant would confer before it is created
Constraint policies sdk.constraintPolicies() usage limits and constraints a grant references
ExchangeDetailResponse draft = sdk.exchanges().create(exchangeRequest);
ValidationResultResponse result = sdk.exchanges().validate(draft.getId());
ExchangeActivationPreviewResponse preview = sdk.exchanges().previewActivation(draft.getId(), "ENCRYPT");
sdk.exchanges().activate(draft.getId(), new ActivateRequest());

State changes of an exchange carry its expected version, so a concurrent change is refused with 409 instead of being overwritten.


10. Internal cryptographic use cases — sdk.internalCryptoUseCases()

try (InternalCryptoUseCaseProvisioning created = sdk.internalCryptoUseCases().create(useCaseRequest)) {
    created.clientSecret().ifPresent(secret ->
            vault.store(created.useCase().getUseCaseId(), secret.chars()));   // shown once: store it now
}

The client secret of a new use case is returned once, in a zeroizable SecretChars holder, and never in the response model. clientSecret() is empty when the use case issued no secret. Closing the provisioning result zeroes the secret, so store it inside the try block.


11. Directory — sdk.directory()

Read-only lookups of the tenant's applications, actors and counterparty types, for building requests that reference them: listApplications, getApplication, listActors, listApplicationActors, getActor, listCounterpartyTypes, getCounterpartyType. Creating or changing them is done in the Admin Console.


12. Values a newer server adds

A server release can add a value to an enumerated member, such as a new key status. The SDK never refuses such a response:

  • the enum getter reads the generated UNKNOWN_DEFAULT_OPEN_API member;
  • the get<Member>AsString() accessor beside it, on every generated model (getStatusAsString() beside getStatus()), returns the value the server sent, verbatim, as a List<String> for a list of enums; for a value this release declares it returns that value's wire form, and for an absent member null;
  • writing the model back, or printing it as JSON, sends the server's value, never the sentinel.
AdminKeyResponse key = sdk.keys().get("payments-data-key");
if (key.getStatus() == AdminKeyResponse.StatusEnum.UNKNOWN_DEFAULT_OPEN_API) {
    System.out.println("Status not known to this SDK release: " + key.getStatusAsString());
}

Branch on the enum for the values you know, read the string when you show or forward one you do not, and upgrade the SDK to interpret the new value.


13. Errors

Every refusal is a KeyManagementSdkException:

Accessor Holds
getHttpStatus() The HTTP status
getProblemType() The RFC 9457 type; classify on it
getProblemTitle(), getProblemDetail(), getProblemInstance() The other problem members
getProblemExtensions() The extension members, including the support requestId
getOAuthError(), getRetryAfter() On a refused token request: the OAuth error and the server's Retry-After

The complete list of problem types is on the error pages.


Version: 3.4.0
Last updated: 2026-10-26