Skip to content

Instantly share code, notes, and snippets.

@BHSDuncan
Last active April 7, 2016 02:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BHSDuncan/55ebe8b37fd01db1e05f to your computer and use it in GitHub Desktop.
Save BHSDuncan/55ebe8b37fd01db1e05f to your computer and use it in GitHub Desktop.
Class for generating an XML Digital Signature according to the W3C spec (https://www.w3.org/TR/xmldsig-core/). Note that the only part of this that requires JAXB is the output, which can then be assigned to the appropriate location in a SOAP envelope/message.
package com.whatever.service;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3._2000._09.xmldsig_.Signature; // NOTE: This package is auto-generated by JAXB!
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xmlsoap.schemas.soap.envelope.Envelope;
public class XMLDSigGenerator {
public Signature generateSignature(Envelope envelope, String idForReference) {
DOMResult domResult = new DOMResult();
try {
JAXBContext context = JAXBContext.newInstance(Envelope.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.marshal(envelope, domResult);
} catch (JAXBException e) {
// log here and bail out
}
String providerName = System.getProperty("jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac = null;
try {
fac = XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
} catch (InstantiationException e2) {
// log here and bail out
} catch (IllegalAccessException e2) {
// log here and bail out
} catch (ClassNotFoundException e2) {
// log here and bail out
}
javax.xml.crypto.dsig.Reference reference = null;
try {
String refId = idForReference == null ? "" : idForReference;
reference = fac.newReference(refId,
fac.newDigestMethod(javax.xml.crypto.dsig.DigestMethod.SHA1, null),
Collections.singletonList
(fac.newTransform ("http://www.w3.org/2001/10/xml-exc-c14n#",
(TransformParameterSpec) null)), null, null);
} catch (NoSuchAlgorithmException e2) {
// log here and bail out
} catch (InvalidAlgorithmParameterException e2) {
// log here and bail out
}
javax.xml.crypto.dsig.SignedInfo signedInfo = null;
try {
signedInfo = fac.newSignedInfo(fac.newCanonicalizationMethod(
javax.xml.crypto.dsig.CanonicalizationMethod.EXCLUSIVE, (XMLStructure) null), fac
.newSignatureMethod(javax.xml.crypto.dsig.SignatureMethod.RSA_SHA1, null),
Collections.singletonList(reference));
} catch (NoSuchAlgorithmException e2) {
// log here and bail out
} catch (InvalidAlgorithmParameterException e2) {
// log here and bail out
}
javax.xml.crypto.dsig.keyinfo.KeyInfoFactory keyInfoFactory = fac.getKeyInfoFactory();
List x509Content = new ArrayList();
KeyStore keyStore = null;
KeyStore.PrivateKeyEntry keyEntry = null;
X509Certificate cert = null;
try {
keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(System.getProperty("JKS_FILE")), System.getProperty("JKS_PWD").toCharArray());
keyEntry =
(KeyStore.PrivateKeyEntry) keyStore.getEntry
(System.getProperty("JKS_CERT"), new KeyStore.PasswordProtection(System.getProperty("JKS_CPWD").toCharArray()));
cert = (X509Certificate) keyEntry.getCertificate();
} catch (KeyStoreException e3) {
// log here and bail out
} catch (NoSuchAlgorithmException e) {
// log here and bail out
} catch (CertificateException e) {
// log here and bail out
} catch (FileNotFoundException e) {
// log here and bail out
} catch (IOException e) {
// log here and bail out
} catch (UnrecoverableEntryException e) {
// log here and bail out
}
x509Content.add(cert.getIssuerX500Principal().getName());
x509Content.add(cert);
String dn = cert.getIssuerDN().toString();
BigInteger sn = cert.getSerialNumber();
X509IssuerSerial xd = keyInfoFactory.newX509IssuerSerial(dn, sn);
javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(keyInfoFactory.newX509Data(Collections.singletonList(xd))));
Document doc = (Document) domResult.getNode();
// this block is necessary as the Document class doesn't seem to properly register the id attribute of any elements after being marshalled by JAXB;
// this is only important if we are creating a Reference element that references a specific section of the XML being signed
// note that the element referenced is entirely dependent upon what we're actually trying to reference so you may need to change the first line
// if you're referening another element (in our case, it's the SOAP "Body" element in our envelope)
//
// TODO: Possibly modify the method to have an XPath parameter that will be used in place of the hardcoded DOM traversal below
Element el = (Element)doc.getFirstChild().getFirstChild().getNextSibling();
el.setIdAttribute("id", true);
DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(),
doc.getDocumentElement());
XMLSignature xmlSignature = fac.newXMLSignature(signedInfo, keyInfo);
try {
xmlSignature.sign(dsc);
} catch (MarshalException e2) {
// log here and bail out
} catch (XMLSignatureException e2) {
// log here and bail out
}
// we need to get at the signed result and move it into the appropriate section of the SOAP message, so let's grab that section
ByteArrayOutputStream os = new ByteArrayOutputStream();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = null;
try {
trans = tf.newTransformer();
trans.transform(new DOMSource(doc.getDocumentElement().getFirstChild().getNextSibling().getNextSibling()), new StreamResult(os));
} catch (TransformerConfigurationException e1) {
// log here and bail out
} catch (TransformerException e) {
// log here and bail out
}
// time to unmarshal that section into the appropriate place in the SOAP message
// *******
// IMPORTANT NOTE: Be sure to suppress the name space prefixes for the Signature block of XML. Since we're using JAXB, the best way to do this
// is to make sure that the Signature schema has been generated into its own package and then set the namespace prefix used to an empty string in
// package-info.java.
// *******
ByteArrayInputStream bis = new ByteArrayInputStream(os.toByteArray());
String s = new String(os.toByteArray());
Signature signature = null;
try {
JAXBContext context = JAXBContext.newInstance(Signature.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
signature = (Signature) unmarshaller.unmarshal(bis);
} catch (JAXBException e) {
// log here and bail out
}
return signature;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment