This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 3_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/3_0_X by this push:
new 9d706af25d [SYNCOPE-1932] Improve AES management (#1243)
9d706af25d is described below
commit 9d706af25d2e60327b8b5b63186f9da51ed79a1d
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Nov 19 16:16:57 2025 +0100
[SYNCOPE-1932] Improve AES management (#1243)
---
.../syncope/core/logic/AccessTokenLogic.java | 4 +-
.../src/test/resources/core-majson-test.properties | 2 +-
.../src/test/resources/core-myjson-test.properties | 2 +-
.../src/test/resources/core-ojson-test.properties | 2 +-
.../test/resources/core-pgjsonb-test.properties | 2 +-
.../jpa/entity/user/JPALinkedAccount.java | 22 ++--
.../core/persistence/jpa/entity/user/JPAUser.java | 26 ++--
.../src/test/resources/core-test.properties | 2 +-
.../provisioning/java/DefaultMappingManager.java | 4 +-
.../java/data/AnyTypeDataBinderImpl.java | 16 +--
.../provisioning/java/utils/ConnObjectUtils.java | 4 +-
.../src/test/resources/core-debug.properties | 2 +-
.../core/spring/policy/DefaultPasswordRule.java | 4 +-
.../spring/policy/HaveIBeenPwnedPasswordRule.java | 17 +--
.../core/spring/security/AuthDataAccessor.java | 4 +-
.../syncope/core/spring/security/Encryptor.java | 133 ++++++++++++---------
.../core/spring/security/SecurityContext.java | 7 +-
.../core/spring/security/SecurityProperties.java | 10 +-
.../spring/security/SyncopeJWTSSOProvider.java | 4 +-
.../UsernamePasswordAuthenticationProvider.java | 6 +-
.../core/spring/security/EncryptorTest.java | 4 +-
core/starter/src/main/resources/core.properties | 9 +-
.../src/main/resources/core-embedded.properties | 2 +-
.../apache/syncope/fit/core/KeymasterITCase.java | 4 +-
pom.xml | 2 +-
.../configuration/configurationparameters.adoc | 15 ++-
26 files changed, 163 insertions(+), 146 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 56d2063e2e..78b1d69b2d 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
@@ -43,12 +43,10 @@ import
org.springframework.security.access.prepost.PreAuthorize;
public class AccessTokenLogic extends
AbstractTransactionalLogic<AccessTokenTO> {
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected static byte[] getAuthorities() {
byte[] authorities = null;
try {
- authorities = ENCRYPTOR.encode(POJOHelper.serialize(
+ authorities = Encryptor.getInstance().encode(POJOHelper.serialize(
AuthContextUtils.getAuthorities()), CipherAlgorithm.AES).
getBytes();
} catch (Exception e) {
diff --git
a/core/persistence-jpa-json/src/test/resources/core-majson-test.properties
b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties
index 9f235bece1..f89cebdcb8 100644
--- a/core/persistence-jpa-json/src/test/resources/core-majson-test.properties
+++ b/core/persistence-jpa-json/src/test/resources/core-majson-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].jdbcURL=jdbc:mariadb://${DB_CONTAINER_IP}:3306/syncope?characterEncoding=UTF-8
persistence.domain[0].poolMaxActive=10
diff --git
a/core/persistence-jpa-json/src/test/resources/core-myjson-test.properties
b/core/persistence-jpa-json/src/test/resources/core-myjson-test.properties
index 4bd6124228..53a03729ad 100644
--- a/core/persistence-jpa-json/src/test/resources/core-myjson-test.properties
+++ b/core/persistence-jpa-json/src/test/resources/core-myjson-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].jdbcURL=jdbc:mysql://${DB_CONTAINER_IP}:3306/syncope?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8
persistence.domain[0].poolMaxActive=10
diff --git
a/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
b/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
index bc5d7a97a1..0565a4cdea 100644
--- a/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
+++ b/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].jdbcURL=jdbc:oracle:thin:@${DB_CONTAINER_IP}:1521/XEPDB1
#persistence.domain[0].jdbcURL=jdbc:oracle:thin:@192.168.0.176:1521/orcl
diff --git
a/core/persistence-jpa-json/src/test/resources/core-pgjsonb-test.properties
b/core/persistence-jpa-json/src/test/resources/core-pgjsonb-test.properties
index a7a7b9594a..4007496a9b 100644
--- a/core/persistence-jpa-json/src/test/resources/core-pgjsonb-test.properties
+++ b/core/persistence-jpa-json/src/test/resources/core-pgjsonb-test.properties
@@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
-security.secretKey=${secretKey}
+security.aesSecretKey=${secretKey}
persistence.domain[0].jdbcURL=jdbc:postgresql://${DB_CONTAINER_IP}:5432/syncope?stringtype=unspecified
persistence.domain[0].poolMaxActive=10
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
index cccd14bc73..663e95c8a6 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPALinkedAccount.java
@@ -61,8 +61,6 @@ public class JPALinkedAccount extends
AbstractGeneratedKeyEntity implements Link
public static final String TABLE = "LinkedAccount";
- private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
@NotNull
private String connObjectKeyValue;
@@ -151,7 +149,7 @@ public class JPALinkedAccount extends
AbstractGeneratedKeyEntity implements Link
throw new IllegalArgumentException("Cannot override existing
cipher algorithm");
}
}
-
+
@Override
public boolean canDecodeSecrets() {
return this.cipherAlgorithm != null &&
this.cipherAlgorithm.isInvertible();
@@ -168,14 +166,22 @@ public class JPALinkedAccount extends
AbstractGeneratedKeyEntity implements Link
this.cipherAlgorithm = cipherAlgoritm;
}
+ protected String encode(final String value) throws Exception {
+ return Encryptor.getInstance().encode(
+ value,
+ Optional.ofNullable(cipherAlgorithm).
+ orElseGet(() -> CipherAlgorithm.valueOf(
+
ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).get(
+ AuthContextUtils.getDomain(),
+ "password.cipher.algorithm",
+ CipherAlgorithm.AES.name(),
+ String.class))));
+ }
+
@Override
public void setPassword(final String password) {
try {
- this.password = ENCRYPTOR.encode(password, cipherAlgorithm == null
- ?
CipherAlgorithm.valueOf(ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).
- get(AuthContextUtils.getDomain(),
"password.cipher.algorithm", CipherAlgorithm.AES.name(),
- String.class))
- : cipherAlgorithm);
+ this.password = encode(password);
} catch (Exception e) {
LOG.error("Could not encode password", e);
this.password = null;
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
index e4d2f7f8d0..1efba4d64b 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
@@ -77,8 +77,6 @@ public class JPAUser
public static final String TABLE = "SyncopeUser";
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected static final TypeReference<List<String>> TYPEREF = new
TypeReference<List<String>>() {
};
@@ -223,14 +221,22 @@ public class JPAUser
setMustChangePassword(false);
}
+ protected String encode(final String value) throws Exception {
+ return Encryptor.getInstance().encode(
+ value,
+ Optional.ofNullable(cipherAlgorithm).
+ orElseGet(() -> CipherAlgorithm.valueOf(
+
ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).get(
+ AuthContextUtils.getDomain(),
+ "password.cipher.algorithm",
+ CipherAlgorithm.AES.name(),
+ String.class))));
+ }
+
@Override
public void setPassword(final String password) {
try {
- this.password = ENCRYPTOR.encode(password, cipherAlgorithm == null
- ?
CipherAlgorithm.valueOf(ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).
- get(AuthContextUtils.getDomain(),
"password.cipher.algorithm", CipherAlgorithm.AES.name(),
- String.class))
- : cipherAlgorithm);
+ this.password = encode(password);
setMustChangePassword(false);
} catch (Exception e) {
LOG.error("Could not encode password", e);
@@ -414,11 +420,7 @@ public class JPAUser
@Override
public void setSecurityAnswer(final String securityAnswer) {
try {
- this.securityAnswer = ENCRYPTOR.encode(securityAnswer,
cipherAlgorithm == null
- ?
CipherAlgorithm.valueOf(ApplicationContextProvider.getBeanFactory().getBean(ConfParamOps.class).
- get(AuthContextUtils.getDomain(),
"password.cipher.algorithm", CipherAlgorithm.AES.name(),
- String.class))
- : cipherAlgorithm);
+ this.securityAnswer = encode(securityAnswer);
} catch (Exception e) {
LOG.error("Could not encode security answer", e);
this.securityAnswer = null;
diff --git a/core/persistence-jpa/src/test/resources/core-test.properties
b/core/persistence-jpa/src/test/resources/core-test.properties
index bcd721e584..b15b2440ef 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.domain[0].key=Master
persistence.domain[0].jdbcDriver=org.h2.Driver
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
index 87e4f5ffda..a0c1a19a11 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
@@ -110,8 +110,6 @@ public class DefaultMappingManager implements
MappingManager {
protected static final Logger LOG =
LoggerFactory.getLogger(MappingManager.class);
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected final AnyTypeDAO anyTypeDAO;
protected final UserDAO userDAO;
@@ -500,7 +498,7 @@ public class DefaultMappingManager implements
MappingManager {
protected String decodePassword(final Account account) {
try {
- return ENCRYPTOR.decode(account.getPassword(),
account.getCipherAlgorithm());
+ return Encryptor.getInstance().decode(account.getPassword(),
account.getCipherAlgorithm());
} catch (Exception e) {
LOG.error("Could not decode password for {}", account, e);
return null;
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 54db91e11e..72045e94fa 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
@@ -49,8 +49,6 @@ public class AnyTypeDataBinderImpl implements
AnyTypeDataBinder {
protected static final Logger LOG =
LoggerFactory.getLogger(AnyTypeDataBinder.class);
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected final SecurityProperties securityProperties;
protected final AnyTypeDAO anyTypeDAO;
@@ -86,15 +84,14 @@ public class AnyTypeDataBinderImpl implements
AnyTypeDataBinder {
AccessToken accessToken =
accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
try {
Set<SyncopeGrantedAuthority> authorities = new
HashSet<>(POJOHelper.deserialize(
- ENCRYPTOR.decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
+ Encryptor.getInstance().decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
new TypeReference<Set<SyncopeGrantedAuthority>>() {
}));
added.forEach(e -> authorities.add(new
SyncopeGrantedAuthority(e, SyncopeConstants.ROOT_REALM)));
- accessToken.setAuthorities(ENCRYPTOR.encode(
- POJOHelper.serialize(authorities),
CipherAlgorithm.AES).
- getBytes());
+ accessToken.setAuthorities(Encryptor.getInstance().encode(
+ POJOHelper.serialize(authorities),
CipherAlgorithm.AES).getBytes());
accessTokenDAO.save(accessToken);
} catch (Exception e) {
@@ -142,16 +139,15 @@ public class AnyTypeDataBinderImpl implements
AnyTypeDataBinder {
AccessToken accessToken =
accessTokenDAO.findByOwner(AuthContextUtils.getUsername());
try {
Set<SyncopeGrantedAuthority> authorities = new
HashSet<>(POJOHelper.deserialize(
- ENCRYPTOR.decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
+ Encryptor.getInstance().decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
new TypeReference<Set<SyncopeGrantedAuthority>>() {
}));
authorities.removeAll(authorities.stream().
filter(authority ->
removed.contains(authority.getAuthority())).collect(Collectors.toList()));
- accessToken.setAuthorities(ENCRYPTOR.encode(
- POJOHelper.serialize(authorities),
CipherAlgorithm.AES).
- getBytes());
+ accessToken.setAuthorities(Encryptor.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 9390daafa3..f537ebc78e 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
@@ -68,8 +68,6 @@ public class ConnObjectUtils {
protected static final Logger LOG =
LoggerFactory.getLogger(ConnObjectUtils.class);
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
public static SyncToken toSyncToken(final String syncToken) {
return Optional.ofNullable(syncToken).map(st ->
POJOHelper.deserialize(st, SyncToken.class)).orElse(null);
}
@@ -266,7 +264,7 @@ public class ConnObjectUtils {
// update password if and only if password is really changed
User user = userDAO.authFind(key);
if (StringUtils.isBlank(updatedUser.getPassword())
- || ENCRYPTOR.verify(updatedUser.getPassword(),
+ ||
Encryptor.getInstance().verify(updatedUser.getPassword(),
user.getCipherAlgorithm(),
user.getPassword())) {
updatedUser.setPassword(null);
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 6876fce530..7425b3bb20 100644
--- a/core/self-keymaster-starter/src/test/resources/core-debug.properties
+++ b/core/self-keymaster-starter/src/test/resources/core-debug.properties
@@ -33,7 +33,7 @@ spring.h2.console.path=/h2
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.h2.Driver
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
index e9a63c2af7..224b2f46b4 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/DefaultPasswordRule.java
@@ -59,8 +59,6 @@ public class DefaultPasswordRule implements PasswordRule {
protected static final Logger LOG =
LoggerFactory.getLogger(DefaultPasswordRule.class);
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
public static List<Rule> conf2Rules(final DefaultPasswordRuleConf conf) {
List<Rule> rules = new ArrayList<>();
@@ -205,7 +203,7 @@ public class DefaultPasswordRule implements PasswordRule {
String clear = null;
if (account.canDecodeSecrets()) {
try {
- clear = ENCRYPTOR.decode(account.getPassword(),
account.getCipherAlgorithm());
+ clear =
Encryptor.getInstance().decode(account.getPassword(),
account.getCipherAlgorithm());
} catch (Exception e) {
LOG.error("Could not decode password for {}", account, e);
}
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
index 11b71dec4b..b5c62d1c9c 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/policy/HaveIBeenPwnedPasswordRule.java
@@ -18,15 +18,10 @@
*/
package org.apache.syncope.core.spring.policy;
-import java.io.UnsupportedEncodingException;
import java.net.URI;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
+import java.security.GeneralSecurityException;
import java.util.Optional;
import java.util.stream.Stream;
-import javax.crypto.BadPaddingException;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf;
import org.apache.syncope.common.lib.policy.PasswordRuleConf;
@@ -51,8 +46,6 @@ public class HaveIBeenPwnedPasswordRule implements
PasswordRule {
protected static final Logger LOG =
LoggerFactory.getLogger(HaveIBeenPwnedPasswordRule.class);
- private static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
private HaveIBeenPwnedPasswordRuleConf conf;
@Override
@@ -72,7 +65,7 @@ public class HaveIBeenPwnedPasswordRule implements
PasswordRule {
protected void enforce(final String clearPassword) {
try {
- String sha1 = ENCRYPTOR.encode(clearPassword,
CipherAlgorithm.SHA1);
+ String sha1 = Encryptor.getInstance().encode(clearPassword,
CipherAlgorithm.SHA1);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.USER_AGENT, "Apache Syncope");
@@ -88,9 +81,7 @@ public class HaveIBeenPwnedPasswordRule implements
PasswordRule {
throw new PasswordPolicyException("Password pwned");
}
}
- } catch (UnsupportedEncodingException | InvalidKeyException |
NoSuchAlgorithmException
- | BadPaddingException | IllegalBlockSizeException |
NoSuchPaddingException e) {
-
+ } catch (GeneralSecurityException e) {
LOG.error("Could not encode the password value as SHA1", e);
} catch (HttpStatusCodeException e) {
LOG.error("Error while contacting the PwnedPasswords service", e);
@@ -115,7 +106,7 @@ public class HaveIBeenPwnedPasswordRule implements
PasswordRule {
String clearPassword = null;
if (account.canDecodeSecrets()) {
try {
- clearPassword = ENCRYPTOR.decode(account.getPassword(),
account.getCipherAlgorithm());
+ clearPassword =
Encryptor.getInstance().decode(account.getPassword(),
account.getCipherAlgorithm());
} catch (Exception e) {
LOG.error("Could not decode password for {}", account, e);
}
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 21b6149323..d53ffa1f71 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -83,8 +83,6 @@ public class AuthDataAccessor {
public static final String GROUP_OWNER_ROLE = "GROUP_OWNER";
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected static final Set<SyncopeGrantedAuthority> ANONYMOUS_AUTHORITIES =
Set.of(new SyncopeGrantedAuthority(IdRepoEntitlement.ANONYMOUS));
@@ -255,7 +253,7 @@ public class AuthDataAccessor {
}
protected boolean authenticate(final User user, final String password) {
- boolean authenticated = ENCRYPTOR.verify(password,
user.getCipherAlgorithm(), user.getPassword());
+ boolean authenticated = Encryptor.getInstance().verify(password,
user.getCipherAlgorithm(), user.getPassword());
LOG.debug("{} authenticated on internal storage: {}",
user.getUsername(), authenticated);
for (Iterator<? extends ExternalResource> itor =
getPassthroughResources(user).iterator();
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
index 52db5fbf8f..8c411c265b 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/Encryptor.java
@@ -18,19 +18,18 @@
*/
package org.apache.syncope.core.spring.security;
-import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
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.spring.ApplicationContextProvider;
@@ -46,53 +45,85 @@ public final class Encryptor {
private static final Map<String, Encryptor> INSTANCES = new
ConcurrentHashMap<>();
- private static final String DEFAULT_SECRET_KEY =
"1abcdefghilmnopqrstuvz2!";
-
public static Encryptor getInstance() {
return getInstance(null);
}
- public static Encryptor getInstance(final String secretKey) {
- String actualKey = StringUtils.isBlank(secretKey) ? DEFAULT_SECRET_KEY
: secretKey;
-
- Encryptor instance = INSTANCES.get(actualKey);
- if (instance == null) {
- instance = new Encryptor(actualKey);
- INSTANCES.put(actualKey, instance);
- }
-
- return instance;
+ public static Encryptor getInstance(final String aesSecretKey) {
+ SecurityProperties securityProperties =
Optional.ofNullable(ApplicationContextProvider.getApplicationContext()).
+ flatMap(ctx -> {
+ try {
+ return
Optional.ofNullable(ctx.getBean(SecurityProperties.class));
+ } catch (Exception e) {
+ return Optional.empty();
+ }
+ }).
+ orElseGet(() -> {
+ SecurityProperties props = new SecurityProperties();
+ props.setAesSecretKey(StringUtils.EMPTY);
+ return props;
+ });
+
+ String actualKey = StringUtils.isBlank(aesSecretKey) ?
securityProperties.getAesSecretKey() : aesSecretKey;
+ return INSTANCES.computeIfAbsent(actualKey, k -> new Encryptor(k,
securityProperties.getDigester()));
}
+ private final SecurityProperties.DigesterProperties digesterProperties;
+
private final Map<CipherAlgorithm, StandardStringDigester> digesters = new
ConcurrentHashMap<>();
- private SecretKeySpec keySpec;
+ private final Optional<SecretKeySpec> aesKeySpec;
- private Encryptor(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);
+ private Encryptor(
+ 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);
}
public String encode(final String value, final CipherAlgorithm
cipherAlgorithm)
- throws UnsupportedEncodingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
+ throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
String encoded = null;
@@ -100,7 +131,8 @@ public final class 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) {
@@ -134,14 +166,15 @@ public final class Encryptor {
}
public String decode(final String encoded, final CipherAlgorithm
cipherAlgorithm)
- throws UnsupportedEncodingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
+ throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
String decoded = null;
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);
}
@@ -150,24 +183,19 @@ public final class Encryptor {
}
private StandardStringDigester getDigester(final CipherAlgorithm
cipherAlgorithm) {
- StandardStringDigester digester = digesters.get(cipherAlgorithm);
- if (digester == null) {
- digester = new StandardStringDigester();
+ 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());
@@ -176,10 +204,7 @@ public final class 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/SecurityContext.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SecurityContext.java
index ece1679733..a1ccb95e63 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.dao.AccessTokenDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
@@ -63,10 +64,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)
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..002c2fa3b5 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 aesSecretKey) {
+ this.aesSecretKey = aesSecretKey;
}
public String getGroovyBlacklist() {
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
index c77123bccf..65b3e064a9 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/SyncopeJWTSSOProvider.java
@@ -47,8 +47,6 @@ public class SyncopeJWTSSOProvider implements JWTSSOProvider {
protected static final Logger LOG =
LoggerFactory.getLogger(SyncopeJWTSSOProvider.class);
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected final SecurityProperties securityProperties;
protected final AccessTokenJWSVerifier delegate;
@@ -104,7 +102,7 @@ public class SyncopeJWTSSOProvider implements
JWTSSOProvider {
if (accessToken.getAuthorities() != null) {
try {
authorities = POJOHelper.deserialize(
- ENCRYPTOR.decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
+ Encryptor.getInstance().decode(new
String(accessToken.getAuthorities()), CipherAlgorithm.AES),
new TypeReference<>() {
});
} catch (Throwable t) {
diff --git
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
index d6e0acb80f..53f51a659b 100644
---
a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
+++
b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
@@ -42,8 +42,6 @@ public class UsernamePasswordAuthenticationProvider
implements AuthenticationPro
protected static final Logger LOG =
LoggerFactory.getLogger(UsernamePasswordAuthenticationProvider.class);
- protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
-
protected final DomainOps domainOps;
protected final AuthDataAccessor dataAccessor;
@@ -97,12 +95,12 @@ public class UsernamePasswordAuthenticationProvider
implements AuthenticationPro
username.set(securityProperties.getAdminUser());
if (SyncopeConstants.MASTER_DOMAIN.equals(domain.getKey())) {
credentialChecker.checkIsDefaultAdminPasswordInUse();
- authenticated = ENCRYPTOR.verify(
+ authenticated = Encryptor.getInstance().verify(
authentication.getCredentials().toString(),
securityProperties.getAdminPasswordAlgorithm(),
securityProperties.getAdminPassword());
} else {
- authenticated = ENCRYPTOR.verify(
+ authenticated = Encryptor.getInstance().verify(
authentication.getCredentials().toString(),
domain.getAdminCipherAlgorithm(),
domain.getAdminPassword());
diff --git
a/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
b/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
index 8f4cf5b75a..eed813e89f 100644
---
a/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
+++
b/core/spring/src/test/java/org/apache/syncope/core/spring/security/EncryptorTest.java
@@ -36,7 +36,9 @@ public class EncryptorTest {
@BeforeAll
public static void setUp() {
-
ApplicationContextProvider.getBeanFactory().registerSingleton("securityProperties",
new SecurityProperties());
+ SecurityProperties props = new SecurityProperties();
+ props.setAesSecretKey("1abcdefghilmnopq");
+
ApplicationContextProvider.getBeanFactory().registerSingleton("securityProperties",
props);
ENCRYPTOR = Encryptor.getInstance();
}
diff --git a/core/starter/src/main/resources/core.properties
b/core/starter/src/main/resources/core.properties
index 933aed4181..cb962828a5 100644
--- a/core/starter/src/main/resources/core.properties
+++ b/core/starter/src/main/resources/core.properties
@@ -96,7 +96,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/fit/core-reference/src/main/resources/core-embedded.properties
b/fit/core-reference/src/main/resources/core-embedded.properties
index 778a6d3478..5e57aa93e6 100644
--- a/fit/core-reference/src/main/resources/core-embedded.properties
+++ b/fit/core-reference/src/main/resources/core-embedded.properties
@@ -40,7 +40,7 @@ spring.datasource.driver-class-name=org.h2.Driver
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.h2.Driver
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 697fba24a9..cc288b62e6 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
@@ -330,8 +330,8 @@ public class KeymasterITCase extends AbstractITCase {
// 1. change admin pwd for domain Two
domainOps.changeAdminPassword(
two.getKey(),
- Encryptor.getInstance().encode("password3",
CipherAlgorithm.AES),
- CipherAlgorithm.AES);
+ Encryptor.getInstance().encode("password3",
CipherAlgorithm.BCRYPT),
+ CipherAlgorithm.BCRYPT);
// 2. attempt to access with old pwd -> fail
try {
diff --git a/pom.xml b/pom.xml
index c889503d10..e1c61d3061 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1710,7 +1710,7 @@ under the License.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.4.2</version>
+ <version>3.5.0</version>
</plugin>
<plugin>
diff --git
a/src/main/asciidoc/reference-guide/configuration/configurationparameters.adoc
b/src/main/asciidoc/reference-guide/configuration/configurationparameters.adoc
index 474a17655a..1f7d15b623 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` -