Skip to content

Import Legacy Keys to AnkaSecure

Two operations bring existing key material into a tenant, and they are not interchangeable:

Operation What it accepts What it produces
POST /api/v3/admin/tenants/{tenantId}/keys/import-keystore a PKCS#12 or JKS keystore file one or more keys, plus their certificates
POST /api/v3/admin/tenants/{tenantId}/keys/import-key a single public key, as JSON one key

Both live on the Admin API, because importing key material is a control-plane act. Both are tenant-scoped: the tenant is in the path, and the token must be authorised for it.


Importing a keystore

import-keystore takes multipart/form-data with three parts:

Part Required What it is
metadata yes JSON: how to name and validate the imported keys (fields below)
keystore yes the binary .p12 / .jks file
password no the keystore password, when the file is protected

The metadata part is validated, and kid is required. A keystore holds aliases, not key identifiers, so you supply the base identifier the imported keys hang off:

Field Required Values
kid yes the base key identifier; bounded in length and character set
kidStrategy no AUTO (default) derives each alias's kid from kid; MANUAL requires kidMappings
kidMappings under MANUAL {alias: kid} — an explicit kid per alias
validationMode no STRICT, IMPORT_ONLY, SKIP — how far certificate chains are validated
purpose no AUTO, ENCRYPT_DECRYPT, SIGN_VERIFY, DUAL — the default for every alias
purposeMappings no {alias: purpose} — overrides purpose for named aliases
curl -sS -X POST \
  "$ANKA_BASE_URL/api/v3/admin/tenants/$TENANT_ID/keys/import-keystore" \
  -H "Authorization: Bearer $TOKEN" \
  -F 'metadata={"kid":"migration-2024-prod-key","kidStrategy":"AUTO","validationMode":"STRICT"};type=application/json' \
  -F "[email protected]" \
  -F "password=$KEYSTORE_PASSWORD"

The response reports what happened to every alias in the file, in four groups:

  • keys — imported successfully
  • certificates — certificate material that accompanied them
  • skipped — aliases deliberately not imported
  • failures — aliases that could not be imported, each with a reason

Read skipped and failures before treating an import as complete. A keystore with ten aliases of which three failed still returns 200; the per-alias outcome is in the body, not in the status code.

Documented failure responses: 400, 401, 403, 422, 502. A 422 means the file was readable but its contents were not admissible — a wrong password produces 400, not 422.


Importing a single public key

import-key takes application/json and imports a public key only. There is no operation that imports a bare private key: private material arrives inside a keystore or not at all.

Required fields are kid, kty and alg:

curl -sS -X POST \
  "$ANKA_BASE_URL/api/v3/admin/tenants/$TENANT_ID/keys/import-key" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "kid": "partner-verification-2026",
        "kty": "RSA",
        "alg": "RSA-3072",
        "type": "SIMPLE",
        "publicKey": "<base64url DER SubjectPublicKeyInfo>",
        "keyOps": ["verify"]
      }'

Fields worth knowing:

  • kty is a coarse key type — RSA, EC, oct, ML-KEM, ML-DSA, SLH-DSA, COMPOSITE. It is not where the construction lives.
  • alg carries the specific algorithm, hyphenated: RSA-3072, ML-KEM-768, ML-DSA-65. Underscored forms are not accepted.
  • type is SIMPLE or COMPOSITE. A composite import additionally supplies components, kdf and verificationPolicy (ALL, ANY, CLASSICAL_REQUIRED, PQC_REQUIRED).
  • keyOps constrains what the key may be used for.
  • Usage bounds — expiresAt, maxUsageLimit, softUsageLimit, softLimitExpiration — may be set at import time. On the two usage fields 0 and null both mean UNLIMITED, and an omitted maxUsageLimit is stored as 0: an imported key has no operation cap unless one is asked for. A positive softUsageLimit requires a finite maxUsageLimit — a soft threshold on an unlimited key is rejected.

Documented failure responses: 400, 401, 403, 409, 502. A 409 is narrower than "the kid is taken": it means a key with that kid exists with mismatched public material. Re-importing the same public key under the same kid is not a conflict.


Before you import: look first

import-keystore/analyze reads a keystore and reports its contents without importing anything. On an unfamiliar file — an old backup, something handed over by a departing vendor — analyse before you import. See Analyze Operations.


After importing

The imported keys appear in the ordinary key listing:

curl -sS "$ANKA_BASE_URL/api/v3/key-management/keys" \
  -H "Authorization: Bearer $TOKEN"

From that point they are ordinary keys: usable by kid from the data plane, and subject to the same policy as any other key in the tenant.


Next