/*
* Copyright (c) 2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.identity.sso.saml.validators;
import org.opensaml.ws.security.SecurityPolicyException;
import org.opensaml.ws.transport.http.HTTPTransportUtils;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.CollectionCredentialResolver;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.criteria.EntityIDCriteria;
import org.opensaml.xml.security.criteria.UsageCriteria;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.signature.SignatureTrustEngine;
import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.DatatypeHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.sso.saml.builders.X509CredentialImpl;
import org.wso2.carbon.identity.sso.saml.exception.IdentitySAML2SSOException;
import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
public class SAML2HTTPRedirectDeflateSignatureValidator implements SAML2HTTPRedirectSignatureValidator {
private final static Log log = LogFactory.getLog(SAML2HTTPRedirectDeflateSignatureValidator.class);
/**
* Build a criteria set suitable for input to the trust engine.
*
* @param issuer
* @return
* @throws SecurityPolicyException
*/
private static CriteriaSet buildCriteriaSet(String issuer) {
CriteriaSet criteriaSet = new CriteriaSet();
if (!DatatypeHelper.isEmpty(issuer)) {
criteriaSet.add(new EntityIDCriteria(issuer));
}
criteriaSet.add(new UsageCriteria(UsageType.SIGNING));
return criteriaSet;
}
/**
* @param queryString
* @return
* @throws SecurityPolicyException
* @throws IdentitySAML2SSOException
*/
private static String getSigAlg(String queryString) throws SecurityPolicyException {
String sigAlgQueryParam = HTTPTransportUtils.getRawQueryStringParameter(queryString, "SigAlg");
if (DatatypeHelper.isEmpty(sigAlgQueryParam)) {
throw new SecurityPolicyException(
"Could not extract Signature Algorithm from query string");
}
String sigAlg = null;
try {
/* Split 'SigAlg=<sigalg_value>' query param using '=' as the delimiter,
and get the Signature Algorithm */
sigAlg = URLDecoder.decode(sigAlgQueryParam.split("=")[1], "UTF-8");
} catch (UnsupportedEncodingException e) {
if (log.isDebugEnabled()) {
log.debug("Encoding not supported.", e);
}
// JVM is required to support UTF-8
return null;
}
return sigAlg;
}
/**
* Extract the signature value from the request, in the form suitable for
* input into
* {@link SignatureTrustEngine#validate(byte[], byte[], String, CriteriaSet, Credential)}
* .
* <p/>
* Defaults to the Base64-decoded value of the HTTP request parameter named
* <code>Signature</code>.
*
* @param queryString
* @return
* @throws SecurityPolicyException
* @throws IdentitySAML2SSOException
*/
protected static byte[] getSignature(String queryString) throws SecurityPolicyException {
String signatureQueryParam = HTTPTransportUtils.getRawQueryStringParameter(queryString, "Signature");
if (DatatypeHelper.isEmpty(signatureQueryParam)) {
throw new SecurityPolicyException("Could not extract the Signature from query string");
}
String signature = null;
try {
/* Split 'Signature=<sig_value>' query param using '=' as the delimiter,
and get the Signature value */
signature = URLDecoder.decode(signatureQueryParam.split("=")[1], "UTF-8");
} catch (UnsupportedEncodingException e) {
if (log.isDebugEnabled()) {
log.debug("Encoding not supported.", e);
}
// JVM is required to support UTF-8
return new byte[0];
}
return Base64.decode(signature);
}
/**
* @param queryString
* @return
* @throws SecurityPolicyException
*/
protected static byte[] getSignedContent(String queryString) throws SecurityPolicyException {
// We need the raw non-URL-decoded query string param values for
// HTTP-Redirect DEFLATE simple signature
// validation.
// We have to construct a string containing the signature input by
// accessing the
// request directly. We can't use the decoded parameters because we need
// the raw
// data and URL-encoding isn't canonical.
if (log.isDebugEnabled()) {
log.debug("Constructing signed content string from URL query string " + queryString);
}
String constructed = buildSignedContentString(queryString);
if (DatatypeHelper.isEmpty(constructed)) {
throw new SecurityPolicyException(
"Could not extract signed content string from query string");
}
if (log.isDebugEnabled()) {
log.debug("Constructed signed content string for HTTP-Redirect DEFLATE " + constructed);
}
try {
return constructed.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
if (log.isDebugEnabled()) {
log.debug("Encoding not supported.", e);
}
// JVM is required to support UTF-8
return new byte[0];
}
}
/**
* Extract the raw request parameters and build a string representation of
* the content that was signed.
*
* @param queryString the raw HTTP query string from the request
* @return a string representation of the signed content
* @throws SecurityPolicyException thrown if there is an error during request processing
*/
private static String buildSignedContentString(String queryString) throws SecurityPolicyException {
StringBuilder builder = new StringBuilder();
// One of these two is mandatory
if (!appendParameter(builder, queryString, "SAMLRequest") && !appendParameter(builder, queryString, "SAMLResponse")) {
throw new SecurityPolicyException(
"Extract of SAMLRequest or SAMLResponse from query string failed");
}
// This is optional
appendParameter(builder, queryString, "RelayState");
// This is mandatory, but has already been checked in superclass
appendParameter(builder, queryString, "SigAlg");
return builder.toString();
}
/**
* Find the raw query string parameter indicated and append it to the string
* builder.
* <p/>
* The appended value will be in the form 'paramName=paramValue' (minus the
* quotes).
*
* @param builder string builder to which to append the parameter
* @param queryString the URL query string containing parameters
* @param paramName the name of the parameter to append
* @return true if parameter was found, false otherwise
*/
private static boolean appendParameter(StringBuilder builder, String queryString,
String paramName) {
String rawParam = HTTPTransportUtils.getRawQueryStringParameter(queryString, paramName);
if (rawParam == null) {
return false;
}
if (builder.length() > 0) {
builder.append('&');
}
builder.append(rawParam);
return true;
}
@Override
public void init() throws IdentityException {
//overridden method, no need to implement here
}
/**
* @param queryString
* @param issuer
* @param alias
* @param domainName
* @return
* @throws SecurityException
* @throws IdentitySAML2SSOException
*/
@Override
public boolean validateSignature(String queryString, String issuer, String alias,
String domainName) throws SecurityException,
IdentitySAML2SSOException {
byte[] signature = getSignature(queryString);
byte[] signedContent = getSignedContent(queryString);
String algorithmUri = getSigAlg(queryString);
CriteriaSet criteriaSet = buildCriteriaSet(issuer);
// creating the SAML2HTTPRedirectDeflateSignatureRule
X509CredentialImpl credential =
SAMLSSOUtil.getX509CredentialImplForTenant(domainName,
alias);
List<Credential> credentials = new ArrayList<Credential>();
credentials.add(credential);
CollectionCredentialResolver credResolver = new CollectionCredentialResolver(credentials);
KeyInfoCredentialResolver kiResolver = SecurityHelper.buildBasicInlineKeyInfoResolver();
SignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine(credResolver, kiResolver);
return engine.validate(signature, signedContent, algorithmUri, criteriaSet, null);
}
}