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