Class AuthenticatedSdk

java.lang.Object
co.ankatech.ankasecure.sdk.AuthenticatedSdk
All Implemented Interfaces:
AutoCloseable

public final class AuthenticatedSdk extends Object implements AutoCloseable
Authenticated SDK instance with immutable JWT token.

This class represents an authenticated session with the ANKASecure© API. Each instance is bound to a specific JWT access token and provides all cryptographic operations available to that authenticated identity.

Thread Safety

Fully thread-safe. Instances are immutable and can be safely shared across thousands of concurrent threads. The underlying HTTP client uses connection pooling and HTTP/2 multiplexing for efficient resource usage.

Lifecycle Management

Instances are created by AnkaSecureSdk.authenticateApplication(String, co.ankatech.ankasecure.sdk.security.SecretChars) or AnkaSecureSdk.authenticateUser(String, co.ankatech.ankasecure.sdk.security.SecretChars, String). They remain valid until the token expires. Use isTokenExpiredLocally() to check validity.

Token Expiry Handling


 AuthenticatedSdk sdk = factory.authenticateApplication(clientId, clientSecret);

 // Use for multiple operations
 EncryptResult r1 = sdk.encrypt("key1", data1);
 EncryptResult r2 = sdk.encrypt("key2", data2);

 // Check expiry before long-running tasks
 if (sdk.isTokenExpiredLocally()) {
     sdk = factory.authenticateApplication(clientId, clientSecret);
 }

 // Continue with new token
 EncryptResult r3 = sdk.encrypt("key3", data3);
 

Concurrent Usage Pattern


 // Single AuthenticatedSdk shared across all threads
 AuthenticatedSdk sdk = factory.authenticateApplication(clientId, clientSecret);

 // Virtual threads (Java 21+) - 10,000+ concurrent operations
 try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
     List<CompletableFuture<EncryptResult>> futures = dataList.stream()
         .map(data -> CompletableFuture.supplyAsync(
             () -> sdk.encrypt("my-key", data),
             executor
         ))
         .toList();

     List<EncryptResult> results = futures.stream()
         .map(CompletableFuture::join)
         .toList();
 }
 

Memory Characteristics

  • Instance size: ~250 bytes (token string + references)
  • Shared HTTP client: single connection pool across all instances
  • 10,000 instances: ~2.5 MB total overhead

File-Output Overwrite Policy

Every file-output method (the *File, *FileCompact, *FileStream and combined/migration families) quarantines its result to a sidecar (<output>.part) and atomically renames it onto output. By default (OverwritePolicy.FAIL_IF_EXISTS) an operation fails if output already exists, throwing DestinationExistsException and leaving the pre-existing file untouched. To replace an existing destination, derive an overwrite-enabled instance with withOverwrite() (or withOverwritePolicy(OverwritePolicy.OVERWRITE)); the derived instance shares this instance's JWT and HTTP client with no re-authentication.

