blob: 53533b31bf5f0449cfd2607a9841e345f700ba39 [file] [log] [blame]
/*
* Copyright 2004,2005 The Apache Software Foundation.
*
* Licensed 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.rampart;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.rampart.policy.RampartPolicyData;
import org.apache.rampart.util.RampartUtil;
import org.apache.ws.secpolicy.Constants;
import org.apache.ws.secpolicy.model.SupportingToken;
import org.apache.ws.secpolicy.model.Token;
import org.apache.ws.secpolicy.model.UsernameToken;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSEncryptionPart;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.message.token.Timestamp;
import org.apache.ws.security.util.WSSecurityUtil;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
public class PolicyBasedResultsValidator {
private static Log log = LogFactory.getLog(PolicyBasedResultsValidator.class);
public void validate(ValidatorData data, Vector results)
throws RampartException {
RampartMessageData rmd = data.getRampartMessageData();
RampartPolicyData rpd = rmd.getPolicyData();
//If there's Security policy present and no results
//then we should throw an error
if(rpd != null && results == null) {
throw new RampartException("noSecurityResults");
}
//Check presence of timestamp
WSSecurityEngineResult tsResult = null;
if(rpd.isIncludeTimestamp()) {
tsResult =
WSSecurityUtil.fetchActionResult(results, WSConstants.TS);
if(tsResult == null) {
throw new RampartException("timestampMissing");
}
}
//sig/encr
Vector encryptedParts = RampartUtil.getEncryptedParts(rmd);
if(rpd.isSignatureProtection() && isSignatureRequired(rpd)) {
encryptedParts.add(new WSEncryptionPart(WSConstants.SIG_LN,
WSConstants.SIG_NS, "Element"));
}
Vector signatureParts = RampartUtil.getSignedParts(rmd);
//Add the timestamp result
if(rpd.isIncludeTimestamp() && !rpd.isTransportBinding()) {
signatureParts.add(new WSEncryptionPart(tsResult.getTimestamp().getID()));
}
validateEncrSig(encryptedParts, signatureParts, results);
validateProtectionOrder(data, results);
validateEncryptedParts(data, encryptedParts, results);
validateSignedPartsHeaders(data, signatureParts, results);
//Supporting tokens
if(!rmd.isInitiator()) {
validateSupportingTokens(data, results);
}
/*
* Now we can check the certificate used to sign the message. In the
* following implementation the certificate is only trusted if either it
* itself or the certificate of the issuer is installed in the keystore.
*
* Note: the method verifyTrust(X509Certificate) allows custom
* implementations with other validation algorithms for subclasses.
*/
// Extract the signature action result from the action vector
WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(
results, WSConstants.SIGN);
if (actionResult != null) {
X509Certificate returnCert = actionResult.getCertificate();
if (returnCert != null) {
if (!verifyTrust(returnCert, rmd)) {
throw new RampartException ("trustVerificationError");
}
}
}
/*
* Perform further checks on the timestamp that was transmitted in the
* header.
* In the following implementation the timestamp is valid if :
* Timestamp->Created < 'now' < Timestamp->Expires (Last test already handled by WSS4J)
*
* Note: the method verifyTimestamp(Timestamp) allows custom
* implementations with other validation algorithms for subclasses.
*/
// Extract the timestamp action result from the action vector
actionResult = WSSecurityUtil.fetchActionResult(results, WSConstants.TS);
if (actionResult != null) {
Timestamp timestamp = actionResult.getTimestamp();
if (timestamp != null) {
if (!verifyTimestamp(timestamp, rmd)) {
throw new RampartException("cannotValidateTimestamp");
}
}
}
}
/**
* @param encryptedParts
* @param signatureParts
*/
private void validateEncrSig(Vector encryptedParts, Vector signatureParts, Vector results)
throws RampartException {
ArrayList actions = getSigEncrActions(results);
boolean sig = false;
boolean encr = false;
for (Iterator iter = actions.iterator(); iter.hasNext();) {
Integer act = (Integer) iter.next();
if(act.intValue() == WSConstants.SIGN) {
sig = true;
} else if(act.intValue() == WSConstants.ENCR) {
encr = true;
}
}
if(sig && signatureParts.size() == 0) {
//Unexpected signature
throw new RampartException("unexprectedSignature");
} else if(!sig && signatureParts.size() > 0) {
//required signature missing
throw new RampartException("signatureMissing");
}
if(encr && encryptedParts.size() == 0) {
//Check whether its just an encrypted key
ArrayList list = this.getResults(results, WSConstants.ENCR);
boolean encrDataFound = false;
for (Iterator iter = list.iterator(); iter.hasNext();) {
WSSecurityEngineResult result = (WSSecurityEngineResult) iter.next();
if(result.getDataRefUris() != null) {
encrDataFound = true;
}
}
if(encrDataFound) {
//Unexpected encryption
throw new RampartException("unexprectedEncryptedPart");
}
} else if(!encr && encryptedParts.size() > 0) {
//required signature missing
throw new RampartException("encryptionMissing");
}
}
/**
* @param data
* @param results
*/
private void validateSupportingTokens(ValidatorData data, Vector results)
throws RampartException {
//Check for UsernameToken
RampartPolicyData rpd = data.getRampartMessageData().getPolicyData();
SupportingToken suppTok = rpd.getSupportingTokens();
handleSupportingTokens(results, suppTok);
SupportingToken signedSuppToken = rpd.getSignedSupportingTokens();
handleSupportingTokens(results, signedSuppToken);
SupportingToken signedEndSuppToken = rpd.getSignedEndorsingSupportingTokens();
handleSupportingTokens(results, signedEndSuppToken);
SupportingToken endSuppToken = rpd.getEndorsingSupportingTokens();
handleSupportingTokens(results, endSuppToken);
}
/**
* @param results
* @param suppTok
* @throws RampartException
*/
private void handleSupportingTokens(Vector results, SupportingToken suppTok) throws RampartException {
if(suppTok == null) {
return;
}
ArrayList tokens = suppTok.getTokens();
for (Iterator iter = tokens.iterator(); iter.hasNext();) {
Token token = (Token) iter.next();
if(token instanceof UsernameToken) {
//Check presence of a UsernameToken
WSSecurityEngineResult utResult = WSSecurityUtil.fetchActionResult(results, WSConstants.UT);
if(utResult == null) {
throw new RampartException("usernameTokenMissing");
}
}
}
}
/**
* @param data
* @param results
*/
private void validateProtectionOrder(ValidatorData data, Vector results)
throws RampartException {
String protectionOrder = data.getRampartMessageData().getPolicyData().getProtectionOrder();
ArrayList sigEncrActions = this.getSigEncrActions(results);
if(sigEncrActions.size() < 2) {
//There are no results to COMPARE
return;
}
boolean done = false;
if(Constants.SIGN_BEFORE_ENCRYPTING.equals(protectionOrder)) {
boolean sigFound = false;
for (Iterator iter = sigEncrActions.iterator();
iter.hasNext() || !done;) {
Integer act = (Integer) iter.next();
if(act.intValue() == WSConstants.ENCR && ! sigFound ) {
// We found ENCR and SIGN has not been found - break and fail
break;
}
if(act.intValue() == WSConstants.SIGN) {
sigFound = true;
} else if(sigFound) {
//We have an ENCR action after sig
done = true;
}
}
} else {
boolean encrFound = false;
for (Iterator iter = sigEncrActions.iterator(); iter.hasNext();) {
Integer act = (Integer) iter.next();
if(act.intValue() == WSConstants.SIGN && ! encrFound ) {
// We found SIGN and ENCR has not been found - break and fail
break;
}
if(act.intValue() == WSConstants.ENCR) {
encrFound = true;
} else if(encrFound) {
//We have an ENCR action after sig
done = true;
}
}
}
if(!done) {
throw new RampartException("protectionOrderMismatch");
}
}
private ArrayList getSigEncrActions(Vector results) {
ArrayList sigEncrActions = new ArrayList();
for (Iterator iter = results.iterator(); iter.hasNext();) {
int action = ((WSSecurityEngineResult) iter.next()).getAction();
if(WSConstants.SIGN == action || WSConstants.ENCR == action) {
sigEncrActions.add(new Integer(action));
}
}
return sigEncrActions;
}
private void validateEncryptedParts(ValidatorData data, Vector encryptedParts, Vector results)
throws RampartException {
RampartMessageData rmd = data.getRampartMessageData();
ArrayList encrRefs = getEncryptedReferences(results);
RampartPolicyData rpd = rmd.getPolicyData();
//Check for encrypted body
if(rpd.isEncryptBody()) {
if(!encrRefs.contains(data.getBodyEncrDataId())){
throw new RampartException("encryptedPartMissing",
new String[]{data.getBodyEncrDataId()});
}
}
int refCount = 0;
refCount += encryptedParts.size();
if(encrRefs.size() != refCount) {
throw new RampartException("invalidNumberOfEncryptedParts",
new String[]{Integer.toString(refCount)});
}
}
private void validateSignedPartsHeaders(ValidatorData data, Vector signatureParts, Vector results)
throws RampartException {
RampartMessageData rmd = data.getRampartMessageData();
Node envelope = rmd.getDocument().getFirstChild();
WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(
results, WSConstants.SIGN);
// Find elements that are signed
Vector actuallySigned = new Vector();
if( actionResult != null ) {
Set signedIDs = actionResult.getSignedElements();
for (Iterator i = signedIDs.iterator(); i.hasNext();) {
String e = (String) i.next();
Element element = WSSecurityUtil.findElementById(envelope, e, WSConstants.WSU_NS);
actuallySigned.add( element );
}
}
for(int i=0; i<signatureParts.size(); i++) {
WSEncryptionPart wsep = (WSEncryptionPart) signatureParts.get( i );
Element headerElement = (Element) WSSecurityUtil.findElement(
envelope, wsep.getName(), wsep.getNamespace() );
if( headerElement == null ) {
// The signedpart header we are checking is not present in Soap header - this is allowed
continue;
}
// header elemement present - verify that it is part of signature
if( actuallySigned.contains( headerElement) ) {
continue;
}
// header defined in policy is present but not signed
throw new RampartException("signedPartHeaderNotSigned", new String[] { wsep.getName() });
}
}
private boolean isSignatureRequired(RampartPolicyData rpd) {
return (rpd.isSymmetricBinding() && rpd.getSignatureToken() != null) ||
(!rpd.isSymmetricBinding() && !rpd.isTransportBinding() &&
rpd.getInitiatorToken() != null);
}
/*
* Verify that ts->Created is before 'now'
* - testing that timestamp has not expired ('now' is before ts->Expires) is handled earlier by WSS4J
*/
private boolean verifyTimestamp(Timestamp timestamp, RampartMessageData rmd) throws RampartException {
Calendar cre = timestamp.getCreated();
if (cre != null) {
long now = Calendar.getInstance().getTimeInMillis();
// ajust 'now' with allowed timeskew
long maxSkew = RampartUtil.getTimestampMaxSkew( rmd );
if( maxSkew > 0 ) {
now += (maxSkew * 1000);
}
// fail if ts->Created is after 'now'
if( cre.getTimeInMillis() > now ) {
return false;
}
}
return true;
}
/**
* Evaluate whether a given certificate should be trusted.
* Hook to allow subclasses to implement custom validation methods however they see fit.
* <p/>
* Policy used in this implementation:
* 1. Search the keystore for the transmitted certificate
* 2. Search the keystore for a connection to the transmitted certificate
* (that is, search for certificate(s) of the issuer of the transmitted certificate
* 3. Verify the trust path for those certificates found because the search for the issuer might be fooled by a phony DN (String!)
*
* @param cert the certificate that should be validated against the keystore
* @return true if the certificate is trusted, false if not (AxisFault is thrown for exceptions during CertPathValidation)
* @throws WSSecurityException
*/
protected boolean verifyTrust(X509Certificate cert, RampartMessageData rmd) throws RampartException {
// If no certificate was transmitted, do not trust the signature
if (cert == null) {
return false;
}
String[] aliases = null;
String alias = null;
X509Certificate[] certs;
String subjectString = cert.getSubjectDN().getName();
String issuerString = cert.getIssuerDN().getName();
BigInteger issuerSerial = cert.getSerialNumber();
boolean doDebug = log.isDebugEnabled();
if (doDebug) {
log.debug("WSHandler: Transmitted certificate has subject " +
subjectString);
log.debug("WSHandler: Transmitted certificate has issuer " +
issuerString + " (serial " + issuerSerial + ")");
}
// FIRST step
// Search the keystore for the transmitted certificate
// Search the keystore for the alias of the transmitted certificate
try {
alias = RampartUtil.getSignatureCrypto(
rmd.getPolicyData().getRampartConfig(),
rmd.getCustomClassLoader()).getAliasForX509Cert(
issuerString, issuerSerial);
} catch (WSSecurityException ex) {
throw new RampartException("cannotFindAliasForCert", new String[]{subjectString}, ex);
}
if (alias != null) {
// Retrieve the certificate for the alias from the keystore
try {
certs = RampartUtil.getSignatureCrypto(
rmd.getPolicyData().getRampartConfig(),
rmd.getCustomClassLoader()).getCertificates(alias);
} catch (WSSecurityException ex) {
throw new RampartException("noCertForAlias", new String[] {alias}, ex);
}
// If certificates have been found, the certificates must be compared
// to ensure againgst phony DNs (compare encoded form including signature)
if (certs != null && certs.length > 0 && cert.equals(certs[0])) {
if (doDebug) {
log.debug("Direct trust for certificate with " + subjectString);
}
return true;
}
} else {
if (doDebug) {
log.debug("No alias found for subject from issuer with " + issuerString + " (serial " + issuerSerial + ")");
}
}
// SECOND step
// Search for the issuer of the transmitted certificate in the keystore
// Search the keystore for the alias of the transmitted certificates issuer
try {
aliases = RampartUtil.getSignatureCrypto(
rmd.getPolicyData().getRampartConfig(),
rmd.getCustomClassLoader()).getAliasesForDN(issuerString);
} catch (WSSecurityException ex) {
throw new RampartException("cannotFindAliasForCert", new String[]{issuerString}, ex);
}
// If the alias has not been found, the issuer is not in the keystore
// As a direct result, do not trust the transmitted certificate
if (aliases == null || aliases.length < 1) {
if (doDebug) {
log.debug("No aliases found in keystore for issuer " + issuerString + " of certificate for " + subjectString);
}
return false;
}
// THIRD step
// Check the certificate trust path for every alias of the issuer found in the keystore
for (int i = 0; i < aliases.length; i++) {
alias = aliases[i];
if (doDebug) {
log.debug("Preparing to validate certificate path with alias " + alias + " for issuer " + issuerString);
}
// Retrieve the certificate(s) for the alias from the keystore
try {
certs = RampartUtil.getSignatureCrypto(
rmd.getPolicyData().getRampartConfig(),
rmd.getCustomClassLoader()).getCertificates(alias);
} catch (WSSecurityException ex) {
throw new RampartException("noCertForAlias", new String[] {alias}, ex);
}
// If no certificates have been found, there has to be an error:
// The keystore can find an alias but no certificate(s)
if (certs == null | certs.length < 1) {
throw new RampartException("noCertForAlias", new String[] {alias});
}
// Form a certificate chain from the transmitted certificate
// and the certificate(s) of the issuer from the keystore
// First, create new array
X509Certificate[] x509certs = new X509Certificate[certs.length + 1];
// Then add the first certificate ...
x509certs[0] = cert;
// ... and the other certificates
for (int j = 0; j < certs.length; j++) {
cert = certs[i];
x509certs[certs.length + j] = cert;
}
certs = x509certs;
// Use the validation method from the crypto to check whether the subjects certificate was really signed by the issuer stated in the certificate
try {
if (RampartUtil.getSignatureCrypto(
rmd.getPolicyData().getRampartConfig(),
rmd.getCustomClassLoader()).validateCertPath(certs)) {
if (doDebug) {
log.debug("WSHandler: Certificate path has been verified for certificate with subject " + subjectString);
}
return true;
}
} catch (WSSecurityException ex) {
throw new RampartException("certPathVerificationFailed", new String[]{subjectString}, ex);
}
}
log.debug("WSHandler: Certificate path could not be verified for certificate with subject " + subjectString);
return false;
}
private ArrayList getEncryptedReferences(Vector results) {
//there can be multiple ref lists
ArrayList encrResults = getResults(results, WSConstants.ENCR);
ArrayList refs = new ArrayList();
for (Iterator iter = encrResults.iterator(); iter.hasNext();) {
WSSecurityEngineResult engineResult = (WSSecurityEngineResult) iter.next();
ArrayList dataRefUris = engineResult.getDataRefUris();
//take only the ref list processing results
if(dataRefUris != null) {
for (Iterator iterator = dataRefUris.iterator(); iterator
.hasNext();) {
String uri = (String) iterator.next();
refs.add(uri);
}
}
}
return refs;
}
private ArrayList getResults(Vector results, int action) {
ArrayList list = new ArrayList();
for (int i = 0; i < results.size(); i++) {
// Check the result of every action whether it matches the given
// action
if (((WSSecurityEngineResult) results.get(i)).getAction() == action) {
list.add((WSSecurityEngineResult) results.get(i));
}
}
return list;
}
}