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 signaturesPOST /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
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).
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)
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:
- Partner signs document with their private key (managed externally)
- Partner sends:
document + signature + public key - ANKASecure verifies using utility endpoint
- If valid: Process document
- 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:
- Partner generates ML-KEM key pair (managed externally)
- Partner shares public key only
- ANKASecure encrypts file using utility endpoint
- Send encrypted file to partner
- 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
Related Documentation
- Overview & Strategy - Migration capabilities overview
- API Reference - Complete endpoint documentation
- Error Reference - one page per failure
typeURI; the decision matrices are delivered with a licensed deployment
Version: 3.0.0
Last Updated: 2025-12-24 Status: Production Ready