Open eSignForms logo

First XML digital signature created

This is the first fully standards-compliant XML digital signature created by Open eSignForms software. It uses our first standard format, an Enveloped Signature that encompasses the entire XML source document as well as a special Seal containing platform identity information. It was captured on 23 February 2011 on a developer's PC in Kirkland, Washington. It can still be validated by any compliant XML Digital Signature implementation.

<?xml version="1.0" encoding="UTF-8" standalone="no"?><onerootonly><myxml>my xml data</myxml><needRoot>some root</needRoot><Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="OpenESignForms_Seal"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>GWV+f3kPfE70H+5hqSHK4l4Qj7IOdxxY1J0qgX2ivQQ=</DigestValue></Reference><Reference URI="#OpenESignForms_Seal_ID"><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>TfXGCUKsxSc6UzDN3rF/lS7I8IIaFoDTTGqLhtj76lk=</DigestValue></Reference></SignedInfo><SignatureValue>YTttBYx7BHT5uVN41OMKApdic8OeQzEQV8fhPEgHDT3W4Hsat8Lyiff84j8RPNAsUFWjjeXtmEJD
Z/ewQrgye0V8Db/Q/dTXAJiOD88b+bXok/l6zK8EW+sNW02+XCM2LeUS6Wo30Jghddtw6Y9pwRly
vwBEbFuKzMDpXjkWnVY8iBuyRtoDUEAgkg281QwqsPZXQWHmGTBqaMjAvl+1Kg+lYo3YEnhHbP+W
UoPYPVR/DBs/jgDnzUSE5PaLbT7JLeJE/1mBxCorxNnNHhfijGqF+VkNxUaxotlUC9mPWr/FaWUW
0aG13oPdcOo0+U7uahVcCbsonEfKzSkAM6YLQQ==</SignatureValue><KeyInfo><KeyName>ac6186d4-e5f0-43ba-96e5-44f5e96e504d</KeyName><KeyValue><RSAKeyValue><Modulus>3nIDqFP4KfGv8Wft+9IRIoOkSm/DQwdgFQZdK1lLBXGqlfhQ99Nven21sHaNHchEbOACSc+Eu3I5
6YT7BaDl9Bf851eg6uhbir/JA2cWn5ANaN+TN1s1F6Ircuk77ST2AVwhzDhSZdBVWg3qmv1On2p2
QWWElGBXrW1UDxl3auKdibkTxqe5kZMaB4juWzvajz4JcdmGJs0AjpXUBLWdltXWDEzpIH3rPbZk
gBxSqlynaiKjXiNNkr2TWL3uOnlwJ3qkqgeJQTavVN9DkzkWWTm3UH0RQW8XhfXYFJa9iauLQ3c1
srFORgaSH1QmuNKdX/0N9mYbetGIVUrfSdultw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue></KeyValue></KeyInfo><Object><SignatureProperties><SignatureProperty Id="OpenESignForms_Seal_ID" Target="OpenESignForms_Seal"><OpenESignForms_XmlDigitalSignatureSeal DeploymentId="b6b88d6c-bbbc-40a0-8c16-b9cbad9b8ee9" HostAddress="192.168.1.10" HostName="DavidHP2009" Timestamp="2011-02-23T14:09:06-08:00" Version="0.8.7_0223"/></SignatureProperty></SignatureProperties></Object></Signature></onerootonly>

This explains the XML digital signature in a bit more detail:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- The XML digital signature encompasses everything in the 
     root element <onerootonly> except for the <Signature> element itself, 
     though the <SignatureProperty> is included. -->
<onerootonly>
  <myxml>my xml data</myxml>
  <needRoot>some root</needRoot>

  <!-- Standard XML Digital Signature inserted into the XML data via Open eSignForms -->
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#" Id="OpenESignForms_Seal">
    <SignedInfo>
      <!-- Uniform XML formatting before signature is applied -->
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> 

      <!-- We use 2048-bit RSA public/private keypairs, managed by the server, with encrypted private key storage. 
           While the digital signature is currently limited to SHA1, we do use SHA-256 for data digests -->
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 

      <!-- The "empty" URI points to the root element, in this case <onerootonly>, 
           and its contents in full -->
      <Reference URI="">
        <Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>GWV+f3kPfE70H+5hqSHK4l4Qj7IOdxxY1J0qgX2ivQQ=</DigestValue>
      </Reference>
      <!-- We include extra deployment identifiers regarding the generating system -->
      <Reference URI="#OpenESignForms_Seal_ID">
        <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
        <DigestValue>TfXGCUKsxSc6UzDN3rF/lS7I8IIaFoDTTGqLhtj76lk=</DigestValue>
      </Reference>
    </SignedInfo>

    <!-- This is the actual digital signature created across the above 
         two References (the XML root element as well as the Open eSignForms Seal) -->
    <SignatureValue>YTttBYx7BHT5uVN41OMKApdic8OeQzEQV8fhPEgHDT3W4Hsat8Lyiff84j8RPNAsUFWjjeXtmEJD
