package de.kp.wsclient.security; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import org.apache.xml.security.algorithms.SignatureAlgorithm; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.transforms.params.InclusiveNamespaces; import org.apache.xml.security.utils.Base64; import org.apache.xml.security.utils.Constants; import org.apache.xml.security.utils.XMLUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.Text; /** * This class provides functionality to sign and * verify SOAP message on the basis of binary * security tokens (X509). * * The private key is used to sign the SOAP message, * the certificate is provided with the message and * finally used to verify the signature. * * @author Stefan Krusche (krusche@dr-kruscheundpartner.de) * */ public class SecSignature extends SecBase { private X509Certificate certificate; private PrivateKey privateKey; private Element wsseSecurity; static { // initialize apache santuario framework org.apache.xml.security.Init.init(); } /** * The constructor of SecSignature is invoked by * providing the user credentials; these must be * retrieved from a keystore in a prior step. * * @param credentialInfo */ public SecSignature(SecCrypto sigCrypto) { this.certificate = sigCrypto.getCertificate(); this.privateKey = sigCrypto.getPrivateKey(); } /** * @param certificate */ public void setCertificate(X509Certificate certificate) { this.certificate = certificate; } /** * @param privateKey */ public void setPrivateKey(PrivateKey privateKey) { this.privateKey = privateKey; } /** * this method adds a signed wsse:Security element to * a SOAP envelope document. * * @param xmlDoc * @return * @throws Exception */ public Document sign(Document xmlDoc) throws Exception { // acquire SOAP header element Element soapHeader = getSOAPHeader(xmlDoc); if (soapHeader == null) throw new Exception("SOAP Header not found."); // this method determines whether there is already // a wsse:Security element present due to former // encryption processing boolean hasSecHeader = isSecHeader(xmlDoc); // add wsse:Security element to SOAP Header this.wsseSecurity = createWSSESecurity(xmlDoc); if (hasSecHeader == false) soapHeader.appendChild(wsseSecurity); return xmlDoc; } /** * @param xmlDoc * @return * @throws Exception */ private Element createWSSESecurity(Document xmlDoc) throws Exception { this.wsseSecurity = getSecHeader(xmlDoc); // add wsse:BinarySecurityToken Element wsseBinarySecurityToken = createWSSEBinarySecurityToken(xmlDoc); wsseSecurity.appendChild(wsseBinarySecurityToken); XMLSignature signature = createSignature(xmlDoc); // finally sign the referenced body and add the signature value // to the respective signature // <ds:SignatureValue>PipXJ2Sfc+LTDnq4pM5JcIYt9gg=</ds:SignatureValue> // add ds:Signature to the security header wsseSecurity.appendChild(signature.getElement()); if (this.privateKey != null) signature.sign(this.privateKey); return wsseSecurity; } /* * <wsse:BinarySecurityToken * EncodingType="http://docs.oasisopen.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" * ValueType="http://docs.oasisopen.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" * wsu:Id="urn:oasis:names:tc:ebxml-regrep:rs:security:SenderCert"> ...(cert)... * </wsse:BinarySecurityToken> */ /** * @param xmlDoc * @return * @throws Exception */ private Element createWSSEBinarySecurityToken(Document xmlDoc) throws Exception { String qualifiedName = SecConstants.WSSE_PRE + ":" + SecConstants.BINARY_TOKEN_LN; Element wsseBinarySecurityToken = xmlDoc.createElementNS(SecConstants.WSSE_NS, qualifiedName); // attribute:: EncodingType wsseBinarySecurityToken.setAttribute("EncodingType", SecConstants.BST_BASE64_ENCODING); // attribute:: ValueType wsseBinarySecurityToken.setAttribute("ValueType", SecConstants.BST_VALUE_TYPE); // wsu:Id wsseBinarySecurityToken.setAttributeNS(SecConstants.WSU_NS, SecConstants.WSU_PRE + ":Id", SecConstants.SENDER_CERT); // add certificate wsseBinarySecurityToken.appendChild(createToken(xmlDoc)); return wsseBinarySecurityToken; } /* * <ds:Signature> * <ds:SignedInfo> * <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" "> * <c14n:InclusiveNamespaces PrefixList="wsse soap" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"/> * </ds:CanonicalizationMethod> * <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> * <ds:Reference URI="#TheBody"> * <ds:Transforms> * <ds:Transform Algorithm="http://www.w3.org/2001/10/xmlexc-c14n#"> * <c14n:InclusiveNamespaces PrefixList="" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"/> * </ds:Transform> * </ds:Transforms> * <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> * <ds:DigestValue>i3qi5GjhHnfoBn/jOjQp2mq0Na4=</ds:DigestValue> * </ds:Reference> * </ds:SignedInfo> * <ds:SignatureValue>PipXJ2Sfc+LTDnq4pM5JcIYt9gg=</ds:SignatureValue> * <ds:KeyInfo> * <wsse:SecurityTokenReference> * <wsse:Reference URI="#urn:oasis:names:tc:ebxmlregrep:rs:security:SenderCert" ValueType="http://docs.oasisopen.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/> * </wsse:SecurityTokenReference> * </ds:KeyInfo> * </ds:Signature> * */ /** * This method creates an instance of XMLSignature. * * @param xmlDoc * @return * @throws Exception */ private XMLSignature createSignature(Document xmlDoc) throws Exception { // <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> // <c14n:InclusiveNamespaces PrefixList="wsse soap" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"/> // </ds:CanonicalizationMethod> String canonAlgo = SecConstants.C14N_EXCL_OMIT_COMMENTS; Element canonElem = XMLUtils.createElementInSignatureSpace(xmlDoc, Constants._TAG_CANONICALIZATIONMETHOD); canonElem.setAttributeNS(null, Constants._ATT_ALGORITHM, canonAlgo); // inclusive namespaces List<String> prefixes = getInclusivePrefixes(this.wsseSecurity, false); InclusiveNamespaces inclusiveNamespaces = new InclusiveNamespaces(xmlDoc, new HashSet<String>(prefixes)); canonElem.appendChild(inclusiveNamespaces.getElement()); // determine signing algorithm // <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> String sigAlgo = getSignatureAlgorithm(); if (sigAlgo == null) throw new Exception("[Signature] Unknown signature algorithm."); // create signature SignatureAlgorithm signatureAlgorithm = new SignatureAlgorithm(xmlDoc, sigAlgo); XMLSignature sig = new XMLSignature(xmlDoc, null, signatureAlgorithm.getElement(), canonElem); /* * <ds:KeyInfo> * <wsse:SecurityTokenReference> * <wsse:Reference URI="#urn:oasis:names:tc:ebxmlregrep:rs:security:SenderCert" ValueType="http://docs.oasisopen.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/> * </wsse:SecurityTokenReference> * </ds:KeyInfo> */ KeyInfo keyInfo = sig.getKeyInfo(); keyInfo.getElement().appendChild(createSTR(xmlDoc)); /* * <ds:Reference URI="#TheBody"> * <ds:Transforms> * <ds:Transform Algorithm="http://www.w3.org/2001/10/xmlexc-c14n#"> * <c14n:InclusiveNamespaces PrefixList="" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"/> * </ds:Transform> * </ds:Transforms> * <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> * <ds:DigestValue>i3qi5GjhHnfoBn/jOjQp2mq0Na4=</ds:DigestValue> * </ds:Reference> */ // <ds:Transforms> // <ds:Transform Algorithm="http://www.w3.org/2001/10/xmlexc-c14n#"> // <c14n:InclusiveNamespaces PrefixList="" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#"/> // </ds:Transform> // </ds:Transforms> Transforms transforms = new Transforms(xmlDoc); transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS); transforms.item(0).getElement().appendChild(inclusiveNamespaces.getElement()); Element body = getSOAPBody(xmlDoc); String referenceURI = "#" + body.getAttribute("id"); // the digest method used with the subsequent call is // <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> sig.addDocument(referenceURI, transforms); return sig; } /** * This method determine signature algorithm from the * public key algorithm. * * @return Signature Algorithm */ private String getSignatureAlgorithm() { if (this.certificate == null) return null; // determine signing algorithm String sigAlgo = null; String pubKeyAlgo = this.certificate.getPublicKey().getAlgorithm(); if (pubKeyAlgo.equalsIgnoreCase("DSA")) { sigAlgo = XMLSignature.ALGO_ID_SIGNATURE_DSA; } else if (pubKeyAlgo.equalsIgnoreCase("RSA")) { sigAlgo = XMLSignature.ALGO_ID_SIGNATURE_RSA; } return sigAlgo; } /** * @param xmlDoc * @return * @throws Exception */ private Text createToken(Document xmlDoc) throws Exception { if (this.certificate == null) throw new Exception("[Binary Security Token] Illegal certificate."); byte[] data = certificate.getEncoded(); return xmlDoc.createTextNode((Base64.encode(data))); } // get the List of inclusive prefixes from the DOM Element argument; // adapted from WSS4J // TODO FAILURE: return list is null private List<String> getInclusivePrefixes(Element target, boolean excludeVisible) { List<String> result = new ArrayList<String>(); Node parent = target; while (parent.getParentNode() != null && !(Node.DOCUMENT_NODE == parent.getParentNode().getNodeType())) { parent = parent.getParentNode(); NamedNodeMap attributes = parent.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attribute = attributes.item(i); if (SecConstants.XMLNS_NS.equals(attribute.getNamespaceURI())) { if ("xmlns".equals(attribute.getNodeName())) { result.add("#default"); } else { result.add(attribute.getLocalName()); } } } } if (excludeVisible == true) { NamedNodeMap attributes = target.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attribute = attributes.item(i); if (SecConstants.XMLNS_NS.equals(attribute.getNamespaceURI())) { if ("xmlns".equals(attribute.getNodeName())) { result.remove("#default"); } else { result.remove(attribute.getLocalName()); } } if (attribute.getPrefix() != null) { result.remove(attribute.getPrefix()); } } if (target.getPrefix() == null) { result.remove("#default"); } else { result.remove(target.getPrefix()); } } return result; } }