santuario-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Sean Mullan <Sean.Mul...@Sun.COM>
Subject Re: problem enveloping a soap body
Date Thu, 16 Oct 2008 20:32:54 GMT
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: Sean.Mullan@Sun.COM [mailto:Sean.Mullan@Sun.COM] 
> 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
> 
> 


Mime
View raw message