| /** |
| * 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.core.samlsso; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| import org.w3c.dom.Document; |
| import org.apache.cxf.fediz.core.config.CertificateValidationMethod; |
| import org.apache.cxf.fediz.core.config.FedizContext; |
| import org.apache.cxf.fediz.core.config.TrustManager; |
| import org.apache.cxf.fediz.core.config.TrustedIssuer; |
| import org.apache.cxf.fediz.core.saml.FedizSignatureTrustValidator; |
| import org.apache.cxf.fediz.core.saml.FedizSignatureTrustValidator.TrustType; |
| 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.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; |
| |
| /** |
| * Validate a SAML (1.1 or 2.0) Protocol Response. It validates the Response against the specs, |
| * the signature of the Response (if it exists), and any internal Assertion stored in the Response |
| * - including any signature. It validates the status code of the Response as well. |
| */ |
| public class SAMLProtocolResponseValidator { |
| |
| public static final String SAML2_STATUSCODE_SUCCESS = |
| "urn:oasis:names:tc:SAML:2.0:status:Success"; |
| public static final String SAML1_STATUSCODE_SUCCESS = "Success"; |
| |
| private static final Logger LOG = LoggerFactory.getLogger(SAMLProtocolResponseValidator.class); |
| |
| // private Validator signatureValidator = new SignatureTrustValidator(); |
| |
| /** |
| * Validate a SAML 2 Protocol Response |
| * @param samlResponse |
| * @throws WSSecurityException |
| */ |
| public void validateSamlResponse( |
| org.opensaml.saml.saml2.core.Response samlResponse, |
| FedizContext config |
| ) throws WSSecurityException { |
| // Check the Status Code |
| if (samlResponse.getStatus() == null |
| || samlResponse.getStatus().getStatusCode() == null) { |
| LOG.debug("Either the SAML Response Status or StatusCode is null"); |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); |
| } |
| if (!SAML2_STATUSCODE_SUCCESS.equals(samlResponse.getStatus().getStatusCode().getValue())) { |
| LOG.debug( |
| "SAML Status code of " + samlResponse.getStatus().getStatusCode().getValue() |
| + "does not equal " + SAML2_STATUSCODE_SUCCESS |
| ); |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); |
| } |
| |
| validateResponseSignature(samlResponse, config); |
| } |
| |
| /** |
| * Validate a SAML 1.1 Protocol Response |
| * @param samlResponse |
| * @throws WSSecurityException |
| */ |
| public void validateSamlResponse( |
| org.opensaml.saml.saml1.core.Response samlResponse, |
| FedizContext config |
| ) throws WSSecurityException { |
| // Check the Status Code |
| if (samlResponse.getStatus() == null |
| || samlResponse.getStatus().getStatusCode() == null |
| || samlResponse.getStatus().getStatusCode().getValue() == null) { |
| LOG.debug("Either the SAML Response Status or StatusCode is null"); |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); |
| } |
| String statusValue = samlResponse.getStatus().getStatusCode().getValue().getLocalPart(); |
| if (!SAML1_STATUSCODE_SUCCESS.equals(statusValue)) { |
| LOG.debug( |
| "SAML Status code of " + samlResponse.getStatus().getStatusCode().getValue() |
| + "does not equal " + SAML1_STATUSCODE_SUCCESS |
| ); |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); |
| } |
| |
| validateResponseSignature(samlResponse, config); |
| } |
| |
| /** |
| * Validate the Response signature (if it exists) |
| */ |
| private void validateResponseSignature( |
| org.opensaml.saml.saml2.core.Response samlResponse, |
| FedizContext config |
| ) throws WSSecurityException { |
| if (!samlResponse.isSigned()) { |
| return; |
| } |
| |
| validateResponseSignature( |
| samlResponse.getSignature(), samlResponse.getDOM().getOwnerDocument(), config |
| ); |
| } |
| |
| /** |
| * Validate the Response signature (if it exists) |
| */ |
| private void validateResponseSignature( |
| org.opensaml.saml.saml1.core.Response samlResponse, |
| FedizContext config |
| ) throws WSSecurityException { |
| if (!samlResponse.isSigned()) { |
| return; |
| } |
| |
| validateResponseSignature( |
| samlResponse.getSignature(), samlResponse.getDOM().getOwnerDocument(), config |
| ); |
| } |
| |
| /** |
| * Validate the response signature |
| */ |
| private void validateResponseSignature( |
| Signature signature, |
| Document doc, |
| FedizContext config |
| ) throws WSSecurityException { |
| RequestData requestData = new RequestData(); |
| WSSConfig wssConfig = WSSConfig.getNewInstance(); |
| requestData.setWssConfig(wssConfig); |
| |
| SAMLKeyInfo samlKeyInfo = null; |
| |
| KeyInfo keyInfo = signature.getKeyInfo(); |
| if (keyInfo != null) { |
| try { |
| samlKeyInfo = |
| SAMLUtil.getCredentialFromKeyInfo( |
| keyInfo.getDOM(), new WSSSAMLKeyInfoProcessor(requestData, new WSDocInfo(doc)), |
| requestData.getSigVerCrypto() |
| ); |
| } catch (WSSecurityException ex) { |
| LOG.debug("Error in getting KeyInfo from SAML Response: " + ex.getMessage(), ex); |
| throw ex; |
| } |
| } |
| if (samlKeyInfo == null) { |
| LOG.debug("No KeyInfo supplied in the SAMLResponse 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()); |
| |
| FedizSignatureTrustValidator trustValidator = new FedizSignatureTrustValidator(); |
| |
| boolean trusted = false; |
| |
| List<TrustedIssuer> trustedIssuers = config.getTrustedIssuers(); |
| for (TrustedIssuer ti : trustedIssuers) { |
| Pattern subjectConstraint = ti.getCompiledSubject(); |
| List<Pattern> subjectConstraints = new ArrayList<>(1); |
| if (subjectConstraint != null) { |
| subjectConstraints.add(subjectConstraint); |
| } |
| |
| if (ti.getCertificateValidationMethod().equals(CertificateValidationMethod.CHAIN_TRUST)) { |
| trustValidator.setSubjectConstraints(subjectConstraints); |
| trustValidator.setSignatureTrustType(TrustType.CHAIN_TRUST_CONSTRAINTS); |
| } else if (ti.getCertificateValidationMethod().equals(CertificateValidationMethod.PEER_TRUST)) { |
| trustValidator.setSignatureTrustType(TrustType.PEER_TRUST); |
| } else { |
| throw new IllegalStateException("Unsupported certificate validation method: " |
| + ti.getCertificateValidationMethod()); |
| } |
| try { |
| for (TrustManager tm: config.getCertificateStores()) { |
| try { |
| requestData.setSigVerCrypto(tm.getCrypto()); |
| trustValidator.validate(trustCredential, requestData); |
| trusted = true; |
| break; |
| } catch (Exception ex) { |
| LOG.debug("Issuer '{}' not validated in keystore '{}'", |
| ti.getName(), tm.getName()); |
| } |
| } |
| if (trusted) { |
| break; |
| } |
| |
| } catch (Exception ex) { |
| LOG.info("Error in validating signature on SAML Response: " + ex.getMessage(), ex); |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); |
| } |
| } |
| |
| if (!trusted) { |
| LOG.warn("SAML Response is not trusted"); |
| throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION); |
| } |
| } |
| |
| /** |
| * 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"); |
| } |
| } |
| |
| } |