Skip to content

Utility Operations

Purpose: Stateless cryptographic operations with external keys without persistence

Version: 3.0.0


Overview of Utility Operations

Utility operations enable stateless cryptographic operations using caller-supplied keys without storing them in ANKASecure's keystore. This is the bridge pattern for interoperability with external systems.

Purpose: Interoperability without persistence

Key Characteristics:

  • Zero persistence: External keys used once and immediately discarded
  • No keystore pollution: No impact on tenant keystore quotas or management overhead
  • Immediate interoperability: Work with external systems managing their own keys
  • Testing and validation: Test compatibility before full integration

Use Cases:

  • Verifying signatures from external partners
  • Encrypting data for external recipients
  • One-time cryptographic operations
  • Proof-of-concept evaluations
  • Testing signature/encryption compatibility before key import

Primary Endpoints:

  • POST /api/v3/interoperability/verify - Verify JWS signatures
  • POST /api/v3/interoperability/encrypt - Encrypt with external public keys

When to Use Utility vs. Import

Decision Matrix: Import vs. Utility

graph TD
    Start{"Will you use this key<br/>multiple times?"}

    Start -->|Yes| Freq{"How frequently?"}
    Start -->|No| OneTime["One-time operation"]

    Freq -->|Daily/Weekly| Import["Import to Keystore<br/>POST .../keys/import-keystore"]
    Freq -->|Monthly/Rarely| ImportMaybe["Consider importing<br/>for lifecycle management"]
    Freq -->|Once| OneTime

    OneTime --> Utility["Utility Operation<br/>POST /api/v3/interoperability/*"]

    Import -->|Benefits| ImportBenefits["✅ Lifecycle management<br/>✅ HSM protection<br/>✅ Policy enforcement<br/>✅ Usage tracking"]

    Utility -->|Benefits| UtilityBenefits["✅ Zero persistence overhead<br/>✅ No quota impact<br/>✅ Instant operation<br/>✅ No cleanup required"]

    style Import fill:#4dabf7,stroke:#333,stroke-width:2px
    style Utility fill:#51cf66,stroke:#333,stroke-width:2px

Import to Keystore when:

  • ✅ Key will be used regularly (daily, weekly)
  • ✅ Need lifecycle management (rotation, expiration tracking)
  • ✅ Want HSM-level protection
  • ✅ Require usage auditing and tracking
  • ✅ Part of long-term migration strategy

Use Utility Operations when:

  • ✅ One-time or infrequent operation
  • ✅ Testing/validating compatibility
  • ✅ External system owns key lifecycle
  • ✅ Don't want keystore management overhead
  • ✅ Immediate interoperability needed

Endpoint: Verify with External Public Key

Endpoint Details

POST /api/v3/interoperability/verify
Content-Type: multipart/form-data
Authorization: Bearer {jwt-token}
Scope Required: SCOPE_interoperability.verifySignature

Purpose

Verify a detached JWS signature using a caller-supplied public key without storing the key in ANKASecure's keystore.

Use Cases:

  • Verifying signatures from external systems
  • One-time signature validation
  • Testing signature compatibility before key import
  • Interoperability with systems that manage their own keys

Request Structure (Multipart)

Part 1: metadata (application/json)

{
  "kty": "ML-DSA",
  "alg": "ML-DSA-87",
  "publicKey": "MHYwEAYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...",
  "signatureBase64": "eyJhbGciOiJNTC1EU0EtODciLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ.VGhpcyBpcyB0aGUgc2lnbmF0dXJl"
}

Part 2: file (application/octet-stream)

  • Binary payload that was originally signed
  • Streaming (no size buffering)

Field Details:

kty: Key type (informative, not used for lookup)
alg: The public key's algorithm name (RSA-2048, P-256, ML-DSA-87, …), used to rebuild the key. An EC key must be on the named curve and an ML-DSA or EdDSA key must be the named parameter set; a key that is not (a secp256k1 key sent as P-256) is refused with 422. It is not the JWS alg: that is read from the protected header, and it selects the padding for an RSA key (RS* = RSASSA-PKCS1-v1_5, PS* = RSASSA-PSS) publicKey: Base64-encoded public key (DER or PEM format)
signatureBase64: Base64-encoded detached JWS object (JSON, not raw signature bytes)

