blob: d25ab1dcdbcfd59b3b196b495be189a4a019c0be [file] [log] [blame]
/**
* 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");
}
}
}