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.
No comments:
Post a Comment