I have written the following .NET Framework 3.5 C# method which takes the location of an XML document and an object representation of an X509 digital certificate (with a private key) and returns the XML document as an object with the XML Signature (XMLDsig) embedded as first child element of the root.
The thing is that I direly need to be able to do the exact same procedure with Java SE 6, but I have not written any Java in ages and have no clue where to begin.
Can anyone provide the equivalent method in Java code that produces the exact same XML output?
private static XmlDocument SignXmlDocument(string xmlFilePath, X509Certificate2 certificate)
{
// load xml from disk preserving whitespaces
XmlDocument xmlDocument = new XmlDocument { PreserveWhitespace = true };
xmlDocument.Load(xmlFilePath);
// create signed xml with a same-document reference containing an enveloped-signature transform
SignedXml signedXml = new SignedXml(xmlDocument) { SigningKey = certificate.PrivateKey };
Reference reference = new Reference { Uri = "" };
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
// embed public key information for signature validation purposes
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data keyInfoX509Data = new KeyInfoX509Data(certificate, X509IncludeOption.ExcludeRoot);
keyInfo.AddClause(keyInfoX509Data);
signedXml.KeyInfo = keyInfo;
// compute and retreive the signature xml
signedXml.ComputeSignature();
XmlElement xmldsigXmlElement = signedXml.GetXml();
// insert the signature xml into the xml document as first child of the root element
xmlDocument.DocumentElement.PrependChild(xmlDocument.ImportNode(xmldsigXmlElement, true));
return xmlDocument;
}
The following does the same thing in Java. It requires a PKCS12 certificate file on disk.
import java.util.*;
import java.io.*;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.*;
import javax.xml.crypto.dsig.spec.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
public class XMLSigner {
public static void signXmlDocumentOnDisk(String fileToBeSignedPath, String signedFileSavePath, String pkcs12CertificateFilePath, String password) throws Exception {
XMLSignatureFactory fac = getXMLSignatureFactory();
Reference ref = getSHA1WholeDocumentEnvelopedTransformReference(fac);
SignedInfo si = getSignedInfo(fac, ref);
PrivateKeyEntry keyEntry = loadPKCS12KeyStoreAndGetSigningKeyEntry(pkcs12CertificateFilePath, password);
KeyInfo ki = getKeyInfoWithX509Data(keyEntry, fac);
Document doc = instantiateDocumentToBeSigned(fileToBeSignedPath);
signDocumentAndPlaceSignatureAsFirstChildElement(doc, keyEntry, fac, si, ki);
writeResultingDocument(doc, signedFileSavePath);
}
private static XMLSignatureFactory getXMLSignatureFactory() {
return XMLSignatureFactory.getInstance("DOM");
}
private static Reference getSHA1WholeDocumentEnvelopedTransformReference(XMLSignatureFactory fac) throws Exception {
return
fac.newReference(
"",
fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null,
null
);
}
private static SignedInfo getSignedInfo(XMLSignatureFactory fac, Reference ref) throws Exception {
return
fac.newSignedInfo(
fac.newCanonicalizationMethod(
CanonicalizationMethod.INCLUSIVE,
(C14NMethodParameterSpec) null
),
fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
Collections.singletonList(ref)
);
}
private static PrivateKeyEntry loadPKCS12KeyStoreAndGetSigningKeyEntry(String pkcs12CertificateFilePath, String password) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(pkcs12CertificateFilePath), password.toCharArray());
return (PrivateKeyEntry)ks.getEntry(ks.aliases().nextElement(), new KeyStore.PasswordProtection(password.toCharArray()));
}
private static KeyInfo getKeyInfoWithX509Data(PrivateKeyEntry keyEntry, XMLSignatureFactory fac) {
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();
KeyInfoFactory kif = fac.getKeyInfoFactory();
List x509Content = new ArrayList();
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(cert);
X509Data xd = kif.newX509Data(x509Content);
return kif.newKeyInfo(Collections.singletonList(xd));
}
private static Document instantiateDocumentToBeSigned(String fileToBeSignedPath) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
return dbf.newDocumentBuilder().parse(new FileInputStream(fileToBeSignedPath));
}
private static void signDocumentAndPlaceSignatureAsFirstChildElement(Document doc, PrivateKeyEntry keyEntry, XMLSignatureFactory fac, SignedInfo si, KeyInfo ki) throws Exception {
DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement(), doc.getDocumentElement().getFirstChild());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
}
private static void writeResultingDocument(Document doc, String signedFileSavePath) throws Exception {
OutputStream os = new FileOutputStream(signedFileSavePath);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
}
}
Check out the Java XML Digital Signature API: https://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html