Detached JWS Format (RFC 7797):

{
  "protected": "eyJhbGciOiJNTC1EU0EtODciLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ",
  "signature": "VGhpcyBpcyB0aGUgc2lnbmF0dXJl"
}

kid in JWS is Ignored

If the JWS protected header contains a kid field, it is ignored. The supplied public key from the request is always used for verification.

Accepted alg Values

The JWS was signed by a third party, so its protected alg is honoured (RFC 7515 §4.1.1): it selects the verification scheme, and it must be a registered JWS algorithm that fits the supplied key.

Supplied key Accepted alg
RSA RS256, RS384, RS512 (RSASSA-PKCS1-v1_5) and PS256, PS384, PS512 (RSASSA-PSS, MGF1 over the same hash, salt of the hash length; RFC 7518 §3.3, §3.5)
EC The one ECDSA alg of its curve, with the fixed-length r‖s signature of RFC 7518 §3.4: ES256 (P-256), ES384 (P-384), ES512 (P-521), and ESB256, ESB384, ESB512 on brainpoolP256r1, brainpoolP384r1 and brainpoolP512r1 (RFC 9864)
Ed25519 or Ed448 EdDSA (RFC 8037) or the curve's fully-specified name, Ed25519 or Ed448 (RFC 9864)
ML-DSA and the other post-quantum signing keys The parameter-set name, e.g. ML-DSA-65

Refused with 422 application/problem+json before any verification: none, an absent alg, an HMAC alg such as HS256, a composite alg (a composite signature is not a single-key signature), an alg that does not fit the key (such as ES256 on a P-384 key), and an ECDSA signature that is not its curve's fixed length. The detail names neither the alg nor the key.

Response - Success

HTTP 200 OK

{
  "valid": true,
  "signatureResults": []
}

Response - Verification Failed

HTTP 200 OK - a signature that does not verify is a successful call with valid: false, not an error status. 4xx/5xx are reserved for a request the endpoint cannot evaluate (malformed key or JWS, missing part, suspended caller).

{
  "valid": false,
  "signatureResults": []
}

The utility endpoint reports a single verdict: signatureResults is always present and always empty here, because the per-signature detail belongs to keystore keys (POST /api/v3/crypto/verify).

Verification Flow

sequenceDiagram
    participant Client
    participant Controller as InteroperabilityController
    participant Service as MigrationApiService
    participant Verifier as JWS Verifier

    Client->>Controller: POST /api/v3/interoperability/verify<br/>multipart: metadata + file

    Controller->>Controller: Validate inputs<br/>(publicKey, signatureBase64 not empty)

    Controller->>Controller: Base64 decode<br/>publicKey and signatureBase64

    Controller->>Service: verifyDetachedJwsUtilityStream<br/>(kty, alg, jwsBytes, pubKeyBytes, fileStream)

    Service->>Service: Parse JWS JSON<br/>Extract protected header & signature

    Service->>Service: Reconstruct signing input<br/>ASCII(BASE64URL(protected)) || "." || payload-stream

    Service->>Verifier: Load public key<br/>Create Signature instance

    Verifier->>Verifier: Verify signature<br/>against reconstructed input

    alt Signature Valid
        Verifier-->>Service: true
        Service-->>Controller: valid=true
        Controller-->>Client: 200 OK {valid: true, signatureResults: []}
    else Signature Invalid
        Verifier-->>Service: false
        Service-->>Controller: valid=false
        Controller-->>Client: 200 OK {valid: false, signatureResults: []}
    end

Key NOT Persisted

The public key is NOT stored in ANKASecure's keystore. It is used only for this single verification operation and discarded immediately.

Use Case Example

Scenario: External system sends signed document, ANKASecure verifies before processing

