RetryingEncryptionService { public String decrypt(final EncryptedValue encryptedValue) throws Exception { LOGGER.info("Attempting decrypt"); return retryOnErrorWithRemappedKey(() -> { final EncryptionKey key = keySet.get(encryptedValue.getEncryptionKeyUuid()); if (key == null) { throw new KeyNotFoundException(ErrorMessages.MISSING_ENCRYPTION_KEY); } return key.decrypt(encryptedValue.getEncryptedValue(), encryptedValue.getNonce()); }); } @Autowired  RetryingEncryptionService(final EncryptionKeySet keySet); EncryptedValue encrypt(final String value); String decrypt(final EncryptedValue encryptedValue);  }
@Test public void decrypt_shouldReturnTheDecryptedStringWithoutAttemptionToReconnect() throws Exception { when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey); when(firstActiveKey.decrypt("fake-encrypted-value".getBytes(UTF_8), "fake-nonce".getBytes(UTF_8))) .thenReturn("fake-plaintext"); assertThat( subject.decrypt(new EncryptedValue(activeKeyUuid, "fake-encrypted-value".getBytes(UTF_8), "fake-nonce".getBytes(UTF_8))), equalTo("fake-plaintext")); verify(encryptionService, times(0)).reconnect(any(IllegalBlockSizeException.class)); verify(keySet, times(0)).reload(); }
@Test public void decrypt_whenThrowsAnError_retriesDecryptionFailure() throws Exception { when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey); when(keySet.getActive()) .thenReturn(firstActiveKey); when(firstActiveKey.decrypt(any(byte[].class), any(byte[].class))) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(encryptionService.isLoggedIn()).thenReturn(true); try { subject.decrypt(new EncryptedValue(activeKeyUuid, "an encrypted value".getBytes(UTF_8), "a nonce".getBytes(UTF_8))); fail("Expected exception"); } catch (final ProviderException e) { } final InOrder inOrder = inOrder(firstActiveKey, encryptionService); inOrder.verify(firstActiveKey).decrypt(any(byte[].class), any(byte[].class)); inOrder.verify(encryptionService).reconnect(any(ProviderException.class)); inOrder.verify(firstActiveKey) .decrypt(any(byte[].class), any(byte[].class)); }
@Test public void decrypt_whenThrowsErrors_unlocksAfterExceptionAndLocksAgainBeforeEncrypting() throws Exception { when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey); when(keySet.getActive()) .thenReturn(firstActiveKey); when(firstActiveKey.decrypt(any(byte[].class), any(byte[].class))) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); reset(writeLock); try { subject.decrypt(new EncryptedValue(activeKeyUuid, "an encrypted value".getBytes(UTF_8), "a nonce".getBytes(UTF_8))); } catch (final ProviderException e) { } verify(readLock, times(2)).lock(); verify(readLock, times(2)).unlock(); verify(writeLock, times(1)).lock(); verify(writeLock, times(1)).unlock(); }
@Test public void decrypt_locksAndUnlocksTheReconnectLockWhenLoginError() throws Exception { when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey); when(firstActiveKey.decrypt(any(byte[].class), any(byte[].class))) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); reset(writeLock); doThrow(new RuntimeException()).when(encryptionService) .reconnect(any(Exception.class)); try { subject.decrypt(new EncryptedValue(activeKeyUuid, "an encrypted value".getBytes(UTF_8), "a nonce".getBytes(UTF_8))); } catch (final IllegalBlockSizeException | RuntimeException e) { } verify(readLock, times(2)).lock(); verify(readLock, times(2)).unlock(); verify(writeLock, times(1)).lock(); verify(writeLock, times(1)).unlock(); }
@Test public void decrypt_whenTheOperationSucceedsOnlyAfterReconnection() throws Exception { when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey) .thenReturn(secondActiveKey); when(keySet.getActive()) .thenReturn(firstActiveKey); when(encryptionService.isLoggedIn()).thenReturn(true); when(firstActiveKey .decrypt("fake-encrypted-value".getBytes(UTF_8), "fake-nonce".getBytes(UTF_8))) .thenThrow(new IllegalBlockSizeException("test exception")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(secondActiveKey .decrypt("fake-encrypted-value".getBytes(UTF_8), "fake-nonce".getBytes(UTF_8))) .thenReturn("fake-plaintext"); when(secondActiveKey.getProvider()).thenReturn(encryptionService); assertThat(subject .decrypt(new EncryptedValue(activeKeyUuid, "fake-encrypted-value".getBytes(UTF_8), "fake-nonce".getBytes(UTF_8))), equalTo("fake-plaintext")); verify(encryptionService, times(1)) .reconnect(any(IllegalBlockSizeException.class)); verify(keySet, times(1)).reload(); }
@Test(expected = KeyNotFoundException.class) public void decrypt_whenTheEncryptionKeyCannotBeFound_throwsAnException() throws Exception { final UUID fakeUuid = UUID.randomUUID(); reset(encryptionService); when(keySet.get(fakeUuid)).thenReturn(null); subject.decrypt(new EncryptedValue(fakeUuid, "something we cant read".getBytes(UTF_8), "nonce".getBytes(UTF_8))); }
@Test public void decryptionLocks_acquiresALunaUsageReadLock() throws Exception { when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey); subject.decrypt(new EncryptedValue(activeKeyUuid, "an encrypted value".getBytes(UTF_8), "a nonce".getBytes(UTF_8))); verify(readLock, times(1)).lock(); verify(readLock, times(1)).unlock(); verify(writeLock, times(0)).lock(); verify(writeLock, times(0)).unlock(); }
@Test public void usingTwoThread_wontRetryTwice() throws Exception { final Object lock = new Object(); final Thread firstThread = new Thread("first") { @SuppressFBWarnings( value = "DE_MIGHT_IGNORE", justification = "Exception is a no-op in this test" ) @Override public void run() { try { subject.decrypt(new EncryptedValue(activeKeyUuid, "a value 1".getBytes(UTF_8), "nonce".getBytes(UTF_8))); } catch (final Exception e) { } } }; final Thread secondThread = new Thread("second") { @SuppressFBWarnings( value = "DE_MIGHT_IGNORE", justification = "Exception is a no-op in this test" ) @Override public void run() { try { subject.decrypt(new EncryptedValue(activeKeyUuid, "a value 2".getBytes(UTF_8), "nonce".getBytes(UTF_8))); } catch (final Exception e) { } } }; subject = new RacingRetryingEncryptionServiceForTest(firstThread, secondThread, lock); when(keySet.get(activeKeyUuid)) .thenReturn(firstActiveKey); when(keySet.getActive()) .thenReturn(firstActiveKey); when(encryptionService.isLoggedIn()).thenReturn(true); when(firstActiveKey.decrypt(any(byte[].class), any(byte[].class))) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); firstThread.start(); firstThread.join(); secondThread.join(); verify(keySet, times(1)).reload(); }