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


Reply via email to