package de.kp.wsclient.security; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.security.PublicKey; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.utils.Base64; import org.apache.xml.security.utils.Constants; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /* * This class verifies a WS-Security signature. It is a modified version * of the respective method from wss4j 'SignatureProcessor' * * The function checks the ds:KeyInfo for a wsse:SecurityTokenReference element. * If yes, the next is to get the certificate. This is done by evaluating the * URI reference to a binary security token contained in the wsse:Security header. * * If the dereferenced token is a binary security token, the contained certificate * is extracted. * * The method then checks if the certificate is valid; to this end the function * org.apache.xml.security.signature.XMLSignature#checkSignatureValue(X509Certificate) * is invoked. */ public class SecValidator { static { // initialize apache santuario framework org.apache.xml.security.Init.init(); } public SecValidator() { } public Document verify(Document xmlDoc) throws Exception { boolean valid = false; // acquire signature element Element sigElement = getSignature(xmlDoc); if (sigElement == null) throw new Exception("<ds:Signature> Element is missing."); // create signature element XMLSignature signature = new XMLSignature(sigElement, null); // acquire KeyInfo // the ds:KeyInfo element does not contain values directly, but // refers to the binary security token KeyInfo keyInfo = signature.getKeyInfo(); if (keyInfo == null) throw new Exception("<ds:KeyInfo> Element is corrupted."); // acquire security token reference Node secTokenRef = getChildNode(keyInfo.getElement(), SecConstants.SECURITY_TOKEN_REFERENCE, SecConstants.WSSE_NS); if (secTokenRef == null) throw new Exception("Security Token Reference not found."); // get reference element (= first element of security token reference) Element refElement = getFirstElement((Element)secTokenRef); if (refElement == null) throw new Exception("Invalid security reference."); SecReference ref = new SecReference(refElement); String refURI = ref.getURI(); if (refURI == null) throw new Exception("Invalid reference URI."); // the reference should refer to the binary security token of the request issuer String refID = (refURI.charAt(0) == '#') ? refURI.substring(1) : null; // we enforce a binary security token Element bsToken = getBSToken(xmlDoc, refID); if (bsToken == null) throw new Exception("No Binary Security Token"); // determine certificate from binary security token X509Certificate cert = getX509Certificate(bsToken); //------------------ check signature value ---------------------- // the signature is either checked from the public key provided // or the X509 certificate that comes with the SOAP message if (cert == null) { PublicKey pk = signature.getKeyInfo().getPublicKey(); if (pk == null) { throw new Exception("Did not find Certificate or Public Key"); } valid = signature.checkSignatureValue(pk); } else { valid = signature.checkSignatureValue(cert); } if (valid == false) throw new Exception("Invalid signature found."); return xmlDoc; } private Element getSignature(Document xmlDoc) throws Exception { NodeList nodes = xmlDoc.getElementsByTagNameNS(Constants.SignatureSpecNS, SecConstants.SIGNATURE); if (nodes.getLength() == 0) return null; return (Element) nodes.item(0); } private Element getBSToken(Document xmlDoc, String tokenID) { NodeList nodes = xmlDoc.getElementsByTagNameNS(SecConstants.WSSE_NS, SecConstants.BINARY_TOKEN_LN); if (nodes.getLength() == 0) return null; Element element = (Element) nodes.item(0); if (element.hasAttributeNS(SecConstants.WSU_NS, "Id") && tokenID.equals(element.getAttributeNS(SecConstants.WSU_NS, "Id"))) return element; return null; } // this method retrieves the X.509 certificate from the <wsse:BinarySecurityToken> private X509Certificate getX509Certificate(Element element) throws Exception { String encodedData = element.getFirstChild().getNodeValue(); byte[] decodedData; try { decodedData = Base64.decode(encodedData); } catch (Exception e) { throw new Exception("X.509 Certificate Decoding Error."); } X509Certificate cert = null; InputStream is = null; try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); is = new ByteArrayInputStream(decodedData); cert = (X509Certificate)certificateFactory.generateCertificate(is); } catch (CertificateException e) { e.printStackTrace(); } finally { if (is != null) { try { is.close(); } catch (Exception e) { } } } return cert; } /************************************************************************ * * DOM UTILS DOM UTILS DOM UTILS DOM UTILS DOM UTILS * ***********************************************************************/ // this is a helper method to retrieve the first element of a parent element private Element getFirstElement(Element parentElement) { for (Node childNode = parentElement.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) { if (childNode instanceof Element) { return (Element) childNode; } } return null; } // this is a helper method to determine a certain child node directly // from refering to the local name and its namespace private Node getChildNode(Node parentNode, String localName, String namespace) { for (Node childNode = parentNode.getFirstChild(); childNode != null; childNode = childNode.getNextSibling()) { if (localName.equals(childNode.getLocalName()) && namespace.equals(childNode.getNamespaceURI())) { return childNode; } } return null; } }