Z/ewQrgye0V8Db/Q/dTXAJiOD88b+bXok/l6zK8EW+sNW02+XCM2LeUS6Wo30Jghddtw6Y9pwRly
vwBEbFuKzMDpXjkWnVY8iBuyRtoDUEAgkg281QwqsPZXQWHmGTBqaMjAvl+1Kg+lYo3YEnhHbP+W
UoPYPVR/DBs/jgDnzUSE5PaLbT7JLeJE/1mBxCorxNnNHhfijGqF+VkNxUaxotlUC9mPWr/FaWUW
0aG13oPdcOo0+U7uahVcCbsonEfKzSkAM6YLQQ==</SignatureValue>

    <!-- The public key that can verify the digital signature, 
         along with its assigned UUID which also points to the 
         private key used to create the signature -->
    <KeyInfo>
      <KeyName>ac6186d4-e5f0-43ba-96e5-44f5e96e504d</KeyName>
      <KeyValue><RSAKeyValue>
        <Modulus>3nIDqFP4KfGv8Wft+9IRIoOkSm/DQwdgFQZdK1lLBXGqlfhQ99Nven21sHaNHchEbOACSc+Eu3I5
6YT7BaDl9Bf851eg6uhbir/JA2cWn5ANaN+TN1s1F6Ircuk77ST2AVwhzDhSZdBVWg3qmv1On2p2
QWWElGBXrW1UDxl3auKdibkTxqe5kZMaB4juWzvajz4JcdmGJs0AjpXUBLWdltXWDEzpIH3rPbZk
gBxSqlynaiKjXiNNkr2TWL3uOnlwJ3qkqgeJQTavVN9DkzkWWTm3UH0RQW8XhfXYFJa9iauLQ3c1
srFORgaSH1QmuNKdX/0N9mYbetGIVUrfSdultw==</Modulus>
        <Exponent>AQAB</Exponent>
      </RSAKeyValue></KeyValue>
    </KeyInfo>

    <!-- Standard extension for the Open eSignForms XML Digital Signature Seal.
         Includes the deployment UUID of the instance of Open eSignForms that created the signature.
         It also includes the Host TCP/IP name and address, along with the timestamp and version of the 
         software used when it was signed. -->
    <Object><SignatureProperties>
      <SignatureProperty Id="OpenESignForms_Seal_ID" Target="OpenESignForms_Seal">
        <OpenESignForms_XmlDigitalSignatureSeal 
            DeploymentId="b6b88d6c-bbbc-40a0-8c16-b9cbad9b8ee9" 
            HostAddress="192.168.1.10" 
            HostName="DavidHP2009" 
            Timestamp="2011-02-23T14:09:06-08:00" 
            Version="0.8.7_0223"
        />
      </SignatureProperty>
    </SignatureProperties></Object>
  </Signature>
</onerootonly>

The Java source code that created it can be seen in com.esignforms.open.crypto.XmlDigitalSignature.

For those who care, this is simple Java code that can be used to verify such an XML digital signature. Of course, in our platform, we can also verify the public key shown is valid or not.

// Copyright (C) 2011-2013 Yozons, Inc.
// Open eSignForms - Web-based electronic contracting software
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License 
// as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
// See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License along with this program.  
// If not, see <http://open.esignforms.com/agpl.txt> or <http://www.gnu.org/licenses/>.
// Contact information is via the public forums at http://open.esignforms.com or via private email to open-esign@yozons.com.
//
package com.esignforms.open.crypto;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.security.Key;
import java.security.KeyException;
import java.security.PublicKey;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyName;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.parsers.DocumentBuilderFactory;

import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaders;
import org.jdom2.output.XMLOutputter;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;