# 1. External system signs the JWS Signing Input: BASE64URL(protected) '.' payload (RFC 7797, b64:false)
PROTECTED=$(printf '{"alg":"PS256","b64":false,"crit":["b64"]}' | base64 | tr '+/' '-_' | tr -d '=\n')
{ printf '%s.' "$PROTECTED"; cat document.txt; } | openssl dgst -sha256 -sign private.pem \
  -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest -sigopt rsa_mgf1_md:sha256 \
  -out document.sig
# For "alg":"RS256" drop the three -sigopt options: RS256 is RSASSA-PKCS1-v1_5.

# 2. Create detached JWS JSON (base64url signature, no padding)
SIG=$(base64 < document.sig | tr '+/' '-_' | tr -d '=\n')
JWS="{\"protected\":\"$PROTECTED\",\"signature\":\"$SIG\"}"

# 3. Extract public key
openssl rsa -in private.pem -pubout -outform DER | base64 > pubkey.b64

# 4. Verify via ANKASecure
curl -X POST https://api.ankatech.co/api/v3/interoperability/verify \
  -H "Authorization: Bearer $TOKEN" \
  -F "metadata={\"kty\":\"RSA\",\"alg\":\"RSA-2048\",\"publicKey\":\"$(cat pubkey.b64)\",\"signatureBase64\":\"$JWS\"}" \
  -F "[email protected]"

# Response (HTTP 200): {"valid": true, "signatureResults": []}

Endpoint: Encrypt with External Public Key

Endpoint Details

POST /api/v3/interoperability/encrypt
Content-Type: multipart/form-data
Authorization: Bearer {jwt-token}
Scope Required: SCOPE_interoperability.encrypt

Purpose

Encrypt plaintext using a caller-supplied public key without storing the key in ANKASecure's keystore.

Use Cases:

  • Testing encryption compatibility before full integration
  • One-time encryption for external recipients
  • Proof-of-concept evaluations
  • Interoperability with systems that manage their own key lifecycle

Request Structure (Multipart)

Part 1: metadata (application/json)

{
  "kty": "ML-KEM",
  "alg": "ML-KEM-1024",
  "publicKey": "MHYwEAYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."
}

Part 2: file (application/octet-stream)

  • Binary plaintext to encrypt
  • Streaming (no memory buffering)

Response - Success

HTTP 200 OK

Content-Type: multipart/mixed; boundary=----ankatech-{uuid}

Structure:

--{boundary}
Content-Type: application/jose+json

{
  "protected": "eyJhbGciOiJNTC1LRU0tMTAyNCIsImVuYyI6IkEyNTZHQ00iLCJ2ZXIiOiIyLjAifQ",
  "iv": "randomIvBase64",
  "recipients": [
    {
      "encrypted_key": "wrappedDekBase64"
    }
  ]
}

--{boundary}
Content-Type: application/octet-stream

{binary-ciphertext-bytes}

--{boundary}--

