Hi all, I'm sure this has been encountered before... I'm trying to use the XML security API to sign a SOAP request. For various reasons I'm not using WS-Security, only XML security.
I've gone through the sample code provided with the API and I can see that the enveloping sample does not load the XML from an existing stream (such as a file), but rather instantiates the XML document programmatically. When I build my Document using any sort of stream, I get a DOMException upon signing, presumably because the Document cannot be altered. org.w3c.dom.DOMException: NOT_SUPPORTED_ERR: The implementation does not support the requested type of object or operation. at org.apache.xerces.dom.CoreDocumentImpl.importNode(Unknown Source) at org.apache.xerces.dom.CoreDocumentImpl.importNode(Unknown Source) at org.jcp.xml.dsig.internal.dom.DOMUtils.appendChild(Unknown Source) at org.jcp.xml.dsig.internal.dom.DOMXMLObject.marshal(Unknown Source) at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.marshal(Unknown Source) at org.jcp.xml.dsig.internal.dom.DOMXMLSignature.sign(Unknown Source) at TestClass2.signDocument(TestClass2.java:125) at TestClass2.main(TestClass2.java:75) My apologies for the elementary question, but how should I generate the Document such that DOMXMLSignature.sign() doesn't have this problem with importNode? Thanks for any help! FYI I'm using Sun JDK 1.5.0_12 and building with only the libraries provided with xmlsec-1.4.2. My source code is here for reference. FWIW I had a heck of a time loading an encrypted private key, I had to scour the net for that code too, so if that’s useful for anyone please help yourselves. :-) import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidParameterSpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Collections; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.xml.crypto.XMLStructure; import javax.xml.crypto.dom.DOMStructure; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.DigestMethod; import javax.xml.crypto.dsig.Reference; import javax.xml.crypto.dsig.SignatureMethod; import javax.xml.crypto.dsig.SignedInfo; import javax.xml.crypto.dsig.Transform; import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMSignContext; import javax.xml.crypto.dsig.keyinfo.KeyInfo; import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; import javax.xml.crypto.dsig.keyinfo.KeyValue; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.crypto.dsig.spec.TransformParameterSpec; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class TestClass2 { /** * @param args */ public static void main(String[] args) { String xmlStr = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body Id=\"body\"><ns2:getGoKartAll xmlns:ns2=\"http://soa.examples.ttg.com\"><ns2:model>1</ns2:model><ns2:width>33</ns2:width><ns2:length>66</ns2:length><ns2:tires>6</ns2:tires><ns2:color>Dark Red</ns2:color></ns2:getGoKartAll></soapenv:Body></soapenv:Envelope>"; // String xmlStr = // "<getGoKartAll><model>1</model><width>33</width><length>66</length><tires>6</tires><color>Dark Red</color></getGoKartAll>"; // String xmlStr = "<Object>Some stuff</Object>"; String X509cert = "c:\\gkconfig.der"; String privateKey = "c:\\gkconfig_key.pk8"; String password = "password"; try { // Create a builder factory ByteArrayInputStream is = new ByteArrayInputStream(xmlStr.getBytes()); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(false); // Create the builder and parse the input Document doc = factory.newDocumentBuilder().parse(is); // Sign and output signDocument(doc, "body", privateKey, password, X509cert); String signed = doc.toString(); System.out.println("Signed: " + signed); } catch (Exception e) { e.printStackTrace(); } } public static void signDocument(Document doc, String reference, String privateKeyFile, String password, String x509certFile) throws Exception { String providerName = System.getProperty("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI"); XMLSignatureFactory xmlSigFactory = XMLSignatureFactory.getInstance("DOM", (Provider) Class.forName(providerName).newInstance()); // Create a Reference to a same-document URI that is an Object // element and specify the SHA1 digest algorithm // Reference ref = xmlSigFactory.newReference(reference, // xmlSigFactory.newDigestMethod(DigestMethod.SHA1, null)); // also specify the SHA1 digest algorithm and the ENVELOPED Transform. Reference ref = xmlSigFactory.newReference("", xmlSigFactory.newDigestMethod(DigestMethod.SHA1, null), Collections.singletonList(xmlSigFactory .newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null, null); // Create the SignedInfo SignedInfo si = xmlSigFactory.newSignedInfo(xmlSigFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null), xmlSigFactory.newSignatureMethod(SignatureMethod.DSA_SHA1, null), Collections.singletonList(ref)); // Create the XML object from the document XMLStructure content = new DOMStructure(doc); XMLObject xmlobj = xmlSigFactory.newXMLObject(Collections.singletonList(content), "body", null, null); // Load the public and private keys Certificate certs[] = loadX509CertificateChain(x509certFile); if (certs.length < 1) return; PublicKey publicKey = certs[0].getPublicKey(); PrivateKey privateKey = loadPKCS8PrivateKey(privateKeyFile, password); // Create a KeyInfo from the public key KeyInfoFactory kif = xmlSigFactory.getKeyInfoFactory(); KeyValue kv = kif.newKeyValue(publicKey); KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv)); // Create the XMLSignature (but don't sign it yet) XMLSignature signature = xmlSigFactory.newXMLSignature(si, ki, Collections.singletonList(xmlobj), null, null); // Create a DOMSignContext, specifying the PrivateKey and the document // location of the XMLSignature DOMSignContext domSignContext = new DOMSignContext(privateKey, doc.getDocumentElement()); // Lastly, generate the enveloping signature using the PrivateKey signature.sign(domSignContext); } /** * Loads the DER-encoded X509 certificate chain * * @param certificateChainFileName * @return * @throws IOException * @throws CertificateException */ public static Certificate[] loadX509CertificateChain(String certificateChainFileName) throws IOException, CertificateException { FileInputStream certificateStream = new FileInputStream(certificateChainFileName); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); java.security.cert.Certificate[] chain = {}; chain = certificateFactory.generateCertificates(certificateStream).toArray(chain); certificateStream.close(); return chain; } /** * Loads the DER-encoded, encrypted PKCS8 private key */ public static PrivateKey loadPKCS8PrivateKey(String privateKeyFile, String password) throws InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException, IOException, InvalidKeySpecException, NoSuchAlgorithmException { File keyFile = new File(privateKeyFile); byte[] encodedKey = new byte[(int) keyFile.length()]; FileInputStream is = new FileInputStream(keyFile); is.read(encodedKey); is.close(); byte[] decryptedKey = decryptPrivateKey(encodedKey, password.toCharArray()); KeyFactory rSAKeyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = rSAKeyFactory.generatePrivate(new PKCS8EncodedKeySpec(decryptedKey)); return privateKey; } /** * Decrypts an encrypted RSA private key * * @param instream * @param password * @return * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException * @throws IllegalStateException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws NoSuchAlgorithmException * @throws NoSuchPaddingException * @throws InvalidKeySpecException * @throws InvalidParameterSpecException * @throws IOException if the key is unencrypted */ public static byte[] decryptPrivateKey(byte[] instream, char[] password) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidParameterSpecException, IOException { EncryptedPrivateKeyInfo epki = new EncryptedPrivateKeyInfo(instream); //System.out.println("Encrypted private key info's algorithm name is '" + epki.getAlgName() + "'"); AlgorithmParameters params = epki.getAlgParameters(); if (params == null) throw new IllegalStateException("The private key info's algorithm parameters are (null). The algorithm is probably not supported!"); //PBEParameterSpec pbeParams = (PBEParameterSpec) (params.getParameterSpec(PBEParameterSpec.class)); SecretKeyFactory sf = SecretKeyFactory.getInstance(epki.getAlgName()); PBEKeySpec keySpec = new PBEKeySpec(password); Key key = sf.generateSecret(keySpec); keySpec.clearPassword(); byte[] privateKeyInfoStream = null; Cipher cipher = Cipher.getInstance(epki.getAlgName()); cipher.init(Cipher.DECRYPT_MODE, key, params); privateKeyInfoStream = cipher.doFinal(epki.getEncryptedData()); return privateKeyInfoStream; } } Best regards, Richard A. Sand, CEO Skyworth TTG USA, Inc. +1 (866) 9-TRIPOD http://www.skyworthttg.com/us