Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ public AccessTokenLogic(
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

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;
Expand Down Expand Up @@ -115,7 +116,9 @@ public ConnectorManager connectorManager() {

@Bean
public EncryptorManager encryptorManager() {
return new DefaultEncryptorManager();
SecurityProperties securityProperties = new SecurityProperties();
securityProperties.setAesSecretKey(StringUtils.EMPTY);
return new DefaultEncryptorManager(securityProperties);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
security.adminUser=${adminUser}
security.anonymousUser=${anonymousUser}
security.jwsKey=${jwsKey}
security.secretKey=${secretKey}
security.aesSecretKey=${secretKey}

persistence.db-type=${DB_TYPE}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

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;
Expand Down Expand Up @@ -107,7 +108,9 @@ public ConnectorManager connectorManager() {

@Bean
public EncryptorManager encryptorManager() {
return new DefaultEncryptorManager();
SecurityProperties securityProperties = new SecurityProperties();
securityProperties.setAesSecretKey(StringUtils.EMPTY);
return new DefaultEncryptorManager(securityProperties);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,8 @@ public AnyType create(final AnyTypeTO anyTypeTO) {

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) {
Expand Down Expand Up @@ -142,17 +141,16 @@ public AnyTypeTO delete(final AnyType anyType) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ public <U extends AnyUR> U getAnyUR(
// 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@
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;
Expand All @@ -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);
Comment thread Dismissed
}
if (truncate) {
actualKey = actualKey.substring(0, 32);
LOG.warn("The configured AES secret key is too long (> 32), truncating: {}", actualKey);
Comment thread Dismissed
}

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
Expand All @@ -82,7 +107,8 @@ public String encode(final String value, final CipherAlgorithm cipherAlgorithm)
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) {
Expand Down Expand Up @@ -125,33 +151,29 @@ public String decode(final String encoded, final CipherAlgorithm cipherAlgorithm

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);
}

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());
Expand All @@ -160,10 +182,7 @@ private StandardStringDigester getDigester(final CipherAlgorithm cipherAlgorithm
}

digester.setStringOutputType(CommonUtils.STRING_OUTPUT_TYPE_HEXADECIMAL);

digesters.put(cipherAlgorithm, digester);
}

return digester;
return digester;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,22 @@

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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;
Expand Down Expand Up @@ -64,10 +65,8 @@ public static GrantedAuthorityDefaults grantedAuthorityDefaults() {
}

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)
Expand Down Expand Up @@ -158,8 +157,8 @@ public ApplicationContextProvider applicationContextProvider() {
}

@Bean
public EncryptorManager encryptorManager() {
return new DefaultEncryptorManager();
public EncryptorManager encryptorManager(final SecurityProperties securityProperties) {
return new DefaultEncryptorManager(securityProperties);
}

@ConditionalOnMissingBean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void setUseLenientSaltSizeCheck(final boolean useLenientSaltSizeCheck) {

private String jwsAlgorithm = JWSAlgorithm.HS512.getName();

private String secretKey;
private String aesSecretKey;

private String groovyBlacklist = "classpath:META-INF/groovy.blacklist";

Expand Down Expand Up @@ -168,12 +168,12 @@ public void setJwsAlgorithm(final String jwsAlgorithm) {
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() {
Expand Down
Loading