2026-04-21 18:00:31 +02:00
|
|
|
package ai.nomyo;
|
|
|
|
|
|
|
|
|
|
import ai.nomyo.errors.SecurityError;
|
|
|
|
|
import ai.nomyo.util.Pass2Key;
|
|
|
|
|
import org.junit.jupiter.api.*;
|
|
|
|
|
import org.junit.jupiter.api.io.TempDir;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.math.BigInteger;
|
|
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.nio.file.Path;
|
|
|
|
|
import java.security.*;
|
|
|
|
|
import java.security.spec.RSAKeyGenParameterSpec;
|
|
|
|
|
import java.security.spec.RSAPrivateCrtKeySpec;
|
|
|
|
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
|
|
|
|
|
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
|
|
|
|
class SecureCompletionClientTest {
|
|
|
|
|
|
|
|
|
|
private static final String TEST_PASSWORD = "test-password-123";
|
|
|
|
|
private static final String PLAINTEXT_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7o4qne60TBb\n-----END PRIVATE KEY-----";
|
|
|
|
|
|
|
|
|
|
private SecureCompletionClient client;
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void setUp() {
|
|
|
|
|
client = new SecureCompletionClient();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
void tearDown() {
|
|
|
|
|
client = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Key Generation Tests ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(1)
|
|
|
|
|
@DisplayName("generateKeys should create 4096-bit RSA key pair")
|
|
|
|
|
void generateKeys_shouldCreateValidKeyPair() {
|
|
|
|
|
client.generateKeys(false);
|
|
|
|
|
|
|
|
|
|
PrivateKey privateKey = client.getPrivateKey();
|
|
|
|
|
String publicPemKey = client.getPublicPemKey();
|
|
|
|
|
|
|
|
|
|
assertNotNull(privateKey, "Private key should not be null");
|
|
|
|
|
assertNotNull(publicPemKey, "Public PEM key should not be null");
|
2026-04-23 13:36:46 +02:00
|
|
|
assertEquals("RSA", privateKey.getAlgorithm(), "Key algorithm should be RSA");
|
2026-04-21 18:00:31 +02:00
|
|
|
assertTrue(publicPemKey.contains("BEGIN PUBLIC KEY"), "Public key should be valid PEM");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(2)
|
|
|
|
|
@DisplayName("generateKeys should produce keys with correct bit size")
|
|
|
|
|
void generateKeys_shouldProduceCorrectKeySize() throws Exception {
|
|
|
|
|
client.generateKeys(false);
|
|
|
|
|
|
|
|
|
|
PrivateKey privateKey = client.getPrivateKey();
|
|
|
|
|
java.security.KeyFactory kf = java.security.KeyFactory.getInstance("RSA");
|
|
|
|
|
java.security.spec.RSAPrivateCrtKeySpec spec = kf.getKeySpec(privateKey, java.security.spec.RSAPrivateCrtKeySpec.class);
|
|
|
|
|
int keySizeBits = spec.getModulus().bitLength();
|
|
|
|
|
|
|
|
|
|
assertEquals(Constants.RSA_KEY_SIZE, keySizeBits,
|
|
|
|
|
"RSA key should be " + Constants.RSA_KEY_SIZE + " bits");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(3)
|
|
|
|
|
@DisplayName("generateKeys should create unique keys on each call")
|
|
|
|
|
void generateKeys_shouldProduceUniqueKeys() {
|
|
|
|
|
client.generateKeys(false);
|
|
|
|
|
PrivateKey firstKey = client.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
SecureCompletionClient client2 = new SecureCompletionClient();
|
|
|
|
|
client2.generateKeys(false);
|
|
|
|
|
PrivateKey secondKey = client2.getPrivateKey();
|
|
|
|
|
|
2026-04-23 13:36:46 +02:00
|
|
|
assertNotEquals(java.util.Arrays.hashCode(firstKey.getEncoded()), java.util.Arrays.hashCode(secondKey.getEncoded()),
|
|
|
|
|
"Different keys should have different encoded content");
|
2026-04-21 18:00:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Key Generation with File Save Tests ───────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(4)
|
|
|
|
|
@DisplayName("generateKeys with saveToFile=true should create key files")
|
2026-04-23 13:36:46 +02:00
|
|
|
void generateKeys_withSaveToFile_shouldCreateKeyFiles(@TempDir Path tempDir) {
|
2026-04-21 18:00:31 +02:00
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
|
|
|
|
|
|
|
|
|
File privateKeyFile = new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE);
|
|
|
|
|
File publicKeyFile = new File(keyDir, Constants.DEFAULT_PUBLIC_KEY_FILE);
|
|
|
|
|
|
|
|
|
|
assertTrue(privateKeyFile.exists(), "Private key file should be created");
|
|
|
|
|
assertTrue(publicKeyFile.exists(), "Public key file should be created");
|
|
|
|
|
assertTrue(privateKeyFile.length() > 0, "Private key file should not be empty");
|
|
|
|
|
assertTrue(publicKeyFile.length() > 0, "Public key file should not be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(5)
|
|
|
|
|
@DisplayName("generateKeys with password should encrypt private key file")
|
|
|
|
|
void generateKeys_withPassword_shouldEncryptPrivateKey(@TempDir Path tempDir) throws Exception {
|
|
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
|
|
|
|
|
|
|
|
|
File privateKeyFile = new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE);
|
|
|
|
|
String content = Files.readString(privateKeyFile.toPath());
|
|
|
|
|
|
|
|
|
|
assertFalse(content.contains("BEGIN PRIVATE KEY"),
|
|
|
|
|
"Encrypted private key should not contain PEM header");
|
|
|
|
|
assertNotEquals(PLAINTEXT_PRIVATE_KEY, content,
|
|
|
|
|
"Encrypted key should differ from plaintext");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(6)
|
|
|
|
|
@DisplayName("generateKeys should not overwrite existing key files")
|
2026-04-23 13:36:46 +02:00
|
|
|
void generateKeys_shouldNotOverwriteExistingKeys(@TempDir Path tempDir) {
|
2026-04-21 18:00:31 +02:00
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
|
|
|
|
File privateKeyFile = new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE);
|
|
|
|
|
long firstSize = privateKeyFile.length();
|
|
|
|
|
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
|
|
|
|
long secondSize = privateKeyFile.length();
|
|
|
|
|
|
|
|
|
|
assertEquals(firstSize, secondSize,
|
|
|
|
|
"Existing key files should not be overwritten");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Key Loading Tests ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(7)
|
|
|
|
|
@DisplayName("loadKeys should load plaintext private key from file")
|
2026-04-23 13:36:46 +02:00
|
|
|
void loadKeys_plaintext_shouldLoadPrivateKey(@TempDir Path tempDir) {
|
2026-04-21 18:00:31 +02:00
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), null);
|
|
|
|
|
|
|
|
|
|
PrivateKey originalKey = client.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
SecureCompletionClient loadClient = new SecureCompletionClient();
|
|
|
|
|
loadClient.loadKeys(
|
|
|
|
|
new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE).getAbsolutePath(),
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
PrivateKey loadedKey = loadClient.getPrivateKey();
|
|
|
|
|
assertNotNull(loadedKey, "Loaded private key should not be null");
|
|
|
|
|
assertEquals(originalKey.getEncoded().length, loadedKey.getEncoded().length,
|
|
|
|
|
"Loaded key should have same size as original");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(8)
|
|
|
|
|
@DisplayName("loadKeys should load encrypted private key with correct password")
|
2026-04-23 13:36:46 +02:00
|
|
|
void loadKeys_encrypted_correctPassword_shouldLoadPrivateKey(@TempDir Path tempDir) {
|
2026-04-21 18:00:31 +02:00
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
|
|
|
|
|
|
|
|
|
PrivateKey originalKey = client.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
SecureCompletionClient loadClient = new SecureCompletionClient();
|
|
|
|
|
loadClient.loadKeys(
|
|
|
|
|
new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE).getAbsolutePath(),
|
|
|
|
|
null,
|
|
|
|
|
TEST_PASSWORD
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
PrivateKey loadedKey = loadClient.getPrivateKey();
|
|
|
|
|
assertNotNull(loadedKey, "Loaded private key should not be null");
|
|
|
|
|
assertEquals(originalKey.getEncoded().length, loadedKey.getEncoded().length,
|
|
|
|
|
"Loaded key should have same size as original");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(9)
|
|
|
|
|
@DisplayName("loadKeys should handle wrong password gracefully")
|
2026-04-23 13:36:46 +02:00
|
|
|
void loadKeys_encrypted_wrongPassword_shouldHandleGracefully(@TempDir Path tempDir) {
|
2026-04-21 18:00:31 +02:00
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
|
|
|
|
|
|
|
|
|
SecureCompletionClient loadClient = new SecureCompletionClient();
|
|
|
|
|
|
|
|
|
|
assertDoesNotThrow(() ->
|
|
|
|
|
loadClient.loadKeys(
|
|
|
|
|
new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE).getAbsolutePath(),
|
|
|
|
|
null,
|
|
|
|
|
"wrong-password"
|
|
|
|
|
),
|
|
|
|
|
"Wrong password should not throw exception"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(10)
|
|
|
|
|
@DisplayName("loadKeys should throw exception for non-existent file")
|
|
|
|
|
void loadKeys_nonExistentFile_shouldThrowException() {
|
|
|
|
|
SecureCompletionClient loadClient = new SecureCompletionClient();
|
|
|
|
|
|
|
|
|
|
RuntimeException error = assertThrows(RuntimeException.class, () ->
|
|
|
|
|
loadClient.loadKeys("/non/existent/path/private_key.pem", null, null));
|
|
|
|
|
|
|
|
|
|
assertTrue(error.getMessage().contains("not found"),
|
|
|
|
|
"Error message should mention file not found");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Key Validation Tests ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(11)
|
|
|
|
|
@DisplayName("validateRsaKey should accept valid 4096-bit key")
|
2026-04-23 13:36:46 +02:00
|
|
|
void validateRsaKey_validKey_shouldPass() {
|
2026-04-21 18:00:31 +02:00
|
|
|
client.generateKeys(false);
|
|
|
|
|
PrivateKey key = client.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
assertDoesNotThrow(() -> client.validateRsaKey(key),
|
|
|
|
|
"Valid 4096-bit key should pass validation");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(12)
|
|
|
|
|
@DisplayName("validateRsaKey should reject null key")
|
2026-04-23 13:36:46 +02:00
|
|
|
void validateRsaKey_nullKey_shouldThrowSecurityError() {
|
2026-04-21 18:00:31 +02:00
|
|
|
SecurityError error = assertThrows(SecurityError.class, () ->
|
|
|
|
|
client.validateRsaKey(null));
|
|
|
|
|
|
|
|
|
|
assertTrue(error.getMessage().contains("null"),
|
|
|
|
|
"Error message should mention null key");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(13)
|
|
|
|
|
@DisplayName("validateRsaKey should reject keys below minimum size")
|
|
|
|
|
void validateRsaKey_tooSmallKey_shouldThrowSecurityError() throws Exception {
|
|
|
|
|
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
|
|
|
|
generator.initialize(1024);
|
|
|
|
|
KeyPair pair = generator.generateKeyPair();
|
|
|
|
|
PrivateKey smallKey = pair.getPrivate();
|
|
|
|
|
|
|
|
|
|
SecurityError error = assertThrows(SecurityError.class, () ->
|
|
|
|
|
client.validateRsaKey(smallKey));
|
|
|
|
|
|
|
|
|
|
assertTrue(error.getMessage().contains("below minimum"),
|
|
|
|
|
"Error message should mention minimum size requirement");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(14)
|
|
|
|
|
@DisplayName("validateRsaKey should accept minimum size key (2048 bits)")
|
|
|
|
|
void validateRsaKey_minimumSizeKey_shouldPass() throws Exception {
|
|
|
|
|
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
|
|
|
|
generator.initialize(new RSAKeyGenParameterSpec(2048, BigInteger.valueOf(65537)));
|
|
|
|
|
KeyPair pair = generator.generateKeyPair();
|
|
|
|
|
PrivateKey minKey = pair.getPrivate();
|
|
|
|
|
|
|
|
|
|
assertDoesNotThrow(() -> client.validateRsaKey(minKey),
|
|
|
|
|
"2048-bit key should pass validation (minimum)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Key Roundtrip Tests ───────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(15)
|
|
|
|
|
@DisplayName("Full roundtrip: generate, save, load should produce same key")
|
2026-04-23 13:36:46 +02:00
|
|
|
void roundtrip_generateSaveLoad_shouldProduceSameKey(@TempDir Path tempDir) {
|
2026-04-21 18:00:31 +02:00
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
|
|
|
|
|
// Generate and save
|
|
|
|
|
client.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
|
|
|
|
PrivateKey originalKey = client.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
// Load into new client
|
|
|
|
|
SecureCompletionClient loadClient = new SecureCompletionClient();
|
|
|
|
|
loadClient.loadKeys(
|
|
|
|
|
new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE).getAbsolutePath(),
|
|
|
|
|
null,
|
|
|
|
|
TEST_PASSWORD
|
|
|
|
|
);
|
|
|
|
|
PrivateKey loadedKey = loadClient.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
// Verify keys match
|
|
|
|
|
assertNotNull(loadedKey, "Loaded key should not be null");
|
|
|
|
|
assertArrayEquals(originalKey.getEncoded(), loadedKey.getEncoded(),
|
|
|
|
|
"Loaded key should match original key bytes");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(16)
|
|
|
|
|
@DisplayName("Multiple generate/load cycles should work correctly")
|
|
|
|
|
void multipleCycles_shouldWorkCorrectly(@TempDir Path tempDir) throws Exception {
|
|
|
|
|
File keyDir = tempDir.toFile();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
SecureCompletionClient cycleClient = new SecureCompletionClient();
|
|
|
|
|
cycleClient.generateKeys(true, keyDir.getAbsolutePath(), TEST_PASSWORD);
|
|
|
|
|
PrivateKey generatedKey = cycleClient.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
SecureCompletionClient loadClient = new SecureCompletionClient();
|
|
|
|
|
loadClient.loadKeys(
|
|
|
|
|
new File(keyDir, Constants.DEFAULT_PRIVATE_KEY_FILE).getAbsolutePath(),
|
|
|
|
|
null,
|
|
|
|
|
TEST_PASSWORD
|
|
|
|
|
);
|
|
|
|
|
PrivateKey loadedKey = loadClient.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
assertNotNull(loadedKey, "Cycle " + i + ": loaded key should not be null");
|
|
|
|
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
|
|
|
|
int generatedBits = kf.getKeySpec(generatedKey, RSAPrivateCrtKeySpec.class).getModulus().bitLength();
|
|
|
|
|
int loadedBits = kf.getKeySpec(loadedKey, RSAPrivateCrtKeySpec.class).getModulus().bitLength();
|
|
|
|
|
assertEquals(generatedBits, loadedBits,
|
|
|
|
|
"Cycle " + i + ": loaded key should have same modulus bit length as generated key");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Utility Method Tests ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(17)
|
|
|
|
|
@DisplayName("urlEncodePublicKey should properly encode PEM keys")
|
|
|
|
|
void urlEncodePublicKey_shouldEncodeCorrectly() {
|
|
|
|
|
String pemKey = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n-----END PUBLIC KEY-----";
|
|
|
|
|
|
|
|
|
|
String encoded = client.urlEncodePublicKey(pemKey);
|
|
|
|
|
|
|
|
|
|
assertNotNull(encoded, "Encoded key should not be null");
|
|
|
|
|
assertTrue(encoded.contains("%0A"), "Encoded key should contain URL-encoded newlines");
|
|
|
|
|
assertTrue(encoded.contains("BEGIN+PUBLIC+KEY"), "Encoded key should contain encoded header");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(18)
|
|
|
|
|
@DisplayName("urlEncodePublicKey should handle empty string")
|
|
|
|
|
void urlEncodePublicKey_emptyString_shouldReturnEmpty() {
|
|
|
|
|
String encoded = client.urlEncodePublicKey("");
|
|
|
|
|
assertEquals("", encoded, "Empty string should encode to empty string");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Pass2Key Encryption/Decryption Tests ──────────────────────────
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(19)
|
|
|
|
|
@DisplayName("Pass2Key encrypt/decrypt should preserve plaintext")
|
|
|
|
|
void pass2Key_encryptDecrypt_shouldPreservePlaintext() throws Exception {
|
|
|
|
|
String plaintext = "Test plaintext content for encryption";
|
|
|
|
|
String password = "my-secret-password";
|
|
|
|
|
|
|
|
|
|
String encrypted = Pass2Key.encrypt("AES/GCM/NoPadding", plaintext, password);
|
|
|
|
|
|
|
|
|
|
assertNotNull(encrypted, "Encrypted text should not be null");
|
|
|
|
|
assertNotEquals(plaintext, encrypted, "Encrypted text should differ from plaintext");
|
|
|
|
|
|
|
|
|
|
String decrypted = Pass2Key.decrypt("AES/GCM/NoPadding", encrypted, password);
|
|
|
|
|
|
|
|
|
|
assertEquals(plaintext, decrypted, "Decrypted text should match original plaintext");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(20)
|
|
|
|
|
@DisplayName("Pass2Key should produce different ciphertext for same plaintext")
|
|
|
|
|
void pass2Key_shouldProduceDifferentCiphertext() throws Exception {
|
|
|
|
|
String plaintext = "Same plaintext";
|
|
|
|
|
String password = "same-password";
|
|
|
|
|
|
|
|
|
|
String encrypted1 = Pass2Key.encrypt("AES/GCM/NoPadding", plaintext, password);
|
|
|
|
|
String encrypted2 = Pass2Key.encrypt("AES/GCM/NoPadding", plaintext, password);
|
|
|
|
|
|
|
|
|
|
assertNotEquals(encrypted1, encrypted2,
|
|
|
|
|
"Same plaintext with same password should produce different ciphertext (due to random salt/IV)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(21)
|
|
|
|
|
@DisplayName("Pass2Key decrypt should fail with wrong password")
|
|
|
|
|
void pass2Key_wrongPassword_shouldFail() throws Exception {
|
|
|
|
|
String plaintext = "Secret content";
|
|
|
|
|
String correctPassword = "correct-password";
|
|
|
|
|
String wrongPassword = "wrong-password";
|
|
|
|
|
|
|
|
|
|
String encrypted = Pass2Key.encrypt("AES/GCM/NoPadding", plaintext, correctPassword);
|
|
|
|
|
|
|
|
|
|
assertThrows(Exception.class, () ->
|
|
|
|
|
Pass2Key.decrypt("AES/GCM/NoPadding", encrypted, wrongPassword),
|
|
|
|
|
"Decryption with wrong password should throw exception"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(22)
|
|
|
|
|
@DisplayName("Pass2Key convertStringToPrivateKey should parse PEM correctly")
|
|
|
|
|
void convertStringToPrivateKey_shouldParsePEM() throws Exception {
|
|
|
|
|
SecureCompletionClient tempClient = new SecureCompletionClient();
|
|
|
|
|
tempClient.generateKeys(false);
|
|
|
|
|
PrivateKey originalKey = tempClient.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
String pem = ai.nomyo.util.PEMConverter.toPEM(originalKey.getEncoded(), true);
|
|
|
|
|
PrivateKey parsedKey = Pass2Key.convertStringToPrivateKey(pem);
|
|
|
|
|
|
|
|
|
|
assertNotNull(parsedKey, "Parsed private key should not be null");
|
|
|
|
|
assertEquals("RSA", parsedKey.getAlgorithm(), "Parsed key should be RSA");
|
|
|
|
|
assertArrayEquals(originalKey.getEncoded(), parsedKey.getEncoded(),
|
|
|
|
|
"Parsed key should match original key bytes");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@Order(23)
|
|
|
|
|
@DisplayName("Pass2Key convertStringToPrivateKey should handle PEM with whitespace")
|
|
|
|
|
void convertStringToPrivateKey_shouldHandleWhitespace() throws Exception {
|
|
|
|
|
SecureCompletionClient tempClient = new SecureCompletionClient();
|
|
|
|
|
tempClient.generateKeys(false);
|
|
|
|
|
PrivateKey originalKey = tempClient.getPrivateKey();
|
|
|
|
|
|
|
|
|
|
String pem = "-----BEGIN PRIVATE KEY-----\n " +
|
|
|
|
|
originalKey.getEncoded().length + "lines\n" +
|
|
|
|
|
"-----END PRIVATE KEY-----";
|
|
|
|
|
|
|
|
|
|
String formattedPem = ai.nomyo.util.PEMConverter.toPEM(originalKey.getEncoded(), true);
|
|
|
|
|
String pemWithWhitespace = formattedPem.replace("\n", "\n ");
|
|
|
|
|
|
|
|
|
|
PrivateKey parsedKey = Pass2Key.convertStringToPrivateKey(pemWithWhitespace);
|
|
|
|
|
|
|
|
|
|
assertNotNull(parsedKey, "Parsed private key should not be null even with whitespace");
|
|
|
|
|
assertArrayEquals(originalKey.getEncoded(), parsedKey.getEncoded(),
|
|
|
|
|
"Parsed key with whitespace should match original key bytes");
|
|
|
|
|
}
|
|
|
|
|
}
|