summaryrefslogtreecommitdiff
path: root/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java
diff options
context:
space:
mode:
Diffstat (limited to 'eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java')
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java214
1 files changed, 214 insertions, 0 deletions
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java
new file mode 100644
index 00000000..f0ef9b38
--- /dev/null
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java
@@ -0,0 +1,214 @@
+package at.gv.egiz.eaaf.core.impl.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+import java.util.Base64;
+
+import javax.annotation.PostConstruct;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DurationFieldType;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import at.gv.egiz.eaaf.core.api.idp.IConfiguration;
+import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy;
+import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFException;
+import at.gv.egiz.eaaf.core.exceptions.EAAFIllegalStateException;
+import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException;
+
+/**
+ * PendingRequestId generation strategy based on signed tokens that facilitates extended token validation
+ *
+ * @author tlenz
+ *
+ */
+public class SecurePendingRequestIdGenerationStrategy implements IPendingRequestIdGenerationStrategy {
+ private static final Logger log = LoggerFactory.getLogger(SecurePendingRequestIdGenerationStrategy.class);
+
+ @Autowired(required=true) IConfiguration baseConfig;
+
+ public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET = "core.pendingrequestid.digist.secret";
+ public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_ALGORITHM = "core.pendingrequestid.digist.algorithm";
+ public static final String CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME = "core.pendingrequestid.maxlifetime";
+
+ public static final String DEFAULT_PENDINGREQUESTID_DIGIST_ALGORITHM = "HmacSHA256";
+ public static final String DEFAULT_PENDINGREQUESTID_MAX_LIFETIME = "300";
+
+ private static final int ENCODED_TOKEN_PARTS = 3;
+ private static final String TOKEN_SEPARATOR = "|";
+ private static final DateTimeFormatter TOKEN_TEXTUAL_DATE_FORMAT =
+ DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss SSS");
+
+ private int maxPendingRequestIdLifeTime = 300;
+ private final int maxPendingReqIdSize = 1024;
+ private String digistAlgorithm = null;
+ private SecretKey key = null;
+ private final byte[] salt = "notRequiredInThisScenario".getBytes();
+
+ @Override
+ public String generateExternalPendingRequestId() throws EAAFException {
+ try {
+ final String toSign = buildInternalToken(Random.nextLongRandom(), DateTime.now());
+ final StringBuilder externalPendingRequestId= new StringBuilder();
+ externalPendingRequestId.append(toSign);
+ externalPendingRequestId.append(TOKEN_SEPARATOR);
+ externalPendingRequestId.append(Base64.getEncoder().encodeToString(calculateHMAC(toSign)));
+ return Base64.getUrlEncoder().encodeToString(externalPendingRequestId.toString().getBytes("UTF-8"));
+
+ } catch (final UnsupportedEncodingException e) {
+ throw new EAAFException("internal.99", new Object[] {e.getMessage()}, e);
+
+ }
+
+ }
+
+ @Override
+ public String getPendingRequestIdWithOutChecks(String externalPendingReqId) throws PendingReqIdValidationException {
+ final String[] tokenElements = extractTokens(externalPendingReqId);
+ return tokenElements[1];
+
+ }
+
+ @Override
+ public String validateAndGetPendingRequestId(String externalPendingReqId) throws PendingReqIdValidationException {
+ try {
+ final String[] tokenElements = extractTokens(externalPendingReqId);
+ final String internalPendingReqId = tokenElements[1];
+ final DateTime timeStamp = TOKEN_TEXTUAL_DATE_FORMAT.parseDateTime(tokenElements[0]);
+
+ log.trace("Checking HMAC from externalPendingReqId ... ");
+ final byte[] tokenDigest = Base64.getDecoder().decode(tokenElements[2]);
+ final byte[] refDigist = calculateHMAC(buildInternalToken(internalPendingReqId, timeStamp));
+ if (!Arrays.equals(tokenDigest, refDigist)) {
+ log.warn("Digest of Token does NOT match");
+ log.debug("Token: {} | Ref: {}", tokenDigest, refDigist);
+ throw new PendingReqIdValidationException(null, "Digest of pendingRequestId does NOT match");
+
+ }
+ log.debug("PendingRequestId HMAC digest check successful");
+
+ log.trace("Checking valid period ... ");
+ final DateTime now = DateTime.now();
+ if (timeStamp.withFieldAdded(
+ DurationFieldType.seconds(), maxPendingRequestIdLifeTime).isBefore(now)) {
+ log.warn("Token exceeds the valid period");
+ log.debug("Token: {} | Now: {}", timeStamp, now );
+ throw new PendingReqIdValidationException(internalPendingReqId, "PendingRequestId exceeds the valid period");
+
+ }
+ log.debug("Token valid-period check successful");
+
+ return internalPendingReqId;
+
+
+ } catch (final IllegalArgumentException | EAAFIllegalStateException e) {
+ log.warn("Token is NOT a valid String. Msg: {}", e.getMessage());
+ log.debug("TokenValue: {}", externalPendingReqId);
+ throw new PendingReqIdValidationException(null, "PendingReqId is NOT a valid String", e);
+
+ }
+ }
+
+ @NonNull
+ private String[] extractTokens(@Nullable String externalPendingReqId) throws PendingReqIdValidationException {
+ if (StringUtils.isEmpty(externalPendingReqId)) {
+ log.info("PendingReqId is 'null' or empty");
+ throw new PendingReqIdValidationException(null, "PendingReqId is 'null' or empty");
+
+ }
+
+ log.trace("RAW external pendingReqId: {}", externalPendingReqId);
+ final byte[] externalPendingReqIdBytes = Base64.getUrlDecoder().decode(externalPendingReqId);
+
+ if (externalPendingReqIdBytes.length > maxPendingReqIdSize) {
+ log.warn("pendingReqId size exceeds {}", maxPendingReqIdSize);
+ throw new PendingReqIdValidationException(null, "pendingReqId exceeds max.size: " + maxPendingReqIdSize);
+
+ }
+
+ final String stringToken = new String(externalPendingReqIdBytes);
+ if (StringUtils.countMatches(stringToken, TOKEN_SEPARATOR) == ENCODED_TOKEN_PARTS - 1) {
+ final String[] tokenElements = StringUtils.split(stringToken,
+ TOKEN_SEPARATOR, ENCODED_TOKEN_PARTS);
+ return tokenElements;
+
+ } else {
+ log.warn("PendingRequestId has an unvalid format");
+ log.debug("PendingRequestId: {}", stringToken);
+ throw new PendingReqIdValidationException(null, "PendingReqId has an unvalid format");
+
+ }
+
+ }
+
+
+ @PostConstruct
+ private void initialize() throws EAAFConfigurationException {
+ log.debug("Initializing " + this.getClass().getName() + " ... ");
+
+ final String pendingReqIdDigistSecret = baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET);
+ if (StringUtils.isEmpty(pendingReqIdDigistSecret))
+ throw new EAAFConfigurationException("config.08", new Object[] {CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET});
+
+ digistAlgorithm = baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_ALGORITHM, DEFAULT_PENDINGREQUESTID_DIGIST_ALGORITHM);
+
+ maxPendingRequestIdLifeTime = Integer.valueOf(
+ baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME, DEFAULT_PENDINGREQUESTID_MAX_LIFETIME));
+
+ try {
+ final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WITHHMACSHA256");
+ final KeySpec spec = new PBEKeySpec(pendingReqIdDigistSecret.toCharArray(), salt, 10000, 128);
+ key = keyFactory.generateSecret(spec);
+
+
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ log.error("Can NOT initialize TokenService with configuration object", e);
+ throw new EAAFConfigurationException("config.09",
+ new Object[] { CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET,
+ "Can NOT generate HMAC key"},
+ e);
+
+ }
+
+ log.info(this.getClass().getName() + " initialized with digistAlg: {} and maxLifeTime: {}", digistAlgorithm, maxPendingRequestIdLifeTime);
+
+ }
+
+ private String buildInternalToken(String internalPendingReqId, DateTime now) {
+ return new StringBuilder()
+ .append(TOKEN_TEXTUAL_DATE_FORMAT.print(now))
+ .append(TOKEN_SEPARATOR)
+ .append(internalPendingReqId).toString();
+ }
+
+ private byte[] calculateHMAC(String toSign) throws EAAFIllegalStateException {
+ try {
+ final Mac mac = Mac.getInstance(digistAlgorithm);
+ mac.init(key);
+ return mac.doFinal(toSign.getBytes("UTF-8"));
+
+ } catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ log.error("Can NOT generate secure pendingRequestId", e);
+ throw new EAAFIllegalStateException(new Object[] {"Can NOT caluclate digist for secure pendingRequestId"}, e);
+
+ }
+
+ }
+
+}