diff --git a/src/main/java/ai/nomyo/SecureChatCompletion.java b/src/main/java/ai/nomyo/SecureChatCompletion.java index 71eb57f..5d4e9e1 100644 --- a/src/main/java/ai/nomyo/SecureChatCompletion.java +++ b/src/main/java/ai/nomyo/SecureChatCompletion.java @@ -146,11 +146,24 @@ public class SecureChatCompletion { /** * Convenience variant with no additional parameters. */ - @SuppressWarnings("UnusedReturnValue") public Map create(String model, List> messages) { return create(model, messages, null); } + /** + * Async alias for {@link #create(String, List, Map)}. + */ + public Map acreate(String model, List> messages, Map kwargs) { + return create(model, messages, kwargs); + } + + /** + * Async alias for {@link #create(String, List)}. + */ + public Map acreate(String model, List> messages) { + return create(model, messages); + } + /** * Delegates to {@link SecureCompletionClient#close()}. */ diff --git a/src/main/java/ai/nomyo/SecureCompletionClient.java b/src/main/java/ai/nomyo/SecureCompletionClient.java index 5bead2d..69d2a4e 100644 --- a/src/main/java/ai/nomyo/SecureCompletionClient.java +++ b/src/main/java/ai/nomyo/SecureCompletionClient.java @@ -295,7 +295,6 @@ public class SecureCompletionClient { * @return encrypted bytes (JSON package) * @throws SecurityError if encryption fails or keys not loaded */ - @SuppressWarnings("JavadocDeclaration") public CompletableFuture encryptPayload(Map payload) { return CompletableFuture.supplyAsync(() -> { try { diff --git a/src/main/java/ai/nomyo/SecureMemory.java b/src/main/java/ai/nomyo/SecureMemory.java index 164e011..07e1121 100644 --- a/src/main/java/ai/nomyo/SecureMemory.java +++ b/src/main/java/ai/nomyo/SecureMemory.java @@ -10,7 +10,6 @@ import java.util.Map; /** * Cross-platform memory locking and secure zeroing for sensitive cryptographic buffers. Fails gracefully if unavailable. */ -@SuppressWarnings("SameReturnValue") public final class SecureMemory { @Getter @@ -76,7 +75,6 @@ public final class SecureMemory { /** * Wraps bytes with memory locking and guaranteed zeroing on close. AutoCloseable for try-with-resources. */ - @SuppressWarnings("SameReturnValue") public static class SecureBuffer implements AutoCloseable { private final Arena arena; diff --git a/src/test/java/ai/nomyo/DecryptResponseTest.java b/src/test/java/ai/nomyo/DecryptResponseTest.java index 672df8d..b8360d1 100644 --- a/src/test/java/ai/nomyo/DecryptResponseTest.java +++ b/src/test/java/ai/nomyo/DecryptResponseTest.java @@ -34,6 +34,7 @@ class DecryptResponseTest { void decryptResponse_validPackage_shouldReturnDecryptedMap() throws Exception { SecureCompletionClient client = new SecureCompletionClient(); client.generateKeys(false); + PrivateKey privateKey = client.getPrivateKey(); String plaintext = "{\"content\":\"Hello, world!\",\"role\":\"assistant\"}"; byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8); @@ -100,6 +101,7 @@ class DecryptResponseTest { void decryptResponse_missingProcessedAt_shouldSetNull() throws Exception { SecureCompletionClient client = new SecureCompletionClient(); client.generateKeys(false); + PrivateKey privateKey = client.getPrivateKey(); String plaintext = "{\"response\":\"ok\"}"; byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8); @@ -219,7 +221,17 @@ class DecryptResponseTest { SecureCompletionClient client = new SecureCompletionClient(); client.generateKeys(false); - byte[] encryptedResponse = getJsonResponse("9.9", Constants.HYBRID_ALGORITHM); + JsonObject packageJson = new JsonObject(); + packageJson.addProperty("version", "9.9"); + packageJson.addProperty("algorithm", Constants.HYBRID_ALGORITHM); + packageJson.addProperty("encrypted_aes_key", "dGVzdA=="); + JsonObject encryptedPayload = new JsonObject(); + encryptedPayload.addProperty("ciphertext", "dGVzdA=="); + encryptedPayload.addProperty("nonce", "dGVzdA=="); + encryptedPayload.addProperty("tag", "dGVzdA=="); + packageJson.add("encrypted_payload", encryptedPayload); + + byte[] encryptedResponse = packageJson.toString().getBytes(StandardCharsets.UTF_8); CompletableFuture> future = client.decryptResponse(encryptedResponse, "test-id"); ExecutionException error = assertThrows(ExecutionException.class, future::get); @@ -235,7 +247,17 @@ class DecryptResponseTest { SecureCompletionClient client = new SecureCompletionClient(); client.generateKeys(false); - byte[] encryptedResponse = getJsonResponse(Constants.PROTOCOL_VERSION, "wrong-algorithm"); + JsonObject packageJson = new JsonObject(); + packageJson.addProperty("version", Constants.PROTOCOL_VERSION); + packageJson.addProperty("algorithm", "wrong-algorithm"); + packageJson.addProperty("encrypted_aes_key", "dGVzdA=="); + JsonObject encryptedPayload = new JsonObject(); + encryptedPayload.addProperty("ciphertext", "dGVzdA=="); + encryptedPayload.addProperty("nonce", "dGVzdA=="); + encryptedPayload.addProperty("tag", "dGVzdA=="); + packageJson.add("encrypted_payload", encryptedPayload); + + byte[] encryptedResponse = packageJson.toString().getBytes(StandardCharsets.UTF_8); CompletableFuture> future = client.decryptResponse(encryptedResponse, "test-id"); ExecutionException error = assertThrows(ExecutionException.class, future::get); @@ -244,10 +266,15 @@ class DecryptResponseTest { "Error message should mention unsupported algorithm"); } - private static byte[] getJsonResponse(String protocolVersion, String value) { + @Test + @Execution(ExecutionMode.SAME_THREAD) + @DisplayName("decryptResponse should throw SecurityError when private key not initialized") + void decryptResponse_noPrivateKey_shouldThrowSecurityError() { + SecureCompletionClient client = new SecureCompletionClient(); + JsonObject packageJson = new JsonObject(); - packageJson.addProperty("version", protocolVersion); - packageJson.addProperty("algorithm", value); + packageJson.addProperty("version", Constants.PROTOCOL_VERSION); + packageJson.addProperty("algorithm", Constants.HYBRID_ALGORITHM); packageJson.addProperty("encrypted_aes_key", "dGVzdA=="); JsonObject encryptedPayload = new JsonObject(); encryptedPayload.addProperty("ciphertext", "dGVzdA=="); @@ -255,16 +282,7 @@ class DecryptResponseTest { encryptedPayload.addProperty("tag", "dGVzdA=="); packageJson.add("encrypted_payload", encryptedPayload); - return packageJson.toString().getBytes(StandardCharsets.UTF_8); - } - - @Test - @Execution(ExecutionMode.SAME_THREAD) - @DisplayName("decryptResponse should throw SecurityError when private key not initialized") - void decryptResponse_noPrivateKey_shouldThrowSecurityError() { - SecureCompletionClient client = new SecureCompletionClient(); - - byte[] encryptedResponse = getJsonResponse(Constants.PROTOCOL_VERSION, Constants.HYBRID_ALGORITHM); + byte[] encryptedResponse = packageJson.toString().getBytes(StandardCharsets.UTF_8); CompletableFuture> future = client.decryptResponse(encryptedResponse, "test-id"); ExecutionException error = assertThrows(ExecutionException.class, future::get); diff --git a/src/test/java/ai/nomyo/SecureCompletionClientE2ETest.java b/src/test/java/ai/nomyo/SecureCompletionClientE2ETest.java index 96da2c8..5db1a73 100644 --- a/src/test/java/ai/nomyo/SecureCompletionClientE2ETest.java +++ b/src/test/java/ai/nomyo/SecureCompletionClientE2ETest.java @@ -218,8 +218,8 @@ class SecureCompletionClientE2ETest { void e2e_multipleClients_independentOperations() throws Exception { File dir1 = tempDir.resolve("dir1").toFile(); File dir2 = tempDir.resolve("dir2").toFile(); - if (!dir1.mkdirs()) return; - if (!dir2.mkdirs()) return; + dir1.mkdirs(); + dir2.mkdirs(); // Client 1 SecureCompletionClient client1 = new SecureCompletionClient(); diff --git a/src/test/java/ai/nomyo/SecureMemoryTest.java b/src/test/java/ai/nomyo/SecureMemoryTest.java index 32ee5b1..aff264a 100644 --- a/src/test/java/ai/nomyo/SecureMemoryTest.java +++ b/src/test/java/ai/nomyo/SecureMemoryTest.java @@ -104,12 +104,11 @@ class SecureMemoryTest { @DisplayName("SecureBuffer zero should clear all bytes") void secureBuffer_zero_shouldClearBytes() { byte[] data = new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF}; - try (SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data)) { + SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data); - buffer.zero(); + buffer.zero(); - assertDoesNotThrow(buffer::zero, "Zeroing should not throw"); - } + assertDoesNotThrow(buffer::zero, "Zeroing should not throw"); } @Test @@ -128,31 +127,28 @@ class SecureMemoryTest { @DisplayName("SecureBuffer close should be idempotent") void secureBuffer_close_idempotent() { byte[] data = new byte[]{1, 2, 3}; - try (SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data)) { + SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data); - assertDoesNotThrow(buffer::close, "First close should not throw"); - assertDoesNotThrow(buffer::close, "Second close should not throw"); - } + assertDoesNotThrow(buffer::close, "First close should not throw"); + assertDoesNotThrow(buffer::close, "Second close should not throw"); } @Test @DisplayName("SecureBuffer lock should return false (not supported)") void secureBuffer_lock_shouldReturnFalse() { byte[] data = new byte[]{1, 2, 3}; - try (SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true)) { + SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true); - assertFalse(buffer.lock(), "Lock should return false (not supported)"); - } + assertFalse(buffer.lock(), "Lock should return false (not supported)"); } @Test @DisplayName("SecureBuffer unlock should return false") void secureBuffer_unlock_shouldReturnFalse() { byte[] data = new byte[]{1, 2, 3}; - try (SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true)) { + SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true); - assertFalse(buffer.unlock(), "Unlock should return false"); - } + assertFalse(buffer.unlock(), "Unlock should return false"); } @Test