/**
 * XmlDigitalSignatureSimpleVerifier is a simple verifier that relies on no Open eSignForms components.
 * It includes a main so it can be run as a program to validate a snapshots XML file created by Open eSignForms.
 * Much of the code understanding came from Oracle's technotes: 
 * http://download.oracle.com/javase/6/docs/technotes/guides/security/xmldsig/XMLDigitalSignature.html
 * It's not actually used except as an example of Java standard code that can verify the signatures
 * independently of any of our platform code.
 * @author Yozons, Inc.
 */
public final class XmlDigitalSignatureSimpleVerifier {
	// This literal is supported, but not in the API yet...
	private static final String SignatureMethod_RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";

	private XMLSignatureFactory xmlSignatureFactory;
    
    public XmlDigitalSignatureSimpleVerifier() {
    	xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");
    }
    
    public void verify(String signedXml) throws Exception {
    	if ( signedXml == null ) {
    		throw new Exception("Missing required parameter signedXml");
    	}

    	try	{
        	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        	dbf.setNamespaceAware(true);
        	Document document = dbf.newDocumentBuilder().parse((new BufferedInputStream(new ByteArrayInputStream(signedXml.getBytes("UTF-8")))));

        	NodeList qualifyingPropertiesNodeList = document.getElementsByTagName("QualifyingProperties");
        	if ( qualifyingPropertiesNodeList != null ) 
        	{
        		for( int i=0; i < qualifyingPropertiesNodeList.getLength(); ++i )
        		{
        			org.w3c.dom.Element qualifyingPropertiesElement = (org.w3c.dom.Element)qualifyingPropertiesNodeList.item(i);
        			if ( qualifyingPropertiesElement != null )
        				qualifyingPropertiesElement.setIdAttribute("Id",true); // mark our id as the "Id" attribute 
        		}
        	}

        	NodeList nl = document.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
        	if (nl.getLength() == 0) {
        		throw new Exception("The signedXML has no Signature element");
        	}

        	// The embedded key must match our signatureKey's values or the validation will fail.
        	DOMValidateContext valContext = new DOMValidateContext(new AcceptEmbeddedKeySelector(), nl.item(0));

        	XMLSignature signature = xmlSignatureFactory.unmarshalXMLSignature(valContext);
        	if ( ! signature.validate(valContext) ) {
        	    boolean isSignatureValueValid = signature.getSignatureValue().validate(valContext);
        		StringBuilder exceptionText = new StringBuilder();
        		exceptionText.append("Invalid signature. SignatureValue ").append(isSignatureValueValid ? "validated" : "INVALID");
    	        Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
    	        for (int j=0; i.hasNext(); j++) {
    	            boolean refValid = ((Reference)i.next()).validate(valContext);
            		exceptionText.append("; ").append(getReferenceNameForOpenESignFormsOnly(j)).append(refValid ? " validated" : " INVALID");
    	        }
        	    throw new Exception(exceptionText.toString());
        	}
    	} catch( Exception e ) {
    		throw e;
    	}
    }
    
	private static class AcceptEmbeddedKeySelector extends KeySelector {
		public AcceptEmbeddedKeySelector() {}
		
		@Override
		public KeySelectorResult select(KeyInfo keyInfo, Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
			if (keyInfo == null) {
				throw new KeySelectorException("NULL KeyInfo");
			}

		    SignatureMethod sm = (SignatureMethod) method;
		    List<?> list = keyInfo.getContent();

    		String keyId = null;
    		PublicKey publicKey = null;
		    for (int i = 0; i < list.size(); i++) {
		    	XMLStructure xmlStructure = (XMLStructure)list.get(i);
		    	if (xmlStructure instanceof KeyName) {
		    		keyId = ((KeyName)xmlStructure).getName();
		    	} else if (xmlStructure instanceof KeyValue) {
		    		try {
		    			publicKey = ((KeyValue)xmlStructure).getPublicKey();
		    		} catch( KeyException ke ) {
		    			throw new KeySelectorException(ke);
		    		}
		    	}
		    }
		    
		    if ( keyId == null || publicKey == null ) {
		    	throw new KeySelectorException("XML Signature element needs KeyName and KeyValue as these are always present when valid, even if not trusted");
		    }
		    
		    if ( algEquals(sm.getAlgorithm(), publicKey.getAlgorithm()) ) {
		    	return new SimpleKeySelectorResult(publicKey);
		    }
		    
		    throw new KeySelectorException("KeyName and KeyValue are not correct");
		}
	} 
	
