From 917fe506828e04df156fb2a724f976ee07fc31d1 Mon Sep 17 00:00:00 2001 From: MrSebastian <13592751+MrSebastian@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:13:06 +0100 Subject: [PATCH 1/6] extend cryptoservice with features of encryptionbuilder --- .../AESEncryptionConfiguration.java | 33 ++++ .../EncryptionConfiguration.java | 20 --- .../exception/ExceptionConstants.java | 2 + .../security/EncryptionBuilder.java | 64 +++++++ .../authservice/service/CryptoService.java | 62 ++++++- .../src/main/resources/application-local.yml | 7 +- .../src/main/resources/application-test.yml | 2 + .../UserRepositoryImplIntegrationTest.java | 4 +- ...okalBenutzerControllerIntegrationTest.java | 2 +- .../service/CryptoServiceIntegrationTest.java | 25 +++ .../service/CryptoServiceTest.java | 166 ++++++++++++++++++ 11 files changed, 353 insertions(+), 34 deletions(-) create mode 100644 wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/AESEncryptionConfiguration.java delete mode 100644 wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/EncryptionConfiguration.java create mode 100644 wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/security/EncryptionBuilder.java create mode 100644 wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceIntegrationTest.java create mode 100644 wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/AESEncryptionConfiguration.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/AESEncryptionConfiguration.java new file mode 100644 index 000000000..e282ee84f --- /dev/null +++ b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/AESEncryptionConfiguration.java @@ -0,0 +1,33 @@ +package de.muenchen.oss.wahllokalsystem.authservice.configuration; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import lombok.val; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AESEncryptionConfiguration { + + private static final String AES = "AES"; + + @Value("${service.config.crypto.key}") + String key; + + @Bean + Cipher encryptionCipher() throws Exception { + val secret = new SecretKeySpec(key.getBytes(), 0, 16, AES); + val encryptCipher = Cipher.getInstance(AES); + encryptCipher.init(Cipher.ENCRYPT_MODE, secret); + return encryptCipher; + } + + @Bean + Cipher decryptionCipher() throws Exception { + val secret = new SecretKeySpec(key.getBytes(), 0, 16, AES); + val decryptCipher = Cipher.getInstance(AES); + decryptCipher.init(Cipher.DECRYPT_MODE, secret); + return decryptCipher; + } +} diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/EncryptionConfiguration.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/EncryptionConfiguration.java deleted file mode 100644 index 4c5ffcc3f..000000000 --- a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/configuration/EncryptionConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.muenchen.oss.wahllokalsystem.authservice.configuration; - -import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; -import de.muenchen.oss.wahllokalsystem.wls.common.security.EncryptionBuilder; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import javax.crypto.NoSuchPaddingException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class EncryptionConfiguration { - - @Bean - public EncryptionBuilder encryptionBuilder(@Value("{serviceauth.crypto.key}") final String cryptoKey, final ServiceIDFormatter serviceIDFormatter) - throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException { - return new EncryptionBuilder(cryptoKey.getBytes(), serviceIDFormatter); - } -} diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/exception/ExceptionConstants.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/exception/ExceptionConstants.java index 5ac4b7a61..d2582a5cd 100644 --- a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/exception/ExceptionConstants.java +++ b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/exception/ExceptionConstants.java @@ -7,6 +7,8 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public class ExceptionConstants { + public static final String CRYPTO_EXCEPTION_CODE = "399"; + public static ExceptionDataWrapper KOMMUNIKATIONSFEHLER_MIT_KONFIGSERVICE = new ExceptionDataWrapper( "100", "Bei der Kommunikation mit dem Konfigurationsservice kam es zu einem Fehler."); } diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/security/EncryptionBuilder.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/security/EncryptionBuilder.java new file mode 100644 index 000000000..d04a1e505 --- /dev/null +++ b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/security/EncryptionBuilder.java @@ -0,0 +1,64 @@ +/** + * + */ +package de.muenchen.oss.wahllokalsystem.authservice.security; + +import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; +import java.util.Base64; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import lombok.val; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +public class EncryptionBuilder { + + private static final Logger log = LoggerFactory.getLogger(EncryptionBuilder.class); + private static final String technischeExceptionKonstante = "399"; + + private final ServiceIDFormatter formatter; + private final Cipher encryptionCipher; + private final Cipher decryptionCipher; + + public EncryptionBuilder(ServiceIDFormatter formatter, + @Qualifier("encryptionCipher") Cipher encryptionCipher, + @Qualifier("decryptionCipher") Cipher decryptionCipher) { + this.formatter = formatter; + this.encryptionCipher = encryptionCipher; + this.decryptionCipher = decryptionCipher; + } + + public String decryptValue(String value) { + if (value != null && !value.isEmpty()) { + try { + val decode = Base64.getUrlDecoder().decode(value.getBytes()); + val finalized = decryptionCipher.doFinal(decode); + return new String(finalized); + } catch (IllegalBlockSizeException | BadPaddingException e) { + log.error("Unable to decrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); + throw TechnischeWlsException.withCode(technischeExceptionKonstante).inService(formatter.getId()) + .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); + } + } + return value; + } + + public String encryptValue(String value) { + if (value != null && !value.isEmpty()) { + try { + val finalized = encryptionCipher.doFinal(value.getBytes()); + value = Base64.getUrlEncoder().encodeToString(finalized); + } catch (IllegalBlockSizeException | BadPaddingException e) { + log.error("Unable to encrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); + throw TechnischeWlsException.withCode(technischeExceptionKonstante).inService(formatter.getId()) + .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); + } + } + return value; + } +} diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java index 77fa5a506..3387d8555 100644 --- a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java +++ b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java @@ -1,37 +1,85 @@ package de.muenchen.oss.wahllokalsystem.authservice.service; -import de.muenchen.oss.wahllokalsystem.wls.common.security.EncryptionBuilder; -import lombok.RequiredArgsConstructor; +import de.muenchen.oss.wahllokalsystem.authservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; +import java.util.Base64; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import lombok.val; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service -@RequiredArgsConstructor @Slf4j +@Getter +@Setter public class CryptoService { - private final EncryptionBuilder encryptionBuilder; + private final ServiceIDFormatter formatter; + private final Cipher encryptionCipher; + private final Cipher decryptionCipher; @Value("${service.config.crypto.encryptionPrefix}") private String encryptedPrefix = ""; + public CryptoService(ServiceIDFormatter formatter, + @Qualifier("encryptionCipher") Cipher encryptionCipher, + @Qualifier("decryptionCipher") Cipher decryptionCipher) { + this.formatter = formatter; + this.encryptionCipher = encryptionCipher; + this.decryptionCipher = decryptionCipher; + } + public boolean isEncrypted(final String value) { return value.startsWith(encryptedPrefix); } public String encrypt(final String value) { - return encryptedPrefix + encryptionBuilder.encryptValue(value); + return encryptedPrefix + encryptValue(value); } public String decrypt(final String value) { - if (value.startsWith(encryptedPrefix)) { + if (isEncrypted(value)) { val encryptedSubstring = value.substring(encryptedPrefix.length()); - return encryptionBuilder.decryptValue(encryptedSubstring); + return decryptValue(encryptedSubstring); } else { log.warn("value was already decrypted"); return value; } } + + private String decryptValue(String value) { + if (value != null && !value.isEmpty()) { + try { + val decode = Base64.getUrlDecoder().decode(value.getBytes()); + val finalized = decryptionCipher.doFinal(decode); + return new String(finalized); + } catch (IllegalBlockSizeException | BadPaddingException e) { + log.error("Unable to decrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); + throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId()) + .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); + } + } + return value; + } + + private String encryptValue(String value) { + if (value != null && !value.isEmpty()) { + try { + val finalized = encryptionCipher.doFinal(value.getBytes()); + value = Base64.getUrlEncoder().encodeToString(finalized); + } catch (IllegalBlockSizeException | BadPaddingException e) { + log.error("Unable to encrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); + throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId()) + .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); + } + } + return value; + } } diff --git a/wls-auth-service/src/main/resources/application-local.yml b/wls-auth-service/src/main/resources/application-local.yml index 3d3cebc29..0e9614386 100644 --- a/wls-auth-service/src/main/resources/application-local.yml +++ b/wls-auth-service/src/main/resources/application-local.yml @@ -3,11 +3,10 @@ server: service: config: + crypto: + key: "please change me" oauth2: jwk: rsa: init: - seed: change_me -serviceauth: - crypto: - key: "please change me" \ No newline at end of file + seed: change_me \ No newline at end of file diff --git a/wls-auth-service/src/main/resources/application-test.yml b/wls-auth-service/src/main/resources/application-test.yml index 64a55fcd1..d396d7126 100644 --- a/wls-auth-service/src/main/resources/application-test.yml +++ b/wls-auth-service/src/main/resources/application-test.yml @@ -17,6 +17,8 @@ spring: service: config: + crypto: + key: "please change me" oauth2: jwk: rsa: diff --git a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/domain/UserRepositoryImplIntegrationTest.java b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/domain/UserRepositoryImplIntegrationTest.java index 539981578..4b4038b99 100644 --- a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/domain/UserRepositoryImplIntegrationTest.java +++ b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/domain/UserRepositoryImplIntegrationTest.java @@ -19,12 +19,12 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.transaction.support.TransactionTemplate; -@SpringBootTest(classes = MicroServiceApplication.class, properties = { "serviceauth.crypto.key=secret" }) +@SpringBootTest(classes = MicroServiceApplication.class, properties = { "service.config.crypto.key=veryLongAndVerySaveKeyIHopeXXXabc123!!" }) @ActiveProfiles({ TestConstants.SPRING_TEST_PROFILE, Profiles.DUMMY_CLIENTS }) class UserRepositoryImplIntegrationTest { private static final String USERNAME_UNENCRYPTED = "username"; - private static final String USERNAME_ENCRYPTED = "ENCRYPTED:AcZZ7iVGyYoE-DTb9rNwgQ=="; + private static final String USERNAME_ENCRYPTED = "ENCRYPTED:TLXm2wsx1kcDLHHU8ZWptQ=="; @Autowired UserRepositoryImpl userRepository; diff --git a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/rest/WahllokalBenutzerControllerIntegrationTest.java b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/rest/WahllokalBenutzerControllerIntegrationTest.java index 43988dfe4..07b1cd952 100644 --- a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/rest/WahllokalBenutzerControllerIntegrationTest.java +++ b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/rest/WahllokalBenutzerControllerIntegrationTest.java @@ -33,7 +33,7 @@ @SpringBootTest( classes = MicroServiceApplication.class, - properties = { "service.config.crypto.key=secret", "service.config.user.authority.wahlvorstand=" + PROP_USER_AUTHORITY_WAHLVORSTAND } + properties = { "service.config.user.authority.wahlvorstand=" + PROP_USER_AUTHORITY_WAHLVORSTAND } ) @AutoConfigureMockMvc @ActiveProfiles({ TestConstants.SPRING_TEST_PROFILE, TestConstants.SPRING_NO_SECURITY_PROFILE, Profiles.DUMMY_CLIENTS }) diff --git a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceIntegrationTest.java b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceIntegrationTest.java new file mode 100644 index 000000000..6e7613787 --- /dev/null +++ b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceIntegrationTest.java @@ -0,0 +1,25 @@ +package de.muenchen.oss.wahllokalsystem.authservice.service; + +import de.muenchen.oss.wahllokalsystem.authservice.configuration.AESEncryptionConfiguration; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + classes = { AESEncryptionConfiguration.class, CryptoService.class, ServiceIDFormatter.class }, + properties = { "service.config.crypto.key = 770A8A65DA156D24EE2A093277530142" } +) +class CryptoServiceIntegrationTest { + + @Autowired + CryptoService cryptoService; + + @Test + void should_useProvidedBeans_when_startingContext() { + val valueToEncrypt = "Mzc2NTI2NzIzQUZEQUIzRA=="; + Assertions.assertThat(cryptoService.decrypt(cryptoService.encrypt(valueToEncrypt))).isEqualTo(valueToEncrypt); + } +} diff --git a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java new file mode 100644 index 000000000..0fdf002b8 --- /dev/null +++ b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java @@ -0,0 +1,166 @@ +package de.muenchen.oss.wahllokalsystem.authservice.service; + +import de.muenchen.oss.wahllokalsystem.authservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; +import java.util.Base64; +import java.util.Set; +import java.util.stream.Stream; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CryptoServiceTest { + + private static final String ENCRYPTION_PREFIX = "encryptionPrefix"; + + @Mock + ServiceIDFormatter idFormatter; + + @Mock + Cipher cipher; + + @InjectMocks + CryptoService unitUnderTest; + + @BeforeEach + void setUp() { + unitUnderTest.setEncryptedPrefix(ENCRYPTION_PREFIX); + } + + @Nested + class IsEncrypted { + + @Test + void should_returnTrue_when_valueStartsWithPrefix() { + Assertions.assertThat(unitUnderTest.isEncrypted(ENCRYPTION_PREFIX + "the encrypted value")).isTrue(); + } + + @Test + void should_returnFalse_when_valueDoesNotStartWithPrefix() { + unitUnderTest.setEncryptedPrefix("prefix"); + Assertions.assertThat(unitUnderTest.isEncrypted("values without encryption prefix")).isFalse(); + } + } + + @Nested + class Encrypt { + + @Test + void should_returnEncryptedValueWithPrefix_when_valueIsGiven() throws Exception { + val valueToEncrypt = "hello world"; + + val mockedEncryptedValue = "encrypted value".getBytes(); + Mockito.when(cipher.doFinal(valueToEncrypt.getBytes())).thenReturn(mockedEncryptedValue); + + val result = unitUnderTest.encrypt(valueToEncrypt); + + val expectedResult = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(mockedEncryptedValue); + Assertions.assertThat(result).isEqualTo(expectedResult); + } + + @Test + void should_returnEncryptionPrefix_when_emptyStringValueIsGiven() { + Assertions.assertThat(unitUnderTest.encrypt("")).isEqualTo(ENCRYPTION_PREFIX); + } + + @Test + void should_returnEncryptionPrefix_when_nullIsGiven() { + Assertions.assertThat(unitUnderTest.encrypt(null)).isEqualTo(ENCRYPTION_PREFIX + null); + } + + @ParameterizedTest + @MethodSource("exceptionsMappedToWlsException") + void should_throwTechnischeWlsException_when_cipherThrowsException(final Exception exceptionThrownByCipher) throws Exception { + val valueToEncrypt = "hello world"; + + val mockedServiceID = "authService"; + Mockito.when(idFormatter.getId()).thenReturn(mockedServiceID); + Mockito.doThrow(exceptionThrownByCipher).when(cipher).doFinal(valueToEncrypt.getBytes()); + + val expectedException = TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(mockedServiceID) + .buildWithMessage(""); + + Assertions.assertThatThrownBy(() -> unitUnderTest.encrypt(valueToEncrypt)) + .satisfies(exception -> { + Assertions.assertThat(exception) + .usingRecursiveComparison() + .ignoringFields("message") + .isEqualTo(expectedException); + Assertions.assertThat(exception).hasNoNullFieldsOrProperties(); + }); + } + + public static Stream exceptionsMappedToWlsException() { + val exceptions = Set.of(new IllegalBlockSizeException(), new BadPaddingException()); + + return exceptions.stream().map(exception -> Arguments.of(exception, exception.getClass().getName())); + } + } + + @Nested + class Decrypt { + + @Test + void should_returnValue_when_valueIsNotEncrypted() { + val notEncryptedValue = Base64.getEncoder().encodeToString("not encrypted value".getBytes()); + Assertions.assertThat(unitUnderTest.decrypt(notEncryptedValue)).isEqualTo(notEncryptedValue); + } + + @Test + void should_returnDecryptedValue_when_valueIsGiven() throws Exception { + val encryptedValue = "the encrypted value"; + val encryptedValueAsBase64WithPrefix = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(encryptedValue.getBytes()); + + val mockDecrypted = "decrypted value"; + Mockito.when(cipher.doFinal(encryptedValue.getBytes())).thenReturn(mockDecrypted.getBytes()); + + val result = unitUnderTest.decrypt(encryptedValueAsBase64WithPrefix); + + Assertions.assertThat(result).isEqualTo(mockDecrypted); + } + + @ParameterizedTest + @MethodSource("exceptionsMappedToWlsException") + void should_throwTechnischeWlsException_when_cipherThrowsException(final Exception exceptionThrownByCipher) throws Exception { + val encryptedValue = "the encrypted value"; + val encryptedValueAsBase64WithPrefix = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(encryptedValue.getBytes()); + + val mockedServiceID = "authService"; + Mockito.when(idFormatter.getId()).thenReturn(mockedServiceID); + Mockito.doThrow(exceptionThrownByCipher).when(cipher).doFinal(encryptedValue.getBytes()); + + val expectedException = TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(mockedServiceID) + .buildWithMessage(""); + + Assertions.assertThatThrownBy(() -> unitUnderTest.decrypt(encryptedValueAsBase64WithPrefix)) + .satisfies(exception -> { + Assertions.assertThat(exception) + .usingRecursiveComparison() + .ignoringFields("message") + .isEqualTo(expectedException); + Assertions.assertThat(exception).hasNoNullFieldsOrProperties(); + }); + } + + public static Stream exceptionsMappedToWlsException() { + val exceptions = Set.of(new IllegalBlockSizeException(), new BadPaddingException()); + + return exceptions.stream().map(exception -> Arguments.of(exception, exception.getClass().getName())); + } + } +} From 6beb5bcbe0e750b93a25ee89a17c3ab4c93d0387 Mon Sep 17 00:00:00 2001 From: MrSebastian <13592751+MrSebastian@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:15:13 +0100 Subject: [PATCH 2/6] encryption builder removed from wls-common --- .../security/AESEncryptionConfiguration.java | 33 ----- .../common/security/EncryptionBuilder.java | 64 --------- .../EncryptionBuilderIntegrationTest.java | 22 --- .../security/EncryptionBuilderTest.java | 135 ------------------ 4 files changed, 254 deletions(-) delete mode 100644 wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/AESEncryptionConfiguration.java delete mode 100644 wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilder.java delete mode 100644 wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderIntegrationTest.java delete mode 100644 wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderTest.java diff --git a/wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/AESEncryptionConfiguration.java b/wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/AESEncryptionConfiguration.java deleted file mode 100644 index c84f8911b..000000000 --- a/wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/AESEncryptionConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.muenchen.oss.wahllokalsystem.wls.common.security; - -import javax.crypto.Cipher; -import javax.crypto.spec.SecretKeySpec; -import lombok.val; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class AESEncryptionConfiguration { - - private static final String AES = "AES"; - - @Value("${app.crypto.key}") - String key; - - @Bean - Cipher encryptionCipher() throws Exception { - val secret = new SecretKeySpec(key.getBytes(), 0, 16, AES); - val encryptCipher = Cipher.getInstance(AES); - encryptCipher.init(Cipher.ENCRYPT_MODE, secret); - return encryptCipher; - } - - @Bean - Cipher decryptionCipher() throws Exception { - val secret = new SecretKeySpec(key.getBytes(), 0, 16, AES); - val decryptCipher = Cipher.getInstance(AES); - decryptCipher.init(Cipher.DECRYPT_MODE, secret); - return decryptCipher; - } -} diff --git a/wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilder.java b/wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilder.java deleted file mode 100644 index cf7925f3b..000000000 --- a/wls-common/security/src/main/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilder.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * - */ -package de.muenchen.oss.wahllokalsystem.wls.common.security; - -import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException; -import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; -import java.util.Base64; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import lombok.val; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -@Component -public class EncryptionBuilder { - - private static final Logger log = LoggerFactory.getLogger(EncryptionBuilder.class); - private static final String technischeExceptionKonstante = "399"; - - private final ServiceIDFormatter formatter; - private final Cipher encryptionCipher; - private final Cipher decryptionCipher; - - public EncryptionBuilder(ServiceIDFormatter formatter, - @Qualifier("encryptionCipher") Cipher encryptionCipher, - @Qualifier("decryptionCipher") Cipher decryptionCipher) { - this.formatter = formatter; - this.encryptionCipher = encryptionCipher; - this.decryptionCipher = decryptionCipher; - } - - public String decryptValue(String value) { - if (value != null && !value.isEmpty()) { - try { - val decode = Base64.getUrlDecoder().decode(value.getBytes()); - val finalized = decryptionCipher.doFinal(decode); - return new String(finalized); - } catch (IllegalBlockSizeException | BadPaddingException e) { - log.error("Unable to decrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); - throw TechnischeWlsException.withCode(technischeExceptionKonstante).inService(formatter.getId()) - .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); - } - } - return value; - } - - public String encryptValue(String value) { - if (value != null && !value.isEmpty()) { - try { - val finalized = encryptionCipher.doFinal(value.getBytes()); - value = Base64.getUrlEncoder().encodeToString(finalized); - } catch (IllegalBlockSizeException | BadPaddingException e) { - log.error("Unable to encrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); - throw TechnischeWlsException.withCode(technischeExceptionKonstante).inService(formatter.getId()) - .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); - } - } - return value; - } -} diff --git a/wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderIntegrationTest.java b/wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderIntegrationTest.java deleted file mode 100644 index 09c9da983..000000000 --- a/wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderIntegrationTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.muenchen.oss.wahllokalsystem.wls.common.security; - -import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest( - classes = { AESEncryptionConfiguration.class, EncryptionBuilder.class, ServiceIDFormatter.class }, - properties = { "app.crypto.key = 770A8A65DA156D24EE2A093277530142" } -) -class EncryptionBuilderIntegrationTest { - - @Autowired - EncryptionBuilder encryptionBuilder; - - @Test - void should_useProvidedBeans_when_startingContext() { - Assertions.assertThatNoException().isThrownBy(() -> encryptionBuilder.encryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")); - } -} diff --git a/wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderTest.java b/wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderTest.java deleted file mode 100644 index 21f00b052..000000000 --- a/wls-common/security/src/test/java/de/muenchen/oss/wahllokalsystem/wls/common/security/EncryptionBuilderTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.muenchen.oss.wahllokalsystem.wls.common.security; - -import de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException; -import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; -import de.muenchen.oss.wahllokalsystem.wls.common.testing.LoggerExtension; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import lombok.val; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class EncryptionBuilderTest { - - @RegisterExtension - public LoggerExtension loggerExtension = new LoggerExtension(); - - @Mock - Cipher cipher; - - @Mock - ServiceIDFormatter formatter; - - @InjectMocks - EncryptionBuilder unitUnderTest; - - @Nested - class DecryptValue { - - @Test - void successful() throws IllegalBlockSizeException, BadPaddingException { - Mockito.when(cipher.doFinal("376526723AFDAB3D".getBytes())).thenReturn("mockedText".getBytes()); - Assertions.assertThat(unitUnderTest.decryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")).isEqualTo("mockedText"); - } - - @Test - void emptyValue() { - Assertions.assertThat(unitUnderTest.decryptValue("")).isEmpty(); - } - - @Test - void valueIsNull() { - Assertions.assertThat(unitUnderTest.decryptValue(null)).isNull(); - } - - @Test - void throwBadPadding() throws IllegalBlockSizeException, BadPaddingException { - val mockedBadPaddingException = new BadPaddingException("MockedBadPadding"); - Mockito.when(formatter.getId()).thenReturn("1"); - Mockito.when(cipher.doFinal("376526723AFDAB3D".getBytes())).thenThrow(mockedBadPaddingException); - - Assertions.assertThatThrownBy(() -> unitUnderTest.decryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")) - .isInstanceOf(TechnischeWlsException.class); - } - - @Test - void throwIllegalBlockSize() throws IllegalBlockSizeException, BadPaddingException { - val mockedIllegalBlockSizeException = new IllegalBlockSizeException("MockedIllegalBlockSize"); - Mockito.when(formatter.getId()).thenReturn("1"); - Mockito.when(cipher.doFinal("376526723AFDAB3D".getBytes())).thenThrow(mockedIllegalBlockSizeException); - - Assertions.assertThatThrownBy(() -> unitUnderTest.decryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")) - .isInstanceOf(TechnischeWlsException.class); - } - - @Test - void correctExceptionCodeIsThrown() throws IllegalBlockSizeException, BadPaddingException { - val mockedIllegalBlockSizeException = new IllegalBlockSizeException("MockedIllegalBlockSize"); - Mockito.when(formatter.getId()).thenReturn("1"); - Mockito.when(cipher.doFinal("376526723AFDAB3D".getBytes())).thenThrow(mockedIllegalBlockSizeException); - - Assertions.assertThatThrownBy(() -> unitUnderTest.decryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")) - .hasFieldOrPropertyWithValue("code", "399"); - } - } - - @Nested - class EncryptValue { - - @Test - void successful() throws Exception { - Mockito.when(cipher.doFinal("376526723AFDAB3D".getBytes())).thenReturn("secret".getBytes()); - Assertions.assertThat(unitUnderTest.encryptValue("376526723AFDAB3D")).isEqualTo("c2VjcmV0"); - } - - @Test - void emptyValue() { - Assertions.assertThat(unitUnderTest.encryptValue("")).isEmpty(); - } - - @Test - void valueIsNull() { - Assertions.assertThat(unitUnderTest.encryptValue(null)).isNull(); - } - - @Test - void throwBadPadding() throws IllegalBlockSizeException, BadPaddingException { - val mockedBadPaddingException = new BadPaddingException("MockedBadPadding"); - Mockito.when(formatter.getId()).thenReturn("1"); - Mockito.when(cipher.doFinal("Mzc2NTI2NzIzQUZEQUIzRA==".getBytes())).thenThrow(mockedBadPaddingException); - - Assertions.assertThatThrownBy(() -> unitUnderTest.encryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")) - .isInstanceOf(TechnischeWlsException.class); - } - - @Test - void throwIllegalBlockSize() throws IllegalBlockSizeException, BadPaddingException { - val mockedIllegalBlockSizeException = new IllegalBlockSizeException("MockedIllegalBlockSize"); - Mockito.when(formatter.getId()).thenReturn("1"); - Mockito.when(cipher.doFinal("Mzc2NTI2NzIzQUZEQUIzRA==".getBytes())).thenThrow(mockedIllegalBlockSizeException); - - Assertions.assertThatThrownBy(() -> unitUnderTest.encryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")) - .isInstanceOf(TechnischeWlsException.class); - } - - @Test - void correctExceptionCodeIsThrown() throws IllegalBlockSizeException, BadPaddingException { - val mockedIllegalBlockSizeException = new IllegalBlockSizeException("MockedIllegalBlockSize"); - - Mockito.when(formatter.getId()).thenReturn("1"); - Mockito.when(cipher.doFinal("Mzc2NTI2NzIzQUZEQUIzRA==".getBytes())).thenThrow(mockedIllegalBlockSizeException); - - Assertions.assertThatThrownBy(() -> unitUnderTest.encryptValue("Mzc2NTI2NzIzQUZEQUIzRA==")) - .hasFieldOrPropertyWithValue("code", "399"); - } - } -} From 79a049da9b42cd573fcdca02b4a79f34116f56de Mon Sep 17 00:00:00 2001 From: MrSebastian <13592751+MrSebastian@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:07:54 +0100 Subject: [PATCH 3/6] fix: refactor env var for api generation --- wls-auth-service/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wls-auth-service/pom.xml b/wls-auth-service/pom.xml index ade10daef..7d761aaba 100644 --- a/wls-auth-service/pom.xml +++ b/wls-auth-service/pom.xml @@ -393,7 +393,7 @@ db-h2,dummy.clients - secret + please change me seed From fbb22e515d6008c82b0c0403effd28ba45c32d2e Mon Sep 17 00:00:00 2001 From: MrSebastian <13592751+MrSebastian@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:13:52 +0100 Subject: [PATCH 4/6] extract test data generation method to whole testsuit --- .../authservice/service/CryptoServiceTest.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java index 0fdf002b8..de289926d 100644 --- a/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java +++ b/wls-auth-service/src/test/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoServiceTest.java @@ -84,7 +84,7 @@ void should_returnEncryptionPrefix_when_nullIsGiven() { } @ParameterizedTest - @MethodSource("exceptionsMappedToWlsException") + @MethodSource("de.muenchen.oss.wahllokalsystem.authservice.service.CryptoServiceTest#exceptionsMappedToWlsException") void should_throwTechnischeWlsException_when_cipherThrowsException(final Exception exceptionThrownByCipher) throws Exception { val valueToEncrypt = "hello world"; @@ -104,12 +104,6 @@ void should_throwTechnischeWlsException_when_cipherThrowsException(final Excepti Assertions.assertThat(exception).hasNoNullFieldsOrProperties(); }); } - - public static Stream exceptionsMappedToWlsException() { - val exceptions = Set.of(new IllegalBlockSizeException(), new BadPaddingException()); - - return exceptions.stream().map(exception -> Arguments.of(exception, exception.getClass().getName())); - } } @Nested @@ -135,7 +129,7 @@ void should_returnDecryptedValue_when_valueIsGiven() throws Exception { } @ParameterizedTest - @MethodSource("exceptionsMappedToWlsException") + @MethodSource("de.muenchen.oss.wahllokalsystem.authservice.service.CryptoServiceTest#exceptionsMappedToWlsException") void should_throwTechnischeWlsException_when_cipherThrowsException(final Exception exceptionThrownByCipher) throws Exception { val encryptedValue = "the encrypted value"; val encryptedValueAsBase64WithPrefix = ENCRYPTION_PREFIX + Base64.getEncoder().encodeToString(encryptedValue.getBytes()); @@ -156,11 +150,11 @@ void should_throwTechnischeWlsException_when_cipherThrowsException(final Excepti Assertions.assertThat(exception).hasNoNullFieldsOrProperties(); }); } + } - public static Stream exceptionsMappedToWlsException() { - val exceptions = Set.of(new IllegalBlockSizeException(), new BadPaddingException()); + public static Stream exceptionsMappedToWlsException() { + val exceptions = Set.of(new IllegalBlockSizeException(), new BadPaddingException()); - return exceptions.stream().map(exception -> Arguments.of(exception, exception.getClass().getName())); - } + return exceptions.stream().map(exception -> Arguments.of(exception, exception.getClass().getName())); } } From 37d13083a0bfbe674ad0e1363e5f1c30e30b2865 Mon Sep 17 00:00:00 2001 From: MrSebastian <13592751+MrSebastian@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:16:40 +0100 Subject: [PATCH 5/6] remove logging of value after failed crypto operation --- .../wahllokalsystem/authservice/service/CryptoService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java index 3387d8555..903a0eeff 100644 --- a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java +++ b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java @@ -61,7 +61,7 @@ private String decryptValue(String value) { val finalized = decryptionCipher.doFinal(decode); return new String(finalized); } catch (IllegalBlockSizeException | BadPaddingException e) { - log.error("Unable to decrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); + log.error("Unable to decrypt the value due to " + e.getClass().getSimpleName() + ". Using direct object reference!", e); throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId()) .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); } @@ -75,7 +75,7 @@ private String encryptValue(String value) { val finalized = encryptionCipher.doFinal(value.getBytes()); value = Base64.getUrlEncoder().encodeToString(finalized); } catch (IllegalBlockSizeException | BadPaddingException e) { - log.error("Unable to encrypt the given value <" + value + "> as of an " + e.getClass().getSimpleName() + ". Using direct object reference!", e); + log.error("Unable to encrypt the value due to " + e.getClass().getSimpleName() + ". Using direct object reference!", e); throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId()) .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); } From 109e4f0caf759c460b05b66002bac662eaacac9f Mon Sep 17 00:00:00 2001 From: MrSebastian <13592751+MrSebastian@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:25:28 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=91=8C=20reviewfeedback=20-=20improve?= =?UTF-8?q?=20exception=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Viviane Johns <145964798+vjohnslhm@users.noreply.github.com> --- .../wahllokalsystem/authservice/service/CryptoService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java index 903a0eeff..bfbec6db0 100644 --- a/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java +++ b/wls-auth-service/src/main/java/de/muenchen/oss/wahllokalsystem/authservice/service/CryptoService.java @@ -63,7 +63,7 @@ private String decryptValue(String value) { } catch (IllegalBlockSizeException | BadPaddingException e) { log.error("Unable to decrypt the value due to " + e.getClass().getSimpleName() + ". Using direct object reference!", e); throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId()) - .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); + .buildWithMessage("Problem bei der Entschlüsselung von Objekt-Referenzen"); } } return value; @@ -77,7 +77,7 @@ private String encryptValue(String value) { } catch (IllegalBlockSizeException | BadPaddingException e) { log.error("Unable to encrypt the value due to " + e.getClass().getSimpleName() + ". Using direct object reference!", e); throw TechnischeWlsException.withCode(ExceptionConstants.CRYPTO_EXCEPTION_CODE).inService(formatter.getId()) - .buildWithMessage("Problem bei Referenzierung/Dereferenzierung von Objekt-Referenzen"); + .buildWithMessage("Problem bei der Verschlüsselung von Objekt-Referenzen"); } } return value;