Repository: cxf-fediz Updated Branches: refs/heads/master b8aa7ea52 -> 6fef44bb1
Refactor SAMLAuthnParser Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/d2830809 Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/d2830809 Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/d2830809 Branch: refs/heads/master Commit: d2830809c15b066c341cee422a4b54f8ae06be2e Parents: b8aa7ea Author: Colm O hEigeartaigh <[email protected]> Authored: Thu Nov 3 12:01:43 2016 +0000 Committer: Colm O hEigeartaigh <[email protected]> Committed: Thu Nov 3 12:01:43 2016 +0000 ---------------------------------------------------------------------- .../cxf/fediz/service/idp/IdpConstants.java | 10 +- .../idp/beans/samlsso/AuthnRequestParser.java | 288 +++++++++++++++++-- .../beans/samlsso/AuthnRequestValidator.java | 269 ----------------- .../idp/beans/samlsso/SamlResponseCreator.java | 14 +- .../service/idp/samlsso/SAMLAuthnRequest.java | 74 +++++ .../WEB-INF/flows/saml-signin-request.xml | 32 +-- 6 files changed, 355 insertions(+), 332 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d2830809/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java index 95a9fc4..d33bbc1 100644 --- a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java @@ -29,14 +29,16 @@ public final class IdpConstants { public static final String TRUSTED_IDP_CONTEXT = "trusted_idp_context"; /** - * A key used to store a parsed SAMLRequest as an OpenSAML AuthnRequest Object + * A key used to store the home realm for the given request. */ - public static final String SAML_AUTHN_REQUEST = "saml_authn_request"; + public static final String HOME_REALM = "home_realm"; /** - * A key used to store the home realm for the given request. + * The SAML Authn Request */ - public static final String HOME_REALM = "home_realm"; + public static final String SAML_AUTHN_REQUEST = "saml_authn_request"; + + private IdpConstants() { // complete http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d2830809/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java index b9e15f8..53feb73 100644 --- a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java @@ -21,19 +21,48 @@ package org.apache.cxf.fediz.service.idp.beans.samlsso; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import java.util.Collections; import org.w3c.dom.Document; import org.apache.cxf.common.util.Base64Utility; import org.apache.cxf.fediz.core.exception.ProcessingException; +import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE; +import org.apache.cxf.fediz.core.util.CertsUtils; import org.apache.cxf.fediz.service.idp.IdpConstants; +import org.apache.cxf.fediz.service.idp.domain.Application; import org.apache.cxf.fediz.service.idp.domain.Idp; +import org.apache.cxf.fediz.service.idp.samlsso.SAMLAuthnRequest; import org.apache.cxf.fediz.service.idp.util.WebUtils; import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder; +import org.apache.cxf.rs.security.saml.sso.SSOConstants; import org.apache.cxf.staxutils.StaxUtils; +import org.apache.wss4j.common.crypto.CertificateStore; +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.saml.OpenSAMLUtil; +import org.apache.wss4j.common.saml.SAMLKeyInfo; +import org.apache.wss4j.common.saml.SAMLUtil; import org.apache.wss4j.common.util.DOM2Writer; +import org.apache.wss4j.dom.WSDocInfo; +import org.apache.wss4j.dom.engine.WSSConfig; +import org.apache.wss4j.dom.handler.RequestData; +import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor; +import org.apache.wss4j.dom.validate.Credential; +import org.apache.wss4j.dom.validate.SignatureTrustValidator; +import org.apache.wss4j.dom.validate.Validator; +import org.apache.xml.security.utils.Base64; import org.opensaml.saml.saml2.core.AuthnRequest; +import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; +import org.opensaml.security.credential.BasicCredential; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.signature.KeyInfo; +import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.support.SignatureException; +import org.opensaml.xmlsec.signature.support.SignatureValidator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -47,33 +76,42 @@ public class AuthnRequestParser { private static final Logger LOG = LoggerFactory.getLogger(AuthnRequestParser.class); private boolean supportDeflateEncoding; + private boolean requireSignature = true; - public void parseSAMLRequest(RequestContext context, Idp idp, String samlRequest) throws ProcessingException { + public void parseSAMLRequest(RequestContext context, Idp idp, String samlRequest, + String signature, String relayState) throws ProcessingException { LOG.debug("Received SAML Request: {}", samlRequest); - AuthnRequest parsedRequest = null; if (samlRequest == null) { - WebUtils.removeAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + WebUtils.removeAttribute(context, IdpConstants.SAML_AUTHN_REQUEST); + throw new ProcessingException(TYPE.BAD_REQUEST); } else { - parsedRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); - if (parsedRequest == null) { - try { - parsedRequest = extractRequest(context, samlRequest); - WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST, parsedRequest); - LOG.debug("SAML Request with id '{}' successfully parsed", parsedRequest.getID()); - } catch (Exception ex) { - LOG.warn("Error parsing request: {}", ex.getMessage()); - } + AuthnRequest parsedRequest = null; + try { + parsedRequest = extractRequest(context, samlRequest); + } catch (Exception ex) { + LOG.warn("Error parsing request: {}", ex.getMessage()); + throw new ProcessingException(TYPE.BAD_REQUEST); } + + // Store various attributes from the AuthnRequest + SAMLAuthnRequest authnRequest = new SAMLAuthnRequest(parsedRequest); + WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST, authnRequest); + + validateSignature(context, parsedRequest, idp, signature, relayState, + samlRequest, authnRequest.getIssuer()); + validateRequest(parsedRequest); + + LOG.debug("SAML Request with id '{}' successfully parsed", parsedRequest.getID()); } } public String retrieveRealm(RequestContext context) { - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); - if (authnRequest != null && authnRequest.getIssuer() != null) { - String issuer = authnRequest.getIssuer().getValue(); + SAMLAuthnRequest authnRequest = + (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + + if (authnRequest != null) { + String issuer = authnRequest.getIssuer(); LOG.debug("Parsed SAML AuthnRequest Issuer: {}", issuer); return issuer; } @@ -83,11 +121,11 @@ public class AuthnRequestParser { } public String retrieveConsumerURL(RequestContext context) { - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + SAMLAuthnRequest authnRequest = + (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); - if (authnRequest != null && authnRequest.getAssertionConsumerServiceURL() != null) { - String consumerURL = authnRequest.getAssertionConsumerServiceURL(); + if (authnRequest != null && authnRequest.getConsumerServiceURL() != null) { + String consumerURL = authnRequest.getConsumerServiceURL(); LOG.debug("Parsed SAML AuthnRequest Consumer URL: {}", consumerURL); return consumerURL; } @@ -97,11 +135,11 @@ public class AuthnRequestParser { } public String retrieveRequestId(RequestContext context) { - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + SAMLAuthnRequest authnRequest = + (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); - if (authnRequest != null && authnRequest.getID() != null) { - String id = authnRequest.getID(); + if (authnRequest != null && authnRequest.getRequestId() != null) { + String id = authnRequest.getRequestId(); LOG.debug("Parsed SAML AuthnRequest Id: {}", id); return id; } @@ -111,11 +149,11 @@ public class AuthnRequestParser { } public String retrieveRequestIssuer(RequestContext context) { - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + SAMLAuthnRequest authnRequest = + (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); if (authnRequest != null && authnRequest.getIssuer() != null) { - String issuer = authnRequest.getIssuer().getValue(); + String issuer = authnRequest.getIssuer(); LOG.debug("Parsed SAML AuthnRequest Issuer: {}", issuer); return issuer; } @@ -125,10 +163,10 @@ public class AuthnRequestParser { } public boolean isForceAuthentication(RequestContext context) { - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + SAMLAuthnRequest authnRequest = + (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); if (authnRequest != null) { - return authnRequest.isForceAuthn().booleanValue(); + return authnRequest.isForceAuthn(); } LOG.debug("No AuthnRequest available to be parsed"); @@ -159,4 +197,192 @@ public class AuthnRequestParser { public void setSupportDeflateEncoding(boolean supportDeflateEncoding) { this.supportDeflateEncoding = supportDeflateEncoding; } + + private void validateRequest(AuthnRequest parsedRequest) throws ProcessingException { + if (parsedRequest.getIssuer() == null) { + LOG.debug("No Issuer is present in the AuthnRequest"); + throw new ProcessingException(TYPE.BAD_REQUEST); + } + + String format = parsedRequest.getIssuer().getFormat(); + if (format != null + && !"urn:oasis:names:tc:SAML:2.0:nameid-format:entity".equals(format)) { + LOG.debug("An invalid Format attribute was received: {}", format); + throw new ProcessingException(TYPE.BAD_REQUEST); + } + + // No SubjectConfirmation Elements are allowed + if (parsedRequest.getSubject() != null + && parsedRequest.getSubject().getSubjectConfirmations() != null + && !parsedRequest.getSubject().getSubjectConfirmations().isEmpty()) { + LOG.debug("An invalid SubjectConfirmation Element was received"); + throw new ProcessingException(TYPE.BAD_REQUEST); + } + } + + private void validateSignature(RequestContext context, AuthnRequest authnRequest, Idp idp, + String signature, String relayState, String samlRequest, + String realm) throws ProcessingException { + try { + if (authnRequest.isSigned()) { + // Check destination + checkDestination(context, authnRequest); + + // Check signature + X509Certificate validatingCert = getValidatingCertificate(idp, realm); + Crypto issuerCrypto = + new CertificateStore(Collections.singletonList(validatingCert).toArray(new X509Certificate[0])); + validateAuthnRequestSignature(authnRequest.getSignature(), issuerCrypto); + } else if (signature != null) { + // Check destination + checkDestination(context, authnRequest); + + // Check signature + X509Certificate validatingCert = getValidatingCertificate(idp, realm); + + java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA"); + sig.initVerify(validatingCert); + + // Recreate request to sign + String requestToSign = SSOConstants.SAML_REQUEST + "=" + URLEncoder.encode(samlRequest, "UTF-8") + + "&" + SSOConstants.RELAY_STATE + "=" + relayState + "&" + SSOConstants.SIG_ALG + + "=" + URLEncoder.encode(SSOConstants.RSA_SHA1, StandardCharsets.UTF_8.name()); + + sig.update(requestToSign.getBytes(StandardCharsets.UTF_8)); + + if (!sig.verify(Base64.decode(signature))) { + LOG.debug("Signature validation failed"); + throw new ProcessingException(TYPE.BAD_REQUEST); + } + } else if (requireSignature) { + LOG.debug("No signature is present, therefore the request is rejected"); + throw new ProcessingException(TYPE.BAD_REQUEST); + } else { + LOG.debug("No signature is present, but this is allowed by configuration"); + } + } catch (Exception ex) { + LOG.debug("Error validating SAML Signature", ex); + throw new ProcessingException(TYPE.BAD_REQUEST); + } + } + + private X509Certificate getValidatingCertificate(Idp idp, String realm) + throws Exception { + Application serviceConfig = idp.findApplication(realm); + if (serviceConfig == null || serviceConfig.getValidatingCertificate() == null) { + LOG.debug("No validating certificate found for realm {}", realm); + throw new ProcessingException(TYPE.ISSUER_NOT_TRUSTED); + } + + return CertsUtils.parseX509Certificate(serviceConfig.getValidatingCertificate()); + } + + private void checkDestination(RequestContext context, AuthnRequest authnRequest) throws ProcessingException { + // Check destination + String destination = authnRequest.getDestination(); + LOG.debug("Validating destination: {}", destination); + + String localAddr = WebUtils.getHttpServletRequest(context).getRequestURL().toString(); + if (destination == null || !localAddr.startsWith(destination)) { + LOG.debug("The destination {} does not match the local address {}", destination, localAddr); + throw new ProcessingException(TYPE.BAD_REQUEST); + } + } + + /** + * Validate the AuthnRequest signature + */ + private void validateAuthnRequestSignature( + Signature signature, + Crypto sigCrypto + ) throws WSSecurityException { + RequestData requestData = new RequestData(); + requestData.setSigVerCrypto(sigCrypto); + WSSConfig wssConfig = WSSConfig.getNewInstance(); + requestData.setWssConfig(wssConfig); + // requestData.setCallbackHandler(callbackHandler); + + SAMLKeyInfo samlKeyInfo = null; + + KeyInfo keyInfo = signature.getKeyInfo(); + if (keyInfo != null) { + try { + Document doc = signature.getDOM().getOwnerDocument(); + samlKeyInfo = + SAMLUtil.getCredentialFromKeyInfo( + keyInfo.getDOM(), new WSSSAMLKeyInfoProcessor(requestData, new WSDocInfo(doc)), sigCrypto + ); + } catch (WSSecurityException ex) { + LOG.debug("Error in getting KeyInfo from SAML AuthnRequest: {}", ex.getMessage(), ex); + throw ex; + } + } + + if (samlKeyInfo == null) { + LOG.debug("No KeyInfo supplied in the AuthnRequest signature"); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } + + // Validate Signature against profiles + validateSignatureAgainstProfiles(signature, samlKeyInfo); + + // Now verify trust on the signature + Credential trustCredential = new Credential(); + trustCredential.setPublicKey(samlKeyInfo.getPublicKey()); + trustCredential.setCertificates(samlKeyInfo.getCerts()); + + try { + Validator signatureValidator = new SignatureTrustValidator(); + signatureValidator.validate(trustCredential, requestData); + } catch (WSSecurityException e) { + LOG.debug("Error in validating signature on SAML AuthnRequest: {}", e.getMessage(), e); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } + } + + /** + * Validate a signature against the profiles + */ + private void validateSignatureAgainstProfiles( + Signature signature, + SAMLKeyInfo samlKeyInfo + ) throws WSSecurityException { + // Validate Signature against profiles + SAMLSignatureProfileValidator validator = new SAMLSignatureProfileValidator(); + try { + validator.validate(signature); + } catch (SignatureException ex) { + LOG.debug("Error in validating the SAML Signature: {}", ex.getMessage(), ex); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } + + BasicCredential credential = null; + if (samlKeyInfo.getCerts() != null) { + credential = new BasicX509Credential(samlKeyInfo.getCerts()[0]); + } else if (samlKeyInfo.getPublicKey() != null) { + credential = new BasicCredential(samlKeyInfo.getPublicKey()); + } else { + LOG.debug("Can't get X509Certificate or PublicKey to verify signature"); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } + try { + SignatureValidator.validate(signature, credential); + } catch (SignatureException ex) { + LOG.debug("Error in validating the SAML Signature: {}", ex.getMessage(), ex); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } + } + + public boolean isRequireSignature() { + return requireSignature; + } + + /** + * Whether to require a signature or not on the AuthnRequest + * @param requireSignature + */ + public void setRequireSignature(boolean requireSignature) { + this.requireSignature = requireSignature; + } + } http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d2830809/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestValidator.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestValidator.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestValidator.java deleted file mode 100644 index cc87432..0000000 --- a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestValidator.java +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.cxf.fediz.service.idp.beans.samlsso; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.cert.X509Certificate; -import java.util.Collections; - -import org.w3c.dom.Document; - -import org.apache.cxf.fediz.core.exception.ProcessingException; -import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE; -import org.apache.cxf.fediz.core.util.CertsUtils; -import org.apache.cxf.fediz.service.idp.IdpConstants; -import org.apache.cxf.fediz.service.idp.domain.Application; -import org.apache.cxf.fediz.service.idp.domain.Idp; -import org.apache.cxf.fediz.service.idp.util.WebUtils; -import org.apache.cxf.rs.security.saml.sso.SSOConstants; -import org.apache.wss4j.common.crypto.CertificateStore; -import org.apache.wss4j.common.crypto.Crypto; -import org.apache.wss4j.common.ext.WSSecurityException; -import org.apache.wss4j.common.saml.SAMLKeyInfo; -import org.apache.wss4j.common.saml.SAMLUtil; -import org.apache.wss4j.dom.WSDocInfo; -import org.apache.wss4j.dom.engine.WSSConfig; -import org.apache.wss4j.dom.handler.RequestData; -import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor; -import org.apache.wss4j.dom.validate.Credential; -import org.apache.wss4j.dom.validate.SignatureTrustValidator; -import org.apache.wss4j.dom.validate.Validator; -import org.apache.xml.security.utils.Base64; -import org.opensaml.saml.saml2.core.AuthnRequest; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.security.credential.BasicCredential; -import org.opensaml.security.x509.BasicX509Credential; -import org.opensaml.xmlsec.signature.KeyInfo; -import org.opensaml.xmlsec.signature.Signature; -import org.opensaml.xmlsec.signature.support.SignatureException; -import org.opensaml.xmlsec.signature.support.SignatureValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.webflow.execution.RequestContext; - -/** - * Validate the received AuthnRequest - */ -@Component -public class AuthnRequestValidator { - - private static final Logger LOG = LoggerFactory.getLogger(AuthnRequestValidator.class); - - private boolean requireSignature = true; - - public void validateAuthnRequest(RequestContext context, Idp idp, String signature, - String relayState, String samlRequest, String realm) - throws Exception { - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); - if (authnRequest == null) { - throw new ProcessingException(TYPE.BAD_REQUEST); - } - - validateSignature(context, authnRequest, idp, signature, relayState, samlRequest, realm); - - if (authnRequest.getIssuer() == null) { - LOG.debug("No Issuer is present in the AuthnRequest"); - throw new ProcessingException(TYPE.BAD_REQUEST); - } - - String format = authnRequest.getIssuer().getFormat(); - if (format != null - && !"urn:oasis:names:tc:SAML:2.0:nameid-format:entity".equals(format)) { - LOG.debug("An invalid Format attribute was received: {}", format); - throw new ProcessingException(TYPE.BAD_REQUEST); - } - - // No SubjectConfirmation Elements are allowed - if (authnRequest.getSubject() != null - && authnRequest.getSubject().getSubjectConfirmations() != null - && !authnRequest.getSubject().getSubjectConfirmations().isEmpty()) { - LOG.debug("An invalid SubjectConfirmation Element was received"); - throw new ProcessingException(TYPE.BAD_REQUEST); - } - } - - private void validateSignature(RequestContext context, AuthnRequest authnRequest, Idp idp, - String signature, String relayState, String samlRequest, - String realm) throws ProcessingException { - try { - if (authnRequest.isSigned()) { - // Check destination - checkDestination(context, authnRequest); - - // Check signature - X509Certificate validatingCert = getValidatingCertificate(idp, realm); - Crypto issuerCrypto = - new CertificateStore(Collections.singletonList(validatingCert).toArray(new X509Certificate[0])); - validateAuthnRequestSignature(authnRequest.getSignature(), issuerCrypto); - } else if (signature != null) { - // Check destination - checkDestination(context, authnRequest); - - // Check signature - X509Certificate validatingCert = getValidatingCertificate(idp, realm); - - java.security.Signature sig = java.security.Signature.getInstance("SHA1withRSA"); - sig.initVerify(validatingCert); - - // Recreate request to sign - String requestToSign = SSOConstants.SAML_REQUEST + "=" + URLEncoder.encode(samlRequest, "UTF-8") - + "&" + SSOConstants.RELAY_STATE + "=" + relayState + "&" + SSOConstants.SIG_ALG - + "=" + URLEncoder.encode(SSOConstants.RSA_SHA1, StandardCharsets.UTF_8.name()); - - sig.update(requestToSign.getBytes(StandardCharsets.UTF_8)); - - if (!sig.verify(Base64.decode(signature))) { - LOG.debug("Signature validation failed"); - throw new ProcessingException(TYPE.BAD_REQUEST); - } - } else if (requireSignature) { - LOG.debug("No signature is present, therefore the request is rejected"); - throw new ProcessingException(TYPE.BAD_REQUEST); - } else { - LOG.debug("No signature is present, but this is allowed by configuration"); - } - } catch (Exception ex) { - LOG.debug("Error validating SAML Signature", ex); - throw new ProcessingException(TYPE.BAD_REQUEST); - } - } - - private X509Certificate getValidatingCertificate(Idp idp, String realm) - throws Exception { - Application serviceConfig = idp.findApplication(realm); - if (serviceConfig == null || serviceConfig.getValidatingCertificate() == null) { - LOG.debug("No validating certificate found for realm {}", realm); - throw new ProcessingException(TYPE.ISSUER_NOT_TRUSTED); - } - - return CertsUtils.parseX509Certificate(serviceConfig.getValidatingCertificate()); - } - - private void checkDestination(RequestContext context, AuthnRequest authnRequest) throws ProcessingException { - // Check destination - String destination = authnRequest.getDestination(); - LOG.debug("Validating destination: {}", destination); - - String localAddr = WebUtils.getHttpServletRequest(context).getRequestURL().toString(); - if (destination == null || !localAddr.startsWith(destination)) { - LOG.debug("The destination {} does not match the local address {}", destination, localAddr); - throw new ProcessingException(TYPE.BAD_REQUEST); - } - } - - /** - * Validate the AuthnRequest signature - */ - private void validateAuthnRequestSignature( - Signature signature, - Crypto sigCrypto - ) throws WSSecurityException { - RequestData requestData = new RequestData(); - requestData.setSigVerCrypto(sigCrypto); - WSSConfig wssConfig = WSSConfig.getNewInstance(); - requestData.setWssConfig(wssConfig); - // requestData.setCallbackHandler(callbackHandler); - - SAMLKeyInfo samlKeyInfo = null; - - KeyInfo keyInfo = signature.getKeyInfo(); - if (keyInfo != null) { - try { - Document doc = signature.getDOM().getOwnerDocument(); - samlKeyInfo = - SAMLUtil.getCredentialFromKeyInfo( - keyInfo.getDOM(), new WSSSAMLKeyInfoProcessor(requestData, new WSDocInfo(doc)), sigCrypto - ); - } catch (WSSecurityException ex) { - LOG.debug("Error in getting KeyInfo from SAML AuthnRequest: {}", ex.getMessage(), ex); - throw ex; - } - } - - if (samlKeyInfo == null) { - LOG.debug("No KeyInfo supplied in the AuthnRequest signature"); - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); - } - - // Validate Signature against profiles - validateSignatureAgainstProfiles(signature, samlKeyInfo); - - // Now verify trust on the signature - Credential trustCredential = new Credential(); - trustCredential.setPublicKey(samlKeyInfo.getPublicKey()); - trustCredential.setCertificates(samlKeyInfo.getCerts()); - - try { - Validator signatureValidator = new SignatureTrustValidator(); - signatureValidator.validate(trustCredential, requestData); - } catch (WSSecurityException e) { - LOG.debug("Error in validating signature on SAML AuthnRequest: {}", e.getMessage(), e); - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); - } - } - - /** - * Validate a signature against the profiles - */ - private void validateSignatureAgainstProfiles( - Signature signature, - SAMLKeyInfo samlKeyInfo - ) throws WSSecurityException { - // Validate Signature against profiles - SAMLSignatureProfileValidator validator = new SAMLSignatureProfileValidator(); - try { - validator.validate(signature); - } catch (SignatureException ex) { - LOG.debug("Error in validating the SAML Signature: {}", ex.getMessage(), ex); - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); - } - - BasicCredential credential = null; - if (samlKeyInfo.getCerts() != null) { - credential = new BasicX509Credential(samlKeyInfo.getCerts()[0]); - } else if (samlKeyInfo.getPublicKey() != null) { - credential = new BasicCredential(samlKeyInfo.getPublicKey()); - } else { - LOG.debug("Can't get X509Certificate or PublicKey to verify signature"); - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); - } - try { - SignatureValidator.validate(signature, credential); - } catch (SignatureException ex) { - LOG.debug("Error in validating the SAML Signature: {}", ex.getMessage(), ex); - throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); - } - } - - public boolean isRequireSignature() { - return requireSignature; - } - - /** - * Whether to require a signature or not on the AuthnRequest - * @param requireSignature - */ - public void setRequireSignature(boolean requireSignature) { - this.requireSignature = requireSignature; - } - -} http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d2830809/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java index 3bc36ea..742797d 100644 --- a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java @@ -33,6 +33,7 @@ import org.apache.cxf.fediz.service.idp.IdpConstants; import org.apache.cxf.fediz.service.idp.domain.Idp; import org.apache.cxf.fediz.service.idp.samlsso.SAML2CallbackHandler; import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder; +import org.apache.cxf.fediz.service.idp.samlsso.SAMLAuthnRequest; import org.apache.cxf.fediz.service.idp.util.WebUtils; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder; @@ -48,7 +49,6 @@ import org.apache.wss4j.common.util.DOM2Writer; import org.apache.wss4j.dom.WSConstants; import org.joda.time.DateTime; import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.Response; import org.opensaml.saml.saml2.core.Status; @@ -103,15 +103,13 @@ public class SamlResponseCreator { callbackHandler.setSubject(receivedToken.getSaml2().getSubject()); // Test Subject against received Subject (if applicable) - AuthnRequest authnRequest = - (AuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); - if (authnRequest.getSubject() != null && authnRequest.getSubject().getNameID() != null - && receivedToken.getSaml2().getSubject().getNameID() != null) { - NameID receivedNameId = authnRequest.getSubject().getNameID(); + SAMLAuthnRequest authnRequest = + (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); + if (authnRequest.getSubjectNameId() != null && receivedToken.getSaml2().getSubject().getNameID() != null) { NameID issuedNameId = receivedToken.getSaml2().getSubject().getNameID(); - if (!receivedNameId.getValue().equals(issuedNameId.getValue())) { + if (!authnRequest.getSubjectNameId().equals(issuedNameId.getValue())) { LOG.debug("Received NameID value of {} does not match issued value {}", - receivedNameId.getValue(), issuedNameId.getValue()); + authnRequest.getSubjectNameId(), issuedNameId.getValue()); throw new ProcessingException(ProcessingException.TYPE.INVALID_REQUEST); } } http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d2830809/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java new file mode 100644 index 0000000..c7ded4b --- /dev/null +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.fediz.service.idp.samlsso; + +import java.io.Serializable; + +import org.opensaml.saml.saml2.core.AuthnRequest; + +/** + * This class encapsulates a (parsed) SAML AuthnRequest Object. The OpenSAML AuthnRequest Object is not + * serializable. + */ +public class SAMLAuthnRequest implements Serializable { + /** + * + */ + private static final long serialVersionUID = 4353024755428346545L; + + private String issuer; + private String consumerServiceURL; + private String requestId; + private boolean forceAuthn; + private String subjectNameId; + + public SAMLAuthnRequest(AuthnRequest authnRequest) { + if (authnRequest.getIssuer() != null) { + issuer = authnRequest.getIssuer().getValue(); + } + + consumerServiceURL = authnRequest.getAssertionConsumerServiceURL(); + requestId = authnRequest.getID(); + forceAuthn = authnRequest.isForceAuthn().booleanValue(); + if (authnRequest.getSubject() != null && authnRequest.getSubject().getNameID() != null) { + subjectNameId = authnRequest.getSubject().getNameID().getValue(); + } + } + + public String getIssuer() { + return issuer; + } + + public String getConsumerServiceURL() { + return consumerServiceURL; + } + + public String getRequestId() { + return requestId; + } + + public boolean isForceAuthn() { + return forceAuthn; + } + + public String getSubjectNameId() { + return subjectNameId; + } +} http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d2830809/services/idp/src/main/webapp/WEB-INF/flows/saml-signin-request.xml ---------------------------------------------------------------------- diff --git a/services/idp/src/main/webapp/WEB-INF/flows/saml-signin-request.xml b/services/idp/src/main/webapp/WEB-INF/flows/saml-signin-request.xml index 93ffba7..6382a48 100644 --- a/services/idp/src/main/webapp/WEB-INF/flows/saml-signin-request.xml +++ b/services/idp/src/main/webapp/WEB-INF/flows/saml-signin-request.xml @@ -27,6 +27,14 @@ <input name="SAMLRequest" /> <input name="RelayState" /> <input name="Signature" /> + + <action-state id="parseAuthnRequest"> + <evaluate expression="authnRequestParser.parseSAMLRequest(flowRequestContext, flowScope.idpConfig, + flowScope.SAMLRequest, flowScope.Signature, + flowScope.RelayState)" /> + <transition to="processHRDSExpression"/> + <transition on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" to="viewBadRequest" /> + </action-state> <decision-state id="processHRDSExpression"> <on-entry> @@ -114,15 +122,11 @@ </decision-state> <action-state id="checkTokenExpiry"> - <on-entry> - <evaluate expression="authnRequestParser.parseSAMLRequest(flowRequestContext, flowScope.idpConfig, - flowScope.SAMLRequest)" /> - </on-entry> <evaluate expression="idpTokenExpiredAction.isTokenExpired(flowScope.home_realm, flowRequestContext) or authnRequestParser.isForceAuthentication(flowRequestContext)" /> <transition on="yes" to="redirectToLocalIDP" /> - <transition on="no" to="parseAndValidateSAMLRequest"> + <transition on="no" to="validateEndpointAddress"> <set name="flowScope.idpToken" value="externalContext.sessionMap[flowScope.home_realm]" /> </transition> <transition on-exception="java.lang.Throwable" to="scInternalServerError" /> @@ -138,29 +142,17 @@ <action-state id="cacheSecurityToken"> <secured attributes="IS_AUTHENTICATED_FULLY" /> <evaluate expression="cacheSecurityToken.submit(flowRequestContext)" /> - <transition to="parseAndValidateSAMLRequest"> + <transition to="validateEndpointAddress"> <set name="flowScope.idpToken" value="externalContext.sessionMap[flowScope.home_realm]" /> </transition> </action-state> - <action-state id="parseAndValidateSAMLRequest"> - <on-entry> - <evaluate expression="authnRequestParser.parseSAMLRequest(flowRequestContext, flowScope.idpConfig, - flowScope.SAMLRequest)" /> - <evaluate expression="authnRequestParser.retrieveRealm(flowRequestContext)" - result="flowScope.realm"/> - </on-entry> - <evaluate expression="authnRequestValidator.validateAuthnRequest(flowRequestContext, flowScope.idpConfig, - flowScope.Signature, flowScope.RelayState, - flowScope.SAMLRequest, flowScope.realm)" /> - <transition to="validateEndpointAddress"/> - <transition on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" to="viewBadRequest" /> - </action-state> - <action-state id="validateEndpointAddress"> <on-entry> <evaluate expression="authnRequestParser.retrieveConsumerURL(flowRequestContext)" result="flowScope.consumerURL"/> + <evaluate expression="authnRequestParser.retrieveRealm(flowRequestContext)" + result="flowScope.realm"/> </on-entry> <evaluate expression="passiveRequestorValidator.isValid(flowRequestContext, flowScope.consumerURL, flowScope.realm)"/> <transition on="yes" to="requestRpToken" />