Detached Format: the JSON header carries no ciphertext and no tag; the ciphertext and the 16-byte tag are the second MIME part. The protected header decodes to {"alg":"ML-KEM-1024","enc":"A256GCM","ver":"2.0"} (no kid: the key is the caller's, not a tenant key), so the one recipient carries its encrypted_key alone: the header locations are disjoint (RFC 7516 §7.2.1).

Encryption Flow

graph TD
    A["Client sends plaintext"] --> B["InteroperabilityController"]

    B --> C{"Validate publicKey"}
    C -->|Invalid| D["400 Bad Request"]
    C -->|Valid| E["Create Virtual Thread"]

    E --> F["Setup Security Context"]
    F --> G["Generate DEK"]
    G --> H["Hybrid Encryption"]

    H --> I["Encrypt DEK with Public Key<br/>ML-KEM or RSA-OAEP"]
    H --> J["Encrypt Plaintext with DEK<br/>AES-256-GCM"]

    I --> K["Detached JWE Header"]
    J --> L["Ciphertext Stream"]

    K --> M["MIME Part 1:<br/>application/jose+json"]
    L --> N["MIME Part 2:<br/>application/octet-stream"]

    M --> O["multipart/mixed Response"]
    N --> O

    O --> P["Client receives<br/>encrypted bundle"]

    style H fill:#4dabf7,stroke:#333,stroke-width:2px
    style K fill:#51cf66,stroke:#333,stroke-width:2px
    style L fill:#51cf66,stroke:#333,stroke-width:2px

Key NOT Persisted

The public key is NOT stored. It is validated, used for encryption, and immediately discarded.

Use Case Example

Scenario: Encrypt file for external recipient who manages their own keys

# 1. External party provides public key
EXTERNAL_PUBKEY="MHYwEAYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."

# 2. Encrypt file via ANKASecure
curl -X POST https://api.ankatech.co/api/v3/interoperability/encrypt \
  -H "Authorization: Bearer $TOKEN" \
  -F "metadata={\"kty\":\"ML-KEM\",\"alg\":\"ML-KEM-768\",\"publicKey\":\"$EXTERNAL_PUBKEY\"}" \
  -F "[email protected]" \
  --output encrypted-bundle.mime

# 3. Send encrypted-bundle.mime to external party
# External party decrypts with their private key (managed outside ANKASecure)

Bridge Pattern Strategies

When to Use Bridge Pattern

The bridge pattern (utility operations) is ideal for:

Organizational Scenarios:

  • Partner organizations: External companies managing their own keys
  • Federated systems: Multiple independent key management systems
  • Temporary integrations: Short-term projects or POCs
  • Legacy interoperability: Systems that can't migrate to ANKASecure

Technical Scenarios:

  • Testing: Validate cryptographic compatibility before full integration
  • One-time operations: Infrequent cryptographic operations
  • Key ownership: External party maintains key lifecycle control
  • Low-volume use cases: Operations that don't justify keystore overhead

Zero-Persistence Operations

How It Works:

graph LR
    Client["Client"] -->|1. Send key + data| API["Utility API"]
    API -->|2. Load key to memory| Memory["Temporary Memory"]
    Memory -->|3. Perform operation| Crypto["Crypto Engine"]
    Crypto -->|4. Return result| Client
    Memory -.->|5. Discard key| GC["Garbage Collection"]

    Keystore["(Keystore<br/>Not touched)"]

    style Memory fill:#ffd43b,stroke:#333,stroke-width:2px
    style Keystore fill:#cccccc,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5

Benefits:

  • ✅ No quota impact: Keystore quotas unaffected
  • ✅ No cleanup required: No key deletion or lifecycle management
  • ✅ Instant operation: No key provisioning delay
  • ✅ Simplified auditing: Operation logged, but no key lifecycle events

Limitations:

  • ⚠️ No persistence: Must provide key with every request
  • ⚠️ No lifecycle management: No rotation, expiration, or policy enforcement
  • ⚠️ Network overhead: Larger requests (public key included each time)

Integration Examples

Example 1: External Partner Signature Verification

Scenario: Verify documents signed by partner organization

Workflow:

  1. Partner signs document with their private key (managed externally)
  2. Partner sends: document + signature + public key
  3. ANKASecure verifies using utility endpoint
  4. If valid: Process document
  5. If invalid: Reject document

Code:

import requests
import base64

# Receive from partner
document = open('partner_document.pdf', 'rb').read()
signature = '{"protected":"eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19","signature":"<base64url RSASSA-PSS signature>"}'
public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."

# Verify via ANKASecure
metadata = {
    "kty": "RSA",
    "alg": "RSA-2048",
    "publicKey": public_key,
    "signatureBase64": signature
}

response = requests.post(
    'https://api.ankatech.co/api/v3/interoperability/verify',
    headers={'Authorization': f'Bearer {token}'},
    files={
        'metadata': ('metadata.json', json.dumps(metadata), 'application/json'),
        'file': ('document.pdf', document, 'application/octet-stream')
    }
)

if response.json()['valid']:
    print("✅ Signature valid, processing document")
else:
    print("❌ Signature invalid, rejecting document")

Example 2: Encrypt for External Recipient

Scenario: Encrypt sensitive file for partner who can't share their private key

Workflow:

  1. Partner generates ML-KEM key pair (managed externally)
  2. Partner shares public key only
  3. ANKASecure encrypts file using utility endpoint
  4. Send encrypted file to partner
  5. Partner decrypts with their private key (never shared)

Code:

# Partner provides public key
partner_public_key = "MHYwEAYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."

# Encrypt sensitive file
sensitive_data = open('financial_report.xlsx', 'rb').read()

metadata = {
    "kty": "ML-KEM",
    "alg": "ML-KEM-768",
    "publicKey": partner_public_key
}

response = requests.post(
    'https://api.ankatech.co/api/v3/interoperability/encrypt',
    headers={'Authorization': f'Bearer {token}'},
    files={
        'metadata': ('metadata.json', json.dumps(metadata), 'application/json'),
        'file': ('financial_report.xlsx', sensitive_data, 'application/octet-stream')
    }
)

# Save encrypted bundle
with open('encrypted_report.mime', 'wb') as f:
    f.write(response.content)

# Send to partner (they decrypt with their private key)

Comparison: Import vs. Utility

Feature Comparison

Feature Import to Keystore Utility Operations
Persistence ✅ Keys stored permanently ❌ Keys discarded after use
HSM Protection ✅ Wrapped with HSM KEK ❌ Memory-only (TLS-protected in transit)
Lifecycle Management ✅ Full lifecycle (rotation, expiration) ❌ No lifecycle management
Policy Enforcement ✅ keyOps, status-based restrictions ❌ No policy enforcement
Usage Tracking ✅ usageCount, lastUsedAt ⚠️ API call logged, but no key-level tracking
Quota Impact ⚠️ Counts toward keystore quota ✅ No quota impact
Performance ✅ Key pre-loaded, faster operations ⚠️ Key loaded per request, slight overhead
Network Overhead ✅ Small requests (kid reference only) ⚠️ Larger requests (full public key)
Use Case Regular, repeated operations One-time, testing, external systems

Cost-Benefit Analysis

Import to Keystore:

  • Cost: Keystore management, quota usage, lifecycle overhead
  • Benefit: HSM protection, policy enforcement, usage tracking, performance

Best for: Keys used ≥ 5 times/month, requiring lifecycle management

Utility Operations:

  • Cost: Larger network requests, no lifecycle management
  • Benefit: Zero persistence overhead, instant operation, no cleanup

Best for: Keys used < 5 times/month, external party owns lifecycle


Operational Impact

Performance Considerations

Request Size Comparison:

Operation Import (kid only) Utility (full key) Size Increase
Verify (RSA-2048) ~50 bytes ~350 bytes 7x larger
Verify (ML-DSA-65) ~50 bytes ~2,000 bytes 40x larger
Encrypt (ML-KEM-768) ~50 bytes ~1,200 bytes 24x larger

Latency Impact:

  • Import: ~10ms (key lookup from cache)
  • Utility: ~15ms (key parse + validation + operation)
  • Difference: ~5ms overhead per operation

Conclusion: Utility operations add minimal latency but significantly larger requests for PQC keys.

Frequency of Use Guidelines

graph LR
    Usage{"Operations per month"}

    Usage -->|1-5 ops| Utility["✅ Use Utility<br/>Zero overhead"]
    Usage -->|5-20 ops| Consider["⚠️ Consider importing<br/>Analyze trade-offs"]
    Usage -->|20+ ops| Import["✅ Import to Keystore<br/>Better performance"]

    style Utility fill:#51cf66,stroke:#333,stroke-width:2px
    style Import fill:#4dabf7,stroke:#333,stroke-width:2px
    style Consider fill:#ffd43b,stroke:#333,stroke-width:2px

Recommendation:

  • < 5 operations/month: Use utility operations
  • 5-20 operations/month: Evaluate based on other factors (lifecycle needs, compliance)
  • > 20 operations/month: Import to keystore for efficiency


Version: 3.0.0
Last Updated: 2025-12-24 Status: Production Ready