matchedConfig = applicationConfigService.findByApplicationId(applicationId).stream()
+ .filter(config -> config.key().equals(key))
+ .findFirst();
+ if (matchedConfig.isPresent()) {
+ final ApplicationConfigService.ApplicationConfig existing = matchedConfig.get();
+ applicationConfigService.createOrUpdate(new ApplicationConfigService.ApplicationConfig(existing.id(), existing.application(), existing.key(), values));
+ } else {
+ applicationConfigService.createOrUpdate(new ApplicationConfigService.ApplicationConfig(null, application, key, values));
+ }
+
final CreateApplicationConfigResponse response = new CreateApplicationConfigResponse();
response.setApplicationId(applicationId);
response.setKey(key);
diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptableData.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptableData.java
new file mode 100644
index 000000000..691f4189b
--- /dev/null
+++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptableData.java
@@ -0,0 +1,57 @@
+/*
+ * PowerAuth Server and related software components
+ * Copyright (C) 2024 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package io.getlime.security.powerauth.app.server.service.encryption;
+
+import io.getlime.security.powerauth.app.server.database.model.enumeration.EncryptionMode;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A wrapper for data encryption, keeping both the mode and the data.
+ *
+ * @param encryptionMode Encryption mode. Determine format of {@link #encryptedData()}.
+ * @param encryptedData Data. May be plain or encrypted. Depends on {@link #encryptionMode()}.
+ * @author Lubos Racansky, lubos.racansky@wultra.com
+ */
+public record EncryptableData(EncryptionMode encryptionMode, byte[] encryptedData) {
+ @Override
+ public String toString() {
+ return "EncryptableRecord{" +
+ "encryptionMode=" + encryptionMode +
+ ", encryptedDataLength=" + ArrayUtils.getLength(encryptedData) +
+ '}';
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof final EncryptableData that)) {
+ return false;
+ }
+ return Objects.deepEquals(encryptedData, that.encryptedData) && encryptionMode == that.encryptionMode;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(encryptionMode, Arrays.hashCode(encryptedData));
+ }
+}
diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/Encryptable.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptableString.java
similarity index 66%
rename from powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/Encryptable.java
rename to powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptableString.java
index 3b4ed6822..a06b3f92d 100644
--- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/Encryptable.java
+++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptableString.java
@@ -20,27 +20,12 @@
import io.getlime.security.powerauth.app.server.database.model.enumeration.EncryptionMode;
/**
- * A generic wrapper for encryption, keeping both the mode and the data.
+ * A wrapper for String encryption, keeping both the mode and the data.
*
+ * @param encryptionMode Encryption mode. Determine format of {@link #encryptedData()}.
+ * @param encryptedData Data. May be plain or encrypted. Depends on {@link #encryptionMode()}.
* @author Lubos Racansky, lubos.racansky@wultra.com
*/
-public interface Encryptable {
+public record EncryptableString(EncryptionMode encryptionMode, String encryptedData) {
- /**
- * Return encryption mode.
- *
- * Determine format of {@link #getEncryptedData()}.
- *
- * @return encryption mode
- */
- EncryptionMode getEncryptionMode();
-
- /**
- * Return the data.
- *
- * May be plain or encrypted. Depends on {@link #getEncryptionMode()}.
- *
- * @return encrypted or plain data
- */
- byte[] getEncryptedData();
}
diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptionService.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptionService.java
index e9dcb925e..6f7411aae 100644
--- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptionService.java
+++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/encryption/EncryptionService.java
@@ -17,20 +17,6 @@
*/
package io.getlime.security.powerauth.app.server.service.encryption;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.List;
-import java.util.Objects;
-
-import javax.crypto.SecretKey;
-
-import org.apache.commons.lang3.ArrayUtils;
-import org.springframework.stereotype.Service;
-
import io.getlime.security.powerauth.app.server.configuration.PowerAuthServiceConfiguration;
import io.getlime.security.powerauth.app.server.database.model.enumeration.EncryptionMode;
import io.getlime.security.powerauth.app.server.service.exceptions.GenericServiceException;
@@ -43,6 +29,16 @@
import io.getlime.security.powerauth.crypto.lib.util.KeyConvertor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.crypto.SecretKey;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
/**
* Service for encryption and decryption database data.
@@ -62,6 +58,23 @@ public class EncryptionService {
private final AESEncryptionUtils aesEncryptionUtils = new AESEncryptionUtils();
private final KeyConvertor keyConvertor = new KeyConvertor();
+
+ /**
+ * Decrypt the given string.
+ *
+ * @param dataString String to decrypt.
+ * @param encryptionMode Encryption mode.
+ * @param secretKeyDerivationInput Values used for derivation of secret key.
+ * @return Decrypted value.
+ * @see #decrypt(byte[], EncryptionMode, List) if you want to encrypt binary data.
+ * @throws GenericServiceException In case decryption fails.
+ */
+ public String decrypt(final String dataString, final EncryptionMode encryptionMode, final List secretKeyDerivationInput) throws GenericServiceException {
+ final byte[] dataBytes = convert(dataString, encryptionMode);
+ final byte[] decrypted = decrypt(dataBytes, encryptionMode, secretKeyDerivationInput);
+ return new String(decrypted, StandardCharsets.UTF_8);
+ }
+
/**
* Decrypt the given data.
*
@@ -69,6 +82,7 @@ public class EncryptionService {
* @param encryptionMode Encryption mode.
* @param secretKeyDerivationInput Values used for derivation of secret key.
* @return Decrypted value.
+ * @see #decrypt(String, EncryptionMode, List) if you want to encrypt string data.
* @throws GenericServiceException In case decryption fails.
*/
public byte[] decrypt(final byte[] data, final EncryptionMode encryptionMode, final List secretKeyDerivationInput) throws GenericServiceException {
@@ -127,20 +141,36 @@ public byte[] decrypt(final byte[] data, final EncryptionMode encryptionMode, fi
}
}
+ /**
+ * Encrypt the given string.
+ *
+ * @param data String to encrypt.
+ * @param secretKeyDerivations Values used for derivation of secret key.
+ * @return Encryptable composite data.
+ * @see #encrypt(byte[], List) if you want to encrypt binary data.
+ * @throws GenericServiceException Thrown when encryption fails.
+ */
+ public EncryptableString encrypt(final String data, final List secretKeyDerivations) throws GenericServiceException {
+ final byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
+ final EncryptableData result = encrypt(dataBytes, secretKeyDerivations);
+ return new EncryptableString(result.encryptionMode(), convert(result));
+ }
+
/**
* Encrypt the given data.
*
* @param data Data to encrypt.
* @param secretKeyDerivations Values used for derivation of secret key.
* @return Encryptable composite data.
+ * @see #encrypt(String, List) if you want to encrypt binary data.
* @throws GenericServiceException Thrown when encryption fails.
*/
- public Encryptable encrypt(final byte[] data, final List secretKeyDerivations) throws GenericServiceException {
+ public EncryptableData encrypt(final byte[] data, final List secretKeyDerivations) throws GenericServiceException {
final String masterDbEncryptionKeyBase64 = powerAuthServiceConfiguration.getMasterDbEncryptionKey();
// In case master DB encryption key does not exist, do not encrypt the value
if (masterDbEncryptionKeyBase64 == null || masterDbEncryptionKeyBase64.isEmpty()) {
- return new EncryptableRecord(EncryptionMode.NO_ENCRYPTION, data);
+ return new EncryptableData(EncryptionMode.NO_ENCRYPTION, data);
}
try {
@@ -162,7 +192,7 @@ public Encryptable encrypt(final byte[] data, final List secretKeyDeriva
baos.write(encrypted);
final byte[] encryptedData = baos.toByteArray();
- return new EncryptableRecord(EncryptionMode.AES_HMAC, encryptedData);
+ return new EncryptableData(EncryptionMode.AES_HMAC, encryptedData);
} catch (InvalidKeyException ex) {
logger.error(ex.getMessage(), ex);
throw localizationProvider.buildExceptionForCode(ServiceError.INVALID_KEY_FORMAT);
@@ -195,39 +225,19 @@ private SecretKey deriveSecretKey(SecretKey masterDbEncryptionKey, final List
- applicationConfig.getValues().stream()
+ applicationConfig.values().stream()
.filter(String.class::isInstance)
.map(String.class::cast)
.map(certPem -> {
diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/persistence/ApplicationConfigService.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/persistence/ApplicationConfigService.java
new file mode 100644
index 000000000..4ab0e7716
--- /dev/null
+++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/persistence/ApplicationConfigService.java
@@ -0,0 +1,122 @@
+/*
+ * PowerAuth Server and related software components
+ * Copyright (C) 2024 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package io.getlime.security.powerauth.app.server.service.persistence;
+
+import io.getlime.security.powerauth.app.server.database.model.converter.ListToJsonConverter;
+import io.getlime.security.powerauth.app.server.database.model.entity.ApplicationConfigEntity;
+import io.getlime.security.powerauth.app.server.database.model.entity.ApplicationEntity;
+import io.getlime.security.powerauth.app.server.database.repository.ApplicationConfigRepository;
+import io.getlime.security.powerauth.app.server.service.encryption.EncryptableString;
+import io.getlime.security.powerauth.app.server.service.encryption.EncryptionService;
+import io.getlime.security.powerauth.app.server.service.exceptions.GenericServiceException;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Service for application configuration.
+ *
+ * @author Lubos Racansky, lubos.racansky@wultra.com
+ */
+@Service
+@Slf4j
+@AllArgsConstructor
+public class ApplicationConfigService {
+
+ private final ApplicationConfigRepository applicationConfigRepository;
+ private final ListToJsonConverter listToJsonConverter;
+ private final EncryptionService encryptionService;
+
+ /**
+ * Find application configuration by application ID.
+ *
+ * @param applicationId Application ID.
+ * @param key Config key.
+ * @return Application config or empty.
+ */
+ @Transactional(readOnly = true)
+ public Optional findByApplicationIdAndKey(final String applicationId, final String key) {
+ return applicationConfigRepository.findByApplicationIdAndKey(applicationId, key)
+ .map(this::convert);
+ }
+
+ /**
+ * Find application configurations by application ID.
+ *
+ * @param applicationId Application ID.
+ * @return List of application config entities.
+ */
+ @Transactional(readOnly = true)
+ public List findByApplicationId(final String applicationId) {
+ return applicationConfigRepository.findByApplicationId(applicationId).stream()
+ .map(this::convert)
+ .toList();
+ }
+
+ @Transactional
+ public void createOrUpdate(final ApplicationConfig source) throws GenericServiceException {
+ applicationConfigRepository.save(convert(source));
+ }
+
+ private ApplicationConfigEntity convert(final ApplicationConfig source) throws GenericServiceException {
+ final ApplicationConfigEntity entity = new ApplicationConfigEntity();
+ entity.setRid(source.id());
+ entity.setApplication(source.application());
+ entity.setKey(source.key());
+
+ final String value = listToJsonConverter.convertToDatabaseColumn(source.values());
+ final EncryptableString encryptable = encryptionService.encrypt(value, secretKeyDerivationInput(entity));
+ entity.setValues(encryptable.encryptedData());
+ entity.setEncryptionMode(encryptable.encryptionMode());
+
+ return entity;
+ }
+
+ private ApplicationConfig convert(ApplicationConfigEntity source) {
+ try {
+ final String decrypted = encryptionService.decrypt(source.getValues(), source.getEncryptionMode(), secretKeyDerivationInput(source));
+ final List