XML signatures apply digital signatures to XML documents. Digital
signatures let parties that exchange data ensure the identity of the
sender and the integrity
of the data. This last item is a benefit that physical signatures
can't provide.
Digital signatures don't have the legal status that physical
signatures have, at least not yet. Over the last few years this has
been changing, as the federal government and many state governments
have moved to accept digital signatures as legally binding for some
applications, where they identify the sender and provide
nonrepudiation (the signer can't deny ever having signed).
Since Web services that use SOAP exchange XML documents, they can
include XML signatures. Web service developers who produce Web
services with XML signatures will be able to realize the benefits of
those signatures, and may be able to use them in place of physical
signatures where the legal community has agreed they are binding.
Putting XML signatures into a Web service, however, is not trivial.
In this article I'll provide some background on XML signatures, and
then explore how you can incorporate them into your Web services.
I'll develop an example Web service, written in Java for Apache Axis,
that uses an XML signature, to illustrate how it can be done.
XML Signatures
XML signatures are the work of the W3C XML Signature Working Group
(www.w3.org/Signature), which produced "W3C Recommendation on XML
Signature Syntax and Processing" (February 12, 2002). The
recommendation gives an XML schema for the signature syntax and also
refers to recommendations on canonicalization, key type,
transformation algorithm, digest algorithm, and identity
certification by authorities. The recommendation specifies what may
go into each part of an XML signature.
IBM and Microsoft have been leading an effort to develop Web service
security, including the use of XML signatures in Web services. They
produced the WS-Security specification (www-106.ibm.com/
developerworks/library/ws-secure) along with VeriSign, Inc. They recently
submitted this specification to OASIS, which created a technical
committee dedicated to that proposal. This specification defines how
a SOAP header entry can be used to carry an XML signature referring
to information elsewhere in the same SOAP envelope (the basis for my
code example). Placing the signature in a SOAP header makes it easier
for software to locate.
XML signatures are the application of digital signatures to XML - you
can digitally sign part or all of an XML document. To create a
digital signature, you calculate a checksum (digest) of the XML to be
signed, and encrypt the digest using the private key of a
public/private key pair (PKI). You send the encrypted digest along
with the data to be signed, and also attach the public key and a
certificate issued by an authority to identify the owner of the
public key (the latter two items don't have to be passed if
the receiver already has them). The receiver decrypts the digest and
checksums it against the received information. If it matches, the
data's integrity is assured and the identity of the sender is tied to
the public key. By using the certificate, the receiver can be assured
that the public key belongs to the desired sender, and not someone
else who may have hijacked the digitally signed data and substituted
their own keys, digest, and signature.
The XML signatures recommendation doesn't concern itself with the
transport used to get the XML document to its destination. XML
signatures could be applied to XML documents that are e-mailed or
carried on a floppy disk. But, of course, sending information across
the Web opens it up to attack, making security more important, and
that is exactly the case for Web services.
There has been recent legislation on both the state and federal
levels covering the use of digital signatures. (For a good survey of
this legislation, see the McBride Baker & Coles Web site legislative
tables [www.mbc.com/ecommerce/ecommerce.asp].) For example, the use
of digital signatures has been addressed in federal legislation such
as the "Electronic Signatures in Global and National Commerce Act"
(E-SIGN), and has been used on a trial basis for IRS tax returns.
Using XML Signatures in Your Web Service
First, let's assume you are working on an application where you need
the benefits of XML signatures. In my example, I'm developing an
insurance claim system wherein agents can submit claims containing
accident reports. Each agent is assigned a private/public key pair,
which is installed on his or her computer in a keystore (a database
of public and private keys and certificates). Note that you can use
keytool (from Sun Microsystems) to create keystores for your own
work. The agent signs each accident report using the private key (by
telling the application where the keystore is and providing the
proper identifier and passwords). The agent submits the claim
containing the signed accident report(s) via a Web service to the
central office.
At the office, each XML signature is verified, and if the identity of
the submitter is accepted, the claim is processed. By verifying the
XML signature, the central office has not only verified the identity
of the sender, but also that the information in the accident report
has not been altered after it was signed.
Now let's consider how to design and implement the Web service. For
my example I will be using Apache Axis (available at
http://XML.apache.org/axis). I'll be writing the code for my Web
service in Java, and I'll develop a message-style Web service. Many
Web service developers start with Java code and generate the portions
of the Web service that talk XML from it automatically, shielding
themselves from dealing with XML. For this insurance claim Web
service though, where an XML signature needs to be applied to the XML
that represents my data, I will produce my own XML.
I choose to design my service interface first so that I'll know what
the XML should look like. I do this by authoring a WSDL file that
describes my service. Its data definitions are done in an XML schema.
Since the WSDL file should describe the messages that pass across the
wire, my data definitions show the XML signature along with the data
it signs. Listing 1 shows an excerpt from the wsdl file, the first
portion from the XML schema that defines my data. The signature element is
carried in the SOAP header, and I define it as a reference to the XML
signature Signature element. The AccidentReport element is the data
to be signed; it's in the SOAP body. The top-level element in the
SOAP body, Claim, contains an AccidentReport element whose type is
AccidentReportType.
The second portion of Listing 1 shows the WSDL message definition for
the request message: the first part (name = "Claim") uses the Claim
type, and goes into the SOAP body part of the SOAP envelope. The
second part (name = "Signature") refers to the W3C Signature element,
and goes into the SOAP header part of the SOAP envelope. When I
define the WSDL describing my Web service operation, I use
document/literal. I don't want SOAP encoding applied to the data
being signed, and I don't plan to do any such encoding myself.
Now I can implement the Web service. I use the Apache XML security
packages (available at http://XML.apache.org/security), which you
can use from your Java classes, and which operate on DOM objects
(which represent XML data). Therefore, I need to operate on the
messages in my Web service at the DOM level.
To do this, I write a message-style Web service; I don't use Apache's
ability to automatically serialize/deserialize Java classes. I use
the Axis sample LogHandler.Java (which you find in the Axis security
samples), which intercepts incoming messages. It receives a
MessageContext, from which it gets the Signature element out of the
SOAP header and verifies it against the SOAP body. If the
verification succeeds, the handler passes the message (without the
signature) on to the Web service server itself. The server proceeds
with processing the claim (in my example, I used a simple server that
echoes back the elements it received). If the signature verification
fails, the log handler raises an exception, which causes a SOAP fault
to be returned.
On the client side, I also work at the DOM and message level. My code
translates the data to be signed into DOM (along with the data that
will not be signed), constructs the XML signature, and puts both into
their proper place in a SOAP envelope (the signature in the SOAP
header, the data in the SOAP body). Finally, it sends the message
(via the invoke method). Listing 2 shows the class that represents
the data to be signed; the class includes a method asDOM() that
returns a DOM representation of itself. Listing 3 shows the main body
of the client, which populates the Claim to be submitted; creates a
SOAP envelope; causes the correct parts of it to be signed; and calls
call.invo ke(), which sends the outgoing message. Listing 4 shows
excerpts from the class that signs the AccidentReport portion of the
SOAP envelope. For more details on how to do the signing,
canonicalization, serializing, and deserializing, see the Apache Axis
security samples.
Of course, my code could just as easily have signed the entire SOAP
body, instead of just signing the AccidentReport. This would have a
different effect - it would ensure that the entire message has been
delivered correctly, and not necessarily that the correct party
vouches for the AccidentReport.
Conclusion
As you can see from my example, it can be a bit of a struggle to use
XML signatures in a Web service. Their relationship to the XML data
they sign makes XML signatures hard to use in a Web service tool that
seeks to hide XML from its user. It would take special mechanisms to
express the relationship between signed data and signature at the
programming language level, so that a Web service tool could handle
the details for you. As my example shows, in Apache Axis it is
easiest to get the XML signature out of the SOAP header of the
message (in a log handler) before the main part of the Web service
begins. So the developer must write some code that can work in a
separate handler. This makes the developer's job a bit harder, since
he or she must split the server logic into two parts, and possibly
make them communicate (so that the handler can pass the service
proper any signature information it needs).
Author Bio
Mark Young is vice president of Kamiak Corporation, the publisher of
Omniopera WS, a WSDL and XML Schema editor. He is an expert in XML
Schema and WSDL, and has written object-oriented libraries to model
them. Mark has 20 years of software development experience, has been
a software team leader for many years, and has developed software for
applications from telephony to sales automation.
mark@kamiak.com
Designing Web Services with XML Signatures, by Mark Young
WSJ Vol 02 Issue 10 - pg.12
Listing 1
<xsd:element name=""Claim"" type=""tns:ClaimType""/>
<xsd:complexType name=""ClaimType"">
<xsd:sequence>
<xsd:element name=""AccidentReport"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""AccidentDescription""
type=""xsd:string""/>
<xsd:element name=""AccidentDate""
type=""xsd:date""/>
</xsd:sequence>
<xsd:attribute name=""id"" type=""xsd:ID"">
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name=""Submitter"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""Name"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""Last""
type=""xsd:string""/>
<xsd:element name=""First""
type=""xsd:string""/>
<xsd:element name=""MiddleInitial""
type=""xsd:string""/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name=""Company""
type=""xsd:string""/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
...
<wsdl:message name=""SubmitClaimSoapIn"">
<wsdl:part name=""Claim"" element=""ns:Claim""/>
<wsdl:part name=""Signature""
element=""soap-sec:Signature""/>
</wsdl:message>
...
<wsdl:binding name=""ClaimServiceSOAPBinding""
type=""ns:ClaimServiceSOAP"">
<soap:binding style=""document"" >
<wsdl:operation name=""SubmitClaim"">
<soap:operation style=""document"">
<wsdl:input name=""SubmitClaimInput"">
<soap:body use=""literal"" parts=""Claim""/>
<soap:header use=""literal"" part=""Signature""
message=""ns:SubmitClaimSoapIn""/>
</wsdl:input>
...
</wsdl:operation>
</wsdl:binding>
Listing 2
package com.myOrg.ClaimService.Client;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Document;
import org.apache.axis.message.MessageElement;
import Java.io.FileOutputStream;
import Java.io.Writer;
public class AccidentReport {
static private Java.lang.String
nsString = ""http://www.claimsRUs.com/claimDef"";
private String mAccidentDescription;
private String mAccidentDate;
public AccidentReport() {
}
public void setDescription(String aString) {
mAccidentDescription = aString;
}
public String getDescription() {
return mAccidentDescription;
}
public void setDate(String aString) {
mAccidentDate = aString;
}
public String getDate() {
return mAccidentDate;
}
public Element asDOM(Document aDoc) {
Element accidentReportEl =
aDoc.createElementNS(nsString,
""AccidentReport"");
// this is how the signature will refer
// to this element
accidentReportEl.setAttribute(""Id"", ""Ar1"");
Element descriptionEl =
aDoc.createElementNS(nsString,
""AccidentDescription"");
Node descriptionTxt =
aDoc.createTextNode(mAccidentDescription);
descriptionEl.appendChild(descriptionTxt);
accidentReportEl.appendChild(descriptionEl);
Element dateEl = aDoc.createElementNS(
nsString, ""AccidentDate"");
Node dateTxt =
aDoc.createTextNode(mAccidentDate);
dateEl.appendChild(dateTxt);
accidentReportEl.appendChild(dateEl);
return accidentReportEl;
}
static public AccidentReport populate() {
// stuff with some test data
AccidentReport aReport = new AccidentReport();
aReport.setDescription(""Fender bender."");
aReport.setDate(""1-2-2002"");
return aReport;
}
}
Listing 3
package com.myOrg.ClaimService.Client;
import org.apache.axis.AxisFault;
import org.apache.axis.MessageContext;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.message.SOAPBodyElement;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.utils.Options;
import org.apache.axis.utils.XMLUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
public class ClaimClient {
private static final String nsString =
""http://www.myOrg.com/ClaimService"";
public static void main(String[] args)
throws Exception {
try {
Options opts = new Options(args);
Service service = new Service();
Call call = (Call)service.createCall();
// set port to 8081 so that can observe
// the messages with tcpmon
// Java org.apache.axis.utils.tcpmon
opts.setDefaultURL(""http://localhost:""
+ ""8081/axis/servlet/AxisServlet"");
call.setTargetEndpointAddress
(new Java.net.URL(opts.getURL()));
Document doc = XMLUtils.newDocument();
// set up the soap envelope
SOAPEnvelope env = new SOAPEnvelope();
// add an element for the method we'll
// invoke needs to have ns = to the
// path of the service
Element anEl = doc.createElementNS(
""http://localhost:8080/"" +
""LogTestService"", ""testMethod"");
SOAPBodyElement sbe =
new SOAPBodyElement(anEl);
env.addBodyElement(sbe);
// and add an element that holds
// the insurance Claim we're
// submitting. It isn't signed yet.
Claim aClaim = Claim.populate();
Element claimEl = aClaim.asDOM(doc);
SOAPBodyElement sbe2 =
new SOAPBodyElement(claimEl);
env.addBodyElement(sbe2);
// now change the envelope into a
// signed envelope, byadding a
// signature (in the SOAPHeader)
// that refers to the AccidentReport
// element added by aClaim.asDOM()
env = new SignedSOAPEnvelope(env,
""http://xml-security"");
// and invoke the web service method
call.invoke(env);
}
catch (Exception e) {
}
}
}
Listing 4
package com.myOrg.ClaimService.Client;
...
public class SignedSOAPEnvelope
extends SOAPEnvelope {
static String SOAPSECNS =
""http://schemas.xmlsoap.org/""
+ ""soap/security/2000-12"";
static String SOAPSECprefix = ""SOAP-SEC"";
static String keystoreType = ""JKS"";
static String keystoreFile = "".keystore"";
static String keystorePass = ""security"";
static String privateKeyAlias = ""georgia"";
static String privateKeyPass = ""secret"";
static String certificateAlias = ""georgia"";
static {
org.apache.xml.security.Init.init();
}
public SignedSOAPEnvelope(MessageContext
msgContext, SOAPEnvelope env, String
baseURI, String keystoreFile) {
this.msgContext = msgContext;
init(env, baseURI, keystoreFile);
}
public SignedSOAPEnvelope(SOAPEnvelope env,
String baseURI) {
init(env, baseURI, keystoreFile);
}
// fill this object by adding a signature to
// the passed envelope env, and serializing/
// deserializing the result back into this
// object
private void init(SOAPEnvelope env, String
baseURI, String
keystoreFile) {
try {
…
// create new header element to hold
// the signature per WS-Security
…
// grab the contents of env as a
// Document (which we'll later
// parse back into this object) so
// that we can operate at the
// DOM level
...
// get the keystore
KeyStore ks =
KeyStore.getInstance(keystoreType);
FileInputStream fis = new
FileInputStream(keystoreFile);
ks.load(fis,
keystorePass.toCharArray());
// get private key from keystore
PrivateKey privateKey =
(PrivateKey) ks.
getKey(privateKeyAlias,
privateKeyPass.toCharArray());
// find the new header element
// (in doc) we added above
Element soapHeaderElement =
(Element) ((Element) doc.
getFirstChild()).
getElementsByTagNameNS(""*"",
""Header"").item(0);
Element soapSignatureElement =
(Element) soapHeaderElement.
getElementsByTagNameNS(""*"",
""Signature"").item(0);
// create a signature object
XMLSignature sig =
new XMLSignature(doc, baseURI,
XMLSignature.ALGO_ID_SIGNATURE_DSA);
// have it create a signature element
// and append that as a child of the
// header element
soapSignatureElement.
appendChild(sig.getElement());
// set up the reference in the
// signature element --
// refers to the AccidentReport
// element created elsewhere
Transforms transforms =
new Transforms(doc);
transforms.addTransform(Transforms.
TRANSFORM_ENVELOPED_SIGNATURE);
transforms.addTransform(Transforms.
TRANSFORM_C14N_WITH_COMMENTS);
sig.addDocument(""#Ar1"", transforms,
org.apache.xml.security.utils.
Constants.ALGO_ID_DIGEST_SHA1);
// add x509 certificate information
// taken from the keystore
X509Certificate cert =
(X509Certificate) ks.
getCertificate(certificateAlias);
sig.addKeyInfo(cert);
// including the public key
sig.addKeyInfo(cert.getPublicKey());
// and finally, sign the message
sig.sign(privateKey);
// now canonicalize the entire document
// (envelope) and parse it back into
// this object (an extension of
// SOAPEnvelope)
…
} catch (Exception e) {
}
}
...
}"
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com