From 5c1b5b863fe8d6c08cfe0749fed7ce9594827f8a Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 23 Apr 2019 15:00:13 +0200 Subject: add different strategies for pendingRequestId generation --- .../SecurePendingRequestIdGenerationStrategy.java | 188 +++++++++++++++++++++ .../SimplePendingRequestIdGenerationStrategy.java | 25 +++ 2 files changed, 213 insertions(+) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SimplePendingRequestIdGenerationStrategy.java (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils') diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java new file mode 100644 index 00000000..0daa0eb7 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SecurePendingRequestIdGenerationStrategy.java @@ -0,0 +1,188 @@ +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 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; + + +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 validateAndGetPendingRequestId(String externalPendingReqId) throws PendingReqIdValidationException { + log.trace("RAW external pendingReqId: {}", externalPendingReqId); + + try { + 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); + + 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; + + } else { + log.warn("PendingRequestId has an unvalid format"); + log.debug("PendingRequestId: {}", stringToken); + throw new PendingReqIdValidationException(null, "PendingReqId has an unvalid format"); + + } + + } 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); + + } + } + + + @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); + + } + + } + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SimplePendingRequestIdGenerationStrategy.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SimplePendingRequestIdGenerationStrategy.java new file mode 100644 index 00000000..59da3d06 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/utils/SimplePendingRequestIdGenerationStrategy.java @@ -0,0 +1,25 @@ +package at.gv.egiz.eaaf.core.impl.utils; + +import org.apache.commons.lang3.StringUtils; + +import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; + +public class SimplePendingRequestIdGenerationStrategy implements IPendingRequestIdGenerationStrategy { + + @Override + public String generateExternalPendingRequestId() { + return Random.nextLongRandom(); + + } + + @Override + public String validateAndGetPendingRequestId(String pendingReqId) throws PendingReqIdValidationException { + if (StringUtils.isEmpty(pendingReqId)) + throw new PendingReqIdValidationException(pendingReqId, "PendingRequestId is empty or null"); + + return pendingReqId; + + } + +} -- cgit v1.2.3