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 --- .../at/gv/egiz/eaaf/core/api/IRequestStorage.java | 30 +++- .../utils/IPendingRequestIdGenerationStrategy.java | 31 ++++ .../PendingReqIdValidationException.java | 59 +++++++ .../eaaf/core/impl/idp/auth/RequestStorage.java | 161 +++++++++++++----- .../impl/idp/controller/AbstractController.java | 20 ++- .../impl/idp/controller/protocols/RequestImpl.java | 35 ++-- .../SecurePendingRequestIdGenerationStrategy.java | 188 +++++++++++++++++++++ .../SimplePendingRequestIdGenerationStrategy.java | 25 +++ 8 files changed, 483 insertions(+), 66 deletions(-) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/utils/IPendingRequestIdGenerationStrategy.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/exceptions/PendingReqIdValidationException.java 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') diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/IRequestStorage.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/IRequestStorage.java index ab928ae5..56179d55 100644 --- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/IRequestStorage.java +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/IRequestStorage.java @@ -46,6 +46,7 @@ package at.gv.egiz.eaaf.core.api; import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; /** * @author tlenz @@ -53,12 +54,37 @@ import at.gv.egiz.eaaf.core.exceptions.EAAFException; */ public interface IRequestStorage { - public IRequest getPendingRequest(String pendingReqID); + /** + * Get a pending-request from storage + * + * @param pendingReqID Id of the pending request + * @return + * @throws PendingReqIdValidationException if the pendingRequestId was invalid + */ + public IRequest getPendingRequest(String pendingReqID) throws PendingReqIdValidationException; + /** + * Store a pending-request in storage + * + * @param pendingRequest + * @throws EAAFException + */ public void storePendingRequest(IRequest pendingRequest) throws EAAFException; - public void removePendingRequest(String requestID); + /** + * Remove a pending-request from storage + * + * @param pendingReqId Id of the pending request + */ + public void removePendingRequest(String pendingReqId); + /** + * change the pendingRequestId of a pending-request + * + * @param pendingRequest current pending-reqeust + * @return new pending-requestId + * @throws EAAFException + */ public String changePendingRequestID(IRequest pendingRequest) throws EAAFException; } diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/utils/IPendingRequestIdGenerationStrategy.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/utils/IPendingRequestIdGenerationStrategy.java new file mode 100644 index 00000000..443404eb --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/api/utils/IPendingRequestIdGenerationStrategy.java @@ -0,0 +1,31 @@ +package at.gv.egiz.eaaf.core.api.utils; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; + +public interface IPendingRequestIdGenerationStrategy { + + /** + * Generate a new external pending-request id + * + * @return + * @throws EAAFException + */ + @NonNull + public String generateExternalPendingRequestId() throws EAAFException; + + /** + * Validate a pendingRequestId according to implemented strategy + * + * @param pendingReqId pending-request Id that should be validated + * @return internalPendingRequestId + * @throws PendingReqIdValidationException + */ + @NonNull + public String validateAndGetPendingRequestId(@Nullable String pendingReqId) throws PendingReqIdValidationException; + + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/exceptions/PendingReqIdValidationException.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/exceptions/PendingReqIdValidationException.java new file mode 100644 index 00000000..57317092 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/exceptions/PendingReqIdValidationException.java @@ -0,0 +1,59 @@ +package at.gv.egiz.eaaf.core.exceptions; + +import org.springframework.lang.NonNull; + +import at.gv.egiz.eaaf.core.api.IRequest; + +public class PendingReqIdValidationException extends EAAFException { + + /** + * + */ + private static final long serialVersionUID = -6886402432880791308L; + + private final String invalidInternalPendingReqId; + private IRequest invalidPendingReq; + + /** + * + * @param pendingReqId + * @param reason + */ + public PendingReqIdValidationException(String internalPendingReqId, @NonNull String reason) { + super("process.99", new Object[] {internalPendingReqId, reason}); + this.invalidInternalPendingReqId = internalPendingReqId; + + } + + public PendingReqIdValidationException(String internalPendingReqId, @NonNull String reason, Throwable e) { + super("process.99", new Object[] {internalPendingReqId, reason}, e ); + this.invalidInternalPendingReqId = internalPendingReqId; + } + + /** + * Get the invalid pending-request + * + * @return + */ + public IRequest getInvalidPendingReq() { + return invalidPendingReq; + } + + + /** + * Get the internal invalid pending-request id + * + * @return + */ + public String getInvalidInternalPendingReqId() { + return invalidInternalPendingReqId; + } + + public void setInvalidPendingReq(IRequest invalidPendingReq) { + this.invalidPendingReq = invalidPendingReq; + + } + + + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java index e4288e62..2115d9b0 100644 --- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java @@ -26,6 +26,7 @@ *******************************************************************************/ package at.gv.egiz.eaaf.core.impl.idp.auth; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -35,35 +36,55 @@ import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.IRequestStorage; import at.gv.egiz.eaaf.core.api.idp.process.ProcessInstanceStoreDAO; import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.api.utils.IPendingRequestIdGenerationStrategy; import at.gv.egiz.eaaf.core.exceptions.EAAFException; import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; -import at.gv.egiz.eaaf.core.impl.utils.Random; import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils; @Service("RequestStorage") public class RequestStorage implements IRequestStorage{ private static final Logger log = LoggerFactory.getLogger(RequestStorage.class); - @Autowired ITransactionStorage transactionStorage; - @Autowired ProcessInstanceStoreDAO processInstanceStore; - + @Autowired(required=true) ITransactionStorage transactionStorage; + @Autowired(required=true) ProcessInstanceStoreDAO processInstanceStore; + @Autowired(required=true) IPendingRequestIdGenerationStrategy pendingReqIdGenerationStrategy; + @Override - public IRequest getPendingRequest(String pendingReqID) { + public IRequest getPendingRequest(String pendingReqID) throws PendingReqIdValidationException { try { - IRequest pendingRequest = transactionStorage.get(pendingReqID, IRequest.class); - if (pendingRequest == null) { - log.info("No PendingRequst found with pendingRequestID " + pendingReqID); - return null; - - } + final String internalPendingReqId = + pendingReqIdGenerationStrategy.validateAndGetPendingRequestId(pendingReqID); + log.debug("PendingReqId is valid"); + + //get pending-request from storage + final IRequest pendingRequest = getInternalPendingRequest(internalPendingReqId); //set transactionID and sessionID to Logger TransactionIDUtils.setAllLoggingVariables(pendingRequest); return pendingRequest; - + + } catch (final PendingReqIdValidationException e) { + log.info("PendingRequestId is invalid. Reason: {} ", e.getMessage()); + + // search invalid pending-request for errorHandling + IRequest invalidPendingRequest = null; + try { + if (StringUtils.isNotEmpty(e.getInvalidInternalPendingReqId())) + invalidPendingRequest = transactionStorage.get(e.getInvalidInternalPendingReqId(), IRequest.class); + + } catch (final EAAFException e1) { + log.info("No PendingRequst found with pendingRequestID " + pendingReqID); + return null; + + } + + e.setInvalidPendingReq(invalidPendingRequest); + throw e; + } catch (EAAFException | NullPointerException e) { log.info("No PendingRequst found with pendingRequestID " + pendingReqID); return null; @@ -74,17 +95,27 @@ public class RequestStorage implements IRequestStorage{ @Override public void storePendingRequest(IRequest pendingRequest) throws EAAFException { try { - if (pendingRequest instanceof IRequest) - transactionStorage.put(((IRequest)pendingRequest).getPendingRequestId(), pendingRequest, -1); - - else + if (pendingRequest instanceof IRequest) { + try { + //validate pending-requestId + final String internalPendingRequestId = pendingReqIdGenerationStrategy.validateAndGetPendingRequestId(pendingRequest.getPendingRequestId()); + + //store pending request + transactionStorage.put(internalPendingRequestId, pendingRequest, -1); + + } catch (final PendingReqIdValidationException e) { + log.warn("Invalid pending-request-Id. Reason: {}", e.getMessage()); + log.warn("Do NOT store pending-request with invalid pending-request-Id. The process will break soon!"); + + } + + } else throw new EAAFException("PendigRequest is NOT of type 'IRequest'", null); - - - } catch (EAAFException e) { - log.warn("PendingRequest with ID=" + ((IRequest)pendingRequest).getPendingRequestId() + + + } catch (final EAAFException e) { + log.warn("PendingRequest with ID=" + pendingRequest.getPendingRequestId() + " can not stored.", e); - throw new EAAFStorageException("PendingRequest with Id: " + ((IRequest)pendingRequest).getPendingRequestId() + throw new EAAFStorageException("PendingRequest with Id: " + pendingRequest.getPendingRequestId() + " can not be stored", e); } @@ -92,25 +123,35 @@ public class RequestStorage implements IRequestStorage{ } @Override - public void removePendingRequest(String requestID) { + public void removePendingRequest(String pendingReqID) { - if (requestID != null) { - - //remove process-management execution instance + if (pendingReqID != null) { + String internalPendingReqId = null; try { - IRequest pendingReq = getPendingRequest(requestID); - - if (pendingReq != null && - pendingReq.getProcessInstanceId() != null) - processInstanceStore.remove(pendingReq.getProcessInstanceId()); - - } catch (EAAFException e) { - log.warn("Removing process associated with pending-request:" + requestID + " FAILED.", e); + internalPendingReqId = pendingReqIdGenerationStrategy.validateAndGetPendingRequestId(pendingReqID); + + } catch (final PendingReqIdValidationException e) { + internalPendingReqId = e.getInvalidInternalPendingReqId(); } - - transactionStorage.remove(requestID); + try { + //remove process-management execution instance# + if (internalPendingReqId != null) { + final IRequest pendingReq = getInternalPendingRequest(internalPendingReqId); + if (pendingReq != null && + pendingReq.getProcessInstanceId() != null) + processInstanceStore.remove(pendingReq.getProcessInstanceId()); + + //remove pending-request + transactionStorage.remove(internalPendingReqId); + } + + } catch (final EAAFException e) { + log.warn("Removing process associated with pending-request:" + pendingReqID + " FAILED.", e); + + } + } } @@ -119,25 +160,59 @@ public class RequestStorage implements IRequestStorage{ */ @Override public String changePendingRequestID(IRequest pendingRequest) throws EAAFException { - + + //TODO!!!! + if (pendingRequest instanceof RequestImpl) { - String newRequestID = Random.nextHexRandom32(); - String oldRequestID = pendingRequest.getPendingRequestId(); + //final String newRequestID = Random.nextHexRandom32(); + final String newRequestID = pendingReqIdGenerationStrategy.generateExternalPendingRequestId(); + ((RequestImpl)pendingRequest).setPendingRequestId(newRequestID); - log.debug("Change pendingRequestID from " + pendingRequest.getPendingRequestId() - + " to " + newRequestID); + String newInternalPendingRequestId = null; + try { + newInternalPendingRequestId = pendingReqIdGenerationStrategy.validateAndGetPendingRequestId(newRequestID); + + } catch (final PendingReqIdValidationException e) { + throw new EAAFException("internal.99", new Object[]{"Generate invalid pendingRequestId. Something looks WRONG"}, e); + + } + + String oldInternalRequestID = null; + try { + oldInternalRequestID = + pendingReqIdGenerationStrategy.validateAndGetPendingRequestId(pendingRequest.getPendingRequestId()); - ((RequestImpl)pendingRequest).setPendingRequestId(newRequestID); - transactionStorage.changeKey(oldRequestID, newRequestID, pendingRequest); + } catch (final PendingReqIdValidationException e) { + //it's no problem, because it must be valid before when pending-request was loaded and we change it now + oldInternalRequestID = e.getInvalidInternalPendingReqId(); + + } - //only delete oldRequestID, no change. + log.debug("Change pendingRequestID from " + pendingRequest.getPendingRequestId() + + " to " + newRequestID); + + transactionStorage.changeKey(oldInternalRequestID, newInternalPendingRequestId, pendingRequest); + //only delete oldRequestID, no change. return newRequestID; } else { log.error("PendingRequest object is not of type 'RequestImpl.class'"); throw new EAAFException("PendingRequest object is not of type 'RequestImpl.class'", null); + } } + + private IRequest getInternalPendingRequest(String internalPendingReqId) throws EAAFException { + final IRequest pendingRequest = transactionStorage.get(internalPendingReqId, IRequest.class); + if (pendingRequest == null) { + log.info("No PendingRequst found with pendingRequestID " + internalPendingReqId); + return null; + + } + + return pendingRequest; + + } } diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java index 4e58868b..1da8036c 100644 --- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/AbstractController.java @@ -27,6 +27,7 @@ package at.gv.egiz.eaaf.core.impl.idp.controller; import java.io.IOException; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -47,6 +48,7 @@ import at.gv.egiz.eaaf.core.api.idp.auth.services.IProtocolAuthenticationService import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.utils.Random; @@ -105,7 +107,7 @@ public abstract class AbstractController { } protected void handleError(final String errorMessage, final Throwable exceptionThrown, - final HttpServletRequest req, final HttpServletResponse resp, final IRequest pendingReq) throws IOException, EAAFException { + final HttpServletRequest req, final HttpServletResponse resp, IRequest pendingReq) throws IOException, EAAFException { Throwable loggedException = null; final Throwable extractedException = extractOriginalExceptionFromProcessException(exceptionThrown); @@ -115,13 +117,17 @@ public abstract class AbstractController { //set original exception loggedException = ((TaskExecutionException) extractedException).getOriginalException(); - //use TaskExecutionException directly, if no Original Exeception is included - if (loggedException == null) - loggedException = exceptionThrown; - - } else + } else if (exceptionThrown instanceof PendingReqIdValidationException) { + log.trace("Find pendingRequestId validation exception. Looking for invalid pending-request ... "); + if (((PendingReqIdValidationException) exceptionThrown).getInvalidPendingReq() != null) + pendingReq = ((PendingReqIdValidationException) exceptionThrown).getInvalidPendingReq(); + + } + + //use TaskExecutionException directly, if no Original Exeception is included + if (loggedException == null) loggedException = exceptionThrown; - + try { //switch to protocol-finalize method to generate a protocol-specific error message diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java index 527b79a1..5667fad7 100644 --- a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/controller/protocols/RequestImpl.java @@ -41,6 +41,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.data.EAAFConstants; @@ -61,11 +62,11 @@ public abstract class RequestImpl implements IRequest, Serializable{ public static final String DATAID_REQUESTER_IP_ADDRESS = "reqestImpl_requesterIPAddr"; private static final long serialVersionUID = 1L; - + private String module = null; private String action = null; - private String pendingRequestId; + private String pendingRequestId = null; private String processInstanceId; private String internalSSOSessionId; @@ -92,7 +93,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ private boolean needUserConsent = false; - private Map genericDataStorage = new HashMap(); + private final Map genericDataStorage = new HashMap(); @@ -100,10 +101,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ * @throws ConfigurationException * */ - public final void initialize(HttpServletRequest req, IConfiguration authConfig) throws EAAFException { - //set pendingRequestId - pendingRequestId = Random.nextLongRandom(); - + public final void initialize(HttpServletRequest req, IConfiguration authConfig) throws EAAFException { //set unique transaction identifier for logging uniqueTransactionIdentifer = Random.nextLongRandom(); TransactionIDUtils.setTransactionId(uniqueTransactionIdentifer); @@ -113,12 +111,12 @@ public abstract class RequestImpl implements IRequest, Serializable{ //genericDataStorage.put(EAAFConstants.VALUE_SESSIONID, Random.nextLongRandom()); //check if End-Point is valid - String authURLString = HTTPUtils.extractAuthURLFromRequest(req); + final String authURLString = HTTPUtils.extractAuthURLFromRequest(req); URL authReqURL; try { authReqURL = new URL(authURLString); - } catch (MalformedURLException e) { + } catch (final MalformedURLException e) { log.error("IDP AuthenticationServiceURL Prefix is not a valid URL." + authURLString, e); throw new EAAFAuthenticationException("errorId", new Object[]{authURLString}, e); @@ -131,7 +129,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ } //set unique session identifier - String uniqueID = (String) req.getAttribute(EAAFConstants.UNIQUESESSIONIDENTIFIER); + final String uniqueID = (String) req.getAttribute(EAAFConstants.UNIQUESESSIONIDENTIFIER); if (StringUtils.isNotEmpty(uniqueID)) this.uniqueSessionIdentifer = uniqueID; @@ -145,7 +143,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ try { setRawDataToTransaction(DATAID_REQUESTER_IP_ADDRESS, req.getRemoteAddr()); - } catch (EAAFStorageException e) { + } catch (final EAAFStorageException e) { log.info("Can NOT store remote IP address into 'pendingRequest'." , e); } @@ -203,7 +201,11 @@ public abstract class RequestImpl implements IRequest, Serializable{ } @Override + @NonNull public final String getPendingRequestId() { + if (pendingRequestId == null) + throw new IllegalStateException("No PendingRequestId set!!!"); + return pendingRequestId; } @@ -333,6 +335,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ return isAuthenticated; } + @Override public final void setAuthenticated(boolean isAuthenticated) { this.isAuthenticated = isAuthenticated; } @@ -341,6 +344,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ public final boolean needSingleSignOnFunctionality() { return needSSO; } + @Override public final void setNeedSingleSignOnFunctionality(boolean needSSO) { this.needSSO = needSSO; @@ -352,6 +356,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ } + @Override public final void setNeedUserConsent(boolean needConsent) { this.needUserConsent = needConsent; @@ -362,6 +367,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ return this.isAbortedByUser; } + @Override public final void setAbortedByUser(boolean isAborted) { this.isAbortedByUser = isAborted; @@ -381,17 +387,18 @@ public abstract class RequestImpl implements IRequest, Serializable{ @Override public final T getRawData(String key, final Class clazz) { if (StringUtils.isNotEmpty(key)) { - Object data = genericDataStorage.get(key); + final Object data = genericDataStorage.get(key); if (data == null) return null; try { @SuppressWarnings("unchecked") + final T test = (T) data; return test; - } catch (Exception e) { + } catch (final Exception e) { log.warn("Generic request-data object can not be casted to requested type", e); return null; @@ -438,7 +445,7 @@ public abstract class RequestImpl implements IRequest, Serializable{ } //validate and store values - for (Entry el : map.entrySet()) + for (final Entry el : map.entrySet()) setRawDataToTransaction(el.getKey(), el.getValue()); } 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