Contact: Ed Simon, Entrust Technologies <ed.simon@entrust.com>
Note: This page summarizes a presentation given to the XML Signature work group at the 46th IETF Meeting in Washington DC, Nov 1999.
Entrust's XML Signature demo servlet was demonstrated. This section describes what the demo does.
When the servlet is started, it sends this page to the browser...
XML Signature Demo
Setting up the signature
This demo illustrates the creation and verification of XML Signatures. To create an XML Signature, do these steps:
Once the user selects the "Sign it!" button, the XML Signature is created and presented. The user may then choose to modify the XML Signature and/or select the public key that will be used when verifying the signature. (Note: The servlet outputs the <Signature> element into an HTML <TEXTAREA> element so that it can be edited.)
XML Signature Demo
Viewing and verifying the signature
Here's the XML Signature you just created:
When the user selects "Verify", the signature is validated. If each digest listed in the <SignedInfo> element is valid and if the <SignatureValue> element contains the correct signature for the <SignedInfo> element, then the signature as a whole is valid. If the signature is valid, the demo servlet would produce output like this:
XML Signature Demo
Verification parameters
Keys
Owner of signing private key: Alice
Owner of verification public key: AliceWas the XML Signature edited?
Yes, modifications were made to the XML Signature. Certain types of modifications (as noted on the previous page) may affect the validity of the signature.
Signature verification results
The verification indicates that the signature is "VALID"
If the signature is invalid, the servlet indicates why. The following output would be returned if the stated and calculated digests did not match.
XML Signature Demo
Verification parameters
Keys
Owner of signing private key: Alice
Owner of verification public key: AliceWas the XML Signature edited?
Yes, modifications were made to the XML Signature. Certain types of modifications (as noted on the previous page) may affect the validity of the signature.
Signature verification results
The verification indicates that the signature is "NOT VALID: calculated and specified digests do NOT match!"
This section contains code snippets illustrating the basic steps for creating and verifying an XML Signature.
String strObjectElement = "\n\t<!-- Text you specified: \"" + textToBeSigned + "\" -->\n" + "\t<Object encoding=\"base64\" id=\"ID1\">\n\t\t" + base64OfTextToBeSigned + "\n\t</Object>\n";
try { MessageDigest msgdig = MessageDigest.getInstance("SHA"); msgdig.update(bytes_textToBeSigned, 0, bytes_textToBeSigned.length); bytes_messageDigestOfTextToBeSigned = msgdig.digest(); } catch (NoSuchAlgorithmException ex) { return "Error: NoSuchAlgorithmException: " + ex; } // Get base64-encoded version of the digest bytes. String base64OfDigest = base64Encode(bytes_messageDigestOfTextToBeSigned); // Create the <SignedInfo> element. String strSignedInfoElement = "\t<SignedInfo>\n" + "\t\t<SignatureAlgorithm name=\"dsaWithSha-1\"/>\n" + "\t\t<ObjectReference>\n" + "\t\t\t<Location uri=\"\" idref=\"ID1\"/>\n" + "\t\t\t<!-- Digest of \"" + textToBeSigned + "\" -->" + "\n\t\t\t<DigestValue algorithm=\"sha-1\">\n\t\t\t\t" + base64OfDigest + "\n\t\t\t</DigestValue>\n" + "\t\t</ObjectReference>\n" + "\t</SignedInfo>\n";
private static String doW3cXmlCanonicalization2(Element element) { StringBuffer out = new StringBuffer("<n1:"); out.append(element.getTagName()); out.append(" xmlns:n1=\"http://www.w3.org/Signature\""); NamedNodeMap nnmAttributes = element.getAttributes(); // sort the attributes Vector vecSortedAttributes = new Vector(); while (nnmAttributes.getLength() > 0) { String strLowestUtf8Name = nnmAttributes.item(0).getNodeName(); for (int i = 0; i < nnmAttributes.getLength(); i ++) { String strCandidateUtf8Name = nnmAttributes.item(i).getNodeName(); if (strCandidateUtf8Name.compareTo(strLowestUtf8Name) < 0) { strLowestUtf8Name = strCandidateUtf8Name; } } vecSortedAttributes.addElement(nnmAttributes.getNamedItem(strLowestUtf8Name)); nnmAttributes.removeNamedItem(strLowestUtf8Name); } for (int i = 0; i < vecSortedAttributes.size(); i++) { Node node = (Node) vecSortedAttributes.elementAt(i); out.append(" n").append(i+2).append(":"); out.append(node.getNodeName()); out.append("=\"").append(node.getNodeValue()); out.append("\" xmlns:n").append(i+2).append("=\"http://www.w3.org/Signature\""); } out.append(">"); NodeList nl = element.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { out.append(doW3cXmlCanonicalization2((Element) node)); } else if (node.getNodeType() == Node.TEXT_NODE) { out.append(node.getNodeValue().trim()); } else if (node.getNodeType() == Node.CDATA_SECTION_NODE ) { out.append(node.getNodeValue().trim()); } } out.append("</n1:" + element.getTagName() + ">"); return out.toString(); }
KeyPair keypair...; //System.out.println("Signing the <SignedInfo> element..."); byte[] bytesSignatureValueOfSignedInfoElement = null;
try { Signature signature = Signature.getInstance("DSA"); signature.initSign(keypair.getPrivate()); signature.update(bytesCanonicalizedSignedInfoElement, 0,
bytesCanonicalizedSignedInfoElement.length); bytesSignatureValueOfSignedInfoElement = signature.sign(); } catch (Exception ex) { return "Error: Exception while signing SignedInfo element: " + ex; } // Get base64-encoded version of the signature value bytes. String base64OfSignatureValue = base64Encode(bytesSignatureValueOfSignedInfoElement); String strSignatureValueElement = "\n\t<SignatureValue encoding=\"base64\">\n\t\t" + base64OfSignatureValue + "\n\t</SignatureValue>\n";
String strKeyInfoElement = "\n\t<!-- " + strSignerName + "'s key -->\n" + "\t<KeyInfo>\n\t\t<KeyValue encoding=\"base64\">\n\t\t\t" + base64PublicKey + "\n\t\t</KeyValue>\n\t</KeyInfo>\n";
String strSignatureElement = "<Signature xmlns=\"http://www.w3.org/Signature/core-19991020\">\n\n" + strSignedInfoElement + "\n\t<!-- Here's the canonicalized <SignedInfo> element: \"" + strCanonicalizedSignedInfoElement + "\" -->\n\n\t" + strSignatureValueElement + strKeyInfoElement + strObjectElement + "\n</Signature>";
NodeList nlObjectReference = docXmlSignature.getElementsByTagName("ObjectReference"); // Iterate through <ObjectReference> elements for (int i = 0; i < nlObjectReference.getLength(); i++) { Element elemObjectReference = (Element) nlObjectReference.item(i);
// Get the <Object> referred to by the <ObjectReference> element Element elemLocation = (Element) elemObjectReference.getElementsByTagName("Location").item(0); String locationIdref = elemLocation.getAttribute("idref").trim(); Element elemObject = getElementWithId(docXmlSignature.getDocumentElement(), locationIdref); if (elemObject == null) { return "Verification failed: Referenced object not found!"; } String strObjectContents = elemObject.getChildNodes().item(0).getNodeValue().trim(); byte[] bytesOfObjectElement = base64Decode(strObjectContents); // Create a SHA-1 message digest of the bytes. byte[] bytes_messageDigestOfObjectElement = null; try { MessageDigest msgdig = MessageDigest.getInstance("SHA"); msgdig.update(bytesOfObjectElement, 0, bytesOfObjectElement.length); bytes_messageDigestOfObjectElement = msgdig.digest(); } catch (NoSuchAlgorithmException ex) { System.out.println("NoSuchAlgorithmException: " + ex); } // Get the digest of the <Object> element. Is the same value as // <ObjectReference>-><DigestValue>. If not, validation fails. // Otherwise continue. String strCalculatedDigest = base64Encode(bytes_messageDigestOfObjectElement); String strDigestInSignedInfoElement = elemObjectReference.getElementsByTagName("DigestValue").item(0).getChildNodes().item(0).getNodeValue().trim(); if (!strCalculatedDigest.equals(strDigestInSignedInfoElement)) { return "NOT VALID: calculated and specified digests do NOT match!"; } } //System.out.println("Digests correlate, verifying <SignedInfo>...");
...same code as used when signing...
try { Signature signature = Signature.getInstance("DSA"); signature.initVerify(keypair.getPublic()); signature.update(bytesCanonicalizedSignedInfoElement, 0,
bytesCanonicalizedSignedInfoElement.length); Element elemSignatureValue = (Element) docXmlSignature.getElementsByTagName("SignatureValue").item(0); String strSignatureValue = elemSignatureValue.getChildNodes().item(0).getNodeValue().trim(); byte[] bytesInputSignatureValue = base64Decode(strSignatureValue); boolValidityOfSignedInfoElement = signature.verify(bytesInputSignatureValue); } catch (Exception ex) { System.out.println("Exception while verifying <SignedInfo> element: " + ex); } if (!boolValidityOfSignedInfoElement) { return "NOT VALID: signature on <SignedInfo> element failed"; } return "VALID"; }
Testing an implementation's conformance to the XML Signature specification is divided into two phases. The first phase (REQUIRED) involves testing the implementation using the conformance test suite. The second phase (RECOMMENDED for implementations that are expected to operate with other vendor's implementations) involves each vendor setting up an autoresponder that attempts to verify the XML Signature object that it receives.
A test suite that exercises the REQUIRED, and perhaps also the RECOMMENDED, features of the XML Signature specification. Requires no inter-vendor effort.
A vendor's (XSAR) enables other vendors to test whether their XML Signature implementations are interoperable with the vendor's own XML Signature implementation. Normally, a vendor's XSAR would receive signed XML Signature objects, attempt to verify them, and return the result to the other vendor.
The XSAR concept is analogous to other autoresponder initiatives such as the S/MIME autoresponders provided by secure email vendors.