summaryrefslogtreecommitdiff
path: root/eaaf_core_utils/src/main
diff options
context:
space:
mode:
authorThomas Lenz <thomas.lenz@egiz.gv.at>2020-06-23 08:57:18 +0200
committerThomas Lenz <thomas.lenz@egiz.gv.at>2020-06-23 08:57:18 +0200
commit234cee5815f8688d45146b792ebe3c8015a7dc74 (patch)
tree73dfacc93f4c743e19a93016fb80e207d12a605c /eaaf_core_utils/src/main
parent04cd51a31e54cea00d53417ceb4d82fc068c4d75 (diff)
downloadEAAF-Components-234cee5815f8688d45146b792ebe3c8015a7dc74.tar.gz
EAAF-Components-234cee5815f8688d45146b792ebe3c8015a7dc74.tar.bz2
EAAF-Components-234cee5815f8688d45146b792ebe3c8015a7dc74.zip
add new pendingRequestId generation-strategy that uses authenticated-encryption protect the internal pendingRequesttId
Diffstat (limited to 'eaaf_core_utils/src/main')
-rw-r--r--eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java291
1 files changed, 291 insertions, 0 deletions
diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java
new file mode 100644
index 00000000..ebfe7500
--- /dev/null
+++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/utils/AuthenticatedEncryptionPendingRequestIdGenerationStrategy.java
@@ -0,0 +1,291 @@
+package at.gv.egiz.eaaf.core.impl.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.security.Provider;
+import java.util.Base64;
+
+import javax.annotation.Nonnull;
+import javax.annotation.PostConstruct;
+import javax.crypto.SecretKey;
+
+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.jose4j.jca.ProviderContext;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jwa.AlgorithmConstraints.ConstraintType;
+import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers;
+import org.jose4j.jwe.JsonWebEncryption;
+import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers;
+import org.jose4j.lang.JoseException;
+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.PendingReqIdValidationException;
+import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory;
+import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration;
+import at.gv.egiz.eaaf.core.impl.credential.SymmetricKeyConfiguration.SymmetricKeyType;
+import at.gv.egiz.eaaf.core.impl.data.Pair;
+
+/**
+ * PendingRequestId generation strategy based on signed tokens that facilitates
+ * extended token validation.
+ *
+ * @author tlenz
+ *
+ */
+public class AuthenticatedEncryptionPendingRequestIdGenerationStrategy
+ implements IPendingRequestIdGenerationStrategy {
+ private static final Logger log =
+ LoggerFactory.getLogger(AuthenticatedEncryptionPendingRequestIdGenerationStrategy.class);
+
+ @Autowired(required = true) IConfiguration baseConfig;
+ @Autowired EaafKeyStoreFactory keyStoreFactory;
+
+ private static final String FRIENDLYNAME = "pendingRequestId key";
+
+ public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_TYPE =
+ "core.pendingrequestid.digist.type";
+ public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET =
+ "core.pendingrequestid.digist.secret";
+
+ public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_KEYSTORE =
+ "core.pendingrequestid.digist.keystore.name";
+ public static final String CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_ALIAS =
+ "core.pendingrequestid.digist.key.alias";
+
+ 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 = 2;
+ 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 Pair<SecretKey, Provider> key = null;
+ private final String salt = "notRequiredInThisScenario";
+
+ @Override
+ public String generateExternalPendingRequestId() throws EaafException {
+ try {
+ final String toSign = buildInternalToken(Random.nextLongRandom(), DateTime.now());
+ JsonWebEncryption encToken = new JsonWebEncryption();
+ encToken.setAlgorithmHeaderValue(selectKeyWrappingAlgorithm(key.getFirst()));
+ encToken.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_GCM);
+ encToken.setKey(key.getFirst());
+ encToken.setPayload(toSign);
+
+
+
+ if (key.getSecond() != null) {
+ final ProviderContext providerCtx = new ProviderContext();
+ providerCtx.getSuppliedKeyProviderContext().setSignatureProvider(
+ key.getSecond().getName());
+ encToken.setProviderContext(providerCtx);
+
+ }
+
+ return Base64.getUrlEncoder()
+ .encodeToString(encToken.getCompactSerialization().getBytes("UTF-8"));
+
+ } catch (final JoseException | UnsupportedEncodingException e) {
+ throw new EaafException("internal.99", new Object[] { e.getMessage() }, e);
+
+ }
+
+ }
+
+ @Override
+ public String getPendingRequestIdWithOutChecks(final String externalPendingReqId)
+ throws PendingReqIdValidationException {
+ try {
+ String stringToken = getDecryptedExternalPendingRequestId(externalPendingReqId);
+ log.debug("Token decryption successful");
+
+ if (!(StringUtils.countMatches(stringToken, TOKEN_SEPARATOR) == ENCODED_TOKEN_PARTS - 1)) {
+ log.warn("PendingRequestId has an unvalid format");
+ log.debug("PendingRequestId: {}", stringToken);
+ throw new PendingReqIdValidationException(null, "PendingReqId has an unvalid format");
+
+ }
+
+ final String[] tokenElements =
+ StringUtils.split(stringToken, TOKEN_SEPARATOR, ENCODED_TOKEN_PARTS);
+ return tokenElements[1];
+
+ } catch (final UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+
+ } catch (JoseException 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);
+
+ }
+ }
+
+ @Override
+ public String validateAndGetPendingRequestId(final String externalPendingReqId)
+ throws PendingReqIdValidationException {
+ try {
+ String stringToken = getDecryptedExternalPendingRequestId(externalPendingReqId);
+ log.debug("Token decryption successful");
+
+ if (!(StringUtils.countMatches(stringToken, TOKEN_SEPARATOR) == ENCODED_TOKEN_PARTS - 1)) {
+ log.warn("PendingRequestId has an unvalid format");
+ log.debug("PendingRequestId: {}", stringToken);
+ throw new PendingReqIdValidationException(null, "PendingReqId has an unvalid format");
+
+ }
+
+ 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 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 (JoseException e) {
+ log.warn("Token is NOT a valid encrypt. Msg: {}", e.getMessage());
+ log.debug("TokenValue: {}", externalPendingReqId);
+ throw new PendingReqIdValidationException(null, "PendingReqId is NOT a valid encrypted", e);
+
+ } catch (final IllegalArgumentException 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);
+
+ } catch (final UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+
+ }
+ }
+
+ @Nonnull
+ private String getDecryptedExternalPendingRequestId(String externalPendingReqId)
+ throws JoseException, PendingReqIdValidationException, UnsupportedEncodingException {
+ 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);
+
+ }
+
+
+ JsonWebEncryption encToken = new JsonWebEncryption();
+ encToken.setContentEncryptionAlgorithmConstraints(new AlgorithmConstraints(
+ ConstraintType.WHITELIST, ContentEncryptionAlgorithmIdentifiers.AES_128_GCM));
+ encToken.setAlgorithmConstraints(new AlgorithmConstraints(
+ ConstraintType.WHITELIST,
+ KeyManagementAlgorithmIdentifiers.DIRECT,
+ KeyManagementAlgorithmIdentifiers.A128GCMKW
+ ));
+ encToken.setKey(key.getFirst());
+
+ if (key.getSecond() != null) {
+ final ProviderContext providerCtx = new ProviderContext();
+ providerCtx.getSuppliedKeyProviderContext().setSignatureProvider(
+ key.getSecond().getName());
+ encToken.setProviderContext(providerCtx);
+
+ }
+
+ encToken.setCompactSerialization(new String(externalPendingReqIdBytes, "UTF-8"));
+ return encToken.getPayload();
+
+ }
+
+ private String selectKeyWrappingAlgorithm(SecretKey first) {
+ if ("AES".equals(first.getAlgorithm())) {
+ return KeyManagementAlgorithmIdentifiers.A128GCMKW;
+
+ } else {
+ return KeyManagementAlgorithmIdentifiers.DIRECT;
+
+ }
+ }
+
+ @PostConstruct
+ private void initialize() throws EaafConfigurationException {
+ log.debug("Initializing " + this.getClass().getName() + " ... ");
+
+ maxPendingRequestIdLifeTime =
+ Integer.parseInt(baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_MAX_LIFETIME,
+ DEFAULT_PENDINGREQUESTID_MAX_LIFETIME));
+
+
+ SymmetricKeyConfiguration secretKeyConfig = new SymmetricKeyConfiguration();
+ secretKeyConfig.setFriendlyName(FRIENDLYNAME);
+ secretKeyConfig.setKeyType(
+ baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_TYPE,
+ SymmetricKeyType.PASSPHRASE.name()));
+
+ secretKeyConfig.setSoftKeyPassphrase(
+ baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_SECRET));
+ secretKeyConfig.setSoftKeySalt(salt);
+
+ secretKeyConfig.setKeyStoreName(
+ baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_KEYSTORE));
+ secretKeyConfig.setKeyAlias(
+ baseConfig.getBasicConfiguration(CONFIG_PROP_PENDINGREQUESTID_DIGIST_HSM_ALIAS));
+
+ //validate symmetric-key configuration
+ secretKeyConfig.validate();
+
+ try {
+ key = keyStoreFactory.buildNewSymmetricKey(secretKeyConfig);
+
+ } catch (EaafException 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 Alg: {} and maxLifeTime: {}",
+ ContentEncryptionAlgorithmIdentifiers.AES_128_GCM, maxPendingRequestIdLifeTime);
+
+ }
+
+ private String buildInternalToken(final String internalPendingReqId, final DateTime now) {
+ return new StringBuilder().append(TOKEN_TEXTUAL_DATE_FORMAT.print(now)).append(TOKEN_SEPARATOR)
+ .append(internalPendingReqId).toString();
+ }
+
+}