This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new f4a8ae5cad [SYNCOPE-1932] Improve AES management (#1241)
f4a8ae5cad is described below
commit f4a8ae5cad9d574e7d333cd8d4354ded91f47381
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Nov 19 13:13:33 2025 +0100
[SYNCOPE-1932] Improve AES management (#1241)
---
.../syncope/core/logic/AccessTokenLogic.java | 5 +-
.../persistence/jpa/PersistenceTestContext.java | 5 +-
.../src/test/resources/core-test.properties | 2 +-
.../persistence/neo4j/PersistenceTestContext.java | 5 +-
.../src/test/resources/core-test.properties | 2 +-
.../java/data/AnyTypeDataBinderImpl.java | 14 ++-
.../provisioning/java/utils/ConnObjectUtils.java | 4 +-
.../src/test/resources/core-test.properties | 2 +-
.../src/test/resources/core-debug.properties | 2 +-
.../core/spring/security/DefaultEncryptor.java | 103 ++++++++++++---------
.../spring/security/DefaultEncryptorManager.java | 12 ++-
.../core/spring/security/SecurityContext.java | 11 +--
.../core/spring/security/SecurityProperties.java | 10 +-
.../core/spring/SpringTestConfiguration.java | 7 +-
.../core/spring/security/DefaultEncryptorTest.java | 12 ++-
core/starter/src/main/resources/core.properties | 9 +-
.../workflow/java/AbstractUserWorkflowAdapter.java | 4 +-
.../src/main/resources/core-embedded.properties | 2 +-
.../org/apache/syncope/fit/AbstractITCase.java | 8 +-
.../apache/syncope/fit/core/KeymasterITCase.java | 4 +-
.../configuration/configurationparameters.adoc | 15 +--
21 files changed, 146 insertions(+), 92 deletions(-)
diff --git
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
index 3e4b17f7fe..f2861a0e65 100644
---
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
+++
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AccessTokenLogic.java
@@ -63,9 +63,8 @@ public class AccessTokenLogic extends
AbstractTransactionalLogic<AccessTokenTO>
protected byte[] getAuthorities() {
byte[] authorities = null;
try {
- authorities =
encryptorManager.getInstance().encode(POJOHelper.serialize(
- AuthContextUtils.getAuthorities()), CipherAlgorithm.AES).
- getBytes();
+ authorities = encryptorManager.getInstance().
+
encode(POJOHelper.serialize(AuthContextUtils.getAuthorities()),
CipherAlgorithm.AES).getBytes();
} catch (Exception e) {
LOG.error("Could not fetch authorities", e);
}
diff --git
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
index 204076a950..3c8d31286d 100644
---
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
+++
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/PersistenceTestContext.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.persistence.jpa;
import jakarta.persistence.EntityManagerFactory;
import javax.sql.DataSource;
+import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
import org.apache.syncope.common.keymaster.client.api.DomainOps;
import org.apache.syncope.common.keymaster.client.api.model.JPADomain;
@@ -115,7 +116,9 @@ public class PersistenceTestContext {
@Bean
public EncryptorManager encryptorManager() {
- return new DefaultEncryptorManager();
+ SecurityProperties securityProperties = new SecurityProperties();
+ securityProperties.setAesSecretKey(StringUtils.EMPTY);
+ return new DefaultEncryptorManager(securityProperties);
}
@Bean
diff --git a/core/persistence-jpa/src/test/resources/core-test.properties
b/core/persistence-jpa/src/test/resources/core-test.properties
index 1b3f95feeb..d904f8db5c 100644
--- a/core/persistence-jpa/src/test/resources/core-test.properties
+++ b/core/persistence-jpa/src/test/resources/core-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.db-type=${DB_TYPE}
diff --git
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/PersistenceTestContext.java
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/PersistenceTestContext.java
index e20638c87c..29cb7fe845 100644
---
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/PersistenceTestContext.java
+++
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/PersistenceTestContext.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.persistence.neo4j;
import javax.cache.CacheManager;
import javax.cache.Caching;
+import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
import org.apache.syncope.common.keymaster.client.api.DomainOps;
import org.apache.syncope.common.keymaster.client.api.model.Neo4jDomain;
@@ -107,7 +108,9 @@ public class PersistenceTestContext {
@Bean
public EncryptorManager encryptorManager() {
- return new DefaultEncryptorManager();
+ SecurityProperties securityProperties = new SecurityProperties();
+ securityProperties.setAesSecretKey(StringUtils.EMPTY);
+ return new DefaultEncryptorManager(securityProperties);
}
@Bean
diff --git a/core/persistence-neo4j/src/test/resources/core-test.properties
b/core/persistence-neo4j/src/test/resources/core-test.properties
index 4df24339aa..ba76db08db 100644
--- a/core/persistence-neo4j/src/test/resources/core-test.properties
+++ b/core/persistence-neo4j/src/test/resources/core-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].key=Master
persistence.domain[0].uri=bolt://${NEO4J_CONTAINER_IP}:7687/
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyTypeDataBinderImpl.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyTypeDataBinderImpl.java
index 1b2f9a840d..111bbda798 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyTypeDataBinderImpl.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyTypeDataBinderImpl.java
@@ -96,9 +96,8 @@ public class AnyTypeDataBinderImpl implements
AnyTypeDataBinder {
added.forEach(e -> authorities.add(new
SyncopeGrantedAuthority(e, SyncopeConstants.ROOT_REALM)));
-
accessToken.setAuthorities(encryptorManager.getInstance().encode(
- POJOHelper.serialize(authorities),
CipherAlgorithm.AES).
- getBytes());
+ accessToken.setAuthorities(encryptorManager.getInstance().
+ encode(POJOHelper.serialize(authorities),
CipherAlgorithm.AES).getBytes());
accessTokenDAO.save(accessToken);
} catch (Exception e) {
@@ -142,17 +141,16 @@ public class AnyTypeDataBinderImpl implements
AnyTypeDataBinder {
orElseThrow(() -> new NotFoundException("AccessToken for "
+ AuthContextUtils.getUsername()));
try {
Set<SyncopeGrantedAuthority> authorities = new
HashSet<>(POJOHelper.deserialize(
- encryptorManager.getInstance().decode(
- new String(accessToken.getAuthorities()),
CipherAlgorithm.AES),
+ encryptorManager.getInstance().
+ decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
new TypeReference<Set<SyncopeGrantedAuthority>>() {
}));
authorities.removeAll(authorities.stream().
filter(authority ->
removed.contains(authority.getAuthority())).toList());
-
accessToken.setAuthorities(encryptorManager.getInstance().encode(
- POJOHelper.serialize(authorities),
CipherAlgorithm.AES).
- getBytes());
+ accessToken.setAuthorities(encryptorManager.getInstance().
+ encode(POJOHelper.serialize(authorities),
CipherAlgorithm.AES).getBytes());
accessTokenDAO.save(accessToken);
} catch (Exception e) {
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index bde38d1b7f..6f91109ab3 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -269,8 +269,8 @@ public class ConnObjectUtils {
// update password if and only if password is really changed
User user = userDAO.authFind(key);
if (StringUtils.isBlank(updatedUser.getPassword())
- ||
encryptorManager.getInstance().verify(updatedUser.getPassword(),
- user.getCipherAlgorithm(),
user.getPassword())) {
+ || encryptorManager.getInstance().
+ verify(updatedUser.getPassword(),
user.getCipherAlgorithm(), user.getPassword())) {
updatedUser.setPassword(null);
}
diff --git a/core/provisioning-java/src/test/resources/core-test.properties
b/core/provisioning-java/src/test/resources/core-test.properties
index 6458168bbc..10da9c173c 100644
--- a/core/provisioning-java/src/test/resources/core-test.properties
+++ b/core/provisioning-java/src/test/resources/core-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].key=Master
persistence.domain[0].jdbcDriver=org.postgresql.Driver
diff --git
a/core/self-keymaster-starter/src/test/resources/core-debug.properties
b/core/self-keymaster-starter/src/test/resources/core-debug.properties
index 05e963d862..4faa0d847c 100644
--- a/core/self-keymaster-starter/src/test/resources/core-debug.properties
+++ b/core/self-keymaster-starter/src/test/resources/core-debug.properties
@@ -29,7 +29,7 @@ keymaster.password=${anonymousKey}
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].key=Master
persistence.domain[0].jdbcDriver=org.postgresql.Driver
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptor.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptor.java
index 4da2b719f7..7b0824ba87 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptor.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptor.java
@@ -23,15 +23,15 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
-import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
-import org.apache.syncope.core.persistence.api.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.Encryptor;
import org.jasypt.commons.CommonUtils;
import org.jasypt.digest.StandardStringDigester;
@@ -43,33 +43,58 @@ public class DefaultEncryptor implements Encryptor {
protected static final Logger LOG =
LoggerFactory.getLogger(DefaultEncryptor.class);
- protected static final String DEFAULT_SECRET_KEY =
"1abcdefghilmnopqrstuvz2!";
+ protected final SecurityProperties.DigesterProperties digesterProperties;
protected final Map<CipherAlgorithm, StandardStringDigester> digesters =
new ConcurrentHashMap<>();
- protected SecretKeySpec keySpec;
+ protected final Optional<SecretKeySpec> aesKeySpec;
- protected DefaultEncryptor(final String secretKey) {
- String actualKey = secretKey;
- if (actualKey.length() < 16) {
- StringBuilder actualKeyPadding = new StringBuilder(actualKey);
- int length = 16 - actualKey.length();
- String randomChars =
SecureRandomUtils.generateRandomPassword(length);
+ protected DefaultEncryptor(
+ final String aesSecretKey,
+ final SecurityProperties.DigesterProperties digesterProperties) {
- actualKeyPadding.append(randomChars);
- actualKey = actualKeyPadding.toString();
- LOG.warn("The secret key is too short (< 16), adding some random
characters. "
- + "Passwords encrypted with AES and this key will not be
recoverable "
- + "as a result if the container is restarted.");
- }
+ this.digesterProperties = digesterProperties;
- try {
- keySpec = new SecretKeySpec(ArrayUtils.subarray(
- actualKey.getBytes(StandardCharsets.UTF_8), 0, 16),
- CipherAlgorithm.AES.getAlgorithm());
- } catch (Exception e) {
- LOG.error("Error during key specification", e);
+ SecretKeySpec sks = null;
+
+ if (StringUtils.isNotBlank(aesSecretKey)) {
+ String actualKey = aesSecretKey;
+
+ Integer pad = null;
+ boolean truncate = false;
+ if (actualKey.length() < 16) {
+ pad = 16 - actualKey.length();
+ } else if (actualKey.length() > 16 && actualKey.length() < 24) {
+ pad = 24 - actualKey.length();
+ } else if (actualKey.length() > 24 && actualKey.length() < 32) {
+ pad = 32 - actualKey.length();
+ } else if (actualKey.length() > 32) {
+ truncate = true;
+ }
+
+ if (pad != null) {
+ StringBuilder actualKeyPadding = new StringBuilder(actualKey);
+ String randomChars =
SecureRandomUtils.generateRandomPassword(pad);
+
+ actualKeyPadding.append(randomChars);
+ actualKey = actualKeyPadding.toString();
+ LOG.warn("The configured AES secret key is too short (< {}),
padding with random chars: {}",
+ actualKey.length(), actualKey);
+ }
+ if (truncate) {
+ actualKey = actualKey.substring(0, 32);
+ LOG.warn("The configured AES secret key is too long (> 32),
truncating: {}", actualKey);
+ }
+
+ try {
+ sks = new
SecretKeySpec(actualKey.getBytes(StandardCharsets.UTF_8),
CipherAlgorithm.AES.getAlgorithm());
+ LOG.debug("AES-{} successfully configured", actualKey.length()
* 8);
+ } catch (Exception e) {
+ LOG.error("Error during key specification", e);
+ }
}
+
+ aesKeySpec = Optional.ofNullable(sks);
}
@Override
@@ -82,7 +107,8 @@ public class DefaultEncryptor implements Encryptor {
if (value != null) {
if (cipherAlgorithm == null || cipherAlgorithm ==
CipherAlgorithm.AES) {
Cipher cipher =
Cipher.getInstance(CipherAlgorithm.AES.getAlgorithm());
- cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+ cipher.init(Cipher.ENCRYPT_MODE, aesKeySpec.
+ orElseThrow(() -> new IllegalArgumentException("AES
not configured")));
encoded =
Base64.getEncoder().encodeToString(cipher.doFinal(value.getBytes(StandardCharsets.UTF_8)));
} else if (cipherAlgorithm == CipherAlgorithm.BCRYPT) {
@@ -125,7 +151,8 @@ public class DefaultEncryptor implements Encryptor {
if (encoded != null && cipherAlgorithm == CipherAlgorithm.AES) {
Cipher cipher =
Cipher.getInstance(CipherAlgorithm.AES.getAlgorithm());
- cipher.init(Cipher.DECRYPT_MODE, keySpec);
+ cipher.init(Cipher.DECRYPT_MODE, aesKeySpec.
+ orElseThrow(() -> new IllegalArgumentException("AES not
configured")));
decoded = new
String(cipher.doFinal(Base64.getDecoder().decode(encoded)),
StandardCharsets.UTF_8);
}
@@ -133,25 +160,20 @@ public class DefaultEncryptor implements Encryptor {
return decoded;
}
- private StandardStringDigester getDigester(final CipherAlgorithm
cipherAlgorithm) {
- StandardStringDigester digester = digesters.get(cipherAlgorithm);
- if (digester == null) {
- digester = new StandardStringDigester();
+ protected StandardStringDigester getDigester(final CipherAlgorithm
cipherAlgorithm) {
+ return digesters.computeIfAbsent(cipherAlgorithm, k -> {
+ StandardStringDigester digester = new StandardStringDigester();
if (cipherAlgorithm.getAlgorithm().startsWith("S-")) {
- SecurityProperties securityProperties =
-
ApplicationContextProvider.getApplicationContext().getBean(SecurityProperties.class);
-
// Salted ...
digester.setAlgorithm(cipherAlgorithm.getAlgorithm().replaceFirst("S\\-", ""));
-
digester.setIterations(securityProperties.getDigester().getSaltIterations());
-
digester.setSaltSizeBytes(securityProperties.getDigester().getSaltSizeBytes());
+ digester.setIterations(digesterProperties.getSaltIterations());
+
digester.setSaltSizeBytes(digesterProperties.getSaltSizeBytes());
digester.setInvertPositionOfPlainSaltInEncryptionResults(
-
securityProperties.getDigester().isInvertPositionOfPlainSaltInEncryptionResults());
+
digesterProperties.isInvertPositionOfPlainSaltInEncryptionResults());
digester.setInvertPositionOfSaltInMessageBeforeDigesting(
-
securityProperties.getDigester().isInvertPositionOfSaltInMessageBeforeDigesting());
- digester.setUseLenientSaltSizeCheck(
-
securityProperties.getDigester().isUseLenientSaltSizeCheck());
+
digesterProperties.isInvertPositionOfSaltInMessageBeforeDigesting());
+
digester.setUseLenientSaltSizeCheck(digesterProperties.isUseLenientSaltSizeCheck());
} else {
// Not salted ...
digester.setAlgorithm(cipherAlgorithm.getAlgorithm());
@@ -160,10 +182,7 @@ public class DefaultEncryptor implements Encryptor {
}
digester.setStringOutputType(CommonUtils.STRING_OUTPUT_TYPE_HEXADECIMAL);
-
- digesters.put(cipherAlgorithm, digester);
- }
-
- return digester;
+ return digester;
+ });
}
}
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptorManager.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptorManager.java
index 903d5c3841..f0d5776f78 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptorManager.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultEncryptorManager.java
@@ -26,16 +26,22 @@ import
org.apache.syncope.core.persistence.api.EncryptorManager;
public class DefaultEncryptorManager implements EncryptorManager {
+ protected final SecurityProperties securityProperties;
+
protected final Map<String, DefaultEncryptor> instances = new
ConcurrentHashMap<>();
+ public DefaultEncryptorManager(final SecurityProperties
securityProperties) {
+ this.securityProperties = securityProperties;
+ }
+
@Override
public Encryptor getInstance() {
return getInstance(null);
}
@Override
- public Encryptor getInstance(final String secretKey) {
- String actualKey = StringUtils.isBlank(secretKey) ?
DefaultEncryptor.DEFAULT_SECRET_KEY : secretKey;
- return instances.computeIfAbsent(actualKey, DefaultEncryptor::new);
+ public Encryptor getInstance(final String aesSecretKey) {
+ String actualKey = StringUtils.isBlank(aesSecretKey) ?
securityProperties.getAesSecretKey() : aesSecretKey;
+ return instances.computeIfAbsent(actualKey, k -> new
DefaultEncryptor(k, securityProperties.getDigester()));
}
}
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
index d01dd14288..fdcc2a14f6 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
@@ -27,6 +27,7 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
+import java.util.Optional;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.EncryptorManager;
@@ -64,10 +65,8 @@ public class SecurityContext {
}
protected static String jwsKey(final JWSAlgorithm jwsAlgorithm, final
SecurityProperties props) {
- String jwsKey = props.getJwsKey();
- if (jwsKey == null) {
- throw new IllegalArgumentException("No JWS key provided");
- }
+ String jwsKey = Optional.ofNullable(props.getJwsKey()).
+ orElseThrow(() -> new IllegalArgumentException("No JWS key
provided"));
if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
int minLength = jwsAlgorithm.equals(JWSAlgorithm.HS256)
@@ -158,8 +157,8 @@ public class SecurityContext {
}
@Bean
- public EncryptorManager encryptorManager() {
- return new DefaultEncryptorManager();
+ public EncryptorManager encryptorManager(final SecurityProperties
securityProperties) {
+ return new DefaultEncryptorManager(securityProperties);
}
@ConditionalOnMissingBean
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityProperties.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityProperties.java
index c5d9690117..cb9ffeb6bb 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityProperties.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityProperties.java
@@ -98,7 +98,7 @@ public class SecurityProperties {
private String jwsAlgorithm = JWSAlgorithm.HS512.getName();
- private String secretKey;
+ private String aesSecretKey;
private String groovyBlacklist = "classpath:META-INF/groovy.blacklist";
@@ -168,12 +168,12 @@ public class SecurityProperties {
this.jwsAlgorithm = jwsAlgorithm;
}
- public String getSecretKey() {
- return secretKey;
+ public String getAesSecretKey() {
+ return aesSecretKey;
}
- public void setSecretKey(final String secretKey) {
- this.secretKey = secretKey;
+ public void setAesSecretKey(final String secretKey) {
+ this.aesSecretKey = secretKey;
}
public String getGroovyBlacklist() {
diff --git
a/core/spring/src/test/java/org/apache/syncope/core/spring/SpringTestConfiguration.java
b/core/spring/src/test/java/org/apache/syncope/core/spring/SpringTestConfiguration.java
index 5547d28dff..82313849c4 100644
---
a/core/spring/src/test/java/org/apache/syncope/core/spring/SpringTestConfiguration.java
+++
b/core/spring/src/test/java/org/apache/syncope/core/spring/SpringTestConfiguration.java
@@ -27,6 +27,7 @@ import
org.apache.syncope.core.persistence.api.EncryptorManager;
import org.apache.syncope.core.provisioning.api.ImplementationLookup;
import org.apache.syncope.core.spring.security.DefaultEncryptorManager;
import org.apache.syncope.core.spring.security.DummyImplementationLookup;
+import org.apache.syncope.core.spring.security.SecurityProperties;
import org.jenkinsci.plugins.scriptsecurity.sandbox.blacklists.Blacklist;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -37,6 +38,8 @@ import org.springframework.context.annotation.Primary;
@Configuration(proxyBeanMethods = false)
public class SpringTestConfiguration {
+ public static final String AES_SECRET_KEY = "1abcdefghilmnopq";
+
@Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
@@ -44,7 +47,9 @@ public class SpringTestConfiguration {
@Bean
public EncryptorManager encryptorManager() {
- return new DefaultEncryptorManager();
+ SecurityProperties securityProperties = new SecurityProperties();
+ securityProperties.setAesSecretKey(AES_SECRET_KEY);
+ return new DefaultEncryptorManager(securityProperties);
}
@Primary
diff --git
a/core/spring/src/test/java/org/apache/syncope/core/spring/security/DefaultEncryptorTest.java
b/core/spring/src/test/java/org/apache/syncope/core/spring/security/DefaultEncryptorTest.java
index 3b55d76559..bba0d18cf9 100644
---
a/core/spring/src/test/java/org/apache/syncope/core/spring/security/DefaultEncryptorTest.java
+++
b/core/spring/src/test/java/org/apache/syncope/core/spring/security/DefaultEncryptorTest.java
@@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.Encryptor;
+import org.apache.syncope.core.spring.SpringTestConfiguration;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -37,8 +38,13 @@ public class DefaultEncryptorTest {
@BeforeAll
public static void setUp() {
-
ApplicationContextProvider.getBeanFactory().registerSingleton("securityProperties",
new SecurityProperties());
- ENCRYPTOR = new DefaultEncryptorManager().getInstance();
+ SecurityProperties props = new SecurityProperties();
+ props.setAesSecretKey(SpringTestConfiguration.AES_SECRET_KEY);
+
ApplicationContextProvider.getBeanFactory().registerSingleton("securityProperties",
props);
+
+ SecurityProperties securityProperties = new SecurityProperties();
+
securityProperties.setAesSecretKey(SpringTestConfiguration.AES_SECRET_KEY);
+ ENCRYPTOR = new
DefaultEncryptorManager(securityProperties).getInstance();
}
@Test
@@ -68,7 +74,7 @@ public class DefaultEncryptorTest {
@Test
public void smallKey() throws Exception {
- DefaultEncryptor smallKeyEncryptor = new DefaultEncryptor("123");
+ DefaultEncryptor smallKeyEncryptor = new DefaultEncryptor("123", new
SecurityProperties().getDigester());
String encPassword = smallKeyEncryptor.encode(PASSWORD_VALUE,
CipherAlgorithm.AES);
String decPassword = smallKeyEncryptor.decode(encPassword,
CipherAlgorithm.AES);
assertEquals(PASSWORD_VALUE, decPassword);
diff --git a/core/starter/src/main/resources/core.properties
b/core/starter/src/main/resources/core.properties
index 54595f25fe..59b6e05dd8 100644
--- a/core/starter/src/main/resources/core.properties
+++ b/core/starter/src/main/resources/core.properties
@@ -91,7 +91,14 @@ security.jwtIssuer=ApacheSyncope
security.jwsAlgorithm=HS512
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+# Key length drives AES algorithm variant selection:
+#
+# * 16 chars => AES-128
+# * 24 chars => AES-192
+# * 32 chars => AES-256
+#
+# Shorter keys will be padded to the nearest longer option available; keys >
32 will be trucated
+security.aesSecretKey=${secretKey}
# default for LDAP / RFC2307 SSHA
security.digester.saltIterations=1
diff --git
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
index 496f1791a3..013af583a9 100644
---
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
+++
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
@@ -137,8 +137,8 @@ public abstract class AbstractUserWorkflowAdapter extends
AbstractWorkflowAdapte
matching =
pwdHistory.subList(policy.getHistoryLength() >= pwdHistory.size()
? 0
: pwdHistory.size() -
policy.getHistoryLength(), pwdHistory.size()).stream().
- map(old ->
encryptorManager.getInstance().verify(
- clearPassword, user.getCipherAlgorithm(),
old)).
+ map(old -> encryptorManager.getInstance().
+ verify(clearPassword,
user.getCipherAlgorithm(), old)).
reduce(matching, (accumulator, item) ->
accumulator | item);
}
if (matching) {
diff --git a/fit/core-reference/src/main/resources/core-embedded.properties
b/fit/core-reference/src/main/resources/core-embedded.properties
index d44c2cc484..d45dc0dac3 100644
--- a/fit/core-reference/src/main/resources/core-embedded.properties
+++ b/fit/core-reference/src/main/resources/core-embedded.properties
@@ -30,7 +30,7 @@ spring.devtools.restart.enabled=false
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=NyefOIpekEJVBASbbETMbcns11HouPNn
persistence.domain[0].key=Master
persistence.domain[0].jdbcDriver=org.postgresql.Driver
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 4dfd1a47e4..d3c7bf7b1e 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -178,6 +178,7 @@ import
org.apache.syncope.common.rest.api.service.wa.WAConfigService;
import
org.apache.syncope.common.rest.api.service.wa.WebAuthnRegistrationService;
import org.apache.syncope.core.persistence.api.EncryptorManager;
import org.apache.syncope.core.spring.security.DefaultEncryptorManager;
+import org.apache.syncope.core.spring.security.SecurityProperties;
import org.apache.syncope.fit.AbstractITCase.KeymasterInitializer;
import org.apache.syncope.fit.core.AbstractTaskITCase;
import org.apache.syncope.fit.core.CoreITContext;
@@ -1158,6 +1159,11 @@ public abstract class AbstractITCase {
@Autowired
protected DataSource testDataSource;
- protected final EncryptorManager encryptorManager = new
DefaultEncryptorManager();
+ protected final EncryptorManager encryptorManager;
+ protected AbstractITCase() {
+ SecurityProperties securityProperties = new SecurityProperties();
+ securityProperties.setAesSecretKey(StringUtils.EMPTY);
+ encryptorManager = new DefaultEncryptorManager(securityProperties);
+ }
}
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
index d2f523e33d..cf30a5de4e 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
@@ -337,8 +337,8 @@ public class KeymasterITCase extends AbstractITCase {
try {
// 1. change admin pwd for domain Two
domainOps.changeAdminPassword(two.getKey(),
- encryptorManager.getInstance().encode("password3",
CipherAlgorithm.AES),
- CipherAlgorithm.AES);
+ encryptorManager.getInstance().encode("password3",
CipherAlgorithm.BCRYPT),
+ CipherAlgorithm.BCRYPT);
// 2. attempt to access with old pwd -> fail
try {
diff --git
a/src/main/asciidoc/reference-guide/configuration/configurationparameters.adoc
b/src/main/asciidoc/reference-guide/configuration/configurationparameters.adoc
index bba639466b..0bfb7b5088 100644
---
a/src/main/asciidoc/reference-guide/configuration/configurationparameters.adoc
+++
b/src/main/asciidoc/reference-guide/configuration/configurationparameters.adoc
@@ -24,13 +24,16 @@ Most run-time configuration options are available as
parameters and can be tuned
* `password.cipher.algorithm` - which cipher algorithm shall be used for
encrypting password values; supported
algorithms include `SHA-1`, `SHA-256`, `SHA-512`, `AES`, `S-MD5`, `S-SHA-1`,
`S-SHA-256`, `S-SHA-512` and `BCRYPT`;
salting options are available in the `core.properties` file;
+* `security.aesSecretKey` - used for AES-based encryption / decryption:
besides password values, this is also used
+whenever reversible encryption is needed, throughout the whole system;
[WARNING]
-The value of the `security.secretKey` property in the `core.properties` file
is used for AES-based encryption / decryption.
-Besides password values, this is also used whenever reversible encryption is
needed, throughout the whole system. +
-When the `secretKey` value has length less than 16, it is right-padded by
random characters during startup, to reach
-such mininum value. +
-It is *strongly* recommended to provide a value long at least 16 characters,
in order to avoid unexpected behaviors
-at runtime, expecially with high-availability.
+The actual length of the `security.aesSecretKey` value is used to drive the
AES algorithm variant selection:
+16 characters implies `AES-128`, 24 selects `AES-192` and 32 configures
`AES-256`. +
+When the `security.aesSecretKey` value has length less than 16, between 17 and
23 or between 25 and 31, it is
+right-padded by random characters during startup, to reach the nearest option.
If the specified value is instead longer
+than 32 characters, it is truncated to 32. +
+It is *strongly* recommended to provide a value long exactly 16, 24 or 32
characters, in order to avoid unexpected
+behaviors at runtime, expecially with high-availability.
* `jwt.lifetime.minutes` - validity of
https://en.wikipedia.org/wiki/JSON_Web_Token[JSON Web Token^] values used for
<<rest-authentication-and-authorization,authentication>> (in minutes);
* `notificationjob.cronExpression` -