Thursday, December 20, 2012

Interoperable XML Digital Signatures (C#-Java), part 3

The third and last part of the XMLDSig tutorial. Make sure you don’t miss the previous entries:

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.