	private static class SimpleKeySelectorResult implements KeySelectorResult {
		PublicKey publicKey;
		SimpleKeySelectorResult(PublicKey publicKey) {
			this.publicKey = publicKey;
		}
		
		public Key getKey() {
			return publicKey;
		}
	}

	private static boolean algEquals(String algURI, String algName) {
	    return (algName.equalsIgnoreCase("DSA") && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
	           (algName.equalsIgnoreCase("RSA") && (algURI.equalsIgnoreCase(SignatureMethod_RSA_SHA512) || algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)));
	}

    private static String getReferenceNameForOpenESignFormsOnly(int i) {
    	if ( i == 0 ) {
    		return "XML data payload";
    	}
    	if ( i == 1 ) {
    		return "OpenESignForms Seal";
    	}
    	return "Unexpected Reference[" + i + "]";
    }
    
    public static void main(String[] args) throws IOException {
    	if ( args.length < 1 ) {
    		System.err.println("Usage: XmlDigitalSignatureSimpleVerifier <signedXmlSnapshotFileName>...");
    		System.exit(1);
    	}
    		
   		SAXBuilder saxBuilder = new SAXBuilder(XMLReaders.NONVALIDATING);
   		saxBuilder.setIgnoringElementContentWhitespace(true);
   		
   		XmlDigitalSignatureSimpleVerifier verifier = new XmlDigitalSignatureSimpleVerifier();
		Namespace ns = Namespace.getNamespace("http://open.esignforms.com/XMLSchema/2011");
    	
    	for( String f : args ) {
    		File file = new File(f);
    		if ( file.exists() && file.canRead() ) {
       			try {
       				org.jdom2.Document doc = saxBuilder.build(file);
       				org.jdom2.Element rootElement = doc.getRootElement();

       				List<org.jdom2.Element> snapshotList;
       				
       				String rootName = rootElement.getName();
       				if ( "snapshot".equals(rootName) ) { // if this is just a single snapshot, not embedded in our snapshots XML, we'll try to work just this one element
       					snapshotList = new java.util.LinkedList<org.jdom2.Element>();
       					snapshotList.add(rootElement);
       				} else if (!"snapshots".equals(rootName)) {
       					System.err.println("Root element is not snapshots.  Found instead: " + rootName + "; in file: " + file.getAbsolutePath());
       					continue;
       				} else {
       					snapshotList = rootElement.getChildren("snapshot", ns);
       				}
       				
       				if ( snapshotList == null ) {
       					System.err.println("No snapshot elements found in file: " + file.getAbsolutePath());
       					continue;
       				}

       				ListIterator<Element> iter = snapshotList.listIterator();
       				while (iter.hasNext()) {
       					Element snapshotElement = iter.next();
       					String type = snapshotElement.getAttributeValue("type");
       	   				String timestamp = snapshotElement.getAttributeValue("timestamp");
           				System.err.println("Found snapshot type: " + type + "; timestamp: " + timestamp + "; in file: " + file.getAbsolutePath());
       	   				XMLOutputter snapshotOutputter = new XMLOutputter();
       	   				StringWriter writer = new StringWriter(snapshotElement.getText().length()+100);
       	   				snapshotOutputter.output(snapshotElement, writer);
       	   				String signedSnapshotXml = writer.toString();
       	   				try	{
       	   					verifier.verify(signedSnapshotXml);
       	    				System.err.println("Signature validated SUCCESSFULLY for file: " + file.getAbsolutePath());
       	   				} catch(Exception e) {
       	    				System.err.println("Signature validation FAILED for file: " + file.getAbsolutePath() + "; exception: " + e.getMessage());
       	   				}
       				}
       				
       			} catch (java.io.IOException e) {
        			System.err.println("Error: Could not read the XML contents from file named: " + file.getAbsolutePath());
       			} catch (JDOMException e) {
        			System.err.println("Error: Could not parse XML contents from file named: " + file.getAbsolutePath());
       			} finally {
       			}

    		} else {
    			System.err.println("Error: Could not find a readable file named: " + file.getAbsolutePath());
    		}
    	}
    	
    	System.exit(0);
    }
}

Return to main page...