1717 */
1818package de .dominikschadow .javasecurity .tink .aead ;
1919
20+ import com .google .crypto .tink .Aead ;
21+ import com .google .crypto .tink .KeyTemplates ;
2022import com .google .crypto .tink .KeysetHandle ;
21- import org .junit .jupiter .api .Assertions ;
23+ import com .google .crypto .tink .aead .AeadConfig ;
24+ import com .google .crypto .tink .integration .awskms .AwsKmsClient ;
25+ import org .junit .jupiter .api .BeforeAll ;
2226import org .junit .jupiter .api .BeforeEach ;
23- import org .junit .jupiter .api .Disabled ;
2427import org .junit .jupiter .api .Test ;
28+ import org .junit .jupiter .api .extension .ExtendWith ;
29+ import org .junit .jupiter .api .io .TempDir ;
30+ import org .mockito .Mock ;
31+ import org .mockito .junit .jupiter .MockitoExtension ;
2532
2633import java .io .File ;
2734import java .nio .charset .StandardCharsets ;
35+ import java .security .GeneralSecurityException ;
2836
29- import static org .junit .jupiter .api .Assertions .assertEquals ;
30- import static org .junit .jupiter .api .Assertions .assertNotEquals ;
37+ import static org .junit .jupiter .api .Assertions .*;
38+ import static org .mockito .ArgumentMatchers .any ;
39+ import static org .mockito .Mockito .*;
3140
32- @ Disabled ( "These test require AWS KMS configuration" )
41+ @ ExtendWith ( MockitoExtension . class )
3342class AesGcmWithAwsKmsSavedKeyTest {
3443 private static final byte [] INITIAL_TEXT = "Some dummy text to work with" .getBytes (StandardCharsets .UTF_8 );
3544 private static final byte [] ASSOCIATED_DATA = "Some additional data" .getBytes (StandardCharsets .UTF_8 );
36- private static final String KEYSET_FILENAME = "src/test/resources/keysets/aead-aes-gcm-kms.json" ;
37- private final File keysetFile = new File (KEYSET_FILENAME );
38- private KeysetHandle secretKey ;
45+
46+ @ Mock
47+ private AwsKmsClient awsKmsClient ;
48+
49+ @ TempDir
50+ File tempDir ;
3951
4052 private AesGcmWithAwsKmsSavedKey aes ;
53+ private KeysetHandle testKeysetHandle ;
54+
55+ @ BeforeAll
56+ static void initTink () throws GeneralSecurityException {
57+ AeadConfig .register ();
58+ }
4159
4260 @ BeforeEach
43- protected void setup () throws Exception {
44- aes = new AesGcmWithAwsKmsSavedKey ();
61+ void setup () throws Exception {
62+ aes = new AesGcmWithAwsKmsSavedKey (awsKmsClient );
63+ testKeysetHandle = KeysetHandle .generateNew (KeyTemplates .get ("AES128_GCM" ));
64+ }
4565
46- aes .generateAndStoreKey (keysetFile );
47- secretKey = aes .loadKey (keysetFile );
66+ @ Test
67+ void constructorInitializesSuccessfully () throws GeneralSecurityException {
68+ AesGcmWithAwsKmsSavedKey instance = new AesGcmWithAwsKmsSavedKey (awsKmsClient );
69+ assertNotNull (instance );
70+ }
71+
72+ @ Test
73+ void constructorWithNullAwsKmsClientThrowsNoException () throws GeneralSecurityException {
74+ // The constructor accepts null - validation happens later when using the client
75+ AesGcmWithAwsKmsSavedKey instance = new AesGcmWithAwsKmsSavedKey (null );
76+ assertNotNull (instance );
77+ }
78+
79+ @ Test
80+ void encryptReturnsEncryptedData () throws Exception {
81+ byte [] cipherText = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
82+
83+ assertNotNull (cipherText );
84+ assertNotEquals (new String (INITIAL_TEXT , StandardCharsets .UTF_8 ), new String (cipherText , StandardCharsets .UTF_8 ));
4885 }
4986
5087 @ Test
51- void encryptionAndDecryptionWithValidInputsIsSuccessful () throws Exception {
52- byte [] cipherText = aes .encrypt (secretKey , INITIAL_TEXT , ASSOCIATED_DATA );
53- byte [] plainText = aes .decrypt (secretKey , cipherText , ASSOCIATED_DATA );
88+ void encryptWithEmptyAssociatedDataSucceeds () throws Exception {
89+ byte [] cipherText = aes .encrypt (testKeysetHandle , INITIAL_TEXT , new byte [0 ]);
90+
91+ assertNotNull (cipherText );
92+ assertTrue (cipherText .length > 0 );
93+ }
5494
55- Assertions .assertAll (
95+ @ Test
96+ void decryptReturnsOriginalData () throws Exception {
97+ byte [] cipherText = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
98+ byte [] plainText = aes .decrypt (testKeysetHandle , cipherText , ASSOCIATED_DATA );
99+
100+ assertEquals (new String (INITIAL_TEXT , StandardCharsets .UTF_8 ), new String (plainText , StandardCharsets .UTF_8 ));
101+ }
102+
103+ @ Test
104+ void decryptWithWrongAssociatedDataThrowsException () throws Exception {
105+ byte [] cipherText = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
106+ byte [] wrongAssociatedData = "Wrong associated data" .getBytes (StandardCharsets .UTF_8 );
107+
108+ assertThrows (GeneralSecurityException .class , () ->
109+ aes .decrypt (testKeysetHandle , cipherText , wrongAssociatedData )
110+ );
111+ }
112+
113+ @ Test
114+ void decryptWithCorruptedCipherTextThrowsException () throws Exception {
115+ byte [] cipherText = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
116+ // Corrupt the ciphertext
117+ cipherText [0 ] = (byte ) (cipherText [0 ] ^ 0xFF );
118+
119+ assertThrows (GeneralSecurityException .class , () ->
120+ aes .decrypt (testKeysetHandle , cipherText , ASSOCIATED_DATA )
121+ );
122+ }
123+
124+ @ Test
125+ void encryptionAndDecryptionRoundTripIsSuccessful () throws Exception {
126+ byte [] cipherText = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
127+ byte [] plainText = aes .decrypt (testKeysetHandle , cipherText , ASSOCIATED_DATA );
128+
129+ assertAll (
56130 () -> assertNotEquals (new String (INITIAL_TEXT , StandardCharsets .UTF_8 ), new String (cipherText , StandardCharsets .UTF_8 )),
57131 () -> assertEquals (new String (INITIAL_TEXT , StandardCharsets .UTF_8 ), new String (plainText , StandardCharsets .UTF_8 ))
58132 );
59133 }
60- }
134+
135+ @ Test
136+ void encryptProducesDifferentCipherTextForSameInput () throws Exception {
137+ byte [] cipherText1 = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
138+ byte [] cipherText2 = aes .encrypt (testKeysetHandle , INITIAL_TEXT , ASSOCIATED_DATA );
139+
140+ // AES-GCM uses random nonces, so encrypting the same plaintext twice should produce different ciphertexts
141+ assertNotEquals (new String (cipherText1 , StandardCharsets .UTF_8 ), new String (cipherText2 , StandardCharsets .UTF_8 ));
142+ }
143+
144+ @ Test
145+ void generateAndStoreKeyDoesNotOverwriteExistingFile () throws Exception {
146+ File keysetFile = new File (tempDir , "existing-keyset.json" );
147+ assertTrue (keysetFile .createNewFile ());
148+ long originalLength = keysetFile .length ();
149+
150+ aes .generateAndStoreKey (keysetFile );
151+
152+ // File should remain unchanged (empty) since it already existed
153+ assertEquals (originalLength , keysetFile .length ());
154+ verify (awsKmsClient , never ()).getAead (any ());
155+ }
156+
157+ @ Test
158+ void generateAndStoreKeyCallsAwsKmsClientForNewFile () throws Exception {
159+ File keysetFile = new File (tempDir , "new-keyset.json" );
160+ Aead mockAead = mock (Aead .class );
161+ when (awsKmsClient .getAead (any ())).thenReturn (mockAead );
162+ // Tink internally validates the encrypted keyset, so we need to throw an exception
163+ // to simulate what happens when AWS KMS is not available, but still verify the call
164+ when (mockAead .encrypt (any (), any ())).thenThrow (new GeneralSecurityException ("Mocked AWS KMS encryption" ));
165+
166+ assertFalse (keysetFile .exists ());
167+
168+ assertThrows (GeneralSecurityException .class , () -> aes .generateAndStoreKey (keysetFile ));
169+
170+ // Verify that AWS KMS client was called
171+ verify (awsKmsClient ).getAead (contains ("aws-kms://" ));
172+ verify (mockAead ).encrypt (any (), any ());
173+ }
174+
175+ @ Test
176+ void loadKeyCallsAwsKmsClient () throws Exception {
177+ // First create a keyset file using the same mock setup
178+ File keysetFile = new File (tempDir , "load-test-keyset.json" );
179+ Aead mockAead = mock (Aead .class );
180+ when (awsKmsClient .getAead (any ())).thenReturn (mockAead );
181+
182+ // Mock encrypt to return the plaintext (simulating encryption that returns same bytes)
183+ when (mockAead .encrypt (any (), any ())).thenAnswer (invocation -> invocation .getArgument (0 ));
184+ // Mock decrypt to return the ciphertext (simulating decryption that returns same bytes)
185+ when (mockAead .decrypt (any (), any ())).thenAnswer (invocation -> invocation .getArgument (0 ));
186+
187+ aes .generateAndStoreKey (keysetFile );
188+
189+ KeysetHandle loadedKey = aes .loadKey (keysetFile );
190+
191+ assertNotNull (loadedKey );
192+ // Verify getAead was called twice - once for generate, once for load
193+ verify (awsKmsClient , times (2 )).getAead (contains ("aws-kms://" ));
194+ }
195+
196+ @ Test
197+ void encryptWithNullKeysetHandleThrowsException () {
198+ assertThrows (NullPointerException .class , () ->
199+ aes .encrypt (null , INITIAL_TEXT , ASSOCIATED_DATA )
200+ );
201+ }
202+
203+ @ Test
204+ void decryptWithNullKeysetHandleThrowsException () {
205+ assertThrows (NullPointerException .class , () ->
206+ aes .decrypt (null , INITIAL_TEXT , ASSOCIATED_DATA )
207+ );
208+ }
209+ }
0 commit comments