Class AnkaSecureSdk

java.lang.Object
co.ankatech.ankasecure.sdk.AnkaSecureSdk

public class AnkaSecureSdk extends Object
Factory for creating authenticated SDK instances.

AnkaSecureSdk serves as the entry point for the ANKASecure© SDK. It handles authentication and returns immutable AuthenticatedSdk instances that provide all cryptographic and key management operations.

Architecture

The SDK separates concerns:

  • AnkaSecureSdk (Factory) - Handles authentication only
  • AuthenticatedSdk (Immutable) - Performs all operations
This design enables:
  • Thread-safe concurrent access (10,000+ threads)
  • Clear token lifecycle management
  • Multiple independent sessions from one factory

Basic Usage


 // 1. Load configuration
 Properties props = new Properties();
 try (var in = new FileInputStream("cli.properties")) {
     props.load(in);
 }

 // 2. Create factory (reusable)
 AnkaSecureSdk factory = new AnkaSecureSdk(props);

 // 3. Authenticate and get SDK instance (the secret is held in a zeroizable wrapper)
 // A secret read from an environment variable leaves a String copy that cannot be
 // cleared; Console.readPassword() returns a char[] and creates none.
 SecretChars clientSecret = new SecretChars(System.getenv("ANKASECURE_CLIENT_SECRET").toCharArray());
 AuthenticatedSdk sdk = factory.authenticateApplication("my-client-id", clientSecret);

 // 4. Perform operations
 EncryptResult result = sdk.encrypt("my-key", data);
 

Thread-Safe Concurrent Usage


 AnkaSecureSdk factory = new AnkaSecureSdk(props);
 AuthenticatedSdk sdk = factory.authenticateApplication("my-client-id", clientSecret);

 // Share SDK across thousands of threads safely
 try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
     List<CompletableFuture<EncryptResult>> futures = dataList.stream()
         .map(data -> CompletableFuture.supplyAsync(
             () -> sdk.encrypt("key", data),
             executor
         ))
         .toList();

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

Multiple Sessions


 AnkaSecureSdk factory = new AnkaSecureSdk(props);

 // Different authenticated sessions from same factory
 AuthenticatedSdk appSdk = factory.authenticateApplication("my-client-id", clientSecret);
 SecretChars password = new SecretChars(System.console().readPassword("Password: "));
 AuthenticatedSdk userSdk = factory.authenticateUser("alice@example.com", password, "my-tenant-id");

 // Each operates independently
 appSdk.encrypt("key1", data1);
 userSdk.listAllKeys(KeyFilters.none());
 

Token Lifecycle Management


 AuthenticatedSdk sdk = factory.authenticateApplication("my-client-id", clientSecret);

 // Check expiry before long-running operations
 if (sdk.isTokenExpiredLocally()) {
     sdk = factory.authenticateApplication("my-client-id", clientSecret);
 }

 processBatch(sdk, largeDataset);
 

Configuration

The SDK is configured via cli.properties file. Key settings for high-concurrency scenarios:

 # Connection pool (optimized for 10,000+ concurrent threads)
 okhttp.pool.maxIdleConnections=1000
 okhttp.pool.keepAliveDurationMinutes=5

 # Dispatcher limits
 okhttp.dispatcher.maxRequests=10000
 okhttp.dispatcher.maxRequestsPerHost=5000

 # HTTP/2 support (enabled by default)
 okhttp.http2.enabled=true

 # Rate limiting (0 = disabled)
 okhttp.rateLimitPerSecond=1000
 
