Development Techniques


Now we’re ready to look at using the XML Security API to perform common XML Signature and Encryption tasks. We’ll do this by working through a series of examples.

Canonicalizing and Computing the Digest

The first task you’ll perform is to take an XML document, compute its canonical form, and then compute a message digest for that canonical form:

  1: /*   2:  *    3:  * DigestDocument.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import java.io.ByteArrayOutputStream;  11: import java.io.FileInputStream;  12: import java.io.FileNotFoundException;  13: import java.io.IOException;  14: import java.security.MessageDigest;  15: import java.security.NoSuchAlgorithmException;  16:   17: import javax.xml.parsers.ParserConfigurationException;  18:   19: import org.apache.xml.security.c14n.CanonicalizationException;  20: import org.apache.xml.security.c14n.Canonicalizer;  21: import   22: org.apache.xml.security.c14n.InvalidCanonicalizerException;  23: import org.apache.xml.security.utils.Base64;  24: import org.xml.sax.SAXException;  25:   26: public class DigestDocument {

Before you can call any methods on the XML Security library you must call the static function org.apache.xml.security.Init.Init to initialize the library:

 27:   28:     public static void main(String[] args) {  29:         org.apache.xml.security.Init.init();

XML Security uses a class called Canonicalizer to canonicalize data. Canonicalizer can operate on a byte array, a DOM tree, or an XPath node set (represented as a DOM NodeList). In this example you use the byte array form, so lines 31-49 open a FileInputStream on the XML document being canonicalized and copy the document into a ByteArrayOutputStream. Once the ByteArrayOutputStream is full, you convert it a byte array that can be used as input to Canonicalizer:

 30:   31:         FileInputStream fis = null;  32:         try {  33:             fis = new FileInputStream(args[0]);  34:         } catch (FileNotFoundException fnfe) {  35:             fnfe.printStackTrace();  36:         }  37:         ByteArrayOutputStream baos =   38:                new ByteArrayOutputStream();  39:       40:         byte buffer[] = new byte[2048];  41:         int count = 0;  42:         try {  43:             while ((count = fis.read(buffer)) > 0) {  44:                 baos.write(buffer,0, count);  45:             }  46:             fis.close();  47:             baos.close();  48:         } catch (IOException ioe) {  49:             ioe.printStackTrace();  50:         }          51:           52:         byte bytes[] = baos.toByteArray();

First you need to set up a Canonicalizer instance. You do this via the static getInstance factory method on Canonicalizer:

 53:   54:         Canonicalizer c14n = null;  55:         try {  56:             c14n = Canonicalizer.getInstance(  57:                 Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);  58:         } catch (InvalidCanonicalizerException ice) {  59:             ice.printStackTrace();  60:         }

The argument to the factory method is a String that contains the URL of the algorithm to be used. For ease of use, Canonicalizer defines a set of String constant values for all the supported algorithms. Canonicalizer supports four algorithms: inclusive XML canonicalization with and without comments (each is a different algorithm) and exclusive XML canonicalization with and without comments:

Canonicalizing the data is a matter of calling canonicalize on the byte array containing the data:

 61:           62:         byte canonicalBytes[] = null;  63:         try {  64:             canonicalBytes = c14n.canonicalize(bytes);

The result is a byte array containing the canonical form. It’s a byte array whether you canonicalize a DOM tree (the method is canonicalizeSubtree) or an XPath node set (the method is canonicalizeXPathNodeSet). The DOM and XPath node set methods also have versions that let you supply a comma-separated list of inclusiveNamespaces prefixes to be treated as the InclusiveNamespacesPrefixList used by exclusive XML canonicalization.

You print the canonical form to the console for your edification:

 65:         } catch (CanonicalizationException ce) {  66:             ce.printStackTrace();  67:         } catch (ParserConfigurationException pce) {  68:             pce.printStackTrace();  69:         } catch (IOException ioe) {  70:             ioe.printStackTrace();  71:         } catch (SAXException se) {  72:             se.printStackTrace();  73:         }  74:   75:         System.out.println(new String(canonicalBytes));  76:         System.out.println();          77: 

XML Security uses the Java Cryptography Architecture’s MessageDigest class to compute message digests. MessageDigest provides a static factory method for creating a MessageDigest instance. The argument is a string that’s the name of a message-digesting algorithm. Valid values are "SHA-1", "SHA-256", "SHA-384", "SHA-512", "MD5", and "MD2":

 78:         MessageDigest md = null;  79:         try {  80:             md = MessageDigest.getInstance("SHA-1");  81:         } catch (NoSuchAlgorithmException nsa) {  82:             nsa.printStackTrace();  83:         }  84:         

You compute the digest by passing the canonicalized byte array to the digest method of MessageDigest. This gives you a byte array with the digest value:

 85:         byte digest[] = md.digest(canonicalBytes);

Here you print out the base64 representation of the digest value, as used in the XML Signature and XML Encryption specifications:

 86:           87:         System.out.println(Base64.encode(digest));  88:     }  89: }

The output of running this program on the book.xml file is as follows:

<book xmlns="http://sauria.com/schemas/apache-xml-book/book"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"  xsi:schemaLocation="http://sauria.com/schemas/apache-xml-book/book  http://www.sauria.com/schemas/apache-xml-book/book.xsd">   <title>Professional XML Development with Apache Tools</title>   <author >Theodore W. Leung</author>   <isbn>0-7645-4355-5</isbn>   <month>December</month>   <year>2003</year>   <publisher>Wrox</publisher>   <address>Indianapolis, Indiana</address> </book> A29reHPotuzItyi749JCC6qBMtg=

Signing

The next topic is signing a document. This example serves two purposes: It introduces you to the XML Security digital signature API and also leaves you with a class you can use to sign documents in your applications:

  1: /*   2:  *    3:  * DocumentSigner.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import java.io.File;  11: import java.io.FileInputStream;  12: import java.io.FileNotFoundException;  13: import java.io.FileOutputStream;  14: import java.io.IOException;  15: import java.io.OutputStream;  16: import java.net.MalformedURLException;  17: import java.security.KeyStore;  18: import java.security.KeyStoreException;  19: import java.security.NoSuchAlgorithmException;  20: import java.security.PrivateKey;  21: import java.security.PublicKey;  22: import java.security.UnrecoverableKeyException;  23: import java.security.cert.CertificateException;  24: import java.security.cert.X509Certificate;  25: import java.util.ArrayList;  26: import java.util.Iterator;  27: import java.util.List;  28:   29: import javax.xml.parsers.DocumentBuilder;  30: import javax.xml.parsers.DocumentBuilderFactory;  31: import javax.xml.parsers.ParserConfigurationException;  32:   33: import org.apache.xml.security.exceptions.XMLSecurityException;  34: import org.apache.xml.security.signature.Manifest;  35: import org.apache.xml.security.signature.ObjectContainer;  36: import org.apache.xml.security.signature.SignatureProperties;  37: import org.apache.xml.security.signature.SignatureProperty;  38: import org.apache.xml.security.signature.XMLSignature;  39: import org.apache.xml.security.signature.XMLSignatureException;  40: import   41:     org.apache.xml.security.transforms.TransformationException;  42: import org.apache.xml.security.transforms.Transforms;  43: import org.apache.xml.security.utils.Constants;  44: import org.apache.xml.security.utils.SignatureElementProxy;  45: import org.apache.xml.security.utils.XMLUtils;  46: import org.w3c.dom.Document;  47: import org.w3c.dom.Node;  48: import org.xml.sax.SAXException;  49:   50: public class DocumentSigner {  51:     protected Document document;  52:     protected PrivateKey privateKey;  53:     protected X509Certificate certificate;  54:     protected PublicKey publicKey;  55:     protected String baseURI;  56:     protected String signatureMethod;  57:     protected String digestMethod;  58:     protected String transformArray[] = null;  59:     protected List objectList = null; 

The class has a number of protected member variables that hold data the class needs. In particular, you need a Document to place the signature in (document) and a private key to do the signing with (privateKey). You need the algorithms to be used for the <SignatureMethod> (signatureMethod) and <DigestMethod> (digestMethod) elements. If you want to use transforms, then you need to store them somehow (transformArray). Should you decide to use <KeyInfo> to specify the public key needed for verification, you may need a public key (publicKey) or a digital certificate (certificate). Any optional <Object> elements need to be stored until you’re ready to do the signing.

Here you initialize the XML Security library so that you can call any methods you need:

 60:       61:     static {  62:         org.apache.xml.security.Init.init();  63:     }

You define a default constructor (line 65) and a constructor that fills in the simple fields (lines 67-77). The actual work of signing a document is done by the sign method. It assumes that other methods have been called to fill in the values of any object fields that are needed:

 64:       65:     public DocumentSigner() {}  66:   67:     public DocumentSigner(Document doc,   68:         PrivateKey privateKey, X509Certificate cert,  69:         PublicKey publicKey, String baseURI,  70:         String signatureMethod, String digestMethod) {  71:         this.document = doc;  72:         this.privateKey = privateKey;  73:         this.certificate = cert;  74:         this.publicKey = publicKey;  75:         this.baseURI = baseURI;  76:         this.signatureMethod = signatureMethod;  77:         this.digestMethod = digestMethod;  78:     }  79

The XMLSignature class is used to create an XMLSignature element. The <Signature> element is related to document. It needs a BaseURI that resolves any relative URIs it encounters, and it needs an algorithm to use for signing:

 80:     public Document sign() {  81:         XMLSignature sig = null;  82:         try {  83:             sig =  84:                 new XMLSignature(document,   85:                     baseURI, signatureMethod);  86:         } catch (XMLSecurityException xse) {  87:             xse.printStackTrace();  88:         }

This class always creates the signature as the last child of document. So, you get the DOM DocumentElement (line 90) and append the signature (as an Element) as a child of that element (line 92):

 89:   90:         Node root = document.getDocumentElement();  91:   92:         root.appendChild(sig.getElement());

Next you execute the addReferences method in DocumentSigner, whose job is to add any <References> to data that needs to be signed:

 93:   94:         try {  95:             addReferences(sig);  96:         } catch (XMLSignatureException xse) {  97:             xse.printStackTrace();  98:         }

If the certificate or publicKey fields have been filled in, then you add a KeyInfo to the signature using the addKeyInfo method. If there’s a certificate, you add the certificate and the public key from the certificate (lines 101-103). If there’s no certificate and a public key, then you add the public key (lines 107-108):

 99:  100:         try { 101:             if (certificate != null ) { 102:                 sig.addKeyInfo(certificate); 103:                 sig.addKeyInfo(certificate.getPublicKey()); 104:             } 105:              106:             if (publicKey != null && certificate == null) { 107:                 sig.addKeyInfo(publicKey); 108:             }

Next you check to see if the objectList contains any values. If it does, you iterate over those values; for each one, you create an ObjectContainer (lines 113-116) whose child is the element from the objectList (line 117). You add this new ObjectContainer to the signature object using appendObject (line 118). Note that ObjectContainers are related to the DOM document:

109:          110:             if (objectList != null) { 111:                 for (Iterator i = objectList.iterator();  112:                     i.hasNext();) { 113:                     SignatureElementProxy sep =  114:                         (SignatureElementProxy) i.next(); 115:                     ObjectContainer oc =  116:                         new ObjectContainer(document); 117:                     oc.appendChild(sep.getElement()); 118:                     sig.appendObject(oc); 119:                 } 120:             }

After all the information has been added to the signature element, you sign the <SignedInfo> by calling the sign method on XMLSignature and passing it the private key:

121:         } catch (XMLSecurityException xse) { 122:             xse.printStackTrace(); 123:         } 124:  125:         try { 126:             sig.sign(privateKey);

The result of the method is the original document with a new child <Signature> element, all filled in with the data for the signature:

127:         } catch (XMLSignatureException xse) { 128:             xse.printStackTrace(); 129:         } 130:  131:         return document; 132:     } 133:  

Now let’s look at some of the support methods you need to make this work. The addReferences method is called to add <Reference> elements to the <Signature> element represented by an XMLSignature instance (sig). Remember that each reference element specifies the URI of the data to be digested, a set of transforms to be applied, and a specification of the digest algorithm to be used for that reference. This method is protected because it shouldn’t be called by any method other than the sign method. The sign method is an instance of the Template method design pattern, with addReference as one of the primitive operations. To change the set of References to add, you need to extend DocumentSigner and override addReferences with your own implementation that adds the correct references with their transformations and digest methods:

134:     protected void addReferences(XMLSignature sig) 135:         throws XMLSignatureException { 136: 

You add transforms by creating an instance of Transforms (which depends on document) (line 137). You can then call the addTransform method on this instance to add as many transforms as you like. The Transforms class defines String constants whose values are the URIs for the transforms defined by the XML Signature spec. There are constants for all the transforms specified in the XML Signature spec and for the two variants of exclusive XML canonicalization. In this case, you use the enveloped signature transform (lines 140-141) to exclude the <Signature> element from the signature, and you use inclusive XML canonicalization with comments (lines 142-143):

137:         Transforms transforms = new Transforms(document); 138:          139:         try { 140:             transforms.addTransform( 141:                 Transforms.TRANSFORM_ENVELOPED_SIGNATURE); 142:             transforms.addTransform( 143:                 Transforms.TRANSFORM_C14N_WITH_COMMENTS); 144:         } catch (TransformationException te) { 145:             te.printStackTrace(); 146:         }

The addDocument method on XMLSignature adds a referenced object to the signature. In this case, you add the URI "#xpointer(/)", which stands for the entire document including comments. So, you’re signing the document that includes the <Signature> element. If you wanted to reference the entire document without comments, you’d give the empty string for the URI. You can reference elements with IDs by placing the fragment identifier in the URI, so you can reference the author element in the book file by supplying "#author" as the URI. For more complicated specifications, you should specify the entire document and then use the XPath transform:

147:          148:         sig.addDocument("#xpointer(/)", transforms,  149:             digestMethod); 150:     }

The URI is transformed using the set of transforms you added earlier, and the digest method is the value of the digestMethod field. You can have multiple calls to addDocument to add multiple URIs. Each call can have different transform and digestMethod argument values. There are multiple versions of the addDocument method.

writeSignature writes a DOM tree to an output stream in inclusive canonical form with comments. You can call this method after the signature has been produced:

151:  152:     public void writeSignature(OutputStream outputStream) { 153:         XMLUtils.outputDOMc14nWithComments(document,  154:             outputStream); 155:     }

The addManifest method creates a new Manifest object and returns it to you. You can then call the addDocument method on Manifest to add references to the Manifest. There’s only one version of the addDocument method on Manifest, and it requires you to specify a URI, a Transforms, a digestMethod URI, an ID for the <Reference>, and a type for the <Reference>, in that order:

156:  157:     public Manifest addManifest() { 158:         if (objectList == null) { 159:             objectList = new ArrayList(); 160:         } 161:         Manifest manifest = new Manifest(document); 162:         objectList.add(manifest); 163:         return manifest; 164:     }

The addSignatureProperties method lets you add a <SignatureProperties> element to the signature. It returns a SignatureProperties object. You can call the addSignatureProperty method to add a <SignatureProperty> to the <SignatureProperties> element. You have to construct a new SignatureProperty object—this is cumbersome because it needs a DOM document object as an argument to the constructor— the sole function of the getDocument method on the SignatureProperties instance to is get this object. You should also pass the target URI and ID value of the SignatureProperty to the constructor. Call the addText method on the SignatureProperty instance to add the value of the property:

165:      166:     public SignatureProperties addSignatureProperties() { 167:         if (objectList == null) { 168:             objectList = new ArrayList(); 169:         } 170:         SignatureProperties signatureProperties =  171:             new SignatureProperties(document); 172:         objectList.add(signatureProperties); 173:         return signatureProperties; 174:     }

This main program shows how to use the DocumentSigner. You instantiate an instance, and then you fill in all the fields that are necessary and call the sign method. This process is encapsulated in a method called testSigning, which we’ll talk about next. Note that testSigning gets its two argument values from the command line:

175:  176:     public static void main(String[] args)  { 177:         DocumentSigner signer = new DocumentSigner(); 178:         testSigning(signer, args[0], args[1]); 179:     }

The first thing testSigning does is parse a document that will be the host for the signature. The URI for the document comes from the argument list:

180:          181:     public static boolean testSigning(DocumentSigner signer, 182:         String uri, String outputFile) {     183:         Document doc = getInputDocument(uri);

Next, it opens a KeyStore (line 185) and gets a private key (line 188). Note that the keystore name and password, the alias for the private key, and the private key password are all hardwired to keep the example short:

184:  185:         KeyStore ks = getKeyStore("keystore.jks", "password"); 186:  187:         PrivateKey privateKey =  188:             getPrivateKey(ks, "johndoe", "password");

You also attempt to get the certificate for the entity if it exists in the keystore:

189:  190:         X509Certificate certificate = null; 191:         try { 192:             certificate = (X509Certificate)  193:                 ks.getCertificate("johndoe"); 194:         } catch (KeyStoreException kse) { 195:             kse.printStackTrace(); 196:         }

The document with the signature in it will be written to a file. The filename is the other argument in the argument list. The base URI is set to the URI for the file containing the signature:

197:          198:         File signatureFile = new File(outputFile); 199:  200:         String baseURI = getBaseURI(signatureFile);

Next you call some setters to set the values you’ve computed thus far:

201:          202:         signer.setDocument(doc); 203:         signer.setPrivateKey(privateKey); 204:         signer.setCertificate(certificate); 205:         signer.setBaseURI(baseURI);

You also call some setters to set the <SignatureMethod> and <DigestMethod>. XMLSignature defines String constants for the names of the digital signature algorithms in the XML Signature Recommendation, and Constants defines constants for the digest algorithm names. Here you use DSA for signing and SHA1 for digesting references:

206:         signer.setSignatureMethod( 207:             XMLSignature.ALGO_ID_SIGNATURE_DSA); 208:         signer.setDigestMethod(Constants.ALGO_ID_DIGEST_SHA1);

In line 210 you add an empty manifest:

209:  210:         Manifest m = signer.addManifest();

Next you add a SignatureProperty. To do this, you ask the signer for a SignatureProperties by calling addSignatureProperties (lines 212-213). Next you construct a new SignatureProperty from the SignatureProperties’ document, a URI, and an ID (lines 216-218). Calling addText on the SignatureProperty sets the value of the property to "smartcard".

211:  212:         SignatureProperties sps =  213:             signer.addSignatureProperties(); 214:         SignatureProperty sp = null; 215:         try { 216:             sp = new SignatureProperty(sps.getDocument(), 217:                                        "http://www.sauria.com", 218:                                        "hardware"); 219:             sp.addText("smartcard");  220:         } catch (XMLSignatureException xse) { 221:             xse.printStackTrace(); 222:         } 223:         sps.addSignatureProperty(sp);

At last all the fields have been set up, so you’re ready to call the sign method and generate a signed document:

224:                      225:         signer.sign();

Finally, you write the file containing the signature to disk:

226:  227:         try { 228:             FileOutputStream signatureStream = 229:                 new FileOutputStream(signatureFile); 230:  231:             signer.writeSignature(signatureStream); 232:  233:             signatureStream.close(); 234:         } catch (FileNotFoundException fnfe) { 235:             fnfe.printStackTrace(); 236:         } catch (IOException ioe) { 237:             ioe.printStackTrace(); 238:         } 239:         return true; 240:     } 241:     

The getBaseURI method computes the URI of a File object. It assumes that this is how you’ll construct the base URI. You’re free to generate the base URI some other way:

242:     public static String getBaseURI(File signatureFile) { 243:         String BaseURI = null; 244:         try { 245:             BaseURI = signatureFile.toURL().toString(); 246:         } catch (MalformedURLException mue) { 247:             mue.printStackTrace(); 248:         } 249:         return BaseURI; 250:     }

getInputDocument hides all the boilerplate code associated with parsing a document using a JAXP DocumentBuilder. The only factor to notice is that the parser should be namespace enabled (line 256).

251:  252:     public static Document getInputDocument(String uri) { 253:         DocumentBuilderFactory dbf =  254:             DocumentBuilderFactory.newInstance(); 255:          256:         dbf.setNamespaceAware(true); 257:          258:         Document doc = null; 259:         try { 260:             DocumentBuilder db = dbf.newDocumentBuilder(); 261:             doc = db.parse(uri); 262:         } catch (ParserConfigurationException pce) { 263:             pce.printStackTrace(); 264:         } catch (SAXException se) { 265:             se.printStackTrace(); 266:         } catch (IOException ioe) { 267:             ioe.printStackTrace(); 268:         } 269:         return doc; 270:     }

The getPrivateKey method takes a KeyStore, an alias for a private key, and the password for that private key, and returns a PrivateKey:

271:  272:     public static PrivateKey getPrivateKey(KeyStore ks,  273:         String privateKeyAlias, String privateKeyPassword) { 274:            PrivateKey privateKey = null; 275:             try { 276:                 privateKey = (PrivateKey)  277:                     ks.getKey(privateKeyAlias,  278:                         privateKeyPassword.toCharArray()); 279:             } catch (KeyStoreException kse) { 280:                 kse.printStackTrace(); 281:             } catch (NoSuchAlgorithmException nsae) { 282:                 nsae.printStackTrace(); 283:             } catch (UnrecoverableKeyException uke) { 284:                 uke.printStackTrace(); 285:             } 286:         return privateKey; 287:     }

The getKeystore method takes the filename of a keystore and the password for accessing that keystore, and returns a KeyStore object that can be searched for keys and certificates:

288:  289:     public static KeyStore getKeyStore(String filename,  290:         String keystorePassword) { 291:         KeyStore ks = null; 292:         try { 293:             ks = KeyStore.getInstance("JKS"); 294:         } catch (KeyStoreException kse) { 295:             kse.printStackTrace(); 296:         } 297:          298:         FileInputStream fis = null; 299:         try { 300:             fis = new FileInputStream(filename); 301:         } catch (FileNotFoundException fnfe) { 302:             fnfe.printStackTrace(); 303:         } 304:          305:         try { 306:             ks.load(fis, keystorePassword.toCharArray()); 307:         } catch (NoSuchAlgorithmException nsae) { 308:             nsae.printStackTrace(); 309:         } catch (CertificateException ce) { 310:             ce.printStackTrace(); 311:         } catch (IOException ioe) { 312:             ioe.printStackTrace(); 313:         } 314:         return ks; 315:     }

The remainder of the class definition is the getters and setters for the fields in DocumentSigner:

316:  317:     public String getBaseURI() { 318:         return baseURI; 319:     } 320:  321:     public X509Certificate getCertificate() { 322:         return certificate; 323:     } 324:  325:     public String getDigestMethod() { 326:         return digestMethod; 327:     } 328:  329:     public PrivateKey getPrivateKey() { 330:         return privateKey; 331:     } 332:  333:     public String getSignatureMethod() { 334:         return signatureMethod; 335:     } 336:  337:     public String[] getTransformArray() { 338:         return transformArray; 339:     } 340:  341:     public Document getDocument() { 342:         return document; 343:     } 344:  345:     public void setBaseURI(String string) { 346:         baseURI = string; 347:     } 348:  349:     public void setCertificate(X509Certificate certificate) { 350:         this.certificate = certificate; 351:     } 352:  353:     public void setDigestMethod(String string) { 354:         digestMethod = string; 355:     } 356:  357:     public void setPrivateKey(PrivateKey key) { 358:         privateKey = key; 359:     } 360:  361:     public void setSignatureMethod(String string) { 362:         signatureMethod = string; 363:     } 364:  365:     public void setTransformArray(String[] strings) { 366:         transformArray = strings; 367:     } 368:  369:     public void setDocument(Document d) { 370:         document = d; 371:     } 372:  373:     public List getObjectList() { 374:         return objectList; 375:     } 376:  377:     public PublicKey getPublicKey() { 378:         return publicKey; 379:     } 380:  381:     public void setObjectList(List list) { 382:         objectList = list; 383:     } 384:  385:     public void setPublicKey(PublicKey key) { 386:         publicKey = key; 387:     } 388: }

Even after almost 400 lines, we haven’t covered everything you can do. DocumentSigner is useful for simple to medium signing applications and can serve as a source of code to help you build your own classes for signature handling.

Verification

Signature verification is much simpler than signing. You don’t need to provide a lot of additional data, and the API is pretty simple. Almost all of the information you need to verify the signature is in the document, so there’s no need to create an object to store the information:

  1: /*   2:  *    3:  * DocumentVerifier.java   4:  *    5:  * Example from "Profesional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import java.io.File;  11: import java.io.IOException;  12: import java.net.MalformedURLException;  13: import java.security.PublicKey;  14: import java.security.cert.X509Certificate;  15: import java.util.ArrayList;  16: import java.util.List;  17:   18: import javax.xml.parsers.DocumentBuilder;  19: import javax.xml.parsers.DocumentBuilderFactory;  20: import javax.xml.parsers.FactoryConfigurationError;  21: import javax.xml.parsers.ParserConfigurationException;  22: import javax.xml.transform.TransformerException;  23:   24: import org.apache.xml.security.exceptions.XMLSecurityException;  25: import org.apache.xml.security.keys.KeyInfo;  26: import   27: org.apache.xml.security.keys.keyresolver.KeyResolverException;  28: import org.apache.xml.security.signature.SignedInfo;  29: import org.apache.xml.security.signature.XMLSignature;  30: import org.apache.xml.security.signature.XMLSignatureException;  31: import org.apache.xml.security.signature.XMLSignatureInput;  32: import org.apache.xml.security.utils.Constants;  33: import org.apache.xml.security.utils.XMLUtils;  34: import org.apache.xpath.XPathAPI;  35: import org.w3c.dom.Document;  36: import org.w3c.dom.Element;  37: import org.xml.sax.SAXException;

You initialize the XML Security library:

 38:   39: public class DocumentVerifier {  40:     static {  41:         org.apache.xml.security.Init.init();  42:     }

The verifySignedDocument method does all the work. It takes a DOM tree containing a signed document and a base URI for resolving relative URI references. It returns an empty List if the signature can be verified and a List of References that could not be verified if the signature can’t be verified:

 43:       44:     public static List verifySignedDocument(Document doc,   45:         String baseURI) {

In lines 47-48, you set up some variables that are used to hold results:

 46:                  47:         List result = new ArrayList();   48:         boolean coarseResult = false; 

You set up an element that declares the ds prefix for the XML Signature namespace URI:

 49:   50:         Element namespaceContext =   51:             XMLUtils.createDSctx(doc,"ds",  52:                 Constants.SignatureSpecNS);

Here you take the namespace context you created earlier and pass it to the XPathAPI’s selectSingleNode method. You’re locating the <Signature> element within the tree:

 53:           54:         Element signatureElement = null;  55:         try {  56:             signatureElement =  57:                 (Element) XPathAPI.selectSingleNode(  58:                     doc,  59:                     "//ds:Signature[1]",  60:                     namespaceContext);

Once you’ve located the <Signature> element, you construct an XMLSignature using it and the base URI:

 61:         } catch (TransformerException te) {  62:             te.printStackTrace();  63:         }  64:   65:         XMLSignature signature = null;  66:         try {  67:             signature =  68:                 new XMLSignature(  69:                     signatureElement,  70:                     baseURI);

DocumentVerifier assumes that the <Signature> element contains a <KeyInfo> element that specifies either an X.509 certificate or a public key that can be used to verify the signature. If these conditions aren’t met, DocumentVerifier can’t verify the signature:

 71:         } catch (XMLSignatureException xse) {  72:             xse.printStackTrace();  73:         } catch (MalformedURLException mue) {  74:             mue.printStackTrace();  75:         } catch (XMLSecurityException xse) {  76:             xse.printStackTrace();  77:         } catch (IOException e2) {  78:             e2.printStackTrace();  79:         }  80:                      81:         KeyInfo ki = signature.getKeyInfo();

You test the KeyInfo object to see if it contains X.509Data. If it does, you extract the X.509 certificate:

 82:           83:         if (ki != null) {  84:             if (!ki.containsX509Data()) {  85:                 System.out.println("No X.509 data");  86:             }  87:               88:             X509Certificate cert = null;  89:             try {  90:                 cert = ki.getX509Certificate();  91:             } catch (KeyResolverException kre) {  92:                 kre.printStackTrace();  93:             }

There are two versions of the checkSignatureValue method on XMLSignature: one that works on Certificates and one that works on PublicKeys. DocumentVerifier prefers to use the X.509 certificate to verify the signature (lines 96-98). If there is no certificate present, then it tries to use a public key (lines 99-112). To do this, it must extract a PublicKey from the KeyInfo instance (lines 100-106) and then use it to check the signature (lines 108-111). The return value from checkSignature is stored as coarseResult. This tells you at a coarse granularity whether the signature verified. If it didn’t, then you’d like to obtain a finer-grained explanation of what went wrong:

 94:               95:             try {  96:                 if (cert != null) {  97:                     coarseResult =   98:                         signature.checkSignatureValue(cert);  99:                 } else { 100:                     PublicKey pk = null; 101:                     try { 102:                         pk =  103:                             signature.getKeyInfo().getPublicKey(); 104:                     } catch (KeyResolverException kre) { 105:                         kre.printStackTrace(); 106:                     } 107:                      108:                     if (pk != null) { 109:                         coarseResult =  110:                             signature.checkSignatureValue(pk); 111:                     } 112:                 }

If there was no <KeyInfo>, exit early:

113:             } catch (XMLSignatureException xse) { 114:                 xse.printStackTrace(); 115:             } 116:         } else { 117:             System.out.println("No key included"); 118:             return result;

If the signature didn’t verify, you get the SignedInfo from the Signature and pass it, along with result, to getFailedReferences, which computes the fine-grained error result:

119:         } 120:          121:         if (!coarseResult) { 122:             SignedInfo si = signature.getSignedInfo(); 123:             try { 124:                 getFailedReferences(result, si);

At the end you return the result List:

125:             } catch (XMLSecurityException xse) { 126:                 xse.printStackTrace(); 127:             } 128:         } 129:          130:         return result;

The getLength method on SignedInfo returns the number of References inside that SignedInfo object (line 136). You can iterate over the References, and for each one you can ask for the verification result (line 138). If that Reference failed to verify, you add the untransformed version of it to your results List (lines 139-140):

131:     } 132:  133:     public static void getFailedReferences(List result,  134:         SignedInfo si) throws XMLSecurityException { 135:              136:         int length = si.getLength(); 137:         for (int i = 0; i < length; i++) { 138:           if (!si.getVerificationResult(i)) 139:             result.add( 140:              si.getReferencedContentBeforeTransformsItem(i)); 141:         } 142:     }

main passes a single argument (the URI of the document to be verified) to the verify method, which does some setup and then calls verifySignedDocument:

143:  144:     public static void main(String[] args) { 145:         verify(args[0]); 146:     }

Fortunately, the amount of setup that verify has to do is pretty small—a File to read the signed file from, a base URI computed from the file, and a DOM tree obtained by parsing the file:

147:      148:     public static boolean verify(String uri) { 149:         File signatureFile = new File(uri); 150:          151:         String baseURI = getBaseURI(signatureFile); 152:          153:         Document doc = getInputDocument(signatureFile);

Here’s the call to verifySignedDocument:

154:          155:         List result = verifySignedDocument(doc, baseURI);

This loop prints out any References that didn’t verify:

156:  157:         if (result.size() > 0) { 158:             for (int i = 0; i < result.size(); i++) { 159:                 XMLSignatureInput xsi =  160:                     (XMLSignatureInput) result.get(i); 161:                 System.out.println("Reference URI " + 162:                     xsi.getSourceURI()+" did not verify."); 163:             }

Otherwise you print a celebratory message

164:         } else { 165:             System.out.println("Signature verified");

And return a boolean:

166:         } 167:         return result.size() == 0; 168:    4 }

getBaseURI computes the URI from a File object:

169:      170:     public static String getBaseURI(File signatureFile) { 171:         String baseURI = null; 172:         try { 173:             baseURI = signatureFile.toURL().toString(); 174:         } catch (MalformedURLException e) { 175:             e.printStackTrace(); 176:         } 177:         return baseURI; 178:     }

getInputDocument hides all the DocumentBuilder boilerplate. Again, the DocumentBuilder must be namespace aware:

179:  180:     public static Document getInputDocument(File signatureFile) 181:         throws FactoryConfigurationError { 182:         Document doc; 183:            DocumentBuilderFactory dbf =  184:                 DocumentBuilderFactory.newInstance(); 185:              186:             dbf.setNamespaceAware(true); 187:          188:             doc = null; 189:             try { 190:                 DocumentBuilder db = dbf.newDocumentBuilder(); 191:                  192:                 doc = db.parse(signatureFile);    193:             } catch (ParserConfigurationException pce) { 194:                 pce.printStackTrace(); 195:             } catch (SAXException se) { 196:                 se.printStackTrace(); 197:             } catch (IOException ioe) { 198:                 ioe.printStackTrace(); 199:             } 200:          201:         return doc; 202:     } 203: }

More Signatures

Here are two examples of how you can extend the DocumentSigner class to do signature-related tasks. The first class shows how to have the data object and the signature in separate files. The second example shows how to use the XPath transform to select the data you want to sign.

Signatures in a Separate File

DetachedDocumentSigner is a subclass of DocumentSigner that allows you to place the data to be signed and the signature in separate files. The key to making this work is to reference a data object that’s distinct from the document where the signature goes. In this case, you create a detached signature of the schema for the book document. No transformations are applied, and the digest method is the default (SHA1):

  1: /*   2:  *    3:  * DetachedDocumentSigner.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import org.apache.xml.security.signature.XMLSignature;  11: import org.apache.xml.security.signature.XMLSignatureException;  12: import org.w3c.dom.Document;  13:   14: public class DetachedDocumentSigner extends DocumentSigner {  15:   16:     protected void addReferences(XMLSignature sig,   17:         Document document) throws XMLSignatureException {  18:         sig.addDocument(  19:   "http://www.sauria.com/schemas/apache-xml-book/book.xsd");

By specifying a separate file, blank.xml, as the destination for the signature, you make DocumentSigner place the signature into this file, while the data being signed remains untouched. The file blank.xml contains a single empty <blank/> element, which the signature is attached to:

 20:     }  21:   22:     public static void main(String[] args) {  23:         DocumentSigner signer = new DetachedDocumentSigner();  24:         testSigning(signer, "blank.xml",   25:             "detached-signature.xml");  26:     }  27: }

XPath Transforms

XPathTransformSigner shows how to use the XPath transform to specify which portions of an XML document should be signed:

  1: /*   2:  *    3:  * XPathTransformSigner.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import org.apache.xml.security.exceptions.XMLSecurityException;  11: import org.apache.xml.security.signature.XMLSignature;  12: import org.apache.xml.security.signature.XMLSignatureException;  13: import  14: org.apache.xml.security.transforms.TransformationException;  15: import org.apache.xml.security.transforms.Transforms;  16: import org.apache.xml.security.transforms.params.XPathContainer;  17: import org.w3c.dom.Document;

The XPath transform works by using an XPathContainer object. Like all the XML signature related objects, this one depends on the document where the <Signature> element is created (line 22). The XPathContainer contains the XPath expression (line 29), and it also provides a namespace context to provide namespace URI to prefix mappings that might be needed by the XPath expression. In this case, you only need a single mapping, from http://sauria.com/schemas/apache-xml-book/book to books (lines 24-25). You can call setXPathNamespaceContext as many times as you need to. The XPath expression selects the <author> and <isbn> nodes to be signed:

 18:   19: public class XPathTransformSigner extends DocumentSigner {  20:     protected void addReferences(XMLSignature sig,   21:         Document document)  22:         throws XMLSignatureException {  23:         XPathContainer xpc = new XPathContainer(document);  24:         try {  25:             xpc.setXPathNamespaceContext("books",  26:                 "http://sauria.com/schemas/apache-xml-book/book");  27:         } catch (XMLSecurityException xse) {  28:             xse.printStackTrace();  29:         }  30:         xpc.setXPath("//books:author|//books:isbn");

You’re already familiar with the enveloped signature transform (lines 33-34) and the inclusive canonicalization with comments transform (lines 37-38). The new transform here is the XPath transform. The XML Security API makes this transform easy. After you’ve specified the correct constant for the transform, you pass in the XPathContainer’s representation as an element, which is obtained by calling getElementPlusReturns (lines 35-36):

 31:                     32:         Transforms ts = new Transforms(document);  33:         try {  34:             ts.addTransform(  35:                 Transforms.TRANSFORM_ENVELOPED_SIGNATURE);  36:             ts.addTransform(Transforms.TRANSFORM_XPATH,   37:                 xpc.getElementPlusReturns());  38:             ts.addTransform(  39:                 Transforms.TRANSFORM_C14N_WITH_COMMENTS);  40:         } catch (TransformationException te) {  41:             te.printStackTrace();  42:         }

You want your transforms to operate on the entire document so that the XPath expressions has the right context from which to select nodes:

 43:           44:         sig.addDocument("#xpointer(/)",ts);

As before, you instantiate an XPathTransformSigner (line 47) and then call testSigning with the right filename parameters (line 48):

 45:     }  46:   47:     public static void main(String[] args) {  48:         DocumentSigner signer = new XPathTransformSigner();  49:         testSigning(signer, "book.xml", "xpath-signature.xml");  50:     }  51: } 

Resolvers

The XML Security library allows you to plug in resolvers that handle requests for certain kinds of information. There are three kinds of resolvers:

  • ResourceResolvers are used by a Reference object when the Reference tries to retrieve the resource to be signed.

  • StorageResolvers are used by a KeyInfo object to retrieve certificates that are specified by the KeyInfo or its children.

  • KeyResolvers are used by KeyInfo when processing its children. KeyResolvers use StorageResolvers to retrieve certificates.

ResourceResolvers

You’re most likely to use a custom ResourceResolver in your application. To create your own ResourceResolver, extend the class org.apache.xml.security.utils.resolver.ResourceResolverSPI and provide implementations for the engineCanResolve and engineResolve methods. Once you’ve implemented this class, then you can call the addResourceResolver method on XMLSignature, Manifest, or SignedInfo to attach the ResourceResolver in the correct place.

Here’s a simple ResourceResolver that maps references to book.xml with no base URI to a local file:

  1: /*   2:  *    3:  * BookResourceResolver.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import java.io.FileInputStream;  11: import java.io.FileNotFoundException;  12: import java.io.IOException;  13:   14: import org.apache.xml.security.signature.XMLSignatureInput;  15: import   16: org.apache.xml.security.utils.resolver.ResourceResolverException;  17: import   18: org.apache.xml.security.utils.resolver.ResourceResolverSpi;  19: import org.w3c.dom.Attr;  20:   21: public class BookResourceResolver extends ResourceResolverSpi {  22:   23:     public XMLSignatureInput engineResolve(Attr uri,   24:         String BaseURI)  25:         throws ResourceResolverException {  26:         if (!uri.equals("book.xml") || !BaseURI.equals(""))  27:             throw   28:                 new ResourceResolverException("can't handle",   29:                     uri, BaseURI);  30:         try {  31:             FileInputStream fis =   32:                 new FileInputStream("book.xml");  33:             return new XMLSignatureInput(fis);  34:         } catch (FileNotFoundException fnfe) {  35:             fnfe.printStackTrace();  36:         } catch (IOException ioe) {  37:             ioe.printStackTrace();  38:         }  39:         return null;  40:     }  41:   42:     public boolean engineCanResolve(Attr uri, String BaseURI) {  43:         if (BaseURI.equals("") && uri.equals("book.xml"))  44:             return true;  45:         return false;  46:     }  47: }

StorageResolvers

Implementing a StorageResolver is relatively simple. You need to extend org.apache.xml.security .key.storage.StorageResolverSpi and provide an implementation for the getIterator method. You may end up creating your own Iterator implementation. Once you’ve created the StorageResolver, you can call addStorageResolver on a KeyInfo object to register the resolver with the KeyInfo.

XML Security provides a few prepackaged StorageResolvers you can use in your own code. They’re in the org.apache.xml.security.keys.storage.implementations package:

  • KeyStoreResolver—retrieves certificates from an open Java KeyStore.

  • SingleCertificateResolver— returns a certificate that was passed into its constructor.

  • CertsInFilesystemDirectoryResolver—retrieves certificate files (that end with .crt) that are stored in a directory in the filesystem.

KeyResolvers

KeyResolvers are the type of resolver you’re least likely to use. To create a KeyResolver, you need to create a new class that extends org.apache.xml.security.keys.keyresolver.KeyResolverSpi and provide implementations for the engineResolvePublicKey, engineResolveSecretKey, and engineResolveX509Certificate methods. After you do this, you need to modify the config.xml file in org.apache.xml.security.resource and add another entry to the <KeyResolver> element.

Encryption

XML Security’s support for XML Encryption is still at the alpha stage and hasn’t been included in a build (at least at the time of this writing—by the time you read this, there will probably be a build containing the encryption support). To work with the XML Encryption support, you need to obtain a copy of XML Security from the Apache CVS repository.

Installation

The simplest way to get a copy of the CVS for XML Security is to use a CVS snapshot. Follow these steps:

  1. Go to http://cvs.apache.org/snapshots/xml-security and pick up a CVS snapshot.

  2. Unpack the .tar.gz file.

You can also use CVS to get a copy of XML Security directly from the Apache public CVS repository. Follow these steps:

  1. Enter this CVS command:

    cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login 

  2. Type "anoncvs" for the password.

  3. Enter this command:

    cvs –d :pserver:anoncvs@cvs.apache.org:/home/cvspublic    checkout xml-security

You now have a copy of the XML Security CVS. To build a copy of the most recent XML Security jars, follow these steps:

  1. Change to the xml-security directory.

  2. Be sure you’re using at least JDK 1.3.

  3. Be sure you have a copy of Ant installed.

  4. Type "ant jar" to create the XML Security jars.

  5. The jar files will be in the build subdirectory.

An Example

This example is designed to give you a flavor of what it will be like to work with the XML Security encryption API:

  1: /*   2:  *    3:  * EncryptionMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch10;   9:   10: import java.io.ByteArrayOutputStream;  11: import java.io.File;  12: import java.io.IOException;  13: import java.security.InvalidKeyException;  14: import java.security.Key;  15: import java.security.NoSuchAlgorithmException;  16: import java.security.spec.InvalidKeySpecException;  17:   18: import javax.crypto.KeyGenerator;  19: import javax.crypto.SecretKey;  20: import javax.crypto.SecretKeyFactory;  21: import javax.crypto.spec.DESedeKeySpec;  22: import javax.crypto.spec.SecretKeySpec;  23: import javax.xml.parsers.DocumentBuilder;  24: import javax.xml.parsers.DocumentBuilderFactory;  25:   26: import org.apache.xml.security.encryption.XMLCipher;  27: import org.apache.xml.security.encryption.XMLEncryptionException;  28: import org.apache.xml.serialize.DOMSerializer;  29: import org.apache.xml.serialize.Method;  30: import org.apache.xml.serialize.OutputFormat;  31: import org.apache.xml.serialize.XMLSerializer;  32: import org.w3c.dom.Document;  33: import org.w3c.dom.Element;

Here’s your static initialization for the XML Security library:

 34:   35: public class EncryptionMain {  36:   37:     static {  38:         org.apache.xml.security.Init.init();  39:     }

In order to encrypt a document, you need a Key to use for encryption, the name of the algorithm to use, a DOM Document that serves as a context, and a DOM Element in that document that will be encrypted:

 40:       41:     public static Document encrypt(Key key, String cipherName,   42:         Document contextDocument, Element elementToEncrypt) {

The XMLCipher class is designed to work in a way familiar to someone who has used the javax.crypto .Cipher class. First you have to get an instance of a particular Cipher (algorithm) (line 45), and then you need to initialize the Cipher in encryption mode (line 46). You have to supply the encryption key to do this:

 43:         Document result = null;  44:         try {  45:             XMLCipher cipher = XMLCipher.getInstance(cipherName);  46:             cipher.init(XMLCipher.ENCRYPT_MODE, key);

The XMLCipher class defines some String constants with the names of the supported encryption algorithms. The list of supported algorithms includes AES (128-, 192-, and 256-bit variants), SHA1 and SHA (256- and 512-bit variants of SHA1), and Triple DES.

Encrypting the element is simple. You call XMLCipher’s doFinal method and pass it the context document and the element (from within that document) to encrypt. This operation is destructive—contextDocument is updated in place:

 47:             result =   48:             cipher.doFinal(contextDocument,elementToEncrypt);  49:         } catch (XMLEncryptionException xee) {  50:             xee.printStackTrace();  51:         }  52:               53:         return result;  54:     }

As you might expect, decrypting is very similar to encrypting. You need a key for decrypting, the name of the encryption algorithm, and a DOM document that contains an <EncryptedData> element:

 55:   56:     public static Document decrypt(Key key, String cipherName,  57:         Document documentToDecrypt) {  58:         Document result = null;  59:         Element encryptedDataElement;  60:         XMLCipher cipher; 

You set up your XMLCipher instance with the correct algorithm (line 63), and this time you initialize it in decryption mode (line 64):

 61:   62:         try {  63:             cipher = XMLCipher.getInstance(cipherName);  64:             cipher.init(XMLCipher.DECRYPT_MODE, key);

Next, you need to find an <EncryptedData> element (from the XML Encryption namespace) somewhere in the document:

 65:               66:             encryptedDataElement = (Element)   67:                 documentToDecrypt.getElementsByTagNameNS(  68:                     "http://www.w3.org/2001/04/xmlenc#",  69:                     "EncryptedData").item(0);

After you find the document, you call doFinal with the document and the element containing the <EncryptedData>. Again, this is a destructive operation:

 70:   71:             result = cipher.doFinal(documentToDecrypt,   72:                 encryptedDataElement);  73:         } catch (XMLEncryptionException xee) {  74:             xee.printStackTrace();  75:         }  76:           77:         return result;  78:     }

test is a simple method that calls some helper functions to assemble all the information needed by the encrypt and decrypt functions:

 79:       80:     public static boolean test() {

The test method operates on the familiar book.xml file, and you need to use the namespace URI of the book schema:

 81:         String xmlFile = "book.xml";  82:         String bookNS =   83:             "http://sauria.com/schemas/apache-xml-book/book";

You set up for encryption by parsing book.xml into a DOM tree (line 85) and then using the namespace-aware getElementsByTagNameNS method to locate the <author> element, which is the element you want to encrypt (lines 86-88):

 84:           85:         Document contextDoc = loadDocument(xmlFile);  86:         Element elementToEncrypt = (Element)   87:             contextDoc.getElementsByTagNameNS(bookNS,  88:                 "author").item(0);

You encrypt and decrypt the document using two different algorithms. The first is Triple DES. The get3DESKey method (line 90) obtains a Triple DES secret key, given a passphrase. You set the string cipher to the constant for the Triple DES algorithm URI (line 91):

 89:   90:         Key key = get3DESKey("3DES or DES-EDE secret key");  91:         String cipher = XMLCipher.TRIPLEDES;

Next you encrypt (lines 92-93) and decrypt (lines 94-95) the document:

 92:         Document encryptedDoc = encrypt(key, cipher,   93:             contextDoc, elementToEncrypt);  94:         Document decryptedDoc =  95:              decrypt(key, cipher, encryptedDoc);

To check your work, you reparse the XML file to get a fresh copy of the unencrypted document (line 97), and then you compare them by converting both DOM trees to Strings and testing the String values for equality (line 98-99):

 96:   97:         contextDoc = loadDocument(xmlFile);  98:         boolean resultDES =   99:             toString(contextDoc).equals(toString(decryptedDoc)); 100:         System.out.println(resultDES);

The code for AES encryption is the same, except that you need to call a different helper method to get the AESKey (line 102), and you need to provide the right value for 256-bit AES (line 103):

101:  102:         key = getAESKey(256); 103:         cipher = XMLCipher.AES_256; 104:         elementToEncrypt = (Element)  105:             contextDoc.getElementsByTagNameNS(bookNS, 106:                 "author").item(0); 107:         encryptedDoc =  108:             encrypt(key, cipher, contextDoc, elementToEncrypt); 109:         decryptedDoc = decrypt(key, cipher, encryptedDoc); 110:  111:         contextDoc = loadDocument(xmlFile); 112:         boolean resultAES = 113:             toString(contextDoc).equals(toString(decryptedDoc)); 114:         System.out.println(resultAES); 115:  116:         return resultDES && resultAES; 117:     }

The main function just calls test:

118:  119:     public static void main(String[] args) { 120:         test(); 121:     }    

The get3DESKey method takes a passphrase and uses it to create a new Triple DES key:

122:      123:     public static SecretKey get3DESKey(String phrase) {

You need to convert the passphrase to a byte array:

124:         byte[] passPhrase = phrase.getBytes();

You then obtain a DESedeKeySpec using the byte array (lines 127-128) and pass that to a SecretKeyFactory for Triple DES (under the name DESede) (lines 131). You call the SecretKeyFactory’s getInstance factory method with the name of the algorithm (lines 129-130):

125:         SecretKey key = null; 126:         try { 127:             DESedeKeySpec keySpec =  128:                 new DESedeKeySpec(passPhrase); 129:             SecretKeyFactory keyFactory =  130:                 SecretKeyFactory.getInstance("DESede"); 131:             key = keyFactory.generateSecret(keySpec); 132:         } catch (InvalidKeyException ike) { 133:             ike.printStackTrace(); 134:         } catch (NoSuchAlgorithmException nsae) { 135:             nsae.printStackTrace(); 136:         } catch (InvalidKeySpecException ikse) { 137:             ikse.printStackTrace(); 138:         } 139:         return key; 140:     }

The getAESKey method shows how to generate an AES secret key. It needs to know how many bits the AES key should use. Sanity-checking the value of bits has been omitted for the sake of brevity:

141:      142:     public static Key getAESKey(int bits) {

You start by obtaining a KeyGenerator for AES:

143:         KeyGenerator kgen = null; 144:         try { 145:             kgen = KeyGenerator.getInstance("AES"); 146:         } catch (NoSuchAlgorithmException nsae) { 147:             nsae.printStackTrace(); 148:         }

Next you initialize the KeyGenerator with the number of bits in the key (line 149) and then generate the key (line 151):

149:         kgen.init(bits); 150:          151:         SecretKey skey = kgen.generateKey();

Because you want to return a Key object, you need to convert the SecretKey into a SecretKeySpec (line 154). To do this, you need to obtain the secret key as a byte array (line 152) and pass it to the constructor for SecretKeySpec along with the algorithm name:

152:         byte[] raw = skey.getEncoded(); 153:          154:         SecretKeySpec skeySpec = new SecretKeySpec(raw,"AES"); 155:          156:         return skeySpec; 157:     } 158:         

The loadDocument method is a convenience method that hides the boilerplate code needed to create a namespace-aware DocumentBuilder and build a DOM tree:

159:  160:     public static Document loadDocument(String uri) { 161:         Document d = null; 162:         try { 163:             DocumentBuilderFactory dbf =  164:                 DocumentBuilderFactory.newInstance(); 165:             dbf.setNamespaceAware(true); 166:             DocumentBuilder db = dbf.newDocumentBuilder(); 167:             File f = new File(uri); 168:             d = db.parse(f); 169:         } catch (Exception e) { 170:             e.printStackTrace(); 171:             System.exit(-1); 172:         } 173:  174:         return (d); 175:     }

The toString method is a convenience method that uses the Xerces serializer classes to serialize a DOM tree as a String:

176:      177:     private static String toString(Document document) { 178:         OutputFormat of = new OutputFormat(); 179:         of.setIndenting(true); 180:         of.setMethod(Method.XML); 181:         ByteArrayOutputStream baos = new ByteArrayOutputStream(); 182:         DOMSerializer serializer = new XMLSerializer(baos, of); 183:         try { 184:             serializer.serialize(document); 185:         } catch (IOException ioe) { 186:             ioe.printStackTrace(); 187:         } 188:         return (baos.toString()); 189:     } 190: }




Professional XML Development with Apache Tools. Xerces, Xalan, FOP, Cocoon, Axis, Xindice
Professional XML Development with Apache Tools: Xerces, Xalan, FOP, Cocoon, Axis, Xindice (Wrox Professional Guides)
ISBN: 0764543555
EAN: 2147483647
Year: 2003
Pages: 95

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net