/* | |
* Copyright 2003-2006 The Apache Software Foundation, or their licensors, as | |
* appropriate. | |
* | |
* 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.ws.security.processor; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.apache.ws.security.CustomTokenPrincipal; | |
import org.apache.ws.security.WSConstants; | |
import org.apache.ws.security.WSDerivedKeyTokenPrincipal; | |
import org.apache.ws.security.WSDocInfo; | |
import org.apache.ws.security.WSDocInfoStore; | |
import org.apache.ws.security.WSPasswordCallback; | |
import org.apache.ws.security.WSSConfig; | |
import org.apache.ws.security.WSSecurityEngine; | |
import org.apache.ws.security.WSSecurityEngineResult; | |
import org.apache.ws.security.WSSecurityException; | |
import org.apache.ws.security.WSUsernameTokenPrincipal; | |
import org.apache.ws.security.components.crypto.Crypto; | |
import org.apache.ws.security.message.EnvelopeIdResolver; | |
import org.apache.ws.security.message.token.BinarySecurity; | |
import org.apache.ws.security.message.token.DerivedKeyToken; | |
import org.apache.ws.security.message.token.PKIPathSecurity; | |
import org.apache.ws.security.message.token.SecurityTokenReference; | |
import org.apache.ws.security.message.token.UsernameToken; | |
import org.apache.ws.security.message.token.X509Security; | |
import org.apache.ws.security.saml.SAMLKeyInfo; | |
import org.apache.ws.security.saml.SAMLUtil; | |
import org.apache.ws.security.util.WSSecurityUtil; | |
import org.apache.xml.security.exceptions.XMLSecurityException; | |
import org.apache.xml.security.keys.KeyInfo; | |
import org.apache.xml.security.signature.Reference; | |
import org.apache.xml.security.signature.SignedInfo; | |
import org.apache.xml.security.signature.XMLSignature; | |
import org.apache.xml.security.signature.XMLSignatureException; | |
import org.opensaml.SAMLAssertion; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Node; | |
import javax.security.auth.callback.Callback; | |
import javax.security.auth.callback.CallbackHandler; | |
import javax.xml.namespace.QName; | |
import java.security.Principal; | |
import java.security.cert.CertificateExpiredException; | |
import java.security.cert.CertificateNotYetValidException; | |
import java.security.cert.X509Certificate; | |
import java.util.HashSet; | |
import java.util.Set; | |
import java.util.Vector; | |
public class SignatureProcessor implements Processor { | |
private static Log log = LogFactory.getLog(SignatureProcessor.class.getName()); | |
private static Log tlog = | |
LogFactory.getLog("org.apache.ws.security.TIME"); | |
private String signatureId; | |
public void handleToken(Element elem, Crypto crypto, Crypto decCrypto, CallbackHandler cb, WSDocInfo wsDocInfo, Vector returnResults, WSSConfig wsc) throws WSSecurityException { | |
if (log.isDebugEnabled()) { | |
log.debug("Found signature element"); | |
} | |
WSDocInfoStore.store(wsDocInfo); | |
X509Certificate[] returnCert = new X509Certificate[1]; | |
Set returnElements = new HashSet(); | |
Set protectedElements = new java.util.TreeSet(); | |
byte[][] signatureValue = new byte[1][]; | |
Principal lastPrincipalFound = null; | |
try { | |
lastPrincipalFound = verifyXMLSignature((Element) elem, | |
crypto, returnCert, returnElements, protectedElements, signatureValue, cb); | |
} catch (WSSecurityException ex) { | |
throw ex; | |
} finally { | |
WSDocInfoStore.delete(wsDocInfo); | |
} | |
if (lastPrincipalFound instanceof WSUsernameTokenPrincipal) { | |
returnResults.add(0, new WSSecurityEngineResult( | |
WSConstants.UT_SIGN, lastPrincipalFound, null, | |
returnElements, protectedElements, signatureValue[0])); | |
} else { | |
returnResults.add(0, new WSSecurityEngineResult( | |
WSConstants.SIGN, lastPrincipalFound, | |
returnCert[0], returnElements, protectedElements, signatureValue[0])); | |
} | |
signatureId = elem.getAttributeNS(null, "Id"); | |
} | |
/** | |
* Verify the WS-Security signature. | |
* <p/> | |
* The functions at first checks if then <code>KeyInfo</code> that is | |
* contained in the signature contains standard X509 data. If yes then | |
* get the certificate data via the standard <code>KeyInfo</code> methods. | |
* <p/> | |
* Otherwise, if the <code>KeyInfo</code> info does not contain X509 data, check | |
* if we can find a <code>wsse:SecurityTokenReference</code> element. If yes, the next | |
* step is to check how to get the certificate. Two methods are currently supported | |
* here: | |
* <ul> | |
* <li> A URI reference to a binary security token contained in the <code>wsse:Security | |
* </code> header. If the derefenced token is | |
* of the correct type the contained certificate is extracted. | |
* </li> | |
* <li> Issuer name an serial number of the certificate. In this case the method | |
* looks up the certificate in the keystore via the <code>crypto</code> parameter. | |
* </li> | |
* </ul> | |
* <p/> | |
* The methods checks is the certificate is valid and calls the | |
* {@link org.apache.xml.security.signature.XMLSignature#checkSignatureValue(X509Certificate) verfication} function. | |
* | |
* @param elem the XMLSignature DOM Element. | |
* @param crypto the object that implements the access to the keystore and the | |
* handling of certificates. | |
* @param returnCert verifyXMLSignature stores the certificate in the first | |
* entry of this array. Ther caller may then further validate | |
* the certificate | |
* @param returnElements verifyXMLSignature adds the wsu:ID attribute values for | |
* the signed elements to this Set | |
* @param cb CallbackHandler instance to extract key passwords | |
* @return the subject principal of the validated X509 certificate (the | |
* authenticated subject). The calling function may use this | |
* principal for further authentication or authorization. | |
* @throws WSSecurityException | |
*/ | |
protected Principal verifyXMLSignature(Element elem, | |
Crypto crypto, | |
X509Certificate[] returnCert, | |
Set returnElements, | |
Set protectedElements, | |
byte[][] signatureValue, | |
CallbackHandler cb) | |
throws WSSecurityException { | |
if (log.isDebugEnabled()) { | |
log.debug("Verify XML Signature"); | |
} | |
long t0 = 0, t1 = 0, t2 = 0; | |
if (tlog.isDebugEnabled()) { | |
t0 = System.currentTimeMillis(); | |
} | |
XMLSignature sig = null; | |
try { | |
sig = new XMLSignature(elem, null); | |
} catch (XMLSecurityException e2) { | |
throw new WSSecurityException(WSSecurityException.FAILED_CHECK, | |
"noXMLSig"); | |
} | |
sig.addResourceResolver(EnvelopeIdResolver.getInstance()); | |
X509Certificate[] certs = null; | |
KeyInfo info = sig.getKeyInfo(); | |
byte[] secretKey = null; | |
UsernameToken ut = null; | |
DerivedKeyToken dkt = null; | |
SAMLKeyInfo samlKi = null; | |
String customTokenId = null; | |
if (info != null) { | |
Node node = WSSecurityUtil.getDirectChild(info.getElement(), | |
SecurityTokenReference.SECURITY_TOKEN_REFERENCE, | |
WSConstants.WSSE_NS); | |
if (node == null) { | |
throw new WSSecurityException( | |
WSSecurityException.INVALID_SECURITY, | |
"unsupportedKeyInfo"); | |
} | |
SecurityTokenReference secRef = new SecurityTokenReference((Element) node); | |
int docHash = elem.getOwnerDocument().hashCode(); | |
/* | |
* Her we get some information about the document that is being | |
* processed, in partucular the crypto implementation, and already | |
* detected BST that may be used later during dereferencing. | |
*/ | |
WSDocInfo wsDocInfo = WSDocInfoStore.lookup(docHash); | |
if (secRef.containsReference()) { | |
Element token = secRef.getTokenElement(elem.getOwnerDocument(), | |
wsDocInfo, cb); | |
/* | |
* at this point check token type: UsernameToken, Binary, SAML | |
* Crypto required only for Binary and SAML | |
*/ | |
QName el = new QName(token.getNamespaceURI(), token | |
.getLocalName()); | |
if (el.equals(WSSecurityEngine.usernameToken)) { | |
String id = token.getAttributeNS(WSConstants.WSU_NS, "Id"); | |
UsernameTokenProcessor utProcessor = | |
(UsernameTokenProcessor) wsDocInfo.getProcessor(id); | |
ut = utProcessor.getUt(); | |
secretKey = ut.getSecretKey(); | |
} else if(el.equals(WSSecurityEngine.DERIVED_KEY_TOKEN_05_02) || | |
el.equals(WSSecurityEngine.DERIVED_KEY_TOKEN_05_12)) { | |
dkt = new DerivedKeyToken(token); | |
String id = dkt.getID(); | |
DerivedKeyTokenProcessor dktProcessor = | |
(DerivedKeyTokenProcessor) wsDocInfo.getProcessor(id); | |
String signatureMethodURI = sig.getSignedInfo().getSignatureMethodURI(); | |
int keyLength = (dkt.getLength() > 0) ? dkt.getLength() : | |
WSSecurityUtil.getKeyLength(signatureMethodURI); | |
secretKey = dktProcessor.getKeyBytes(keyLength); | |
} | |
else { | |
if (crypto == null) { | |
throw new WSSecurityException(WSSecurityException.FAILURE, | |
"noSigCryptoFile"); | |
} | |
if (el.equals(WSSecurityEngine.binaryToken)) { | |
//TODO: Use results from BinarySecurityTokenProcessor | |
certs = getCertificatesTokenReference((Element) token, | |
crypto); | |
} else if (el.equals(WSSecurityEngine.SAML_TOKEN)) { | |
samlKi = SAMLUtil.getSAMLKeyInfo( | |
(Element) token, crypto, cb); | |
certs = samlKi.getCerts(); | |
secretKey = samlKi.getSecret(); | |
} else { | |
//Try custom token through callback handler | |
//try to find a custom token | |
String id = secRef | |
.getReference().getURI().substring(1); | |
WSPasswordCallback pwcb = new WSPasswordCallback(id, | |
WSPasswordCallback.CUSTOM_TOKEN); | |
try { | |
cb.handle(new Callback[]{pwcb}); | |
} catch (Exception e) { | |
throw new WSSecurityException(WSSecurityException.FAILURE, | |
"noPassword", new Object[] { id }); | |
} | |
secretKey = pwcb.getKey(); | |
customTokenId = id; | |
if(secretKey == null) { | |
throw new WSSecurityException( | |
WSSecurityException.INVALID_SECURITY, | |
"unsupportedKeyInfo", new Object[]{el | |
.toString()}); | |
} | |
} | |
} | |
} else if (secRef.containsX509Data() || secRef.containsX509IssuerSerial()) { | |
certs = secRef.getX509IssuerSerial(crypto); | |
} else if (secRef.containsKeyIdentifier()) { | |
certs = secRef.getKeyIdentifier(crypto); | |
} else { | |
throw new WSSecurityException( | |
WSSecurityException.INVALID_SECURITY, | |
"unsupportedKeyInfo", new Object[]{node.toString()}); | |
} | |
} else { | |
if (crypto == null) { | |
throw new WSSecurityException(WSSecurityException.FAILURE, | |
"noSigCryptoFile"); | |
} | |
if (crypto.getDefaultX509Alias() != null) { | |
certs = crypto.getCertificates(crypto.getDefaultX509Alias()); | |
} else { | |
throw new WSSecurityException( | |
WSSecurityException.INVALID_SECURITY, | |
"unsupportedKeyInfo"); | |
} | |
} | |
if (tlog.isDebugEnabled()) { | |
t1 = System.currentTimeMillis(); | |
} | |
if ((certs == null || certs.length == 0 || certs[0] == null) && secretKey == null) { | |
throw new WSSecurityException(WSSecurityException.FAILED_CHECK); | |
} | |
if (certs != null) { | |
try { | |
certs[0].checkValidity(); | |
} catch (CertificateExpiredException e) { | |
throw new WSSecurityException(WSSecurityException.FAILED_CHECK, | |
"invalidCert"); | |
} catch (CertificateNotYetValidException e) { | |
throw new WSSecurityException(WSSecurityException.FAILED_CHECK, | |
"invalidCert"); | |
} | |
} | |
try { | |
boolean signatureOk = false; | |
if (certs != null) { | |
signatureOk = sig.checkSignatureValue(certs[0]); | |
} else { | |
signatureOk = sig.checkSignatureValue(sig | |
.createSecretKey(secretKey)); | |
} | |
if (signatureOk) { | |
if (tlog.isDebugEnabled()) { | |
t2 = System.currentTimeMillis(); | |
tlog.debug("Verify: total= " + (t2 - t0) | |
+ ", prepare-cert= " + (t1 - t0) + ", verify= " | |
+ (t2 - t1)); | |
} | |
signatureValue[0] = sig.getSignatureValue(); | |
/* | |
* Now dig into the Signature element to get the elements that | |
* this Signature covers. Build the QName of these Elements and | |
* return them to caller | |
*/ | |
SignedInfo si = sig.getSignedInfo(); | |
int numReferences = si.getLength(); | |
for (int i = 0; i < numReferences; i++) { | |
Reference siRef; | |
try { | |
siRef = si.item(i); | |
} catch (XMLSecurityException e3) { | |
throw new WSSecurityException( | |
WSSecurityException.FAILED_CHECK); | |
} | |
String uri = siRef.getURI(); | |
if(uri != null && !"".equals(uri)) { | |
Element se = WSSecurityUtil.getElementByWsuId(elem.getOwnerDocument(), uri); | |
if (se == null) { | |
se = WSSecurityUtil.getElementByGenId(elem | |
.getOwnerDocument(), uri); | |
} | |
if (se == null) { | |
throw new WSSecurityException( | |
WSSecurityException.FAILED_CHECK); | |
} | |
returnElements.add(WSSecurityUtil.getIDfromReference(uri)); | |
} else { | |
//This is the case where the signed element is identified | |
//by a transform such as XPath filtering | |
//We add the complete reference element to the return | |
//elements | |
returnElements.add(siRef); | |
} | |
} | |
if (certs != null) { | |
returnCert[0] = certs[0]; | |
return certs[0].getSubjectDN(); | |
} else if(ut != null){ | |
WSUsernameTokenPrincipal principal = new WSUsernameTokenPrincipal( | |
ut.getName(), ut.isHashed()); | |
principal.setNonce(ut.getNonce()); | |
principal.setPassword(ut.getPassword()); | |
principal.setCreatedTime(ut.getCreated()); | |
return principal; | |
} else if (dkt != null) { | |
WSDerivedKeyTokenPrincipal principal = new WSDerivedKeyTokenPrincipal(dkt.getID()); | |
principal.setNonce(dkt.getNonce()); | |
principal.setLabel(dkt.getLabel()); | |
principal.setLength(dkt.getLength()); | |
principal.setOffset(dkt.getOffset()); | |
String basetokenId = dkt.getSecuityTokenReference().getReference().getURI().substring(1); | |
principal.setBasetokenId(basetokenId); | |
return principal; | |
} else if(samlKi != null) { | |
final SAMLAssertion assertion = samlKi.getAssertion(); | |
CustomTokenPrincipal principal = new CustomTokenPrincipal(assertion.getId()); | |
principal.setTokenObject(assertion); | |
return principal; | |
} else if(secretKey != null) { | |
//This is the custom key scenario | |
CustomTokenPrincipal principal = new CustomTokenPrincipal(customTokenId); | |
return principal; | |
}else { | |
throw new WSSecurityException("Cannot determine principal"); | |
} | |
} else { | |
throw new WSSecurityException(WSSecurityException.FAILED_CHECK); | |
} | |
} catch (XMLSignatureException e1) { | |
throw new WSSecurityException(WSSecurityException.FAILED_CHECK); | |
} | |
} | |
/** | |
* Extracts the certificate(s) from the Binary Security token reference. | |
* <p/> | |
* | |
* @param elem The element containing the binary security token. This is | |
* either X509 certificate(s) or a PKIPath. | |
* @return an array of X509 certificates | |
* @throws WSSecurityException | |
*/ | |
public X509Certificate[] getCertificatesTokenReference(Element elem, | |
Crypto crypto) | |
throws WSSecurityException { | |
BinarySecurity token = createSecurityToken(elem); | |
if (token instanceof PKIPathSecurity) { | |
return ((PKIPathSecurity) token).getX509Certificates(false, crypto); | |
} else if (token instanceof X509Security) { | |
X509Certificate cert = ((X509Security) token).getX509Certificate(crypto); | |
X509Certificate[] certs = new X509Certificate[1]; | |
certs[0] = cert; | |
return certs; | |
} | |
return null; | |
} | |
/** | |
* Checks the <code>element</code> and creates appropriate binary security object. | |
* | |
* @param element The XML element that contains either a <code>BinarySecurityToken | |
* </code> or a <code>PKIPath</code> element. Other element types a not | |
* supported | |
* @return the BinarySecurity object, either a <code>X509Security</code> or a | |
* <code>PKIPathSecurity</code> object. | |
* @throws WSSecurityException | |
*/ | |
private BinarySecurity createSecurityToken(Element element) throws WSSecurityException { | |
BinarySecurity token = new BinarySecurity(element); | |
String type = token.getValueType(); | |
X509Security x509 = null; | |
PKIPathSecurity pkiPath = null; | |
if (X509Security.getType().equals(type)) { | |
x509 = new X509Security(element); | |
return (BinarySecurity) x509; | |
} else if (PKIPathSecurity.getType().equals(type)) { | |
pkiPath = new PKIPathSecurity(element); | |
return (BinarySecurity) pkiPath; | |
} | |
throw new WSSecurityException(WSSecurityException.UNSUPPORTED_SECURITY_TOKEN, | |
"unsupportedBinaryTokenType", new Object[]{type}); | |
} | |
/* (non-Javadoc) | |
* @see org.apache.ws.security.processor.Processor#getId() | |
*/ | |
public String getId() { | |
return signatureId; | |
} | |
} |