diff --git a/src/main/java/ai/nomyo/SecureChatCompletion.java b/src/main/java/ai/nomyo/SecureChatCompletion.java index 5d4e9e1..71eb57f 100644 --- a/src/main/java/ai/nomyo/SecureChatCompletion.java +++ b/src/main/java/ai/nomyo/SecureChatCompletion.java @@ -146,24 +146,11 @@ 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 69d2a4e..5bead2d 100644 --- a/src/main/java/ai/nomyo/SecureCompletionClient.java +++ b/src/main/java/ai/nomyo/SecureCompletionClient.java @@ -295,6 +295,7 @@ 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 07e1121..164e011 100644 --- a/src/main/java/ai/nomyo/SecureMemory.java +++ b/src/main/java/ai/nomyo/SecureMemory.java @@ -10,6 +10,7 @@ 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 @@ -75,6 +76,7 @@ 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 b8360d1..672df8d 100644 --- a/src/test/java/ai/nomyo/DecryptResponseTest.java +++ b/src/test/java/ai/nomyo/DecryptResponseTest.java @@ -34,7 +34,6 @@ 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); @@ -101,7 +100,6 @@ 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); @@ -221,17 +219,7 @@ class DecryptResponseTest { SecureCompletionClient client = new SecureCompletionClient(); client.generateKeys(false); - 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); + byte[] encryptedResponse = getJsonResponse("9.9", Constants.HYBRID_ALGORITHM); CompletableFuture> future = client.decryptResponse(encryptedResponse, "test-id"); ExecutionException error = assertThrows(ExecutionException.class, future::get); @@ -247,17 +235,7 @@ class DecryptResponseTest { SecureCompletionClient client = new SecureCompletionClient(); client.generateKeys(false); - 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); + byte[] encryptedResponse = getJsonResponse(Constants.PROTOCOL_VERSION, "wrong-algorithm"); CompletableFuture> future = client.decryptResponse(encryptedResponse, "test-id"); ExecutionException error = assertThrows(ExecutionException.class, future::get); @@ -266,15 +244,10 @@ class DecryptResponseTest { "Error message should mention unsupported algorithm"); } - @Test - @Execution(ExecutionMode.SAME_THREAD) - @DisplayName("decryptResponse should throw SecurityError when private key not initialized") - void decryptResponse_noPrivateKey_shouldThrowSecurityError() { - SecureCompletionClient client = new SecureCompletionClient(); - + private static byte[] getJsonResponse(String protocolVersion, String value) { JsonObject packageJson = new JsonObject(); - packageJson.addProperty("version", Constants.PROTOCOL_VERSION); - packageJson.addProperty("algorithm", Constants.HYBRID_ALGORITHM); + packageJson.addProperty("version", protocolVersion); + packageJson.addProperty("algorithm", value); packageJson.addProperty("encrypted_aes_key", "dGVzdA=="); JsonObject encryptedPayload = new JsonObject(); encryptedPayload.addProperty("ciphertext", "dGVzdA=="); @@ -282,7 +255,16 @@ class DecryptResponseTest { encryptedPayload.addProperty("tag", "dGVzdA=="); packageJson.add("encrypted_payload", encryptedPayload); - byte[] encryptedResponse = packageJson.toString().getBytes(StandardCharsets.UTF_8); + 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); 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 5db1a73..96da2c8 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(); - dir1.mkdirs(); - dir2.mkdirs(); + if (!dir1.mkdirs()) return; + if (!dir2.mkdirs()) return; // 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 aff264a..32ee5b1 100644 --- a/src/test/java/ai/nomyo/SecureMemoryTest.java +++ b/src/test/java/ai/nomyo/SecureMemoryTest.java @@ -104,11 +104,12 @@ 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}; - SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data); + try (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 @@ -127,28 +128,31 @@ class SecureMemoryTest { @DisplayName("SecureBuffer close should be idempotent") void secureBuffer_close_idempotent() { byte[] data = new byte[]{1, 2, 3}; - SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data); + try (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}; - SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true); + try (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}; - SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true); + try (SecureMemory.SecureBuffer buffer = SecureMemory.secureByteArray(data, true)) { - assertFalse(buffer.unlock(), "Unlock should return false"); + assertFalse(buffer.unlock(), "Unlock should return false"); + } } @Test