See Also:
  • Constructor Details

    • AnkaSecureSdk

      public AnkaSecureSdk(Properties cliProperties)
      Constructs the SDK factory using the provided CLI properties.

      This constructor creates an unauthenticated SDK factory. To obtain an authenticated instance, call authenticateApplication(String, co.ankatech.ankasecure.sdk.security.SecretChars) or authenticateUser(String, co.ankatech.ankasecure.sdk.security.SecretChars, String).

      Typical properties include:

      • openapi.host – API server hostname
      • openapi.port – API server port
      • openapi.connectTimeoutMs – connection timeout in milliseconds
      • okhttp.pool.maxIdleConnections – connection pool size (default: 1000)
      • okhttp.dispatcher.maxRequests – max concurrent requests (default: 10000)
      • okhttp.http2.enabled – enable HTTP/2 (default: true). Setting it to false makes the true-streaming operations refuse rather than downgrade: they send their request body as a duplex stream, which HTTP/1.1 cannot frame, so a downgrade would deadlock instead of degrading. The refusal is typed and is raised before any request byte is written.

        The same applies to the SCHEME: HTTP/2 is not negotiated over an http:// origin without prior knowledge, so a cleartext deployment (openapi.scheme=http) cannot use the streaming operations at all. Use https.

      
       Properties props = new Properties();
       try (var stream = new FileInputStream("cli.properties")) {
           props.load(stream);
       }
       AnkaSecureSdk factory = new AnkaSecureSdk(props);
       // A secret read from an environment variable leaves a String copy that cannot be
       // cleared; Console.readPassword() returns a char[] and creates none.
       SecretChars clientSecret = new SecretChars(System.getenv("ANKASECURE_CLIENT_SECRET").toCharArray());
       AuthenticatedSdk sdk = factory.authenticateApplication("my-client-id", clientSecret);
       
      Parameters:
      cliProperties - properties loaded from your cli.properties file; must not be null
      Throws:
      NullPointerException - if cliProperties is null
      InvalidInputException - if pqc.transport.enabled=true and pqc.kem.algorithm is not a supported transport KEM
  • Method Details

    • authenticateApplication

      public AuthenticatedSdk authenticateApplication(String clientId, SecretChars clientSecret) throws AnkaSecureSdkException
      Authenticates as an application and returns an authenticated SDK instance.

      This method performs application authentication via client credentials and returns an immutable AuthenticatedSdk instance bound to the JWT access token. The returned instance is thread-safe and can be shared across thousands of concurrent threads.

      Usage Pattern

      
       AnkaSecureSdk factory = new AnkaSecureSdk(props);
       // A secret read from an environment variable leaves a String copy that cannot be
       // cleared; Console.readPassword() returns a char[] and creates none.
       SecretChars clientSecret = new SecretChars(System.getenv("ANKASECURE_CLIENT_SECRET").toCharArray());
       AuthenticatedSdk sdk = factory.authenticateApplication("my-client-id", clientSecret);
      
       // Use sdk for all operations
       EncryptResult result = sdk.encrypt("my-key", data);
      
       // Share across threads safely
       executor.submit(() -> sdk.encrypt("key1", data1));
       executor.submit(() -> sdk.encrypt("key2", data2));
       

      Token Lifecycle

      The returned instance remains valid until the JWT token expires. Check AuthenticatedSdk.isTokenExpiredLocally() before long-running operations and re-authenticate if needed.

      Parameters:
      clientId - the client identifier issued by the administration console; must not be null
      clientSecret - the client secret issued by the administration console; must not be null
      Returns:
      an authenticated SDK instance with immutable token; never null
      Throws:
      AnkaSecureSdkException - if the credentials are invalid, the server is unreachable, or authentication fails
      NullPointerException - if clientId or clientSecret is null
    • authenticateApplicationWithKeyPair

      public AuthenticatedSdk authenticateApplicationWithKeyPair(String clientId, ClientAssertionSigner signer, String expectedAudience) throws AnkaSecureSdkException
      Authenticates a Cryptographic ACTOR that proves itself with a registered key pair.

      The assertion is signed HERE, by the caller's signer, and only the signature travels. That is the difference this credential kind buys: the platform holds the public half, so it could not present this workload's identity even if it wanted to.

      A fresh assertion is minted on every call, and that is not an optimisation to skip. The server claims each jti exactly once, so re-sending one — for instance after a timeout whose request was actually received — is a replay of the client's own credential, refused, and read by everyone as a server fault.

      Parameters:
      clientId - the actor UUID
      signer - holds the private key; it never leaves the caller's process
      expectedAudience - the audience this deployment returned when the key was registered. Passed rather than derived: a client computing it would be guessing at a value the server owns.
      Throws:
      AnkaSecureSdkException
    • authenticateUser

      public AuthenticatedSdk authenticateUser(String username, SecretChars password, String tenantId) throws AnkaSecureSdkException
      Authenticates as a user and returns an authenticated SDK instance.

      This method performs user authentication via username/password and returns an immutable AuthenticatedSdk instance bound to the JWT access token. Use this in scenarios where user-level context is required instead of application-level authentication.

      Usage Pattern

      
       AnkaSecureSdk factory = new AnkaSecureSdk(props);
       SecretChars password = new SecretChars(System.console().readPassword("Password: "));
       AuthenticatedSdk sdk = factory.authenticateUser("alice@example.com", password, "my-tenant-id");
      
       // Use sdk for all operations with user context
       List<KeyMetadata> keys = sdk.listAllKeys(KeyFilters.none());
       
      Parameters:
      username - the username (email format); must not be null
      password - the user password; must not be null
      tenantId - the tenant identifier; must not be null
      Returns:
      an authenticated SDK instance with immutable token; never null
      Throws:
      AnkaSecureSdkException - if the credentials are invalid, the user does not exist, the tenant does not exist, the server is unreachable, or authentication fails
      NullPointerException - if any parameter is null
    • authenticate

      public AuthenticatedSdk authenticate() throws AnkaSecureSdkException
      Authenticates using whatever credential the deployment configured, without being told which.

      This is the entry point a workload should use, and the reason the SDK can claim that a consumer authenticating through Auth0 - or Entra, Okta, Keycloak, Cognito, or any other OAuth 2.0 / OIDC authorization server - runs the same code as one using an ANKASecure client secret. There is no provider argument and no mechanism argument, because a provider is not something application code should know: it is a property of where the application is deployed, and it changes without the application changing.

      The credential is read from this factory's properties, with the process environment as a fallback, and resolved by TokenSourceChain - whose javadoc records the order and why it is that order. Nothing is discovered from the server: the SDK asks the deployment no questions about which identity provider a tenant uses, because the answer would disclose tenant configuration, would undo a timing control the platform deliberately built, and would let whoever owns that configuration name the endpoint a caller's credential is posted to.

      Returns:
      an authenticated SDK instance; never null
      Throws:
      AnkaSecureSdkException - if nothing is configured, if the configuration is ambiguous or incomplete, or if the platform refuses the credential
    • authenticate

      public AuthenticatedSdk authenticate(AuthSettings settings) throws AnkaSecureSdkException
      Authenticates using a caller-supplied configuration view.

      For callers holding a credential that is not in their properties file - one decrypted at runtime, one handed over by a secret manager - so they can supply what they have without writing it down first, and without this class needing to know where it came from.

      Parameters:
      settings - the configuration to resolve the credential from; must not be null
      Returns:
      an authenticated SDK instance; never null
      Throws:
      AnkaSecureSdkException - if nothing is configured, if the configuration is ambiguous or incomplete, or if the platform refuses the credential
      NullPointerException - if settings is null
    • configuredCredential

      public Optional<TokenSourceChain.Link> configuredCredential() throws AnkaSecureSdkException
      Reports which credential authenticate() would use, without using it.

      Reads no file, starts no process and opens no connection, so an operator can ask what is configured - in a status command, in a health check - and get an answer that costs nothing and changes nothing.

      Returns:
      the link that would answer, or empty when no credential is configured
      Throws:
      AnkaSecureSdkException - if two external token sources are configured at once