Since:
3.0.0
Author:
ANKATech Solutions Inc.
See Also:
  • Method Details

    • close

      public void close()
      SDK-006 (secure-memory-cutover Slice 4): clears the bearer token from the underlying AnkaSecureOpenApiClient's HttpBearerAuth. After this call:
      • Subsequent operations on this instance throw IllegalStateException.
      • The bearer token is no longer reachable via the OkHttp request chain.

      Idempotent. Use try-with-resources for deterministic cleanup:

      
         try (AuthenticatedSdk sdk = factory.authenticateApplication(clientId, secret)) {
             sdk.encrypt(...);
         } // close() runs here, clearing the bearer token
       

      Note: the accessToken field is a String (immutable, interned-eligible) and cannot be reliably zeroized — this is documented in SECURE_MEMORY.md as a known JVM-level limitation. close() reduces the in-memory lifetime by clearing the OkHttp interceptor reference, but the String itself remains until GC.

      Wither ownership (security REQ-3.x)

      Instances derived via withOverwritePolicy(OverwritePolicy) / withOverwrite() SHARE the root's single bearer token and HTTP client. Only the factory-created ROOT owns the token: closing a DERIVED instance marks that instance closed but MUST NOT clear the token still in use by the root (and by any sibling derived instance), preventing a use-after-free of a zeroized secret. Closing the ROOT clears the shared token, ending the session for all views.

      Specified by:
      close in interface AutoCloseable
    • overwritePolicy

      public OverwritePolicy overwritePolicy()
      Returns the file-output overwrite policy in effect for this instance.
      Returns:
      the OverwritePolicy (default OverwritePolicy.FAIL_IF_EXISTS)
      Since:
      3.0.0
      See Also:
    • withOverwritePolicy

      public AuthenticatedSdk withOverwritePolicy(OverwritePolicy overwritePolicy)
      Returns a NEW immutable AuthenticatedSdk that applies overwritePolicy to every file-output operation, sharing this instance's JWT and HTTP client.

      This is a pure, local, immutable derivation: it performs NO network round-trip and NO re-authentication. The derived instance shares the SAME JWT reference and the SAME HTTP client (there is no plaintext copy of the token). Per the wither ownership model, only the original factory-created instance owns the token — closing the derived instance never clears the token still in use by the original (security REQ-3.x).

      Parameters:
      overwritePolicy - the overwrite policy for the derived instance; must not be null
      Returns:
      a distinct immutable instance with the requested policy; never null
      Throws:
      NullPointerException - if overwritePolicy is null
      Since:
      3.0.0
      See Also:
    • withOverwrite

      public AuthenticatedSdk withOverwrite()
      Convenience wither equivalent to withOverwritePolicy(OverwritePolicy.OVERWRITE): returns a NEW immutable AuthenticatedSdk whose file-output operations atomically replace an existing destination.

      Like withOverwritePolicy(OverwritePolicy), this performs NO network round-trip and NO re-authentication and shares the same JWT and HTTP client (see the wither ownership model).

      Returns:
      a distinct immutable instance with OverwritePolicy.OVERWRITE; never null
      Since:
      3.0.0
      See Also:
    • isClosed

      public boolean isClosed()
      Returns:
      true if close() has been called; never throws
    • isTokenExpiredLocally

      public boolean isTokenExpiredLocally()
      Checks whether the access token has expired according to its local exp claim only.

      This is a local, offline check. It extracts the exp claim from the JWT payload and compares it with the current time; it does NOT contact the server and therefore does NOT detect server-side revocation — a token that was revoked (logout / blacklist) or superseded by the subject's revocation epoch (bulk revoke, e.g. an admin force-reset, a suspend/delete, or a role scope shrink) is still unexpired locally and returns false here while the server rejects it. To detect that case, perform an operation and handle TokenRevokedException (thrown on a 401), then re-authenticate.

      Usage Pattern

      
       AuthenticatedSdk sdk = factory.authenticateApplication(id, secret);
      
       // Before an expensive batch operation, cheaply skip a definitely-expired token
       if (sdk.isTokenExpiredLocally()) {
           sdk = factory.authenticateApplication(id, secret);
       }
      
       try {
           processBatch(sdk, largeDataset);
       } catch (TokenRevokedException revoked) {
           // The server rejected the still-unexpired token — re-authenticate and retry.
           sdk = factory.authenticateApplication(id, secret);
       }
       
      Returns:
      true if the token is locally expired or malformed, false if still valid by its local exp claim (which does not imply the server will accept it)
    • getTokenExpiration

      public Instant getTokenExpiration()
      Extracts the expiration time from the JWT token.

      Decodes the JWT payload (Base64URL) and reads the exp claim (seconds since Unix epoch). Returns null if the token is malformed.

      Returns:
      the token expiration as Instant, or null if malformed
    • encrypt

      public EncryptResult encrypt(String kid, byte[] plaintext) throws AnkaSecureSdkException
      Encrypts raw bytes using the key identified by kid.

      The key type determines the algorithm automatically: asymmetric keys (ML-KEM, RSA, ECC) perform key encapsulation + symmetric encryption; symmetric keys (AES) encrypt directly. The result is a compact JWE (RFC 7516) string containing the ciphertext and all metadata needed for decryption.

      This method is suitable for in-memory payloads up to ~100 MB. For larger files, use encryptFileStream(String, Path, Path) to avoid loading the entire payload into memory.

      Example

      
       byte[] plaintext = "Sensitive data".getBytes(StandardCharsets.UTF_8);
       EncryptResult result = sdk.encrypt("my-ml-kem-768-key", plaintext);
      
       // The JWE token is self-contained and can be stored or transmitted
       String jweToken = result.getJweToken();
      
       // Decrypt later
       DecryptResult decrypted = sdk.decrypt(jweToken);
       byte[] recovered = decrypted.getDecryptedData();
       
      Parameters:
      kid - key identifier referencing a key stored in ANKASecure©; must not be null
      plaintext - the raw bytes to encrypt; must not be null
      Returns:
      encryption result containing the JWE token and operation metadata; never null
      Throws:
      AnkaSecureSdkException - if the key identified by kid does not exist, does not support encryption, the authenticated client lacks permission, or the server returns an error
      NullPointerException - if kid or plaintext is null
      Since:
      3.0.0
      See Also:
    • encryptFileCompact

      public EncryptResult encryptFileCompact(String kid, Path input, Path output) throws AnkaSecureSdkException
      Encrypts a file with forced compact format. Use when you need a single-line string for API/JSON transmission. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: JSON responses, HTTP headers, query parameters
      • Storage: Single database field (text/varchar)
      • Interoperability: JWE standard format (RFC 7516)

      Output Format

       eyJhbGc...header...XVCJ9.eyJkYXRh...encrypted-payload...9.dBjftJ...tag...
       

      Single-line base64url string, suitable for text-based transmission.

      Alternative Methods

      Example

      
       // Encrypt small document for API transmission
       EncryptResult result = sdk.encryptFileCompact(
           "my-ml-kem-key",
           Path.of("contract.pdf"),
           Path.of("contract.jwe")
       );
      
       System.out.println("Algorithm: " + result.getAlgorithmUsed());
       
      Parameters:
      kid - key identifier for encryption; must not be null
      input - input file (up to the server's configured compact limit, default 4 MB); must not be null
      output - output file (.jwe recommended); must not be null
      Returns:
      encryption result with metadata; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      See Also:
    • encryptFile

      public EncryptResult encryptFile(String kid, Path input, Path output) throws AnkaSecureSdkException, IOException
      Encrypts a file. Recommended for most use cases. Automatically selects the optimal format based on the server-discovered compact payload limit (below the limit = compact, at or above = streaming).

      This method automatically selects the optimal format:

      • Files below the server's maxPlaintextBytes: Compact JWE format (single-line string)
      • Files at or above that limit: Streaming JWE format (binary chunks)

      Why Use This Method?

      You don't need to worry about file sizes or server limits. The threshold is discovered from the server (not hardcoded) and cached briefly; if a compact request is still rejected as too large (HTTP 413) the SDK transparently retries via streaming, ensuring your encryption always succeeds regardless of file size.

      When to Use Explicit Formats

      Use encryptFileCompact(String, Path, Path) or encryptFileStream(String, Path, Path) only when you specifically need a particular output format (e.g., compact JWE for API transmission).

      Example

      
       // Encrypt any file - works for both small and large files
       EncryptResult result = sdk.encryptFile(
           "my-ml-kem-key",
           Path.of("data.bin"),
           Path.of("data.jwe")
       );
      
       System.out.println("Encrypted with: " + result.getAlgorithmUsed());
       
      Parameters:
      kid - key identifier for encryption; must not be null
      input - input file (any size); must not be null
      output - output file (.jwe or .jwet); must not be null
      Returns:
      encryption result with metadata; never null

      By default fails if output exists; call withOverwrite() to overwrite.

      Throws:
      AnkaSecureSdkException - if operation fails
      IOException - if file cannot be accessed
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default)
      See Also:
    • encryptFileStream

      public EncryptResult encryptFileStream(String kid, Path input, Path output) throws AnkaSecureSdkException
      Encrypts a file with forced streaming format. Use for large files (100MB+), low-memory environments, or when binary output is acceptable.

      When to Use Streaming Format

      • Large files: Files above the server's configured compact limit (required), optimal for 100MB+ (recommended)
      • Memory constraints: Low-memory environments or embedded systems
      • Binary output: When base64 encoding overhead is undesirable
      • Progressive processing: Start processing before full upload

      Output Format

      Binary streaming format (.jwet file) with multipart chunks. Not suitable for text-based transmission (API/JSON).

      Alternative Methods

      Example

      
       // Encrypt large video file
       EncryptResult result = sdk.encryptFileStream(
           "my-ml-kem-key",
           Path.of("movie.mp4"),
           Path.of("movie.jwet")
       );
       
      Parameters:
      kid - key identifier for encryption; must not be null
      input - input file (any size, up to 100MB recommended); must not be null
      output - output file (.jwet recommended); must not be null
      Returns:
      encryption result with metadata; never null
      Throws:
      AnkaSecureSdkException - if operation fails
      See Also:
    • decrypt

      public DecryptResult decrypt(String ciphertextJwe) throws AnkaSecureSdkException
      Decrypts a compact JWE (RFC 7516) token to recover the original plaintext.

      The JWE token is self-contained and includes all metadata required for decryption (algorithm, encrypted key, IV, ciphertext, authentication tag). The server resolves the correct decryption key based on the kid header in the JWE.

      This method is suitable for in-memory payloads up to ~100 MB. For larger files, use decryptFileStream(Path, Path) to avoid loading the entire payload into memory.

      Example

      
       // Decrypt a previously encrypted message
       String jweToken = "eyJhbGciOiJNTC1LRU0tNzY4IiwiZW5jIjoiQTI1NkdDTSIsImtpZCI6Im15LWtleSJ9...";
       DecryptResult result = sdk.decrypt(jweToken);
      
       byte[] plaintext = result.getDecryptedData();
       String message = new String(plaintext, StandardCharsets.UTF_8);
      
       System.out.println("Decrypted using key: " + result.getMeta().getKeyRequested());
       System.out.println("Algorithm: " + result.getMeta().getAlgorithmUsed());
       
      Parameters:
      ciphertextJwe - the compact JWE string to decrypt; must not be null
      Returns:
      decryption result containing the plaintext bytes and operation metadata; never null
      Throws:
      AnkaSecureSdkException - if the JWE is malformed, the decryption key is not accessible to the authenticated client, decryption fails (authentication tag mismatch), or the server returns an error
      NullPointerException - if ciphertextJwe is null
      Since:
      3.0.0
      See Also:
    • decryptFileCompact

      public DecryptResultMetadata decryptFileCompact(Path input, Path output) throws AnkaSecureSdkException
      Decrypts a file with forced compact format. Use when you need to decrypt a single-line JWE string. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: Decrypting JWE from JSON responses or HTTP headers
      • Storage: Decrypting from single database field (text/varchar)
      • Interoperability: JWE standard format (RFC 7516)

      Input Format

      Expects compact JWE format (single-line base64url string). Typically created by encryptFileCompact(String, Path, Path).

      Alternative Methods

      Example

      
       // Decrypt compact JWE file
       DecryptResultMetadata result = sdk.decryptFileCompact(
           Path.of("contract.jwe"),
           Path.of("contract.pdf")
       );
      
       System.out.println("Decrypted using key: " + result.getKeyRequested());
       
      Parameters:
      input - compact JWE file (up to the server's configured compact limit, default 4 MB); must not be null
      output - output file for decrypted plaintext; must not be null
      Returns:
      decryption result metadata; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      See Also:
    • decryptFile

      public DecryptResultMetadata decryptFile(Path input, Path output) throws AnkaSecureSdkException, IOException
      Decrypts a file. Recommended for most use cases. Automatically selects the decryption path from the input envelope's actual format, not its size.

      This method inspects the input file's structure and routes accordingly:

      Why Format, Not Size?

      The input here is an envelope, not plaintext. A compact JWE is roughly twice the size of the plaintext it wraps, so its size may exceed the server's compact plaintext ceiling even though it is a compact envelope that must be decrypted via the compact path. Selecting by format guarantees that any file produced by encryptFile/encryptFileCompact is always decryptable, and that a genuinely detached/streaming artifact is always decrypted via streaming.

      When to Use Explicit Formats

      Use decryptFileCompact(Path, Path) or decryptFileStream(Path, Path) only when you specifically need to force a particular input format.

      Example

      
       // Decrypt any file - works for both small and large files
       DecryptResultMetadata result = sdk.decryptFile(
           Path.of("data.jwe"),
           Path.of("data.bin")
       );
      
       System.out.println("Decrypted using key: " + result.getKeyRequested());
       
      Parameters:
      input - input JWE file (any size); must not be null
      output - output file for decrypted plaintext; must not be null
      Returns:
      decryption result metadata; never null

      By default fails if output exists; call withOverwrite() to overwrite.

      Throws:
      AnkaSecureSdkException - if operation fails
      IOException - if file cannot be accessed
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default)
      See Also:
    • decryptFileStream

      public DecryptResultMetadata decryptFileStream(Path input, Path output) throws AnkaSecureSdkException
      Decrypts a file with forced streaming format. Use for large files (100MB+), low-memory environments, or when binary input is acceptable.

      When to Use Streaming Format

      • Large files: Files above the server's configured compact limit (required), optimal for 100MB+ (recommended)
      • Memory constraints: Low-memory environments or embedded systems
      • Binary output: When decrypting streaming JWE format (.jwet files)
      • Progressive processing: Start processing before full download

      Input Format

      Binary streaming format (.jwet file) with multipart chunks. Typically created by encryptFileStream(String, Path, Path).

      Alternative Methods

      Example

      
       // Decrypt large video file
       DecryptResultMetadata result = sdk.decryptFileStream(
           Path.of("movie.jwet"),
           Path.of("movie.mp4")
       );
       
      Parameters:
      input - streaming JWE file (any size, up to 100MB recommended); must not be null
      output - output file for decrypted plaintext; must not be null
      Returns:
      decryption result metadata; never null
      Throws:
      AnkaSecureSdkException - if operation fails
      See Also:
    • sign

      public SignResult sign(String kid, byte[] data) throws AnkaSecureSdkException
      Signs raw bytes using the key identified by kid.

      Creates a compact JWS (RFC 7515) containing the signature and all metadata needed for verification. The signing algorithm is determined by the key type: ML-DSA, ECDSA, or RSA-PSS.

      This method is suitable for in-memory payloads up to ~100 MB. For larger files, use signFileStream(String, Path, Path) to avoid loading the entire payload into memory.

      Example

      
       byte[] document = Files.readAllBytes(Path.of("/contracts/agreement.pdf"));
       SignResult result = sdk.sign("legal-ml-dsa-key", document);
      
       // The JWS token contains the signature and can be verified later
       String jwsToken = result.getJwsToken();
      
       // Verify later
       VerifySignatureResult verified = sdk.verifySignature(jwsToken);
       if (verified.isValid()) {
           System.out.println("Signature verified using key: " + verified.getKeyRequested());
       }
       
      Parameters:
      kid - key identifier referencing a signing key stored in ANKASecure©; must not be null
      data - the raw bytes to sign; must not be null
      Returns:
      signature result containing the JWS token and operation metadata; never null
      Throws:
      AnkaSecureSdkException - if the key identified by kid does not exist, does not support signing, the authenticated client lacks permission, or the server returns an error
      NullPointerException - if kid or data is null
      Since:
      3.0.0
      See Also:
    • sign

      public SignResult sign(String kid, byte[] data, JwsSerialization serialization) throws AnkaSecureSdkException
      Signs raw bytes, explicitly selecting the JWS serialization (PRD §60).

      Ergonomic overload of sign(String, byte[]). Pass JwsSerialization.COMPACT or JwsSerialization.JSON to request that representation; passing null is equivalent to sign(String, byte[]) (the server chooses the standards-correct representation). This axis never adds or drops a qualified timestamp.

      Parameters:
      kid - key identifier referencing a signing key; must not be null
      data - the raw bytes to sign; must not be null
      serialization - requested serialization, or null to let the server choose
      Returns:
      signature result; never null
      Throws:
      AnkaSecureSdkException - on error, including HTTP 400 when COMPACT is requested but the deployment mandates a qualified timestamp
      NullPointerException - if kid or data is null
      Since:
      3.0.0
      See Also:
    • signFileCompact

      public SignResult signFileCompact(String kid, Path input, Path output) throws AnkaSecureSdkException
      Signs a file with forced compact format. Use when you need a single-line string for API/JSON transmission. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: JSON responses, HTTP headers, query parameters
      • Storage: Single database field (text/varchar)
      • Interoperability: JWS standard format (RFC 7515)

      Output Format

       eyJhbGc...header...XVCJ9.eyJkYXRh...payload...9.dBjftJ...signature...
       

      Single-line base64url string with embedded payload, suitable for text-based transmission.

      Alternative Methods

      Example

      
       // Sign small document for API transmission
       SignResult result = sdk.signFileCompact(
           "my-ml-dsa-key",
           Path.of("contract.pdf"),
           Path.of("contract.jws")
       );
      
       System.out.println("Algorithm: " + result.getAlgorithmUsed());
       
      Parameters:
      kid - key identifier for signing; must not be null
      input - input file (up to the server's configured compact limit, default 4 MB); must not be null
      output - output file (.jws recommended); must not be null
      Returns:
      signature result with metadata; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      See Also:
    • signFileCompact

      public SignResult signFileCompact(String kid, Path input, Path output, JwsSerialization serialization) throws AnkaSecureSdkException
      Signs a file (attached), explicitly selecting the JWS serialization (PRD §60).

      Ergonomic overload of signFileCompact(String, Path, Path). Passing null defers to the server. When the deployment mandates a qualified timestamp, the written output is a JWS-JSON artifact carrying the JAdES sigTst (it is never collapsed to a compact string that would drop the stamp).

      Parameters:
      kid - key identifier for signing; must not be null
      input - input file (max 5MB); must not be null
      output - output file; must not be null
      serialization - requested serialization, or null to let the server choose
      Returns:
      signature result; never null
      Throws:
      AnkaSecureSdkException - on error, including HTTP 400 when COMPACT is requested but the deployment mandates a qualified timestamp
      Since:
      3.0.0
      See Also:
    • signFile

      public SignResult signFile(String kid, Path input, Path output) throws AnkaSecureSdkException, IOException
      Signs a file. Recommended for most use cases. Automatically selects optimal format based on file size (<5MB = compact, ≥5MB = streaming).

      This method automatically selects the optimal format:

      • Files below the server's maxPlaintextBytes: Compact JWS format (single-line string)
      • Files at or above that limit: Streaming JWS format (detached signature)

      Why Use This Method?

      You don't need to worry about file sizes or server limits. The threshold is discovered from the server (not hardcoded) and cached briefly; if a compact request is still rejected as too large (HTTP 413) the SDK transparently retries via streaming. This method handles everything automatically, ensuring your signing operation always succeeds regardless of file size.

      When to Use Explicit Formats

      Use signFileCompact(String, Path, Path) or signFileStream(String, Path, Path) only when you specifically need a particular output format (e.g., compact JWS for API transmission).

      Example

      
       // Sign any file - works for both small and large files
       SignResult result = sdk.signFile(
           "my-ml-dsa-key",
           Path.of("document.pdf"),
           Path.of("document.jws")
       );
      
       System.out.println("Signed with: " + result.getAlgorithmUsed());
       
      Parameters:
      kid - key identifier for signing; must not be null
      input - input file (any size); must not be null
      output - output file (.jws or .jwst); must not be null
      Returns:
      signature result with metadata; never null

      By default fails if output exists; call withOverwrite() to overwrite.

      Throws:
      AnkaSecureSdkException - if operation fails
      IOException - if file cannot be accessed
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default)
      See Also:
    • signFileStream

      public SignResult signFileStream(String kid, Path input, Path output) throws AnkaSecureSdkException
      Signs a file with forced streaming format. Use for large files (100MB+), low-memory environments, or when binary output is acceptable.

      When to Use Streaming Format

      • Large files: Files above the server's configured compact limit (required), optimal for 100MB+ (recommended)
      • Memory constraints: Low-memory environments or embedded systems
      • Binary output: When detached signature format is acceptable
      • Progressive processing: Start processing before full upload

      Output Format

      Detached JWS signature (.jwst file) where payload is stored separately. Suitable for large files where embedding the payload in the signature would be impractical.

      Alternative Methods

      Example

      
       // Sign large archive file
       SignResult result = sdk.signFileStream(
           "my-ml-dsa-key",
           Path.of("backup.tar.gz"),
           Path.of("backup.tar.gz.jwst")
       );
       
      Parameters:
      kid - key identifier for signing; must not be null
      input - input file (any size, up to 100MB recommended); must not be null
      output - output file (.jwst recommended); must not be null
      Returns:
      signature result with metadata; never null
      Throws:
      AnkaSecureSdkException - if operation fails
      See Also:
    • verifySignature

      public VerifySignatureResult verifySignature(Path jwsFile) throws AnkaSecureSdkException
      Verifies a JWS signature from a file, accepting either the compact form or the JWS-JSON form (serialization-agnostic; PRD §60).

      The file may contain an RFC 7515 §7.1 compact string OR an RFC 7515 §7.2 JWS-JSON object (which MAY carry a JAdES qualified timestamp in its unprotected header). The correct representation is detected automatically. Fails if the file is ≥5MB (server limit).

      Alternative Methods

      Note: No auto-detection method available because streaming verification requires separate data and signature file parameters.

      Example

      
       // Verify a compact or JWS-JSON signature file
       VerifySignatureResult result = sdk.verifySignature(
           Path.of("document.jws")
       );
      
       if (result.isValid()) {
           System.out.println("Signature valid, signed by: " + result.getKeyRequested());
       }
       // Informational: never affects isValid()
       if (result.getQualifiedTimestamp() != null) {
           System.out.println("Qualified timestamp trust: "
                   + result.getQualifiedTimestamp().getTrustStatus());
       }
       
      Parameters:
      jwsFile - compact or JWS-JSON signature file; must not be null
      Returns:
      verification result; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      Since:
      3.0.0
      See Also:
    • verifySignature

      public VerifySignatureResult verifySignature(String jws) throws AnkaSecureSdkException
      Verifies a compact JWS (RFC 7515) signature.

      The JWS token is self-contained and includes all metadata required for verification (algorithm, signature, protected header). The server resolves the verification key based on the kid header in the JWS.

      Example

      
       String jwsToken = "eyJhbGciOiJNTC1EU0EtNjUiLCJraWQiOiJteS1rZXkifQ...";
       VerifySignatureResult result = sdk.verifySignature(jwsToken);
      
       if (result.isValid()) {
           System.out.println("Signature verified");
           System.out.println("Signed by key: " + result.getKeyRequested());
       } else {
           System.out.println("Signature verification failed");
       }
       
      Parameters:
      jws - the compact JWS string to verify; must not be null
      Returns:
      verification result containing validity status and metadata; never null
      Throws:
      AnkaSecureSdkException - if the JWS is malformed, the verification key is not accessible, or the server returns an error
      NullPointerException - if jws is null
      Since:
      3.0.0
      See Also:
    • verifySignatureStream

      public VerifySignatureResult verifySignatureStream(Path dataFile, Path signatureFile) throws AnkaSecureSdkException
      Verifies a detached JWS signature with forced streaming format. Use for large files (100MB+), low-memory environments, or when binary output is acceptable.

      When to Use Streaming Format

      • Large files: Files above the server's configured compact limit (required), optimal for 100MB+ (recommended)
      • Memory constraints: Low-memory environments or embedded systems
      • Binary output: When verifying detached JWS signature format (.jwst files)
      • Progressive processing: Start verification before full upload

      Input Format

      Detached JWS signature (.jwst file) + separate data file. Typically created by signFileStream(String, Path, Path).

      Alternative Methods

      Note: No auto-detection method available because streaming verification requires separate data and signature file parameters.

      Example

      
       // Verify detached signature for large file
       VerifySignatureResult result = sdk.verifySignatureStream(
           Path.of("backup.tar.gz"),        // Original data
           Path.of("backup.tar.gz.jwst")    // Detached signature
       );
      
       if (result.isValid()) {
           System.out.println("Signature valid!");
       }
       
      Parameters:
      dataFile - original data file (any size, up to 100MB recommended); must not be null
      signatureFile - detached JWS signature file; must not be null
      Returns:
      verification result; never null
      Throws:
      AnkaSecureSdkException - if operation fails
      See Also:
    • listKeys

      public KeyPage listKeys(KeyFilters filters, int page, int size) throws AnkaSecureSdkException
      Lists ONE PAGE of the keys accessible to the authenticated client.

      Filters apply server-side BEFORE pagination, so the returned totals are filtered, tenant-scoped counts. Use listAllKeys(KeyFilters) when the complete set is needed (client-side auto-pagination).

      Example

      
       KeyPage page = sdk.listKeys(new KeyFilters(null, null, "ML-KEM", "ACTIVE"), 0, 50);
       System.out.println("Page 1/" + page.totalPages() + " - " + page.totalElements() + " keys");
       page.content().forEach(key ->
           System.out.println(key.getKid() + " (" + key.getAlg() + ")")
       );
       
      Parameters:
      filters - server-side filters; KeyFilters.none() for all keys
      page - zero-based page index
      size - page size (1..200; values above 200 are rejected by the server with 400)
      Returns:
      the page of key metadata with totals; never null
      Throws:
      AnkaSecureSdkException - if the server returns an error
      Since:
      3.0.0
      See Also:
    • listAllKeys

      public List<KeyMetadata> listAllKeys(KeyFilters filters) throws AnkaSecureSdkException
      Lists ALL keys matching the filters by auto-paginating through the server's paginated listing (the AWS-CLI pattern: the API pages, the client iterates).

      Bounded by the first page's totalPages snapshot with a defensive hard cap; exceeding it THROWS PaginationCeilingExceededException rather than silently truncating. Offset-pagination drift between page fetches is accepted best-effort.

      Example

      
       List<KeyMetadata> allKeys = sdk.listAllKeys(KeyFilters.none());
       List<KeyMetadata> activeRsa =
           sdk.listAllKeys(new KeyFilters(null, null, "RSA", "ACTIVE"));
       
      Parameters:
      filters - server-side filters; KeyFilters.none() for all keys
      Returns:
      all matching key metadata; never null, may be empty
      Throws:
      AnkaSecureSdkException - if the server returns an error
      Since:
      3.0.0
      See Also:
    • getKeyMetadata

      public KeyMetadata getKeyMetadata(String kid) throws AnkaSecureSdkException
      Retrieves metadata for a specific key.

      Example

      
       KeyMetadata key = sdk.getKeyMetadata("my-ml-kem-768-key");
      
       System.out.println("Key: " + key.getKid());
       System.out.println("Algorithm: " + key.getAlg());
       System.out.println("Status: " + key.getStatus());
       System.out.println("Created: " + key.getCreatedAt());
       
      Parameters:
      kid - key identifier; must not be null
      Returns:
      key metadata; never null
      Throws:
      AnkaSecureSdkException - if the key does not exist, the authenticated client lacks permission, or the server returns an error
      NullPointerException - if kid is null
      Since:
      3.0.0
      See Also:
    • reencrypt

      public ReencryptResult reencrypt(String newKid, String jweToken) throws AnkaSecureSdkException
      Re-encrypts data with a different key without decrypting to plaintext.

      Decrypts the JWE using the original key, then immediately re-encrypts with the new key. The plaintext never leaves the server, providing better security than decrypt + encrypt in the client.

      This method is suitable for in-memory JWE tokens up to ~100 MB. For larger files, use reencryptFileStream(String, String, Path, Path).

      Example

      
       // Original encryption
       EncryptResult encrypted = sdk.encrypt("old-aes-key", data);
       String jweToken = encrypted.getJweToken();
      
       // Migrate to post-quantum encryption
       ReencryptResult reencrypted = sdk.reencrypt("new-ml-kem-key", jweToken);
      
       System.out.println("Migrated from: " + reencrypted.getOldKeyAlgorithmUsed());
       System.out.println("Migrated to: " + reencrypted.getNewKeyAlgorithmUsed());
       
      Parameters:
      newKid - identifier of the new encryption key; must not be null
      jweToken - the JWE token to re-encrypt; must not be null
      Returns:
      re-encryption result containing new JWE and migration metadata; never null
      Throws:
      AnkaSecureSdkException - if either key does not exist, the JWE is malformed, the authenticated client lacks permission, or the server returns an error
      NullPointerException - if newKid or jweToken is null
      Since:
      3.0.0
      See Also:
    • reencryptFileCompact

      public ReencryptResult reencryptFileCompact(String newKid, Path input, Path output) throws AnkaSecureSdkException
      Re-encrypts a file with forced compact format. Use when you need a single-line string for API/JSON transmission. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: Re-encrypting for JSON responses or HTTP headers
      • Storage: Re-encrypting for single database field (text/varchar)
      • Interoperability: JWE standard format (RFC 7516)

      Alternative Methods

      Example

      
       // Re-encrypt small file with new key
       ReencryptResult result = sdk.reencryptFileCompact(
           "new-ml-kem-key",
           Path.of("old-data.jwe"),
           Path.of("new-data.jwe")
       );
      
       System.out.println("Migrated from: " + result.getOldKeyAlgorithmUsed());
       System.out.println("Migrated to: " + result.getNewKeyAlgorithmUsed());
       
      Parameters:
      newKid - identifier of the new encryption key; must not be null
      input - compact JWE file (up to the server's configured compact limit, default 4 MB); must not be null
      output - output file (.jwe recommended); must not be null
      Returns:
      re-encryption result with migration metadata; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      See Also:
    • reencryptFile

      public ReencryptResult reencryptFile(String newKid, Path input, Path output) throws AnkaSecureSdkException, IOException
      Re-encrypts a file. Recommended for most use cases. Automatically selects the re-encryption path from the input envelope's actual format, not its size.

      This method inspects the input file's structure and routes accordingly:

      Why Format, Not Size?

      The input here is an envelope, not plaintext. A compact JWE is roughly twice the size of the plaintext it wraps, so its size may exceed the server's compact plaintext ceiling even though it is a compact envelope that must be re-encrypted via the compact path. Selecting by format guarantees that any file produced by encryptFile/encryptFileCompact is always re-encryptable, and that a genuinely detached/streaming artifact is always re-encrypted via streaming.

      When to Use Explicit Formats

      Use reencryptFileCompact(String, Path, Path) or reencryptFileStream(String, String, Path, Path) only when you specifically need to force a particular format.

      Example

      
       // Re-encrypt any file - works for both small and large files
       ReencryptResult result = sdk.reencryptFile(
           "new-ml-kem-key",
           Path.of("old-data.jwe"),
           Path.of("new-data.jwe")
       );
      
       System.out.println("Migrated from: " + result.getOldKeyAlgorithmUsed());
       System.out.println("Migrated to: " + result.getNewKeyAlgorithmUsed());
       
      Parameters:
      newKid - identifier of the new encryption key; must not be null
      input - input JWE file (any size); must not be null
      output - output file (.jwe or .jwet); must not be null
      Returns:
      re-encryption result with migration metadata; never null

      By default fails if output exists; call withOverwrite() to overwrite.

      Throws:
      AnkaSecureSdkException - if operation fails
      IOException - if file cannot be accessed
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default)
      See Also:
    • reencryptFileStream

      public ReencryptResult reencryptFileStream(String newKid, String sourceKidOverride, Path input, Path output) throws AnkaSecureSdkException
      Re-encrypts a file with forced streaming format. Use for large files (100MB+), low-memory environments, or when binary output is acceptable.

      When to Use Streaming Format

      • Large files: Files above the server's configured compact limit (required), optimal for 100MB+ (recommended)
      • Memory constraints: Low-memory environments or embedded systems
      • Binary output: When re-encrypting to streaming JWE format (.jwet files)
      • Progressive processing: Start processing before full upload
      • Key rotation: Migrating archived data to new encryption keys

      Alternative Methods

      Example

      
       // Re-encrypt large file with new key
       ReencryptResult result = sdk.reencryptFileStream(
           "new-ml-kem-key",
           null,  // Auto-detect old key from JWE header
           Path.of("backup.jwet"),
           Path.of("backup-new.jwet")
       );
       
      Parameters:
      newKid - identifier of the new encryption key; must not be null
      sourceKidOverride - identifier of the old key; null to auto-detect from JWE
      input - streaming JWE file (any size, up to 100MB recommended); must not be null
      output - output file (.jwet recommended); must not be null
      Returns:
      re-encryption result with migration metadata; never null
      Throws:
      AnkaSecureSdkException - if operation fails
      See Also:
    • resign

      public ResignResult resign(String newKid, String jws) throws AnkaSecureSdkException
      Re-signs data with a different key without verifying the original signature.

      Strips the old signature and creates a new JWS with the new signing key. This is useful for key rotation or migrating signatures to post-quantum algorithms.

      This method is suitable for in-memory JWS tokens up to ~100 MB. For larger files, use resignFileStream(Path, String, Path, Path).

      Example

      
       // Original signature
       SignResult signed = sdk.sign("old-ecdsa-key", document);
       String jwsToken = signed.getJwsToken();
      
       // Migrate to post-quantum signature
       ResignResult resigned = sdk.resign("new-ml-dsa-key", jwsToken);
      
       System.out.println("Migrated from: " + resigned.getOldKeyAlgorithmUsed());
       System.out.println("Migrated to: " + resigned.getNewKeyAlgorithmUsed());
       
      Parameters:
      newKid - identifier of the new signing key; must not be null
      jws - the JWS token to re-sign; must not be null
      Returns:
      re-sign result containing new JWS and migration metadata; never null
      Throws:
      AnkaSecureSdkException - if either key does not exist, the JWS is malformed, the authenticated client lacks permission, or the server returns an error
      NullPointerException - if newKid or jws is null
      Since:
      3.0.0
      See Also:
    • resign

      public ResignResult resign(String newKid, String jws, JwsSerialization serialization) throws AnkaSecureSdkException
      Re-signs a JWS, explicitly selecting the JWS serialization (PRD §60).

      Ergonomic overload of resign(String, String). Passing null defers to the server. The input JWS may be compact or JWS-JSON.

      Parameters:
      newKid - identifier of the new signing key; must not be null
      jws - the JWS to re-sign (compact or JWS-JSON); must not be null
      serialization - requested serialization, or null to let the server choose
      Returns:
      re-sign result with migration metadata; never null
      Throws:
      AnkaSecureSdkException - on error, including HTTP 400 when COMPACT is requested but the deployment mandates a qualified timestamp
      NullPointerException - if newKid or jws is null
      Since:
      3.0.0
      See Also:
    • resignFileCompact

      public ResignResult resignFileCompact(String newKid, Path input, Path output) throws AnkaSecureSdkException
      Re-signs a compact JWS file with forced compact format. Use when you need a single-line string for API/JSON transmission. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: Re-signing for JSON responses or HTTP headers
      • Storage: Re-signing for single database field (text/varchar)
      • Interoperability: JWS standard format (RFC 7515)

      Alternative Methods

      Note: No auto-detection method available because streaming re-signing requires separate data and signature file parameters.

      Example

      
       // Re-sign compact JWS with new key
       ResignResult result = sdk.resignFileCompact(
           "new-ml-dsa-key",
           Path.of("old-signature.jws"),
           Path.of("new-signature.jws")
       );
      
       System.out.println("Migrated from: " + result.getOldKeyAlgorithmUsed());
       System.out.println("Migrated to: " + result.getNewKeyAlgorithmUsed());
       
      Parameters:
      newKid - identifier of the new signing key; must not be null
      input - compact JWS file (up to the server's configured compact limit, default 4 MB); must not be null
      output - output file (.jws recommended); must not be null
      Returns:
      re-sign result with migration metadata; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      See Also:
    • resignFileCompact

      public ResignResult resignFileCompact(String newKid, Path input, Path output, JwsSerialization serialization) throws AnkaSecureSdkException
      Re-signs a JWS file (attached), explicitly selecting the JWS serialization (PRD §60).

      Ergonomic overload of resignFileCompact(String, Path, Path). Passing null defers to the server. When the deployment mandates a qualified timestamp, the written output is a JWS-JSON artifact carrying the JAdES sigTst.

      Parameters:
      newKid - identifier of the new signing key; must not be null
      input - input JWS file (compact or JWS-JSON, max 5MB); must not be null
      output - output file; must not be null
      serialization - requested serialization, or null to let the server choose
      Returns:
      re-sign result with migration metadata; never null
      Throws:
      AnkaSecureSdkException - on error, including HTTP 400 when COMPACT is requested but the deployment mandates a qualified timestamp
      Since:
      3.0.0
      See Also:
    • resignFileStream

      public ResignResult resignFileStream(Path oldJwsHeader, String newKid, Path input, Path output) throws AnkaSecureSdkException
      Re-signs a detached JWS signature with forced streaming format. Use for large files (100MB+), low-memory environments, or when binary output is acceptable.

      When to Use Streaming Format

      • Large files: Files above the server's configured compact limit (required), optimal for 100MB+ (recommended)
      • Memory constraints: Low-memory environments or embedded systems
      • Binary output: When re-signing detached JWS signature format (.jwst files)
      • Progressive processing: Start processing before full upload
      • Key rotation: Migrating archived signatures to new signing keys

      Input Format

      Detached JWS signature (.jwst file) + separate data file. Typically created by signFileStream(String, Path, Path).

      Alternative Methods

      Note: No auto-detection method available because streaming re-signing requires separate data and signature file parameters.

      Example

      
       // Re-sign detached signature for large file
       ResignResult result = sdk.resignFileStream(
           Path.of("backup.tar.gz.jwst"),    // Old JWS header
           "new-ml-dsa-key",
           Path.of("backup.tar.gz"),          // Original data
           Path.of("backup-new.tar.gz.jwst") // New signature
       );
       
      Parameters:
      oldJwsHeader - old JWS header file (for metadata extraction); must not be null
      newKid - identifier of the new signing key; must not be null
      input - original data file (any size, up to 100MB recommended); must not be null
      output - output file (.jwst recommended); must not be null
      Returns:
      re-sign result with migration metadata; never null
      Throws:
      AnkaSecureSdkException - if operation fails
      See Also:
    • signThenEncrypt

      public SignEncryptResult signThenEncrypt(String signKid, String encryptKid, byte[] data) throws AnkaSecureSdkException
      Signs then encrypts data in a single operation (nested JWE containing JWS).

      Creates a JWS signature, then encrypts the entire JWS as a JWE. This provides both authenticity (signature) and confidentiality (encryption). The recipient must decrypt first, then verify the signature.

      This method is suitable for in-memory payloads up to ~100 MB. For larger files, use signThenEncryptFileCompact(String, String, Path, Path).

      Example

      
       byte[] sensitiveDoc = Files.readAllBytes(Path.of("/contracts/agreement.pdf"));
      
       SignEncryptResult result = sdk.signThenEncrypt(
           "my-ml-dsa-key",    // signing key
           "my-ml-kem-key",    // encryption key
           sensitiveDoc
       );
      
       // Later: decrypt then verify
       DecryptVerifyResult verified = sdk.decryptThenVerify(result.getJweToken());
       if (verified.isSignatureValid()) {
           System.out.println("Signature valid, data authentic");
       }
       
      Parameters:
      signKid - identifier of the signing key; must not be null
      encryptKid - identifier of the encryption key; must not be null
      data - the raw bytes to sign and encrypt; must not be null
      Returns:
      result containing nested JWE token and operation metadata; never null
      Throws:
      AnkaSecureSdkException - if either key does not exist, the authenticated client lacks permission, or the server returns an error
      NullPointerException - if any parameter is null
      Since:
      3.0.0
      See Also:
    • signThenEncrypt

      public SignEncryptResult signThenEncrypt(String signKid, String encryptKid, byte[] data, JwsSerialization serialization) throws AnkaSecureSdkException
      Signs then encrypts, explicitly selecting the inner-JWS serialization (PRD §60).

      Ergonomic overload of signThenEncrypt(String, String, byte[]). Passing null defers to the server. Governs only the inner JWS representation and never changes the outer JWE.

      Parameters:
      signKid - private key identifier for signing; must not be null
      encryptKid - public key identifier for encryption; must not be null
      data - plaintext bytes to sign and encrypt; must not be null
      serialization - requested inner-JWS serialization, or null to defer
      Returns:
      combined sign-encrypt result; never null
      Throws:
      AnkaSecureSdkException - on error, including HTTP 400 (before any signing or encryption) when COMPACT is requested but the deployment mandates a qualified timestamp
      NullPointerException - if any of signKid, encryptKid, or data is null
      Since:
      3.0.0
      See Also:
    • decryptThenVerify

      public DecryptVerifyResult decryptThenVerify(String jweToken) throws AnkaSecureSdkException
      Decrypts a JWE then verifies the inner JWS signature.

      Reverses signThenEncrypt(String, String, byte[]) by decrypting the outer JWE, then verifying the inner JWS signature. Both operations must succeed for the overall operation to succeed.

      Example

      
       // Received encrypted and signed document
       String jweToken = "eyJhbGciOiJNTC1LRU0tNzY4IiwiZW5jIjoiQTI1NkdDTSJ9...";
      
       DecryptVerifyResult result = sdk.decryptThenVerify(jweToken);
      
       if (result.isSignatureValid()) {
           byte[] authenticData = result.getDecryptedData();
           System.out.println("Decrypted and verified successfully");
       } else {
           System.out.println("Signature verification failed");
       }
       
      Parameters:
      jweToken - the nested JWE token to decrypt and verify; must not be null
      Returns:
      result containing decrypted data and verification status; never null
      Throws:
      AnkaSecureSdkException - if the JWE is malformed, decryption fails, the JWS is malformed, or the server returns an error
      NullPointerException - if jweToken is null
      Since:
      3.0.0
      See Also:
    • signThenEncryptFileCompact

      public SignEncryptResult signThenEncryptFileCompact(String signKid, String encryptKid, Path input, Path output) throws AnkaSecureSdkException
      Signs then encrypts a file with forced compact format. Use when you need a single-line string for API/JSON transmission. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: Signed and encrypted data for JSON responses or HTTP headers
      • Storage: Single database field (text/varchar) with both authenticity and confidentiality
      • Interoperability: Nested JWE(JWS) standard format

      Output Format

       JWE(
         JWS(payload)
       )
       

      Alternative Methods

      Note: No streaming variant available for combined operations. For large files, split into separate sign and encrypt operations.

      Example

      
       // Sign and encrypt sensitive document
       SignEncryptResult result = sdk.signThenEncryptFileCompact(
           "my-ml-dsa-key",   // Signing key
           "my-ml-kem-key",   // Encryption key
           Path.of("contract.pdf"),
           Path.of("contract-secure.jwe")
       );
      
       System.out.println("Signed with: " + result.getSignKeyRequested());
       System.out.println("Encrypted with: " + result.getEncryptKeyRequested());
       
      Parameters:
      signKid - signing key identifier; must not be null
      encryptKid - encryption key identifier; must not be null
      input - input file (up to the server's configured compact limit, default 4 MB); must not be null
      output - output file (.jwe); must not be null
      Returns:
      operation result with metadata; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default); call withOverwrite() to overwrite
    • signThenEncryptFileCompact

      public SignEncryptResult signThenEncryptFileCompact(String signKid, String encryptKid, Path input, Path output, JwsSerialization serialization) throws AnkaSecureSdkException
      Signs a file then encrypts it, explicitly selecting the inner-JWS serialization (PRD §60).

      Ergonomic overload of signThenEncryptFileCompact(String, String, Path, Path). Passing null defers to the server.

      Parameters:
      signKid - private key identifier for signing; must not be null
      encryptKid - public key identifier for encryption; must not be null
      input - input file; must not be null
      output - output file; must not be null
      serialization - requested inner-JWS serialization, or null to defer
      Returns:
      combined sign-encrypt result; never null
      Throws:
      AnkaSecureSdkException - on error, including HTTP 400 (before any signing or encryption) when COMPACT is requested but the deployment mandates a qualified timestamp
      Since:
      3.0.0
      See Also:
    • decryptThenVerifyFileCompact

      public DecryptVerifyResult decryptThenVerifyFileCompact(Path input, Path output) throws AnkaSecureSdkException
      Decrypts and verifies a nested JWE(JWS) file with forced compact format. Use when you need to decrypt and verify a single-line string. Fails if the input exceeds the server's configured compact limit (maxPlaintextBytes, default 4 MB).

      When to Use Compact Format

      • API transmission: Decrypting and verifying from JSON responses or HTTP headers
      • Storage: Decrypting and verifying from single database field (text/varchar)
      • Interoperability: Nested JWE(JWS) standard format

      Alternative Methods

      Note: No streaming variant available for combined operations. For large files, split into separate decrypt and verify operations.

      Example

      
       // Decrypt and verify secured document
       DecryptVerifyResult result = sdk.decryptThenVerifyFileCompact(
           Path.of("contract-secure.jwe"),
           Path.of("contract-recovered.pdf")
       );
      
       if (result.isSignatureValid()) {
           System.out.println("Signature valid!");
           System.out.println("Signed by: " + result.getSigningKey());
       }
       
      Parameters:
      input - nested JWE file (up to the server's configured compact limit, default 4 MB); must not be null
      output - recovered plaintext file; must not be null
      Returns:
      operation result with verification status; never null
      Throws:
      AnkaSecureSdkException - if the input exceeds the server's configured compact limit (HTTP 413) or operation fails
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default); call withOverwrite() to overwrite
    • signThenEncryptFileStream

      public SignEncryptResult signThenEncryptFileStream(String signKid, String encryptKid, Path input, Path output) throws AnkaSecureSdkException
      Signs a file then encrypts it in streaming mode (nested JWE(JWS), bounded memory) — the large-file counterpart of signThenEncryptFileCompact(String, String, Path, Path).

      The payload is signed with signKid (inner JWS) and the resulting JWS is encrypted with encryptKid (outer JWE) in a single incremental pass, so the signature is carried ENCRYPTED. The nested ciphertext artifact is written to output via the SDK's sidecar write contract (quarantine to <output>.part, then atomic promotion). This is a PRODUCER operation — its result is deterministic (complete-or-abort), not verdict-framed. Because the artifact is streamed to disk, SignEncryptResult.getJweToken() is null for this variant; the artifact is the output file.

      Parameters:
      signKid - private key identifier for signing (inner JWS); must not be null
      encryptKid - public key identifier for encryption (outer JWE); must not be null
      input - input file to stream; must not be null
      output - output file for the nested JWE(JWS) artifact; must not be null
      Returns:
      sign-encrypt result with the outer-JWE metadata; never null
      Throws:
      AnkaSecureSdkException - if signing, encryption, or streaming fails
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default); call withOverwrite() to overwrite
      See Also:
    • decryptThenVerifyFileStream

      public DecryptVerifyResult decryptThenVerifyFileStream(Path input, Path output) throws AnkaSecureSdkException
      Decrypts a nested JWE(JWS) file then verifies the inner signature in streaming mode — the large-file counterpart of decryptThenVerifyFileCompact(Path, Path), and the inverse of signThenEncryptFileStream(String, String, Path, Path).

      The server returns a TWO-verdict result carrying BOTH the outer AES-GCM tag outcome and the inner JWS signature outcome. The SDK quarantines the recovered plaintext to <output>.part and promotes it to output ONLY when the overall verdict is VALID (both dimensions passed). An INVALID or absent verdict is FAIL-CLOSED: the sidecar is deleted, no file is written, and a typed StreamIntegrityException is thrown — success is never inferred from a closed socket.

      On success, DecryptVerifyResult.isSignatureValid() is true and DecryptVerifyResult.getQualifiedTimestamp() surfaces the §60 informational timestamp when present (it never affects validity).

      Parameters:
      input - nested JWE(JWS) streaming artifact (the output of signThenEncryptFileStream(String, String, Path, Path)); must not be null
      output - destination for the recovered plaintext; must not be null
      Returns:
      decrypt-verify result with verification status and metadata; never null
      Throws:
      AnkaSecureSdkException - if decryption or I/O fails
      StreamIntegrityException - if the integrity verdict is INVALID or absent
      DestinationExistsException - if output already exists and this instance uses OverwritePolicy.FAIL_IF_EXISTS (the default); call withOverwrite() to overwrite
      See Also:
    • analyzePkcs7

      public co.ankatech.secure.client.model.Pkcs7AnalysisResponse analyzePkcs7(co.ankatech.secure.client.model.Pkcs7AnalysisRequest request) throws AnkaSecureSdkException
      Analyzes a PKCS#7 file to extract metadata and certificate information.

      Parses a PKCS#7/CMS file and returns information about the signing certificates, signature algorithm, and content type without verifying the signature.

      Example

      
       co.ankatech.secure.client.model.Pkcs7AnalysisRequest request =
           new co.ankatech.secure.client.model.Pkcs7AnalysisRequest();
      
       co.ankatech.secure.client.model.Pkcs7AnalysisResponse analysis =
           sdk.analyzePkcs7(request);
      
       System.out.println("Content type: " + analysis.getContentType());
       System.out.println("Certificates: " + analysis.getCertificates().size());
       
      Parameters:
      request - PKCS#7 analysis request; must not be null
      Returns:
      analysis result containing PKCS#7 metadata; never null
      Throws:
      AnkaSecureSdkException - if the file cannot be read, is not valid PKCS#7, or the server returns an error
      NullPointerException - if request is null
      Since:
      3.0.0
    • analyzePkcs7Stream

      public co.ankatech.secure.client.model.Pkcs7AnalysisResponse analyzePkcs7Stream(co.ankatech.secure.client.model.Pkcs7AnalysisStreamRequest metadata, File file) throws AnkaSecureSdkException
      Analyzes a PKCS#7 file using streaming to avoid loading the entire file into memory.

      Example

      
       co.ankatech.secure.client.model.Pkcs7AnalysisStreamRequest metadata =
           new co.ankatech.secure.client.model.Pkcs7AnalysisStreamRequest();
       java.io.File file = new java.io.File("/legacy/large-signed-archive.p7s");
      
       co.ankatech.secure.client.model.Pkcs7AnalysisResponse analysis =
           sdk.analyzePkcs7Stream(metadata, file);
       
      Parameters:
      metadata - PKCS#7 analysis metadata; must not be null
      file - PKCS#7 file to analyze; must not be null
      Returns:
      analysis result containing PKCS#7 metadata; never null
      Throws:
      AnkaSecureSdkException - if the file cannot be read, is not valid PKCS#7, or the server returns an error
      NullPointerException - if any parameter is null
      Since:
      3.0.0
    • convertPkcs7ToJose

      public co.ankatech.secure.client.model.Pkcs7ConversionResponse convertPkcs7ToJose(co.ankatech.secure.client.model.Pkcs7ConversionRequest request) throws AnkaSecureSdkException
      Converts a PKCS#7 signature to JOSE (JWS) format.

      Extracts the signature from a PKCS#7/CMS file and converts it to compact JWS (RFC 7515) format. This enables legacy PKCS#7 signatures to be verified using modern JOSE tooling.

      Example

      
       co.ankatech.secure.client.model.Pkcs7ConversionRequest request =
           new co.ankatech.secure.client.model.Pkcs7ConversionRequest();
      
       co.ankatech.secure.client.model.Pkcs7ConversionResponse conversion =
           sdk.convertPkcs7ToJose(request);
      
       String jws = conversion.getJws();
       System.out.println("Converted to JWS: " + jws);
       
      Parameters:
      request - PKCS#7 conversion request; must not be null
      Returns:
      conversion result containing JWS token; never null
      Throws:
      AnkaSecureSdkException - if the file cannot be read, is not valid PKCS#7, conversion fails, or the server returns an error
      NullPointerException - if request is null
      Since:
      3.0.0
    • convertPkcs7ToJoseStream

      public File convertPkcs7ToJoseStream(co.ankatech.secure.client.model.Pkcs7ConversionStreamRequest metadata, File inputFile, File outputFile) throws AnkaSecureSdkException
      Converts a PKCS#7 signature to JOSE using streaming.

      Example

      
       co.ankatech.secure.client.model.Pkcs7ConversionStreamRequest metadata =
           new co.ankatech.secure.client.model.Pkcs7ConversionStreamRequest();
       java.io.File inputFile = new java.io.File("/legacy/large-signed-archive.p7s");
       java.io.File outputFile = new java.io.File("/converted/signature.jws");
      
       java.io.File result = sdk.convertPkcs7ToJoseStream(metadata, inputFile, outputFile);
       
      Parameters:
      metadata - conversion metadata; must not be null
      inputFile - path to PKCS#7 file; must not be null and file must exist
      outputFile - path where JWS will be written; must not be null
      Returns:
      the output file; never null
      Throws:
      AnkaSecureSdkException - if the file cannot be read, is not valid PKCS#7, conversion fails, or the server returns an error
      NullPointerException - if any parameter is null
      Since:
      3.0.0
    • performMigrationWorkflow

      public MigrationWorkflowResult performMigrationWorkflow(File pkcs7File, co.ankatech.secure.client.model.Pkcs7ConversionStreamRequest conversionRequest, File outputFile) throws AnkaSecureSdkException
      Performs a complete PKCS#7-to-JOSE migration workflow with analysis and conversion.

      Analyzes a PKCS#7 file, converts it to JOSE format, and writes the result. This is a complete end-to-end workflow for migrating legacy PKCS#7 signatures to modern JOSE format.

      Example

      
       java.io.File pkcs7 = new java.io.File("/legacy/signed.p7s");
       java.io.File output = new java.io.File("/converted/signed.jws");
      
       co.ankatech.secure.client.model.Pkcs7ConversionStreamRequest request =
           new co.ankatech.secure.client.model.Pkcs7ConversionStreamRequest();
      
       MigrationWorkflowResult result = sdk.performMigrationWorkflow(pkcs7, request, output);
      
       System.out.println("Migration complete");
       
      Parameters:
      pkcs7File - the PKCS#7 file to convert; must not be null
      conversionRequest - the conversion parameters; must not be null
      outputFile - where to write the JOSE output; must not be null
      Returns:
      migration result with details; never null
      Throws:
      AnkaSecureSdkException - if the workflow fails or the server returns an error
      NullPointerException - if any parameter is null
      Since:
      3.0.0
      See Also:
    • encryptFileUtilityStream

      public EncryptResult encryptFileUtilityStream(String kty, String alg, String publicKeyB64, Path input, Path output) throws AnkaSecureSdkException
      Encrypts a file using a provided public key (without requiring key in ANKASecure).

      This utility method allows encryption using an externally-managed public key, bypassing the normal key management workflow. Useful for one-off encryptions or interoperability with external systems.

      Example

      
       String publicKeyBase64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...";
      
       EncryptResult result = sdk.encryptFileUtilityStream(
           "RSA",
           "RSA-OAEP",
           publicKeyBase64,
           Path.of("/data/document.pdf"),
           Path.of("/data/document.pdf.jwe")
       );
       
      Parameters:
      kty - key type (e.g., "RSA", "EC", "ML-KEM"); must not be null
      alg - algorithm (e.g., "RSA-OAEP", "ML-KEM-768"); must not be null
      publicKeyB64 - base64-encoded public key; must not be null
      input - path to plaintext file; must not be null
      output - path where JWE will be written; must not be null
      Returns:
      encryption result containing operation metadata; never null
      Throws:
      AnkaSecureSdkException - if the key is invalid, algorithm is unsupported, files cannot be read/written, or the server returns an error
      NullPointerException - if any parameter is null
      Since:
      3.0.0
    • verifySignatureUtilityStream

      public VerifySignatureResult verifySignatureUtilityStream(String kty, String alg, String publicKeyB64, String signatureB64, Path dataFile) throws AnkaSecureSdkException
      Verifies a signature using a provided public key (without requiring key in ANKASecure).

      This utility method allows signature verification using an externally-managed public key, bypassing the normal key management workflow. Useful for verifying signatures from external systems.

      Example

      
       String publicKeyBase64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...";
       String signatureBase64 = "...";
      
       VerifySignatureResult result = sdk.verifySignatureUtilityStream(
           "RSA",
           "RS256",
           publicKeyBase64,
           signatureBase64,
           Path.of("/data/document.pdf")
       );
      
       if (result.isValid()) {
           System.out.println("Signature verified");
       }
       
      Parameters:
      kty - key type (e.g., "RSA", "EC", "ML-DSA"); must not be null
      alg - algorithm (e.g., "RS256", "ML-DSA-65"); must not be null
      publicKeyB64 - base64-encoded public key; must not be null
      signatureB64 - base64-encoded signature; must not be null
      dataFile - path to original data file; must not be null
      Returns:
      verification result containing validity status; never null
      Throws:
      AnkaSecureSdkException - if the key is invalid, algorithm is unsupported, files cannot be read, or the server returns an error
      NullPointerException - if any parameter is null
      Since:
      3.0.0
    • getSupportedAlgorithms

      public List<AlgorithmInfo> getSupportedAlgorithms() throws AnkaSecureSdkException
      Retrieves the complete list of cryptographic algorithms supported by the server.

      Returns all algorithms available for key generation, encryption, signing, and other cryptographic operations. Each AlgorithmInfo includes detailed metadata about the algorithm's characteristics, security level, and supported operations.

      Example

      
       List<AlgorithmInfo> allAlgorithms = sdk.getSupportedAlgorithms();
      
       // Find all post-quantum algorithms
       allAlgorithms.stream()
           .filter(alg -> alg.getCategory().equals("POST_QUANTUM"))
           .forEach(alg ->
               System.out.println(alg.getKty() + " / " + alg.getAlg() +
                   " (Level " + alg.getSecurityLevel() + ")")
           );
       
      Returns:
      list of all supported algorithm information; never null, never empty
      Throws:
      AnkaSecureSdkException - if the server returns an error or the request fails
      Since:
      3.0.0
      See Also:
    • getSupportedAlgorithms

      public List<AlgorithmInfo> getSupportedAlgorithms(AlgorithmFilter filter) throws AnkaSecureSdkException
      Retrieves cryptographic algorithms matching the specified filter criteria.

      Filters the algorithm catalog by category (classical, post-quantum, hybrid), key operations (encrypt, sign, etc.), security level, and other characteristics. Multiple filter criteria are combined with AND logic.

      Example

      
       // Find all NIST Level 5 post-quantum signing algorithms
       AlgorithmFilter filter = new AlgorithmFilter()
           .setCategory("POST_QUANTUM")
           .setKeyOps(List.of("sign", "verify"))
           .setNistSecurityLevel(5);
      
       List<AlgorithmInfo> pqcSigning = sdk.getSupportedAlgorithms(filter);
      
       pqcSigning.forEach(alg ->
           System.out.println(alg.getKty() + " / " + alg.getAlg() +
               " (Level " + alg.getSecurityLevel() + ")")
       );
      
       // Use the first result to drive a data-plane operation
       if (!pqcSigning.isEmpty()) {
           AlgorithmInfo chosen = pqcSigning.get(0);
           System.out.println("Selected algorithm: " + chosen.getKty() + " / " + chosen.getAlg());
       }
       
      Parameters:
      filter - the algorithm filter specifying criteria such as category, key operations, and security level; must not be null
      Returns:
      list of algorithm information matching all specified filter criteria; never null, may be empty if no algorithms match
      Throws:
      AnkaSecureSdkException - if the server returns an error or the request fails
      NullPointerException - if filter is null
      Since:
      3.0.0
      See Also: