diff options
Diffstat (limited to 'eaaf_modules/eaaf_module_auth_sl20/src/main')
13 files changed, 741 insertions, 301 deletions
diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java index e9932ae8..d561a0bc 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/AbstractSL20AuthenticationModulImpl.java @@ -29,11 +29,9 @@ public abstract class AbstractSL20AuthenticationModulImpl implements AuthModule private int priority = 3; public static final List<String> VDA_TYPE_IDS = Arrays.asList("1", "2", "3", "4"); - + @Autowired(required = true) protected IConfiguration authConfig; - @Autowired(required = true) - private AbstractAuthenticationManager authManager; @Override public int getPriority() { @@ -51,9 +49,9 @@ public abstract class AbstractSL20AuthenticationModulImpl implements AuthModule @PostConstruct protected void initalSL20Authentication() { - // parameter to whiteList - authManager.addHeaderNameToWhiteList(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE); - authManager.addHeaderNameToWhiteList(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE); + // parameter to whiteList + AbstractAuthenticationManager.addHeaderNameToWhiteList(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE); + AbstractAuthenticationManager.addHeaderNameToWhiteList(SL20Constants.HTTP_HEADER_SL20_VDA_TYPE); } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java index 11fd41fb..74d67d01 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/Constants.java @@ -7,37 +7,41 @@ public class Constants { public static final String CONFIG_PROP_VDA_AUTHBLOCK_TRANSFORMATION_ID = CONFIG_PROP_PREFIX + ".vda.authblock.transformation.id"; - + //KeyStore configuration - public static final String CONFIG_PROP_SECURITY_KEYSTORE_TYPE = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_TYPE = CONFIG_PROP_PREFIX + ".security.keystore.type"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_NAME = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_NAME = CONFIG_PROP_PREFIX + ".security.keystore.name"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_PATH = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_PATH = CONFIG_PROP_PREFIX + ".security.keystore.path"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_PASSWORD = CONFIG_PROP_PREFIX + ".security.keystore.password"; - - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS = + + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_ALIAS = CONFIG_PROP_PREFIX + ".security.sign.alias"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_SIGN_PASSWORD = CONFIG_PROP_PREFIX + ".security.sign.password"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_ALIAS = CONFIG_PROP_PREFIX + ".security.encryption.alias"; - public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD = + public static final String CONFIG_PROP_SECURITY_KEYSTORE_KEY_ENCRYPTION_PASSWORD = CONFIG_PROP_PREFIX + ".security.encryption.password"; //TrustStore configuration - public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_TYPE = + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_TYPE = CONFIG_PROP_PREFIX + ".security.truststore.type"; - public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_NAME = + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_NAME = CONFIG_PROP_PREFIX + ".security.truststore.name"; - public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PATH = + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PATH = CONFIG_PROP_PREFIX + ".security.truststore.path"; - public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PASSWORD = + public static final String CONFIG_PROP_SECURITY_TRUSTSTORE_PASSWORD = CONFIG_PROP_PREFIX + ".security.truststore.password"; - - + + public static final String CONFIG_PROP_SECURITY_SIG_ALG_RSA = + CONFIG_PROP_PREFIX + ".security.sigalg.rsa"; + public static final String CONFIG_PROP_SECURITY_SIG_ALG_ECC = + CONFIG_PROP_PREFIX + ".security.sigalg.ecc"; + public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT_ELEMENT = "default"; public static final String CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT = CONFIG_PROP_VDA_ENDPOINT_QUALeID + CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT_ELEMENT; diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java index af155206..62779072 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/EventCodes.java @@ -12,5 +12,5 @@ public class EventCodes { public static final int AUTHPROCESS_SL20_ENDPOINT_URL = 4112; public static final int AUTHPROCESS_SL20_DATAURL_IP = 4113; - public static final int AUTHPROCESS_SL20_CONSENT_VALID = 4113; + public static final int AUTHPROCESS_SL20_CONSENT_VALID = 4114; } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20EidDataValidationException.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20EidDataValidationException.java index a14fbe9e..f0d993ca 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20EidDataValidationException.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20EidDataValidationException.java @@ -1,15 +1,26 @@ package at.gv.egiz.eaaf.modules.auth.sl20.exceptions; public class SL20EidDataValidationException extends SL20Exception { - private static final long serialVersionUID = 1L; + + private static final long serialVersionUID = -2604130523926584663L; public SL20EidDataValidationException(final Object[] parameters) { - super("sl20.07", parameters); + this("99", parameters); } - + public SL20EidDataValidationException(final Object[] parameters, final Throwable e) { - super("sl20.07", parameters, e); + this("99", parameters, e); + + } + + public SL20EidDataValidationException(final String subErrorId, final Object[] parameters) { + super("sl20.07." + subErrorId, parameters); + + } + + public SL20EidDataValidationException(final String subErrorId, final Object[] parameters, final Throwable e) { + super("sl20.07." + subErrorId, parameters, e); } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java index 12921ad6..08373a2d 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/exceptions/SL20Exception.java @@ -4,7 +4,7 @@ import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; public class SL20Exception extends EaafAuthenticationException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -6716465236880571576L; public SL20Exception(final String messageId, final Object[] parameters) { super(messageId, parameters); diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java index 6c11fa63..9dcfbe75 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java @@ -1,25 +1,29 @@ package at.gv.egiz.eaaf.modules.auth.sl20.tasks; import java.io.Serializable; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.nio.charset.StandardCharsets; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; +import org.apache.commons.lang3.time.StopWatch; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.message.BasicNameValuePair; import org.jose4j.base64url.Base64Url; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.i18n.LocaleContextHolder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -33,7 +37,6 @@ import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; import at.gv.egiz.eaaf.core.impl.utils.Random; -import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils; import at.gv.egiz.eaaf.modules.auth.sl20.Constants; import at.gv.egiz.eaaf.modules.auth.sl20.EventCodes; import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; @@ -42,12 +45,16 @@ import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants.VdaAuthMethod; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20HttpBindingUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20HttpBindingUtils.Sl20ResponseHolder; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonBuilderUtils; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonExtractorUtils; +import lombok.extern.slf4j.Slf4j; +@Slf4j public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServletTask { - private static final Logger log = LoggerFactory.getLogger(AbstractCreateQualEidRequestTask.class); + private static final String FRIENDLYNAME_HTTP_CLIENT = "A-Trust Client"; + @Autowired(required = true) private IHttpClientFactory httpClientFactory; @Autowired(required = true) @@ -60,7 +67,8 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl log.debug("Starting SL2.0 authentication process .... "); revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_SELECTED, "sl20auth"); - + + String vdaQualEidDUrl = null; try { // get service-provider configuration final ISpConfiguration oaConfig = pendingReq.getServiceProviderConfiguration(); @@ -72,7 +80,7 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl } // get basic configuration parameters - final String vdaQualEidDUrl = extractVdaUrlForSpecificOa(oaConfig, executionContext); + vdaQualEidDUrl = extractVdaUrlForSpecificOa(oaConfig, executionContext); if (StringUtils.isEmpty(vdaQualEidDUrl)) { log.error("NO VDA URL for qualified eID (" + Constants.CONFIG_PROP_VDA_ENDPOINT_QUALeID_DEFAULT + ")"); throw new SL20Exception("sl20.03", new Object[] { "NO VDA URL for qualified eID" }); @@ -89,55 +97,48 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl // build request container final String qualEidReqId = Random.nextProcessReferenceValue(); - final ObjectNode sl20Req = SL20JsonBuilderUtils.createGenericRequest(qualEidReqId, null, null, - signedQualEidCommand); + final ObjectNode sl20Req = SL20JsonBuilderUtils.createGenericRequest(qualEidReqId, + pendingReq.getUniqueTransactionIdentifier(), null, signedQualEidCommand); // build http POST request final HttpPost httpReq = new HttpPost(new URIBuilder(vdaQualEidDUrl).build()); final List<NameValuePair> parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, - Base64Url.encode(sl20Req.toString().getBytes("UTF-8")))); - - //set specific authentication method if it was selection by process step before - VdaAuthMethod authMethod = getVdaAuthMethodFromContext(executionContext); - if (authMethod != null) { - log.debug("Request VDA with authType: {}", authMethod); - parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_AUTH_METHOD_PARAM, - authMethod.getAuthMethod())); - } + Base64Url.encode(sl20Req.toString().getBytes(StandardCharsets.UTF_8)))); - //set VDA sessionId if it was available on context - String vdaSessionId = getVdaSessionIdFromContext(executionContext); - if (vdaSessionId != null) { - log.trace("Request VDA with sessionId: {}", vdaSessionId); - parameters.add(new BasicNameValuePair( - SL20Constants.PARAM_SL20_REQ_AUTH_VDA_SESSIONID, - vdaSessionId)); - - } - - - - httpReq.setEntity(new UrlEncodedFormEntity(parameters)); + //inject additional request parameters + injectAdditionalSL20RequestParams(parameters, executionContext, request); - // build http GET request - // URIBuilder sl20ReqUri = new URIBuilder(vdaQualeIDUrl); - // sl20ReqUri.addParameter(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, - // Base64Url.encode(sl20Req.toString().getBytes())); - // HttpGet httpReq = new HttpGet(sl20ReqUri.build()); + httpReq.setEntity(new UrlEncodedFormEntity(parameters)); // set native client header httpReq.addHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE, SL20Constants.HTTP_HEADER_VALUE_NATIVE); - log.trace("Request VDA via SL20 with: " + Base64Url.encode(sl20Req.toString().getBytes("UTF-8"))); + log.trace("Request VDA via SL20 with: {}", + Base64Url.encode(sl20Req.toString().getBytes(StandardCharsets.UTF_8))); // request VDA - final HttpResponse httpResp = httpClientFactory.getHttpClient(false).execute(httpReq); - - // parse response - log.info("Receive response from VDA ... "); - final JsonNode sl20Resp = SL20JsonExtractorUtils.getSL20ContainerFromResponse(httpResp); - final VerificationResult respPayloadContainer = SL20JsonExtractorUtils.extractSL20PayLoad(sl20Resp, null, false); + + final StopWatch watch = StopWatch.createStarted(); + log.info("Requesting {} for authentication ... ", FRIENDLYNAME_HTTP_CLIENT); + final Sl20ResponseHolder httpResp = httpClientFactory.getHttpClient(false).execute( + httpReq, SL20HttpBindingUtils.sl20ResponseHandler()); + + watch.stop(); + log.info("Respone from {} received after: {}[ms] with statusCode: {}", FRIENDLYNAME_HTTP_CLIENT, + watch.getTime(TimeUnit.MILLISECONDS), httpResp.getResponseStatus().getStatusCode()); + + //check on error on http channel + if (httpResp.getError() != null) { + log.info("Basic SL2.0 response processing has an error. HTTP-StatusCode: {} ErrorMsg: {}", + httpResp.getResponseStatus().getStatusCode(), httpResp.getError().getMessage()); + throw httpResp.getError(); + + } + + // parse response + final VerificationResult respPayloadContainer = + SL20JsonExtractorUtils.extractSL20PayLoad(httpResp.getResponseBody(), null, false); if (respPayloadContainer.isValidSigned() == null) { log.debug("Receive unsigned payLoad from VDA"); @@ -158,10 +159,10 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND, false); // create forward SL2.0 command - final ObjectNode sl20Forward = sl20Resp.deepCopy(); + final ObjectNode sl20Forward = httpResp.getResponseBody().deepCopy(); SL20JsonBuilderUtils.addOnlyOnceOfTwo(sl20Forward, SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD, command.deepCopy(), signedCommand); - + // store pending request pendingReq.setRawDataToTransaction(Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_REQID, qualEidReqId); @@ -201,21 +202,58 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl } catch (final EaafAuthenticationException e) { throw new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e); + } catch (final SocketException | SocketTimeoutException e) { + log.error("SL2.0 Authentication has a VDA connector error. Endpoint: {}", + vdaQualEidDUrl, e); + throw new TaskExecutionException(pendingReq, e.getMessage(), + new SL20Exception("sl20.02", new Object[] { e.getMessage()}, e)); + } catch (final Exception e) { log.warn("SL2.0 Authentication FAILED with a generic error.", e); throw new TaskExecutionException(pendingReq, e.getMessage(), e); - } finally { - TransactionIdUtils.removeTransactionId(); - TransactionIdUtils.removeSessionId(); + } + + } + protected void injectAdditionalSL20RequestParams(List<NameValuePair> parameters, + ExecutionContext executionContext, HttpServletRequest request) { + //set specific authentication method if it was selection by process step before + final VdaAuthMethod authMethod = getVdaAuthMethodFromContext(executionContext); + if (authMethod != null) { + log.debug("Request VDA with authType: {}", authMethod); + parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_AUTH_METHOD_PARAM, + authMethod.getAuthMethod())); } + //set VDA sessionId if it was available on context + final String vdaSessionId = getVdaSessionIdFromContext(executionContext); + if (vdaSessionId != null) { + log.trace("Request VDA with sessionId: {}", vdaSessionId); + parameters.add(new BasicNameValuePair( + SL20Constants.PARAM_SL20_REQ_AUTH_VDA_SESSIONID, + vdaSessionId)); + + } + + //set i18n language into VDA request + final Locale locale = LocaleContextHolder.getLocale(); + final String language = locale.getLanguage(); + if (StringUtils.isNotEmpty(language)) { + log.trace("Find i18n context). Inject locale: {} into VDA request", locale.getLanguage()); + parameters.add(new BasicNameValuePair( + SL20Constants.PARAM_SL20_REQ_AUTH_VDA_LOCALE, + language.toUpperCase(locale))); + + } else { + log.info("Find i18n context, but Language is UNKNOWN. It will be ignored"); + + } } /** - * Get ExecutionContext parameter-key for VDA AuthMethod information. - * + * Get ExecutionContext parameter-key for VDA AuthMethod information. + * * @return Key to get AuthMethod from {@link ExecutionContext} */ protected abstract String getAuthMethodContextParamKey(); @@ -231,34 +269,34 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl */ protected abstract String buildSignedQualifiedEidCommand() throws CertificateEncodingException, SL20Exception; - + private VdaAuthMethod getVdaAuthMethodFromContext(ExecutionContext executionContext) { - Serializable authMethodRaw = executionContext.get(getAuthMethodContextParamKey()); + final Serializable authMethodRaw = executionContext.get(getAuthMethodContextParamKey()); if (authMethodRaw instanceof String) { log.trace("Find authMethod parameter: {} on context", authMethodRaw); return VdaAuthMethod.fromString((String) authMethodRaw); - + } - + return null; } - + private String getVdaSessionIdFromContext(ExecutionContext executionContext) { - Serializable vdaSessionId = executionContext.get( + final Serializable vdaSessionId = executionContext.get( SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID); - if (vdaSessionId instanceof String + if (vdaSessionId instanceof String && StringUtils.isNotEmpty((CharSequence) vdaSessionId)) { executionContext.remove( SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID); - + log.trace("Find vdaSessionId parameter: {} on context", vdaSessionId); return (String) vdaSessionId; - + } - + return null; } - + private String extractVdaUrlForSpecificOa(final ISpConfiguration oaConfig, final ExecutionContext executionContext) { // load SP specific config for development and testing purposes diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java index 4786ff39..dfa05a89 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java @@ -8,8 +8,6 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.jose4j.base64url.Base64Url; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.fasterxml.jackson.core.JsonParseException; @@ -22,7 +20,6 @@ import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.DataUrlBuilder; import at.gv.egiz.eaaf.core.impl.utils.StreamUtils; -import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils; import at.gv.egiz.eaaf.modules.auth.sl20.Constants; import at.gv.egiz.eaaf.modules.auth.sl20.EventCodes; import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; @@ -35,9 +32,10 @@ import at.gv.egiz.eaaf.modules.auth.sl20.utils.JsonMapper; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonExtractorUtils; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20ResponseUtils; +import lombok.extern.slf4j.Slf4j; +@Slf4j public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask { - private static final Logger log = LoggerFactory.getLogger(AbstractReceiveQualEidTask.class); @Autowired(required = true) private IJoseTools joseTools; @@ -82,13 +80,14 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask sl20ReqObj = new JsonMapper().getMapper().readTree(Base64Url.decodeToUtf8String(sl20Result)); } catch (final JsonParseException e) { - log.warn("SL2.0 command or result is NOT valid JSON.", e); - log.debug("SL2.0 msg: " + sl20Result); + log.error("SL2.0 command or result is NOT valid JSON. Received msg: {}", sl20Result, e); throw new SL20Exception("sl20.02", new Object[] { "SL2.0 command or result is NOT valid JSON." }, e); } + log.info("Receive response from A-Trust. Starting response-message validation ... "); + // check on errorMessage final VerificationResult payLoadContainerErrorCheck = SL20JsonExtractorUtils.extractSL20PayLoad( sl20ReqObj, @@ -117,7 +116,7 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask log.debug("VDA provides an optional sessionId. Inject it to internal error-holder "); ex.setVdaSessionId(vdaSessionId); - } + } throw ex; } else { @@ -161,7 +160,6 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask } } catch (final EaafAuthenticationException e) { - log.warn("SL2.0 processing error:", e); if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } @@ -170,8 +168,8 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e)); } catch (final Exception e) { - log.warn("ERROR:", e); - log.warn("SL2.0 Authentication FAILED with a generic error.", e); + + if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } @@ -212,11 +210,6 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask log.error("Can NOT send error message. SOMETHING IS REALY WRONG!", e); } - - } finally { - TransactionIdUtils.removeTransactionId(); - TransactionIdUtils.removeSessionId(); - } } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java new file mode 100644 index 00000000..5b221bbe --- /dev/null +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java @@ -0,0 +1,374 @@ +package at.gv.egiz.eaaf.modules.auth.sl20.utils; + +import java.io.IOException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; + +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.jose4j.jca.ProviderContext; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwx.Headers; +import org.jose4j.jwx.JsonWebStructure; +import org.jose4j.keys.resolvers.X509VerificationKeyResolver; +import org.jose4j.lang.JoseException; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.exception.EaafKeyUsageException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * {@link JoseUtils} provides static methods JWS and JWE processing. + * + * @author tlenz + * + */ +@Slf4j +public class JoseUtils { + + private static final Provider provider = new BouncyCastleProvider(); + + /** + * Create a JWS signature. + * + * <p> + * Use {@link org.jose4j.jws.AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case + * of a RSA based key and + * {@link org.jose4j.jws.AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256} + * in case of an ECC based key. + * </p> + * + * @param keyStore KeyStore that should be used + * @param keyAlias Alias of the private key + * @param keyPassword Password to access the key + * @param payLoad PayLoad to sign + * @param addFullCertChain If true the full certificate chain will be + * added, otherwise only the + * X509CertSha256Fingerprint is added into JOSE + * header + * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging + * purposes only + * @return Signed PayLoad in serialized form + * @throws EaafException In case of a key-access or key-usage error + * @throws JoseException In case of a JOSE error + */ + public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore, + @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, + @Nonnull final String payLoad, boolean addFullCertChain, + @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, Collections.emptyMap(), + AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, + friendlyNameForLogging); + + } + + /** + * Create a JWS signature. + * + * <p> + * Use {@link org.jose4j.jws.AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case + * of a RSA based key and + * {@link org.jose4j.jws.AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256} + * in case of an ECC based key. + * </p> + * + * @param keyStore KeyStore that should be used + * @param keyAlias Alias of the private key + * @param keyPassword Password to access the key + * @param payLoad PayLoad to sign + * @param addFullCertChain If true the full certificate chain will be + * added, otherwise only the + * X509CertSha256Fingerprint is added into JOSE + * header + * @param joseHeaders HeaderName and HeaderValue that should be set + * into JOSE header + * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging + * purposes only + * @return Signed PayLoad in serialized form + * @throws EaafException In case of a key-access or key-usage error + * @throws JoseException In case of a JOSE error + */ + public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore, + @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, + @Nonnull final String payLoad, boolean addFullCertChain, + @Nonnull final Map<String, String> joseHeaders, + @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, joseHeaders, + AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, + friendlyNameForLogging); + + } + + /** + * Create a JWS signature. + * + * @param keyStore KeyStore that should be used + * @param keyAlias Alias of the private key + * @param keyPassword Password to access the key + * @param payLoad PayLoad to sign + * @param addFullCertChain If true the full certificate chain will be + * added, otherwise only the + * X509CertSha256Fingerprint is added into JOSE + * header + * @param joseHeaders HeaderName and HeaderValue that should be set + * into JOSE header + * @param rsaAlgToUse Signing algorithm that should be used in case + * of a signing key based on RSA + * @param eccAlgToUse Signing algorithm that should be used in case + * of a signing key based on ECC + * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging + * purposes only + * @return Signed PayLoad in serialized form + * @throws EaafException In case of a key-access or key-usage error + * @throws JoseException In case of a JOSE error + */ + public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore, + @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, + @Nonnull final String payLoad, boolean addFullCertChain, + @Nonnull final Map<String, String> joseHeaders, + @Nonnull final String rsaAlgToUse, @Nonnull final String eccAlgToUse, + @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + + final JsonWebSignature jws = new JsonWebSignature(); + + // set payload + jws.setPayload(payLoad); + + // set JOSE headers + for (final Entry<String, String> el : joseHeaders.entrySet()) { + log.trace("Set JOSE header: {} with value: {} into JWS", el.getKey(), el.getValue()); + jws.setHeader(el.getKey(), el.getValue()); + + } + + // set signing information + final Pair<Key, X509Certificate[]> signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates( + keyStore.getFirst(), keyAlias, keyPassword, true, friendlyNameForLogging); + + // set verification key + jws.setKey(convertToBcKeyIfRequired(signingCred.getFirst())); + + jws.setAlgorithmHeaderValue(getKeyOperationAlgorithmFromCredential( + jws.getKey(), rsaAlgToUse, eccAlgToUse, friendlyNameForLogging)); + + // set special provider if required + if (keyStore.getSecond() != null) { + log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getSuppliedKeyProviderContext().setSignatureProvider(keyStore.getSecond().getName()); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + jws.setProviderContext(providerCtx); + + } else { + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + jws.setProviderContext(providerCtx); + + } + + if (addFullCertChain) { + jws.setCertificateChainHeaderValue(signingCred.getSecond()); + + } + + jws.setX509CertSha256ThumbprintHeaderValue(signingCred.getSecond()[0]); + + return jws.getCompactSerialization(); + + } + + /** + * Verify a JOSE signature. + * + * @param serializedContent Serialized content that should be verified + * @param trustedCerts Trusted certificates that should be used for + * verification + * @param constraints {@link AlgorithmConstraints} for verification + * @return {@link JwsResult} object + * @throws JoseException In case of a signature verification error + * @throws IOException In case of a general error + */ + public static JwsResult validateSignature(@Nonnull final String serializedContent, + @Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints) + throws JoseException, IOException { + final JsonWebSignature jws = new JsonWebSignature(); + // set payload + jws.setCompactSerialization(serializedContent); + + // set security constrains + jws.setAlgorithmConstraints(constraints); + + // load signinc certs + Key selectedKey = null; + final List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue(); + final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue(); + if (x5cCerts != null) { + log.debug("Found x509 certificate in JOSE header ... "); + log.trace("Sorting received X509 certificates ... "); + final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); + + + + if (trustedCerts.contains(sortedX5cCerts.get(0))) { + selectedKey = sortedX5cCerts.get(0).getPublicKey(); + + } else { + log.info("Can NOT find JOSE certificate in truststore."); + if (log.isDebugEnabled()) { + try { + log.debug("Cert: {}", Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded())); + + } catch (final CertificateEncodingException e) { + log.warn("Can not create DEBUG output", e); + + } + } + } + + } else if (StringUtils.isNotEmpty(x5t256)) { + log.debug("Found x5t256 fingerprint in JOSE header .... "); + final X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver( + trustedCerts); + selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList()); + + } else { + throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint"); + + } + + if (selectedKey == null) { + throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED"); + + } + + //set BouncyCastleProvider as default provider + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + jws.setProviderContext(providerCtx); + + // set verification key + jws.setKey(convertToBcKeyIfRequired(selectedKey)); + + // load payLoad + return new JwsResult( + jws.verifySignature(), + jws.getUnverifiedPayload(), + jws.getHeaders(), + x5cCerts); + + } + + + /** + * Convert an ECC public-key into BouncyCastle implementation. + * + * <p> IAIK JCE / Eccelerate ECC Keys are not compatible to JWS impl.</p> + * @param input Key + * @return input Key, or BC ECC-Key in case of a ECC Key + */ + public static Key convertToBcKeyIfRequired(Key input) { + try { + if (input instanceof ECPublicKey + && "iaik.security.ec.common.ECPublicKey".equals(input.getClass().getName())) { + + //convert Key to BouncyCastle KeyImplemenation because there is an + //incompatibility with IAIK EC Keys and JWS signature-verfification implementation + PublicKey publicKey = KeyFactory.getInstance( + input.getAlgorithm(), provider).generatePublic( + new X509EncodedKeySpec(input.getEncoded())); + return publicKey; + + } else if (input instanceof ECPrivateKey + && "iaik.security.ec.common.ECPrivateKey".equals(input.getClass().getName())) { + //convert Key to BouncyCastle KeyImplemenation because there is an + //incompatibility with IAIK EC Keys and JWS signature-creation implementation + Key privateKey = KeyFactory.getInstance( + input.getAlgorithm(), provider).generatePrivate( + new PKCS8EncodedKeySpec(input.getEncoded())); + + return privateKey; + + } + + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + log.warn("Can NOT convert {} to {}. The verification may FAIL.", + input.getClass().getName(), PublicKey.class.getName(), e); + + } + + return input; + + } + + /** + * Select signature algorithm for a given credential. + * + * @param key {@link X509Credential} that will be used for + * key operations + * @param rsaSigAlgorithm RSA based algorithm that should be used in case + * of RSA credential + * @param ecSigAlgorithm EC based algorithm that should be used in case + * of RSA credential + * @param friendlyNameForLogging KeyStore friendlyName for logging purposes + * @return either the RSA based algorithm or the EC based algorithm + * @throws EaafKeyUsageException In case of an unsupported private-key type + */ + private static String getKeyOperationAlgorithmFromCredential(Key key, + String rsaSigAlgorithm, String ecSigAlgorithm, String friendlyNameForLogging) + throws EaafKeyUsageException { + if (key instanceof RSAPrivateKey) { + return rsaSigAlgorithm; + + } else if (key instanceof ECPrivateKey) { + return ecSigAlgorithm; + + } else { + log.warn("Could NOT select the cryptographic algorithm from Private-Key type"); + throw new EaafKeyUsageException(EaafKeyUsageException.ERROR_CODE_01, + friendlyNameForLogging, + "Can not select cryptographic algorithm"); + + } + + } + + private JoseUtils() { + + } + + @Getter + @AllArgsConstructor + public static class JwsResult { + final boolean valid; + final String payLoad; + final Headers fullJoseHeader; + final List<X509Certificate> x5cCerts; + + } +} diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java index 43c44647..58e3e41c 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java @@ -14,15 +14,14 @@ import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.jose4j.jca.ProviderContext; import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; import org.jose4j.jwe.JsonWebEncryption; import org.jose4j.jws.AlgorithmIdentifiers; -import org.jose4j.jws.JsonWebSignature; -import org.jose4j.jwx.JsonWebStructure; +import org.jose4j.jwx.HeaderParameterNames; import org.jose4j.keys.X509Util; -import org.jose4j.keys.resolvers.X509VerificationKeyResolver; import org.jose4j.lang.JoseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +36,7 @@ import com.fasterxml.jackson.databind.JsonNode; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; @@ -49,6 +49,7 @@ import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20SecurityException; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoBuildException; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.JoseUtils.JwsResult; @Service public class JsonSecurityUtils implements IJoseTools { @@ -125,40 +126,16 @@ public class JsonSecurityUtils implements IJoseTools { @Override public String createSignature(final String payLoad, boolean addFullCertChain) throws SlCommandoBuildException { try { - final JsonWebSignature jws = new JsonWebSignature(); - - // set payload - jws.setPayload(payLoad); - - // set basic header - jws.setContentTypeHeaderValue(SL20Constants.SL20_CONTENTTYPE_SIGNED_COMMAND); - - // set signing information - jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); - final Pair<Key, X509Certificate[]> signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates( - keyStore.getFirst(), getSigningKeyAlias(), getSigningKeyPassword(), true, FRIENDLYNAME_KEYSTORE); - jws.setKey(signingCred.getFirst()); - - // set special provider if required - if (keyStore.getSecond() != null) { - log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); - final ProviderContext providerCtx = new ProviderContext(); - providerCtx.getSuppliedKeyProviderContext().setSignatureProvider( - keyStore.getSecond().getName()); - jws.setProviderContext(providerCtx); - - } - - if (addFullCertChain) { - jws.setCertificateChainHeaderValue(signingCred.getSecond()); - - } - - jws.setX509CertSha256ThumbprintHeaderValue(signingCred.getSecond()[0]); - - return jws.getCompactSerialization(); + return JoseUtils.createSignature(keyStore, getSigningKeyAlias(), getSigningKeyPassword(), + payLoad, addFullCertChain, + Collections.singletonMap( + HeaderParameterNames.CONTENT_TYPE, + SL20Constants.SL20_CONTENTTYPE_SIGNED_COMMAND), + getRsaSigningAlgorithm(), + getEccSigningAlgorithm(), + FRIENDLYNAME_KEYSTORE); - } catch (final JoseException | EaafKeyAccessException e) { + } catch (final JoseException | EaafException e) { log.warn("Can NOT sign SL2.0 command.", e); throw new SlCommandoBuildException("Can NOT sign SL2.0 command.", e); @@ -179,61 +156,12 @@ public class JsonSecurityUtils implements IJoseTools { public VerificationResult validateSignature(@Nonnull final String serializedContent, @Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints) throws JoseException, IOException { - final JsonWebSignature jws = new JsonWebSignature(); - // set payload - jws.setCompactSerialization(serializedContent); - - // set security constrains - jws.setAlgorithmConstraints(constraints); - - // load signinc certs - Key selectedKey = null; - final List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue(); - final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue(); - if (x5cCerts != null) { - log.debug("Found x509 certificate in JOSE header ... "); - log.trace("Sorting received X509 certificates ... "); - final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); - - if (trustedCerts.contains(sortedX5cCerts.get(0))) { - selectedKey = sortedX5cCerts.get(0).getPublicKey(); - - } else { - log.info("Can NOT find JOSE certificate in truststore."); - try { - log.debug("Cert: " + Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded())); - - } catch (final CertificateEncodingException e) { - e.printStackTrace(); - } - - } - - } else if (StringUtils.isNotEmpty(x5t256)) { - log.debug("Found x5t256 fingerprint in JOSE header .... "); - final X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver( - trustedCerts); - selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList()); - - } else { - throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint"); - - } - - if (selectedKey == null) { - throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED"); - - } - - // set verification key - jws.setKey(selectedKey); - - // load payLoad + final JwsResult result = JoseUtils.validateSignature(serializedContent, trustedCerts, constraints); return new VerificationResult( - mapper.getMapper().readTree(jws.getHeaders().getFullHeaderAsJsonString()), - mapper.getMapper().readTree(jws.getPayload()), - x5cCerts, jws.verifySignature()); + mapper.getMapper().readTree(result.getFullJoseHeader().getFullHeaderAsJsonString()), + mapper.getMapper().readTree(result.getPayLoad()), + result.getX5cCerts(), result.isValid()); } @@ -241,7 +169,7 @@ public class JsonSecurityUtils implements IJoseTools { @Nonnull public VerificationResult validateSignature(@Nonnull final String serializedContent) throws SL20Exception { try { - final AlgorithmConstraints algConstraints = new AlgorithmConstraints(ConstraintType.WHITELIST, + final AlgorithmConstraints algConstraints = new AlgorithmConstraints(ConstraintType.PERMIT, SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING .toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_SIGNING.size()])); @@ -251,7 +179,7 @@ public class JsonSecurityUtils implements IJoseTools { if (!result.isValidSigned()) { log.info("JWS signature invalide. Stopping authentication process ..."); - log.debug("Received JWS msg: " + serializedContent); + log.debug("Received JWS msg: {}", serializedContent); throw new SL20SecurityException("JWS signature invalide."); } @@ -278,11 +206,11 @@ public class JsonSecurityUtils implements IJoseTools { // set security constrains receiverJwe.setAlgorithmConstraints( - new AlgorithmConstraints(ConstraintType.WHITELIST, + new AlgorithmConstraints(ConstraintType.PERMIT, SL20Constants.SL20_ALGORITHM_WHITELIST_KEYENCRYPTION .toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_KEYENCRYPTION.size()]))); receiverJwe.setContentEncryptionAlgorithmConstraints( - new AlgorithmConstraints(ConstraintType.WHITELIST, SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION + new AlgorithmConstraints(ConstraintType.PERMIT, SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION .toArray(new String[SL20Constants.SL20_ALGORITHM_WHITELIST_ENCRYPTION.size()]))); // set payload @@ -292,6 +220,21 @@ public class JsonSecurityUtils implements IJoseTools { keyStore.getFirst(), getEncryptionKeyAlias(), getEncryptionKeyPassword(), true, FRIENDLYNAME_KEYSTORE); + // set special provider if required + if (keyStore.getSecond() != null) { + log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getSuppliedKeyProviderContext().setGeneralProvider(keyStore.getSecond().getName()); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + receiverJwe.setProviderContext(providerCtx); + + } else { + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + receiverJwe.setProviderContext(providerCtx); + + } + // validate key from header against key from config final List<X509Certificate> x5cCerts = receiverJwe.getCertificateChainHeaderValue(); final String x5t256 = receiverJwe.getX509CertSha256ThumbprintHeaderValue(); @@ -318,7 +261,7 @@ public class JsonSecurityUtils implements IJoseTools { final String certFingerPrint = X509Util.x5tS256(encryptionCred.getSecond()[0]); if (!certFingerPrint.equals(x5t256)) { log.info("X5t256 from JOSE header does NOT match encryption certificate"); - log.debug("X5t256 from JOSE header: " + x5t256 + " Encrytption cert: " + certFingerPrint); + log.debug("X5t256 from JOSE header: {} Encrytption cert: {}", x5t256, certFingerPrint); throw new SL20Exception("sl20.05", new Object[] { "X5t256 from JOSE header does NOT match encryption certificate" }); @@ -332,7 +275,7 @@ public class JsonSecurityUtils implements IJoseTools { } // set key - receiverJwe.setKey(encryptionCred.getFirst()); + receiverJwe.setKey(JoseUtils.convertToBcKeyIfRequired(encryptionCred.getFirst())); // decrypt payload return mapper.getMapper().readTree(receiverJwe.getPlaintextString()); @@ -377,8 +320,7 @@ public class JsonSecurityUtils implements IJoseTools { config.setFriendlyName(FRIENDLYNAME_KEYSTORE); config.setKeyStoreType(authConfig.getBasicConfiguration( - authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_TYPE), - KeyStoreType.JKS.getKeyStoreType())); + Constants.CONFIG_PROP_SECURITY_KEYSTORE_TYPE, KeyStoreType.JKS.getKeyStoreType())); config.setKeyStoreName( authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_KEYSTORE_NAME)); config.setSoftKeyStoreFilePath( @@ -398,8 +340,7 @@ public class JsonSecurityUtils implements IJoseTools { config.setFriendlyName(FRIENDLYNAME_TRUSTSTORE); config.setKeyStoreType(authConfig.getBasicConfiguration( - authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_TYPE), - KeyStoreType.JKS.getKeyStoreType())); + Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_TYPE, KeyStoreType.JKS.getKeyStoreType())); config.setKeyStoreName( authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_TRUSTSTORE_NAME)); config.setSoftKeyStoreFilePath( @@ -452,4 +393,26 @@ public class JsonSecurityUtils implements IJoseTools { return null; } + private String getRsaSigningAlgorithm() { + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_SIG_ALG_RSA, + AlgorithmIdentifiers.RSA_PSS_USING_SHA256); + if (value != null) { + value = value.trim(); + } + + return value; + + } + + private String getEccSigningAlgorithm() { + String value = authConfig.getBasicConfiguration(Constants.CONFIG_PROP_SECURITY_SIG_ALG_ECC, + AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256); + if (value != null) { + value = value.trim(); + } + + return value; + + } + } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java index edf70cc8..c95bcc45 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20Constants.java @@ -13,14 +13,15 @@ import org.jose4j.jws.AlgorithmIdentifiers; public class SL20Constants { public static final int CURRENT_SL20_VERSION = 10; - + // http binding parameters public static final String PARAM_SL20_REQ_COMMAND_PARAM = "slcommand"; public static final String PARAM_SL20_REQ_COMMAND_PARAM_OLD = "sl2command"; public static final String PARAM_SL20_REQ_AUTH_METHOD_PARAM = "authtype"; public static final String PARAM_SL20_REQ_AUTH_VDA_SESSIONID = "session"; - + public static final String PARAM_SL20_REQ_AUTH_VDA_LOCALE = "locale"; + public enum VdaAuthMethod { ANY("any"), MOBILEPHONE("handy"), CARD("card"), SMARTPHONE("smartphone"); @@ -51,7 +52,7 @@ public class SL20Constants { } catch (IllegalArgumentException | NullPointerException e) { return VdaAuthMethod.ANY; - + } } @@ -59,9 +60,9 @@ public class SL20Constants { public String toString() { return getAuthMethod(); - } + } } - + public static final String PARAM_SL20_REQ_ICP_RETURN_URL_PARAM = "slIPCReturnUrl"; public static final String PARAM_SL20_REQ_TRANSACTIONID = "slTransactionID"; @@ -97,7 +98,11 @@ public class SL20Constants { KeyManagementAlgorithmIdentifiers.RSA_OAEP_256; public static final List<String> SL20_ALGORITHM_WHITELIST_KEYENCRYPTION = Collections - .unmodifiableList(Arrays.asList(JSON_ALGORITHM_ENC_KEY_RSAOAEP, JSON_ALGORITHM_ENC_KEY_RSAOAEP256)); + .unmodifiableList(Arrays.asList( + JSON_ALGORITHM_ENC_KEY_RSAOAEP, + JSON_ALGORITHM_ENC_KEY_RSAOAEP256, + KeyManagementAlgorithmIdentifiers.ECDH_ES_A128KW, + KeyManagementAlgorithmIdentifiers.ECDH_ES_A256KW)); public static final String JSON_ALGORITHM_ENC_PAYLOAD_A128CBCHS256 = ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256; @@ -177,7 +182,7 @@ public class SL20Constants { // error command public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE = "errorCode"; public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE = "errorMessage"; - public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID + public static final String SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID = "handySignaturSession"; // qualified eID command diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java index 1d7c9646..2b6ddb96 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java @@ -3,23 +3,133 @@ package at.gv.egiz.eaaf.modules.auth.sl20.utils; import java.io.IOException; import java.io.StringWriter; import java.net.URISyntaxException; +import java.text.MessageFormat; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.ParseException; +import org.apache.http.StatusLine; +import org.apache.http.client.ResponseHandler; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.util.EntityUtils; import org.jose4j.base64url.Base64Url; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import com.fasterxml.jackson.databind.JsonNode; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; +import lombok.Data; +import lombok.Getter; + public class SL20HttpBindingUtils { private static final Logger log = LoggerFactory.getLogger(SL20HttpBindingUtils.class); + private static JsonMapper mapper = new JsonMapper(); + + @Data + @Getter + public static class Sl20ResponseHolder { + private final JsonNode responseBody; + private final StatusLine responseStatus; + private SlCommandoParserException error; + + } + + /** + * Security-Layer 2.0 specific response-handler for Apache HTTP client. + * + * @return {@link Sl20ResponseHolder} + */ + public static ResponseHandler<Sl20ResponseHolder> sl20ResponseHandler() { + return response -> { + try { + final int httpStatusCode = response.getStatusLine().getStatusCode(); + if (httpStatusCode == HttpStatus.OK.value()) { + if (response.getEntity().getContentType() == null) { + throw new SlCommandoParserException("SL20 response contains NO ContentType"); + + } + + final ContentType contentType = ContentType.getOrDefault(response.getEntity()); + if (!ContentType.APPLICATION_JSON.getMimeType().equals(contentType.getMimeType())) { + log.error("SL20 response with statuscode: {} has wrong http ContentType: {}", + response.getStatusLine(), contentType); + throw new SlCommandoParserException( + "SL20 response with a wrong http ContentType: " + contentType); + + } + + //parse OK response from body + return new Sl20ResponseHolder(parseSL20ResultFromResponse(response.getEntity()), + response.getStatusLine()); + + } else if (httpStatusCode == HttpStatus.SEE_OTHER.value() + || httpStatusCode == HttpStatus.TEMPORARY_REDIRECT.value()) { + final Header[] locationHeader = response.getHeaders("Location"); + if (locationHeader == null) { + throw new SlCommandoParserException("Find Redirect statuscode but not Location header"); + + } + + final String sl20RespString = new URIBuilder(locationHeader[0].getValue()).getQueryParams().get(0).getValue(); + return new Sl20ResponseHolder(mapper.getMapper().readTree(Base64Url.decode(sl20RespString)), + response.getStatusLine()); + + } else if ( + httpStatusCode == HttpStatus.INTERNAL_SERVER_ERROR.value() + || httpStatusCode == HttpStatus.UNAUTHORIZED.value() + || httpStatusCode == HttpStatus.BAD_REQUEST.value()) { + log.info("SL20 response with http-code: {}. Search for error message", httpStatusCode); + + String bodyMsg = "_EMPTY_"; + try { + //extract JSON body from defined http error-codes + bodyMsg = EntityUtils.toString(response.getEntity()); + log.info("SL20 response with http-code: {0} and errorMsg: {1}", httpStatusCode, bodyMsg); + Sl20ResponseHolder holder = new Sl20ResponseHolder( + mapper.getMapper().readTree(bodyMsg), response.getStatusLine()); + return holder; + + } catch (final IOException | ParseException e) { + log.warn("SL20 response contains no )valid JSON", e); + throw new SlCommandoParserException(MessageFormat.format( + "SL20 response with http-code: {0} with body: {1} and generic response-processing error: {2}", + httpStatusCode, bodyMsg, e.getMessage())); + + } + + } else { + //all other HTTP StatusCodes + throw new SlCommandoParserException(MessageFormat.format( + "SL20 response with http-code: {0} and errorMsg: {1}", + httpStatusCode, EntityUtils.toString(response.getEntity()))); + + } + + } catch (SlCommandoParserException e) { + Sl20ResponseHolder holder = new Sl20ResponseHolder(null, response.getStatusLine()); + holder.setError(e); + return holder; + + } catch (final Exception e) { + Sl20ResponseHolder holder = new Sl20ResponseHolder(null, response.getStatusLine()); + holder.setError( + new SlCommandoParserException("SL20 response parsing FAILED! Reason: " + e.getMessage(), e)); + return holder; + + } + }; + } + /** * Write SL2.0 response into http-response object * @@ -59,6 +169,34 @@ public class SL20HttpBindingUtils { httpResp.setHeader("Location", clientRedirectUri.build().toString()); } + } + + private static JsonNode parseSL20ResultFromResponse(final HttpEntity resp) throws Exception { + if (resp != null && resp.getContent() != null) { + final String rawSL20Resp = EntityUtils.toString(resp); + try { + final JsonNode sl20Resp = mapper.getMapper().readTree(rawSL20Resp); + if (sl20Resp != null) { + return sl20Resp; + + } else { + log.error("SL2.0 can NOT parse to a JSON object from msg: {}", rawSL20Resp); + throw new SlCommandoParserException("SL2.0 can NOT parse to a JSON object"); + } + + } catch (SlCommandoParserException e) { + throw e; + + } catch (Exception e) { + log.error("SL2.0 can NOT parse to a JSON object from msg: {}", rawSL20Resp); + throw new SlCommandoParserException("SL2.0 can NOT parse to a JSON object"); + + } + + } else { + throw new SlCommandoParserException("Can NOT find any content in http response"); + + } } } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java index 40ea0430..bed25c0c 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java @@ -8,12 +8,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.util.EntityUtils; -import org.jose4j.base64url.Base64Url; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -299,84 +293,6 @@ public class SL20JsonExtractorUtils { } - /** - * Extract generic transport container from httpResponse. - * - * @param httpResp Http response object - * @return JSON with SL2.0 response - * @throws SlCommandoParserException In case of an error - */ - public static JsonNode getSL20ContainerFromResponse(final HttpResponse httpResp) throws SlCommandoParserException { - try { - JsonNode sl20Resp = null; - if (httpResp.getStatusLine().getStatusCode() == 303 || httpResp.getStatusLine().getStatusCode() == 307) { - final Header[] locationHeader = httpResp.getHeaders("Location"); - if (locationHeader == null) { - throw new SlCommandoParserException("Find Redirect statuscode but not Location header"); - } - - final String sl20RespString = new URIBuilder(locationHeader[0].getValue()).getQueryParams().get(0).getValue(); - sl20Resp = mapper.getMapper().readTree(Base64Url.decode(sl20RespString)); - - } else if (httpResp.getStatusLine().getStatusCode() == 200) { - if (httpResp.getEntity().getContentType() == null) { - throw new SlCommandoParserException("SL20 response contains NO ContentType"); - } - - if (!httpResp.getEntity().getContentType().getValue().startsWith("application/json")) { - throw new SlCommandoParserException( - "SL20 response with a wrong ContentType: " + httpResp.getEntity().getContentType().getValue()); - } - sl20Resp = parseSL20ResultFromResponse(httpResp.getEntity()); - - } else if (httpResp.getStatusLine().getStatusCode() == 500 || httpResp.getStatusLine().getStatusCode() == 401 - || httpResp.getStatusLine().getStatusCode() == 400) { - log.info( - "SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode() + ". Search for error message"); - - try { - sl20Resp = parseSL20ResultFromResponse(httpResp.getEntity()); - - } catch (final Exception e) { - log.warn("SL20 response contains no valid JSON", e); - throw new SlCommandoParserException("SL20 response with http-code: " - + httpResp.getStatusLine().getStatusCode() + " AND NO valid JSON errormsg", e); - - } - - } else { - throw new SlCommandoParserException( - "SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode()); - } - - log.info("Find JSON object in http response"); - return sl20Resp; - - } catch (final Exception e) { - throw new SlCommandoParserException("SL20 response parsing FAILED! Reason: " + e.getMessage(), e); - - } - } - - private static JsonNode parseSL20ResultFromResponse(final HttpEntity resp) throws Exception { - if (resp != null && resp.getContent() != null) { - final String rawSL20Resp = EntityUtils.toString(resp); - final JsonNode sl20Resp = mapper.getMapper().readTree(rawSL20Resp); - - // TODO: check sl20Resp type like && sl20Resp.isJsonObject() - if (sl20Resp != null) { - return sl20Resp; - - } else { - throw new SlCommandoParserException("SL2.0 can NOT parse to a JSON object"); - } - - } else { - throw new SlCommandoParserException("Can NOT find content in http response"); - } - - } - private static JsonNode getAndCheck(final JsonNode input, final String keyID, final boolean isRequired) throws SlCommandoParserException { final JsonNode internal = input.get(keyID); diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java index 4bb91634..c3826087 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20ResponseUtils.java @@ -11,17 +11,17 @@ import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import at.gv.egiz.eaaf.core.api.IRequest; -import at.gv.egiz.eaaf.core.api.data.EaafConstants; -import at.gv.egiz.eaaf.core.api.idp.IConfiguration; -import at.gv.egiz.eaaf.modules.auth.sl20.Constants; -import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; - import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ContentType; import com.fasterxml.jackson.databind.node.ObjectNode; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -134,7 +134,7 @@ public class SL20ResponseUtils { } else { log.info("SL2.0 DataURL communication needs http header: '" + SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE + "'"); - log.debug("Client request containts is no native client ... "); + log.debug("Client request is no a native client. SL2.0 anwser will be a http redirect ... "); final URIBuilder clientRedirectUri = new URIBuilder(fullRedirectUrl); response.setStatus(Integer.parseInt(authConfig.getBasicConfiguration(Constants.CONFIG_PROP_HTTP_REDIRECT_CODE, Constants.CONFIG_PROP_HTTP_REDIRECT_CODE_DEFAULT_VALUE))); |