The third and last part of the XMLDSig tutorial. Make sure you don’t miss the previous entries:
- Interoperable XML Digital Signatures (C#-Java), part 1
- Interoperable XML Digital Signatures (C#-Java), part 2
In this part of the tutorial we will learn how to create and validate XML digital signatures in Java. Let’s start with the interface:
public interface IDemo {
String getName();
String SignXml( org.w3c.dom.Document doc );
Boolean VerifyXml( String SignedXmlDocumentString );
Boolean VerifyXmlFromStream( InputStream SignedXmlDocumentStream );
}
Again, we will provide three implementations of the interface, for Enveloped, Enveloping and Detached signatures.
Let’s start however with the certificate manager:
public class CertManager {
private KeyStore _keyStore = null;
private KeyStore getKeyStore()
{
if ( _keyStore == null )
{
try
{
_keyStore = KeyStore.getInstance("pkcs12");
_keyStore.load(
new FileInputStream( "./Cert/demo.pfx" ), "demo".toCharArray() );
}
catch ( Exception ex )
{
ex.printStackTrace();
}
}
return _keyStore;
}
public X509Certificate getCertificate()
{
try {
String alias = getKeyStore().aliases().nextElement();
X509Certificate cc =
(X509Certificate)getKeyStore().getCertificateChain(alias)[0];
return cc;
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
public Key getKey()
{
try {
String alias = getKeyStore().aliases().nextElement();
Key key = getKeyStore().getKey(alias, "demo".toCharArray());
return key;
} catch ( Exception e ) {
e.printStackTrace();
return null;
}
}
}
We also need a Java-specific helper class to extract X509 certificates from existing signatures:
public class KeyValueKeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose, AlgorithmMethod method,
XMLCryptoContext context) throws KeySelectorException {
if (keyInfo == null) {
throw new KeySelectorException("Null KeyInfo object!");
}
SignatureMethod sm = (SignatureMethod) method;
List list = keyInfo.getContent();
for (int i = 0; i < list.size(); i++) {
XMLStructure xmlStructure = (XMLStructure) list.get(i);
if (xmlStructure instanceof X509Data) {
X509Data x509 = (X509Data) xmlStructure;
for (Object content : x509.getContent()) {
if (content instanceof X509Certificate) {
PublicKey pk = ((X509Certificate) content)
.getPublicKey();
return new SimpleKeySelectorResult(pk);
}
}
return null;
}
if (xmlStructure instanceof X509Certificate) {
PublicKey pk = ((X509Certificate) xmlStructure).getPublicKey();
return new SimpleKeySelectorResult(pk);
}
PublicKey pk = ((X509Certificate) xmlStructure).getPublicKey();
return new SimpleKeySelectorResult(pk);
}
throw new KeySelectorException("No KeyValue element found!");
}
}
class SimpleKeySelectorResult implements KeySelectorResult {
private PublicKey pk;
SimpleKeySelectorResult(PublicKey pk) {
this.pk = pk;
}
public Key getKey() {
return pk;
}
}
Similar to C#, we will have a common base class for all three types of signatures. The common base class will contain a method do verify signatures and the same verification code will apply to three different types of signatures:
public abstract class BaseXmlDsig implements IDemo {
public abstract String getName();
public abstract String SignXml(Document doc);
public Boolean VerifyXml(String SignedXmlDocumentString) {
try
{
InputStream stream =
new ByteArrayInputStream(SignedXmlDocumentString.getBytes("UTF-8"));
return VerifyXmlFromStream( stream );
}
catch ( Exception ex )
{
ex.printStackTrace();
return false;
}
}
public Boolean VerifyXmlFromStream(InputStream SignedXmlDocumentStream) {
try
{
DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document sourceDoc =
dbf
.newDocumentBuilder()
.parse( SignedXmlDocumentStream );
NodeList nl =
sourceDoc.getElementsByTagNameNS(XMLSignature.XMLNS,
"Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element");
}
String providerName = System.getProperty(
"jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
DOMValidateContext valContext = new DOMValidateContext
(new KeyValueKeySelector(), nl.item(0));
XMLSignature signature =
fac.unmarshalXMLSignature(valContext);
boolean coreValidity = signature.validate(valContext);
if (coreValidity == false) {
// optional. Java allows me to get more information
// on failed verification
System.out.println("Signature failed core validation!");
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("Signature validation status: " + sv);
// Check the validation status of each Reference
Iterator i = signature.getSignedInfo().getReferences().iterator();
for (int j = 0; i.hasNext(); j++)
{
boolean refValid = ((Reference) i.next()).validate(valContext);
System.out.println("Reference (" + j + ") validation status: "
+ refValid);
}
return false;
}
else
{
return true;
}
}
catch ( Exception e )
{
e.printStackTrace();
return false;
}
}
}
And now the three providers.
Enveloped. Straightforward.
public class XmlDsigEnveloped extends BaseXmlDsig implements IDemo {
private CertManager manager;
public XmlDsigEnveloped( CertManager manager )
{
this.manager = manager;
}
public String getName() {
return "XmlDsigEnveloped";
}
/**
* http://docs.oracle.com/javase/6/docs/technotes/guides/security/xmldsig/XMLDigitalSignature.html
*/
public String SignXml(Document doc) {
String providerName = System.getProperty("jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
try
{
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider)Class.forName(providerName).newInstance());
Reference ref =
fac.newReference("",
fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(
fac.newTransform(Transform.ENVELOPED,
(TransformParameterSpec)null)),
null, null);
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec)null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA1,
null),
Collections.singletonList(ref));
KeyInfoFactory kif = fac.getKeyInfoFactory();
X509Certificate cert = manager.getCertificate();
X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509d));
DOMSignContext dsc =
new DOMSignContext( manager.getKey(), doc.getDocumentElement());
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
StringWriter sw = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(
new DOMSource(doc),
new StreamResult( sw ));
return sw.toString();
}
catch ( Exception ex )
{
ex.printStackTrace();
return null;
}
}
}
Enveloping. Straightforward (compare to C# case).
public class XmlDsigEnveloping extends BaseXmlDsig implements IDemo {
private CertManager manager;
public XmlDsigEnveloping( CertManager manager )
{
this.manager = manager;
}
public String getName() {
return "XmlDsigEnveloping";
}
/**
* http://docs.oracle.com/javase/7/docs/technotes/guides/security/xmldsig/GenEnveloping.java
*/
public String SignXml(Document sourceDoc) {
String providerName = System.getProperty("jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
try
{
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider)Class.forName(providerName).newInstance());
Reference ref =
fac.newReference("#MyObjectID1",
fac.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(
fac.newTransform(Transform.ENVELOPED,
(TransformParameterSpec)null)),
null, null);
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec)null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA1,
null),
Collections.singletonList(ref));
KeyInfoFactory kif = fac.getKeyInfoFactory();
X509Certificate cert = manager.getCertificate();
X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509d));
// create a new envelope
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
XMLStructure content = new DOMStructure( sourceDoc.getDocumentElement() );
XMLObject obj = fac.newXMLObject
(Collections.singletonList(content), "MyObjectID1", null, null);
DOMSignContext dsc = new DOMSignContext( manager.getKey(), doc);
XMLSignature signature =
fac.newXMLSignature(si, ki, Collections.singletonList(obj), null, null);
signature.sign(dsc);
StringWriter sw = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(
new DOMSource(doc),
new StreamResult( sw ));
return sw.toString();
}
catch ( Exception ex )
{
ex.printStackTrace();
return null;
}
}
}
Detached. Straightforward.
public class XmlDsigDetached extends BaseXmlDsig implements IDemo {
private CertManager manager;
public XmlDsigDetached( CertManager manager )
{
this.manager = manager;
}
public String getName() {
return "XmlDsigDetached";
}
/**
* http://www.oracle.com/technetwork/articles/javase/dig-signatures-141823.html
*/
public String SignXml(Document sourceDoc) {
String providerName = System.getProperty("jsr105Provider",
"org.jcp.xml.dsig.internal.dom.XMLDSigRI");
try
{
XMLSignatureFactory fac =
XMLSignatureFactory.getInstance("DOM",
(Provider)Class.forName(providerName).newInstance());
Reference ref =
fac.newReference("#foo",
fac.newDigestMethod(DigestMethod.SHA1, null));
SignedInfo si = fac.newSignedInfo
(fac.newCanonicalizationMethod
(CanonicalizationMethod.INCLUSIVE,
(C14NMethodParameterSpec)null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA1,
null),
Collections.singletonList(ref));
KeyInfoFactory kif = fac.getKeyInfoFactory();
X509Certificate cert = manager.getCertificate();
X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509d));
// create a new envelope
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document envelope = dbf.newDocumentBuilder().newDocument();
envelope.appendChild( envelope.createElement( "Envelope" ) );
org.w3c.dom.Element message = envelope.createElement( "Message" );
org.w3c.dom.Element sourceDocCopy =
(org.w3c.dom.Element)envelope.importNode( sourceDoc.getDocumentElement(), true );
message.setAttribute("Id", "foo");
message.appendChild( sourceDocCopy );
envelope.getDocumentElement().appendChild( message );
DOMSignContext dsc = new DOMSignContext( manager.getKey(), envelope.getDocumentElement() );
XMLSignature signature = fac.newXMLSignature(si, ki);
signature.sign(dsc);
StringWriter sw = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(
new DOMSource(envelope),
new StreamResult( sw ));
//new FileOutputStream("mySignedFile"));
return sw.toString();
}
catch ( Exception ex )
{
ex.printStackTrace();
return null;
}
}
}
All these three implementations allow me to sign my XML documents with a signature of my choice. Also, all three should correctly verify signed documents.
Now, as both C# and Java sign and verify, the reader is encouraged to conduct experiments that test the interoperability of provided implementation.