Right you are! The sample code threw me off because of how it was dynamically constructing the document to be signed. Thanks for all of your help!
Best regards, Richard A. Sand, CEO Skyworth TTG USA, Inc. +1 (866) 9-TRIPOD http://www.skyworthttg.com/us -----Original Message----- From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] Sent: Thursday, October 16, 2008 4:33 PM To: security-dev@xml.apache.org Subject: Re: problem enveloping a soap body I finally found some time to look at this and fixed your program. The problem is that you are also trying to insert the document inside a ds:Object element, which is for enveloping signatures, not enveloped signatures. If you remove the following lines from your program: XMLStructure content = new DOMStructure(doc.getDocumentElement()); XMLObject xmlobj = xmlSigFactory.newXMLObject(Collections.singletonList(content), null, null, null); and change: XMLSignature signature = xmlSigFactory.newXMLSignature(si, ki, Collections.singletonList(xmlobj), reference, null); to: XMLSignature signature = xmlSigFactory.newXMLSignature(si, ki, null, reference, null); you should be all set. --Sean Richard Sand wrote: > Hi Sean, > > I'm able to add the enveloped signature to a soap header if I use the > org.apache.xml.security.signature package but not the javax.xml.crypto > package. Am I still using the javax.xml.crypto package incorrectly? My test > program attempts to sign a simple SOAP document with both techniques. > "signDocument1" throws the DOMException but "signDocument2" succeeds. The > output: > > Raw: <?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> > > org.w3c.dom.DOMException: HIERARCHY_REQUEST_ERR: An attempt was made to > insert a node where it is not permitted. > at org.apache.xerces.dom.ParentNode.internalInsertBefore(Unknown Source) > at org.apache.xerces.dom.ParentNode.insertBefore(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.signDocument1(TestClass2.java:180) > at TestClass2.main(TestClass2.java:99) > > Result2: <?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:Header><ds:Signature > xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> > <ds:SignedInfo> > <ds:CanonicalizationMethod > Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> > <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> > <ds:Reference URI="#body"> > <ds:Transforms> > <ds:Transform > Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> > <ds:Transform > Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> > </ds:Transforms> > <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> > <ds:DigestValue>DRGYMs2/nrFwPkXkx5yk03rEbu4=</ds:DigestValue> > </ds:Reference> > </ds:SignedInfo> > <ds:SignatureValue> > eodGz9w1M07SAvEPyYiSyUKut1oCYRMP8UTCuDRz7TCvI+ZRSVszdIDxNCalUsXDjPRkApfLn/RF > TTw57AWNVw2hgkDtofN/rMfVSW9mWVVciVoDvW0FAx2/sP6gEznD5CcKiH8Zjxy3egmV/wtsgU4F > DdAT1E2LR5x+q4dOCo0= > </ds:SignatureValue> > <ds:KeyInfo> > <ds:KeyValue> > <ds:RSAKeyValue> > <ds:Modulus> > rwYPeTtK0+3PtXSZbRmmU0VL4iKbp1c8jINxoFsBtSvhzfwEu+jDotZjsGvBP4QmgLaa6GkEmno1 > 9whsajllSbCPfPeAqpToJkygLzIPhx4wr9bm3XAOp89kApY17ImjdIIdA6xofTZwIQBEZ6deoNbh > yk7dKj90LCMxObtDHZs= > </ds:Modulus> > <ds:Exponent>AQAB</ds:Exponent> > </ds:RSAKeyValue> > </ds:KeyValue> > </ds:KeyInfo> > </ds:Signature></soapenv:Header></soapenv:Envelope> > > > import java.io.ByteArrayInputStream; > import java.io.File; > import java.io.FileInputStream; > import java.io.IOException; > import java.io.StringWriter; > 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.Security; > 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.apache.xml.serialize.OutputFormat; > import org.apache.xml.serialize.XMLSerializer; > import org.bouncycastle.jce.provider.BouncyCastleProvider; > import org.w3c.dom.Document; > import org.w3c.dom.Element; > import org.w3c.dom.NodeList; > > public class TestClass2 { > public static DocumentBuilderFactory factory = null; > > // Add this header to the SOAP message if it does not exist > public static String soap_header = > "http://schemas.xmlsoap.org/soap/envelope/"; > > // Provider name > static String providerName = System.getProperty("jsr105Provider", > "org.jcp.xml.dsig.internal.dom.XMLDSigRI"); > > /** > * @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 x509certFile = "c:\\gkconfig.der"; > String privateKeyFile = "c:\\gkconfig_key.pk8"; > String password = "password"; > > try { > // Load the public and private keys > Security.addProvider(new BouncyCastleProvider()); > Certificate certs[] = > loadX509CertificateChain(x509certFile); > if (certs.length < 1) > throw new Exception("No certificate found in " > + x509certFile); > PublicKey publicKey = certs[0].getPublicKey(); > PrivateKey privateKey = > loadPKCS8PrivateKey(privateKeyFile, password); > > // Create a builder factory > factory = DocumentBuilderFactory.newInstance(); > factory.setNamespaceAware(true); > factory.setValidating(false); > > // Create the builder and parse the input > Document doc = factory.newDocumentBuilder().parse(new > ByteArrayInputStream(xmlStr.getBytes())); > String raw = xmlToString(doc); > System.out.println("Raw: " + raw + "\n"); > > // Sign and output - attempt 1 > try { > Document doc1 = signDocument1(doc, "#body", > publicKey, privateKey, certs[0]); > String signed1 = xmlToString(doc1); > System.out.println("Result1: " + signed1); > } catch (Exception e) { > e.printStackTrace(); > } > > // Sign and output - attempt 2 > doc = factory.newDocumentBuilder().parse(new > ByteArrayInputStream(xmlStr.getBytes())); > Document doc2 = signDocument2(doc, "#body", publicKey, > privateKey, certs[0]); > String signed2 = xmlToString(doc2); > System.out.println("\nResult2: " + signed2); > > } catch (Exception e) { > e.printStackTrace(); > } > } > > /** > * Attempt 1... using javax.xml.crypto package > * > * @param doc > * @param reference > * @param publicKey > * @param privateKey > * @param cert > * @return > * @throws Exception > */ > public static Document signDocument1(Document doc, String reference, > PublicKey publicKey, PrivateKey privateKey, Certificate cert) throws > Exception { > // System.out.println("Provider name: " + providerName); > 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 and the ENVELOPED > Transform. > Reference ref = xmlSigFactory.newReference(reference, > 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.RSA_SHA1, null), > Collections.singletonList(ref)); > > // 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 XML object from the document > XMLStructure content = new > DOMStructure(doc.getDocumentElement()); > XMLObject xmlobj = > xmlSigFactory.newXMLObject(Collections.singletonList(content), null, null, > null); > > // Create the XMLSignature (but don't sign it yet) > XMLSignature signature = xmlSigFactory.newXMLSignature(si, ki, > Collections.singletonList(xmlobj), reference, null); > > // Look for the SOAP header > Element headerElement = null; > Element envelopeElement = null; > NodeList nodes = doc.getElementsByTagNameNS(soap_header, > "Header"); > if (nodes.getLength() == 0) { > // No nodes found - add header here > //System.out.println("Searching for the SOAP Envelope > Element"); > nodes = doc.getElementsByTagNameNS(soap_header, > "Envelope"); > if (nodes != null) { > //System.out.println("Adding a SOAP Header > Element"); > headerElement = > doc.createElementNS(soap_header, "Header"); > envelopeElement = (Element) nodes.item(0); > > headerElement.setPrefix(envelopeElement.getPrefix()); > envelopeElement.appendChild(headerElement); > } > } else { > // This shouldn't happen unless explicitly done > elsewhere > // System.out.println("Found " + nodes.getLength() + > // " SOAP Header elements."); > headerElement = (Element) nodes.item(0); > } > > // Create a DOMSignContext, specifying the PrivateKey and the > document > // location of the XMLSignature > DOMSignContext domSignContext = new DOMSignContext(privateKey, > headerElement); > > // Lastly, generate the enveloping signature using the > PrivateKey > signature.sign(domSignContext); > return doc; > } > > /** > * Attempt 2... using org.apache.xml.security.signature package > * > * @param doc > * @param reference > * @param publicKey > * @param privateKey > * @param cert > * @return > * @throws Exception > */ > public static Document signDocument2(Document doc, String reference, > PublicKey publicKey, PrivateKey privateKey, Certificate cert) throws > Exception { > // Initialize the library > org.apache.xml.security.Init.init(); > javax.xml.parsers.DocumentBuilderFactory docFactory = > javax.xml.parsers.DocumentBuilderFactory.newInstance(); > docFactory.setNamespaceAware(true); > String baseURI = null; > org.apache.xml.security.signature.XMLSignature xmlSig = new > org.apache.xml.security.signature.XMLSignature(doc, baseURI, > > org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1); > > // Look for the SOAP header > Element headerElement = null; > Element envelopeElement = null; > NodeList nodes = doc.getElementsByTagNameNS(soap_header, > "Header"); > if (nodes.getLength() == 0) { > // No nodes found - add header here > //System.out.println("Searching for the SOAP Envelope > Element"); > nodes = doc.getElementsByTagNameNS(soap_header, > "Envelope"); > if (nodes != null) { > //System.out.println("Adding a SOAP Header > Element"); > headerElement = > doc.createElementNS(soap_header, "Header"); > envelopeElement = (Element) nodes.item(0); > > headerElement.setPrefix(envelopeElement.getPrefix()); > envelopeElement.appendChild(headerElement); > } > } else { > // This shouldn't happen unless explicitly done > elsewhere > // System.out.println("Found " + nodes.getLength() + > // " SOAP Header elements."); > headerElement = (Element) nodes.item(0); > } > org.w3c.dom.Element sigElement = xmlSig.getElement(); > headerElement.appendChild(sigElement); > > // We also need to define the rules for XML document > transformation and > // canonicalization, necessary for parsing and signature > verification by > // others > org.apache.xml.security.transforms.Transforms transforms = new > org.apache.xml.security.transforms.Transforms(doc); > > transforms.addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_ENVELOPED_SIGNATURE); > > transforms.addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_C14N_WITH_COMMENTS); > xmlSig.addDocument(reference, transforms, > org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1); > > // Optionally, we can include the public key certificate > information > // associated with the private key we used to create this > digital > // signature. > xmlSig.addKeyInfo(publicKey); > > // All we have to do now is sign the document and write it to a > file, > // using a convenience class found in the Apache XML Security > package: > > xmlSig.sign(privateKey); > return doc; > } > > /** > * 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; > } > > public static String xmlToString(Document doc) throws IOException { > StringWriter sw = new StringWriter(); > XMLSerializer ser = new XMLSerializer(sw, new > OutputFormat(doc)); > ser.serialize(doc.getDocumentElement()); > > String XMLStr = sw.toString(); > return XMLStr; > } > } > > Best regards, > > Richard A. Sand, CEO > Skyworth TTG USA, Inc. > +1 (866) 9-TRIPOD > http://www.skyworthttg.com/us > > -----Original Message----- > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] > Sent: Friday, October 10, 2008 9:40 AM > To: security-dev@xml.apache.org > Subject: Re: problem enveloping a soap body > > Richard Sand wrote: >> Hi Sean, >> >> I guess I'm confused. I thought the whole point of the enveloping technique >> was that the signature would become part of the original document? > > But when you are enveloping an existing Document into the Object element, you > get yourself into a problem in that you are asking it to insert the Signature > at > the top of the Document, but also insert the same Document inside the XML > Signature Object element. Thus the DOMException is thrown because it is not > possible to do that, since it violates the DOM hierarchy. Unless you have > some > tight memory constraints, I don't see that creating a new Document to hold > the > signature is a problem ... I assume it will be quickly serialized and sent > over > the network anyway. > > --Sean > >