Object and KeyInfo
Before we proceed to introduce JSR-105, we need to briefly
discuss two optional sub-elements of Signature:
Object and KeyInfo.
The Object element in the W3C recommendation is a
placeholder for an XML Signature application to carry information
for several different purposes. On possibility is for an
Object to contain the data object being signed for an
enveloping signature. This is the scenario our example will work
with.
XML Signature's KeyInfo element in can be used for
signing applications to communicate information associated with the
cryptographic (private) key so that validating applications can
obtain the relevant (public) key. Our example will include several
scenarios illustrating the use cases of KeyInfo.
Cryptography Key and Certificate
We are now ready to present our XML Signature example
application.
Let us look at the following XML document,
./etc/invoice.xml:
<?XML version="1.0" encoding="UTF-8" standalone="no"?>
<invoice XMLns="http://www.company.com/accounting">
<items>
<item>
<desc>Applied Cryptography</desc>
<type>book</type>
<unitprice>44.50</unitprice>
<quantity>1</quantity>
</item>
</items>
<creditcard>
<number>123456789</number>
<expiry>10/20/2009</expiry>
<lastname>John</lastname>
<firstname>Smith</firstname>
</creditcard>
</invoice>
We plan to sign it with an XML signature and hope to use a
signature method based on a public key.
Let us start by generating the cryptography key. For this, we
can use the keytool utility that comes with the JDK.
Move the command prompt to ./etc, and execute the
following:
keytool -genkey -keysize 512 -sigalg DSA -dname "cn=Young
Yang, ou=Architecture, o=Company, L=New York, ST=NY, c=US" -alias
biz -keypass kp1234 -keystore bizkeystore -storepass sp1234
-validity 180
This creates the key store, named bizkeystore, in the
working directory ./etc, and assigns it the password
sp1234. It also generates a public/private key pair
for the entity whose distinguished name has a common name of
Young Yang, an organizational unit of
Architecture, and other information. This commend
uses the DSA key generation algorithm to create the public and
private keys, both 512 bits long.
The above command further creates a self-signed certificate
using the SHA1withDSA algorithm (DSA_SHA1 by JSR-105
notations) that includes the public key and the distinguished name
information. This certificate will be valid for 180 days, and is
associated with the private key in a key store entry referred to by
the alias biz. The private key is assigned the
password kp1234.
Our example includes a simple Java class,
KeyStoreInfo, for printing to System.out
the keys and certificates in a key store, and for applications to
get from it the key pair, the private and public key matching
criteria specified as input parameters. To try it out by printing
out information contained in bizkeystore, readers may
just run the Ant target ksInfo.
The following code snippet displays the method in
KeyStoreInfo for retrieving a
KeyPair:
public static KeyPair getKeyPair(String store, String sPass,
String kPass, String alias)
throws CertificateException,
IOException,
UnrecoverableKeyException,
KeyStoreException,
NoSuchAlgorithmException{
KeyStore ks = loadKeyStore(store, sPass);
KeyPair keyPair = null;
Key key = null;
PublicKey publicKey = null;
PrivateKey privateKey = null;
if (ks.containsAlias(alias)){
key = ks.getKey(alias,kPass.toCharArray());
if (key instanceof PrivateKey){
Certificate cert = ks.getCertificate(alias);
publicKey = cert.getPublicKey();
privateKey = (PrivateKey)key;
return new KeyPair(publicKey,privateKey);
}else{
return null;
}
} else {
return null;
}
}
With a KeyPair, we can easily get the
PrivateKey and PublicKey by invoking the
corresponding operations, getPrivate() and
getPublic().
To get a PublicKey from the KeyStore,
we do not really need the key password required in the above
approach, and this is what the following method achieves:
public static PublicKey getPublicKey(String store,
String sPass, String alias)
throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException{
KeyStore ks = loadKeyStore(store, sPass);
Certificate cert = ks.getCertificate(alias);
return cert.getPublicKey();
}
In both code snippets, the method
KeyStore loadKeyStore(String store, String sPass)
is a utility function for instantiating a KeyStore
object, and loading entries from the file system. We implement it
as follows:
private static KeyStore loadKeyStore(String store, String sPass)
throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException{
KeyStore myKS = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream(store);
myKS.load(fis,sPass.toCharArray());
fis.close();
return myKS;
}
The keytool supplied with the JDK can also export
certificates in a key store to system files. For example, to create
a biz.cer file containing the X509 certificate
associated with the key entry having the alias biz, we
can run the following command from the ./etcdirectory:
keytool -export -alias biz -file biz.cer -keystore
bizkeystore -storepass sp1234
This certificate authenticates the public key we discussed
above.
We also include in our example a Java class,
CertificateInfo, for printing to
System.out some of the interesting information in a
certificate. To try it out, readers may run the Ant target
certInfo. However, basic knowledge of DSA and RSA
algorithms is required to comprehend the code and its output.
Readers may safely bypass this program, and continue to read the
remaining sections of this article without difficulties.
Generate an Enveloping Signature
This section discusses signing the invoice.xml file with the
JSR-105 APIs and its default implementation.
Our example creates an enveloping signature, though there would
only be minor differences if you were to work with a detached or
enveloped signature scenario.
Let us look at the program Sign.java, which generates the
XML signature for invoice.xml.
public class Sign {
public static void main(String[] args) throws Exception {
String input = "./etc/invoice.xml ";
String output = "./etc/signature.xml";
if (args.length > 2) {
input = args[0];
output = args[1];
}
// Prepare
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
// Step 1
String providerName = System.getProperty("jsr105Provider",
"org.jcp.XML.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
// Step 2
Reference ref = fac.newReference("#invoice",
fac.newDigestMethod(DigestMethod.SHA1, null));
// Step 3
Document XML = dbf.newDocumentBuilder().parse(new File(input));
Node invoice = XML.getDocumentElement();
XMLStructure content = new DOMStructure(invoice);
XMLObject obj = fac.newXMLObject(Collections.singletonList(content),
"invoice", null, null);
// Step 4
SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(
CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null),
Collections.singletonList(ref));
// Step 5, follow 5.0 or 5.1
PrivateKey privateKey = null;
// Scenario 5.0
privateKey = KeyStoreInfo.getPrivateKey("./etc/bizkeystore","sp1234",
"kp1234", "biz");
// Scenario 5.1, follow 5.1.1 or 5.1.2
// Scenario 5.1.1
// KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
// kpg.initialize(512);
// KeyPair kp = kpg.generateKeyPair();
// Scenario 5.1.2
// KeyPair kp = KeyStoreInfo.getKeyPair("./etc/bizkeystore", "sp1234",
// "kp1234", "biz");
// Keep this line if 5.1 is followed
// privateKey = kp.getPrivate();
// Step 6, follow 6.0, 6.1 or 6.2
// Scenario 6.0, also keep this line if 6.1 or 6.2 is followed
KeyInfo ki = null;
// Keep this line if 6.1 or 6.2 is followed
// KeyInfoFactory kif = fac.getKeyInfoFactory();
// Scenario 6.1
// KeyValue kv = kif.newKeyValue(kp.getPublic());
// ki = kif.newKeyInfo(Collections.singletonList(kv));
// Scenario 6.2
// CertificateFactory cf = CertificateFactory.getInstance("X.509");
// FileInputStream fis = new FileInputStream("./etc/biz.cer");
// java.security.cert.Certificate cert = cf.generateCertificate(fis);
// fis.close();
// X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
// ki = kif.newKeyInfo(Collections.singletonList(x509d));
// Step 7
XMLSignature signature = fac.newXMLSignature(si, ki,
Collections.singletonList(obj), null, null);
// Step 8
Document doc = dbf.newDocumentBuilder().newDocument();
DOMSignContext dsc = new DOMSignContext(privateKey, doc);
// Step 9
signature.sign(dsc);
// Materialize into an xml document
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(new FileOutputStream(output)));
}
}
To try out this program, readers may run the Ant target
sign, and it will create the XML document
./etc/signature.xml. This is the XML signature. To keep our
code clean and focused, we omit all the formatting settings in
parsing XML or/and transforming the DOM tree. As a result,
signature.xml is a bit messy as a text file.
Let us now walk through this program to demonstrate how to sign
an XML Signature in JSR-105.
The process of signing invoice.xml can be divided
into nine steps.
Step 1: Load an XMLSignatureFactory
instance. This factory class will be responsible for constructing
almost all the major objects we need in working with XML Signature
in JSR-105 APIs, except those related to KeyInfo.
Step 2: Decide on a digest method and create the
reference object. We use the XMLSignatureFactory
instance created in the first step to create both the
DigestMethod and Reference objects.
The factory operation in XMLSignatureFactory for
DigestMethod objects is:
public abstract DigestMethod newDigestMethod(String algorithm,
DigestMethodParameterSpec params) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException
The params argument is for specifying parameters
the digest algorithm may need. In the cases of SHA-1, SHA-256 or
SHA-512, we may just use null.
For the purpose of creating a Reference object,
XMLSignatureFactory provides four operations:
public abstract Reference newReference(String uri, DigestMethod dm);
public abstract Reference newReference(String uri, DigestMethod dm,
List transforms, String type, String id);
public abstract Reference newReference(String uri, DigestMethod dm,
List transforms, String type, String id, byte[] digestValue);
......
To fully understand the meaning of the input parameters in those
operations, we need to look at the XML schema definition of
Reference element in the W3C recommendation:
<element name="Reference" type="ds:ReferenceType"/>
<complexType name="ReferenceType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
<element ref="ds:DigestMethod"/>
<element ref="ds:DigestValue"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="URI" type="anyURI" use="optional"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
The URI attribute refers to the data object that the
Reference corresponds to.
For our example, we use SHA-1 as the digest method, and use
#invoice to refer to an element in the same XML
Signature document that will contain the XML representation of this
Reference object. The element referred by
#invoice is what we are going to discuss in the next
step.
Step 3: Load invoice.xml and wrap it in an
XMLObject object. Not all signature generation
processes require this step. XMLObject in JSR-105
models the optional Object element we briefly
discussed before. The Object element has the following
schema definition:
<element name="Object" type="ds:ObjectType"/>
<complexType name="ObjectType" mixed="true">
<sequence minOccurs="0" maxOccurs="unbounded">
<any namespace="##any" processContents="lax"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="MimeType" type="string" use="optional"/>
<attribute name="Encoding" type="anyURI" use="optional"/>
</complexType>
XMLSignatureFactory provides the following method
for creating an XMLObject instance:
public abstract XMLObject newXMLObject(List content, String id,
String mimeType, String encoding)
We use a DOMStructure object to wrap the root node
of invoice.xml. DOMStructure in JSR-105 can
help import nodes from the original to-be-signed XML document to
the JSR-105 runtime.
We specify #invoice as the id of the resulting
Object element. The JSR-105 implementation knows the
Reference object created in Step 2 refers to the
invoice.xml document, because this id links
them together (on the Reference side, and the
URI attribute points to this id).
Step 4: Create the SignedInfo object. In
the W3C recommendation, the SignedInfo element has the
following schema definition:
<element name="SignedInfo" type="ds:SignedInfoType"/>
<complexType name="SignedInfoType">
<sequence>
<element ref="ds:CanonicalizationMethod"/>
<element ref="ds:SignatureMethod"/>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
To create a SignedInfo object, we need the
Reference created in Step 2. We also need both an
instance of CanonicalizationMethod and an instance of
SignatureMethod. We refer interested readers to the
specifications for an accurate description of the differences among
the four XML canonicalization algorithms, and just simply point out
here that after we decide on a specific algorithm,
alg, the following call on an instance of
XMLSignatureFactory, fac, will be able to
create the CanonicalizationMethod instance:
fac.newCanonicalizationMethod(alg, null)
We can create a SignatureMethod instance by
invoking the following operation defined in
XMLSigantureFactory:
public abstract SignatureMethod newSignatureMethod(String algorithm,
SignatureMethodParameterSpec params) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException
In our example, we have a DSA type key pair; therefore, we need
to choose a DSA based algorithm such as DSA_SHA1. For DSA-SHA1, we
can set the params argument to null.
To create a SignedInfo instance,
XMLSignatureFactory defines two factory methods:
public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
SignatureMethod sm, List references);
public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
SignatureMethod sm, List references, String id).
The id argument in the second one will correspond
to the Id attribute of the SignedInfo
element in the resulting XML Signature document.
Step 5: Obtain the private key for signing.
In our example, we demonstrate three different approaches to get
the private key. In the first approach, we call the
getPrivateKey() method of our
KeyStoreInfo class to retrieve the DSA-type private
key which we created with keytool and stored in the
key store bizkeystore (scenario 5.0). To obtain the
private key, we may also retrieve the KeyPair from
bizkeystore by calling the getKeyPair()
method of KeyStoreInfo, and then call
getPrivate() of the KeyPair instance
(scenario 5.1.2). On the other hand, JCA provides a class named
KeyPairGenerator for dynamically creating a
KeyPair on the fly, and this is the scenario
documented as 5.1.1 in Sign.java.
Readers should also note that JSR-105 allows obtaining the
private key through a KeySelector object. We will have
more to say about KeySelector in next section.
Step 6: Create a KeyInfo object. This step
is optional, just as KeyInfo as an element in
Signature element is optional. In scenario 6.0 of our
example, we make KeyInfo to be null, and thus omit it
completely from the resulting XML signature.
The W3C recommendation and JSR-105 define KeyValues
of RSA and DSA types for wrapping, respectively, RSA and DSA public
keys, and allow them to be the content for KeyInfo.
Scenario 6.1 of our example creates a KeyValue object
from the public key we generated with JDK keytool
before, and puts it into a KeyInfo object. Later, when
we discuss our core validation program, we will see how it uses
such a KeyInfo object to retrieve the public key for
signature validation.
In JSR-105, we create both KeyValue and
KeyInfo objects by invoking operations in a
KeyInfoFactory instance. KeyInfoFactory
is responsible for creating all the major objects associated with
KeyInfo such as KeyName,
KeyValue, X509Data, etc. We can get a
KeyInfoFactory instance in the same way we get the
XMLSignatureFactory instance in Step 1. Our example
gets the KeyInfoFactory instance by calling the
getKeyInfoFactory() method of the
XMLSignatureFactory object.
Scenario 6.2 of our example creates an X509Data
object from the certificate biz.cer we exported with
keytool from bizkeystore before, and puts
it into a KeyInfo object as the content. Again, the
core validation program we will discuss later will demonstrate how
we can obtain the public key for signature validation from such a
KeyInfo object.
Step 7: Create an XMLSignature object. In
JSR-105, the XMLSignature interface models the
Signature element of the W3C recommendation. We
already saw the structure of the Signature element
before. To create an XMLSiganture instance, we can
invoke one of the following two methods in
XMLSignatureFactory:
public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki);
public abstract XMLSignature newXMLSignature(SignedInfo si, KeyInfo ki,
List objects, String id, String signatureValueId).
The id and signatureValueId arguments
in the second method will be the XML element ID in the resulting
XML Signature document. In our example, the XML signature will have
an Object element; therefore, we need to use this
second factory method.
Step 8: Instantiate a DOMSignContext
object, and register the private key with it. The
XMLSignContext interface (which DOMSignContext
implements) contains context information for generating
XML signatures.
DOMSignContext provides in some of its constructors
a way for the signing application to register the private key to be
used, and this is the approach our example takes.
Before moving to the final step of the signing process, we need
to point out that both XMLSignContext and
DOMSignContext instances may contain information and
state specific to the XML Signature structure they are used with. The
JSR-105 spec states that if an XMLSignContext (or
DOMSignContext) is used with different signature
structures, the results are unpredictable. For instance, we should
not use the same XMLSignContext (or
DOMSignContext) instance to sign two different
XMLSignature objects.
Step 9: Sign. The sign() operation in
the XMLSignature interface signs the
XMLSignature. Under the surface, the method carries
out several actions, including computing the digest values for all
the References based on the corresponding digest
methods, and calculating the signature value based on the signature
method and the private key. The signature value is captured by the
embedded SignatureValue class in the
XMLSignature instance, and calling
getSignatureValue() of the XMLSignature
instance will return the SignatureValue object
populated with the resulting value.
At the end of our signing program, we marshal the
XMLSignature into an XML document,
signature.xml.
Core Validation of XML Signature
In the preceding section, we signed the invoice.xml
document into an enveloping XML signature captured in the
signature.xml file.
To validate the signature, we can use the following program:
Validate.java.
public class Validate {
public static void main(String[] args) throws Exception {
// Step 1
String providerName = System.getProperty("jsr105Provider",
"org.jcp.XML.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
// Step 2
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(args[0]));
// Step 3
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.xmlNS,"Signature");
if (nl.getLength() == 0) {
throw new Exception("Cannot find Signature element!");
}
// Step 4, follow 4.0, 4.1, 4.2 or 4.3
// Scenario 4.0
DOMValidateContext valContext = new DOMValidateContext(
new KeyStoreKeySelector(), nl.item(0));
// Scenario 4.1, requires 6.1 in Sign.java
// DOMValidateContext valContext = new DOMValidateContext(
// new KeyValueKeySelector(), nl.item(0));
// Scenario 4.2, requires 6.2 in Sign.java
// KeyStore ks = KeyStore.getInstance("JKS");
// FileInputStream fis = new FileInputStream("./etc/bizkeystore");
// ks.load(fis,"sp1234".toCharArray());
// fis.close();
// X509KeySelector x509ks = new X509KeySelector(ks);
// DOMValidateContext valContext = new DOMValidateContext(x509ks, nl.item(0));
//Scenario 4.3
// PublicKey pKey = KeyStoreInfo.getPublicKey("./etc/bizkeystore",
// "sp1234", "biz");
// Step 5
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
//XMLSignature signature = fac.unmarshalXMLSignature(new DOMStructure(nl.item(0)));
// Step 6
boolean coreValidity = signature.validate(valContext);
// Check core validation status
if (coreValidity == false) {
System.err.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);
}
} else {
System.out.println("Signature passed core validation!");
}
}
}
To try out this program, readers may run the Ant target
validate. The program prints the core validation
status to System.out. If the signature is valid, the
printout is "Signature passed core validation!"; if it is
not, the printout shows the validation status of both the reference
and the signature, and we may thus know exactly which, if not all,
of them, cause(s) the failure.
The process of validating signature.xml can be divided
into six steps.
Step 1: Load an XMLSignatureFactory
instance. This step is the same as in the signing program.
Step 2: Load the XML signature to be validated. In this
step, we need to load the XML that contains the XML signature into
memory and transfer the XML document into a DOM tree.
Step 3: Identify the Signature node in the
DOM tree. Signature is defined in the namespace
http://www.w3.org/2000/09/XMLdsig#, which is
represented by the static variable XMLNS of the
XMLSignature interface in JSR-105.
Step 4: Create a DOMValidateContext
instance.
One of the most critical pieces of information in a validation
context is obviously the cryptography key. We can register the
public key with DOMValidateContext through two
different approaches. In the first approach, if the validating
application already has the public key, it may put the key directly
into the context through the following constructor of
DOMValidateContext:
public DOMValidateContext(Key validatingKey, Node node)
This is scenario 4.3 in our example.
The second approach is to register a KeySelector
with DOMValidateContext, and let the
KeySelector select the public key based on information
available in the XMLSignature object to be validated.
In JSR-105, KeySelector is an abstract class that
defines two operations:
public abstract KeySelectorResult select(KeyInfo keyInfo, Purpose purpose,
AlgorithmMethod method, XMLCryptoContext context)
throws KeySelectorException
public static KeySelector singletonKeySelector(Key key)
The second operation creates a KeySelector that
always returns the same key. The first operation tries to select a
key that satisfies the requirements passed as inputs.
KeySelectorResult is an interface in JSR-105. The
specification requires it to contain the Key selected
by the KeySelector. Our example implements this
interface with the SimpleKeySelectorResult class,
simply wrapping the selected public key.
We implement and utilize three different
KeySelectors in our example to demonstrate some of the
scenarios that a validating application may work with.
In scenario 4.0, KeyStoreKeySelector retrieves the
public key from a key store based on the input arguments.
In scenario 4.1, KeyValueKeySelector selects a key
based on the KeyValue information in the input
KeyInfo object (which should contain a
KeyValue object as part of its contents; see scenario
6.1 in Sign.java).
In scenario 4.2, X509KeySelector selects a key
based on the X509Data contained in the
KeyInfo object (which should contain a
X509Data object as part of its contents; see scenario
6.2 in Sign.java) and others. We take
X509KeySelector from the Javadocs of JSR-105. The
original author is Sean Mullan, and we slightly modified the
private certSelect() method so that it can work with
the certificate we generated with keytool.
Since KeyInfo in Signature may contain
various information, obviously, an application must choose a
KeySelector implementation that can
work with the kind of information contained in the
KeyInfos it will deal with.
Step 5: Unmarshal the Signature node into
an XMLSiganture object. In previous steps, among other
things, we loaded the signature.xml file into a DOM tree,
identified the node that corresponded to the Signature
element in the tree, and registered the node with a
DOMValidateContext together with the
KeySelector (or private key). To validate the XML
signature, we need to unmarshal the Signature node
into an XMLSignature object. We accomplish this by
calling the following operation of
XMLSignatureFactory:
public abstract XMLSignature unmarshalXMLSignature(XMLValidateContext context)
throws MarshalException
Step 6: Validate the XML signature. This is
accomplished by calling the validate() method of the
XMLSignature instance with the
DOMValidateContext as the only input argument.
The validate() method validates the XML signature
according to the core validation process defined in the W3C
recommendation. As mentioned before, this process includes two
parts. One is validating all references. In JSR-105, this can be
accomplished by invoking the validate() operation of
the Reference interface with the relevant validation
context as input.
The other part in core validation is signature validation,
validating the signature value of the canonicalized
SignedInfo element. With JSR-105, we may complete this
part explicitly by invoking the validate() method of
the SignatureValue object associated with the
XMLSignature instance, again with the relevant
validation context as input.
In our example, we use such knowledge to print out the
validation status for each Reference and the
SigantureValue when the XML Signature (core)
validation fails, to get more detailed information on what causes
the failure.
Tampering with the XML Signature
To demonstrate that the validating program does capture
modifications to the generated XML signature, we can create a
Tamper.java program in our example allowing us to modify the
credit card number in the invoice element, or the
SignatureValue element in
signature.xml.
This program takes the XML Signature document and a Boolean
value as arguments. When the Boolean parameter is
true, the program changes the credit card number;
otherwise, it modifies the signature value.
public class Tamper {
public static void main(String[] args) throws Exception {
String sigfile = "etc/signature.xml";
// Flag to decide what to modify: Reference or SignatureValue
boolean tamperRef = true ;
if (args.length >= 2) {
sigfile = args[0];
tamperRef = Boolean.parseBoolean(args[1]);
}
File file = new File(sigfile);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document signature = dbf.newDocumentBuilder().parse(file);
if (tamperRef){
// Modify the credit card number
NodeList targets =
signature.getDocumentElement().getElementsByTagName("number");
Node number = targets.item(0);
if (!number.getTextContent().equals("987654321")){
number.setTextContent("987654321");
}else{
number.setTextContent("000000000");
}
}else{
// Modify the SignatureValue (the first byte)
BASE64Encoder en = new BASE64Encoder();
BASE64Decoder de = new BASE64Decoder();
NodeList sigValues =
signature.getDocumentElement().getElementsByTagName("SignatureValue");
Node sigValue = sigValues.item(0);
byte[] oldValue = de.decodeBuffer(sigValue.getTextContent());
if (oldValue[0]!= 111){
oldValue[0] = (byte)111;
}else{
oldValue[0] = (byte)112;
}
sigValue.setTextContent(en.encode(oldValue));
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(signature),
new StreamResult(new FileOutputStream(file)));
}
}
To run it, readers may execute the tamper Ant
target. After running this tampering program, if we run the
validating program again, the core validation will fail, and
System.out will show the status of the reference and
signature validation, respectively. With the value of the second
Boolean input parameter varying, reference and/or signature
validation may report failure.
This completes our walkthrough of the example application.
Conclusion
Both XML Signature and JSR-105 contain a lot of details we are
not able to cover in this short article, such as transforms,
canonicalization methods, and the Manifest and
SignatureProperties elements (both are possible sub-elements of Object). We did not really drill down to the
digest and signature algorithms, either. Interested readers should
consult the W3C recommendation, JSR-105 API docs, and literature in
cryptography for more information.
The W3C recommendation only requires implementations to support
digest and signature methods based on the SHA-1 hash function. A
group of Chinese mathematicians, led by Professor Xiaoyun Wang, have
challenged the security of some of the widely applied hash
functions including SHA-1 (see one of the
articles by Wang and
comments by Bruce Schneier). While their results do not
immediately mean that SHA-1 as used in XML Signature is insecure
now, experts in cryptography do recommend new applications and
systems to consider employing more secure algorithms.
Readers should also be aware that, even though JCP plans for
JSR-105 to be released with JSE 6, JSR-105 requires all compliant
implementations to support JDK 1.4 and up. Therefore, applications
developed with JDK 1.4 should have no problem taking advantage of
this technology.
Resources
Application source package -->
- JDK 6.0 Project
- XML Signature
Syntax and Processing
- XML
Encryption Syntax and Processing
- Web
Services Security: SOAP Message Security
-
JSR-105: XML Digital Signature APIs
- JSR-106: XML
Encryption APIs
- RFC 2828,
Internet Security Glossary
- JCA
Provider Model
- Canonical XML,
version 1.0
- Exclusive XML
Canonicalization, version 1.0
- Wang, Xiaoyun,
et al, "How to Break MD5 and Other Hash Functions"
-
Comments by Bruce Schneier on the attack against SHA-1
- Federal
Information Processing Standards Publication 180-2: Specifications
for the Secure Hash Standard
- Preneel,
A. Bosselaers, and H. Dobbertin, "The cryptographic hash function
RIPEMD-160," CryptoBytes, Vol. 3, No. 2, 1997, pp. 9-14
- RFC 2104,
HMAC: Keyed-Hashing for Message Authentication
- RFC 2437,
PKCS #1: RSA Cryptography Specifications Version 2.0
-
FIPS 186-2 + Change Notice, Digital Signature Standard
(DSS)
Young Yang works in software, consulting and corporate IT as a tech lead, architect, development and project manager.