Source code: hk/hku/cecid/phoenix/pki/ApacheXMLDSigner.java
1 /*
2 * Academic Free License
3 * Version 1.0
4 *
5 * This Academic Free License applies to any software and associated
6 * documentation (the "Software") whose owner (the "Licensor") has placed the
7 * statement "Licensed under the Academic Free License Version 1.0" immediately
8 * after the copyright notice that applies to the Software.
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of the Software (1) to use, copy, modify, merge, publish, perform,
12 * distribute, sublicense, and/or sell copies of the Software, and to permit
13 * persons to whom the Software is furnished to do so, and (2) under patent
14 * claims owned or controlled by the Licensor that are embodied in the Software
15 * as furnished by the Licensor, to make, use, sell and offer for sale the
16 * Software and derivative works thereof, subject to the following conditions:
17 *
18 * - Redistributions of the Software in source code form must retain all
19 * copyright notices in the Software as furnished by the Licensor, this list
20 * of conditions, and the following disclaimers.
21 * - Redistributions of the Software in executable form must reproduce all
22 * copyright notices in the Software as furnished by the Licensor, this list
23 * of conditions, and the following disclaimers in the documentation and/or
24 * other materials provided with the distribution.
25 * - Neither the names of Licensor, nor the names of any contributors to the
26 * Software, nor any of their trademarks or service marks, may be used to
27 * endorse or promote products derived from this Software without express
28 * prior written permission of the Licensor.
29 *
30 * DISCLAIMERS: LICENSOR WARRANTS THAT THE COPYRIGHT IN AND TO THE SOFTWARE IS
31 * OWNED BY THE LICENSOR OR THAT THE SOFTWARE IS DISTRIBUTED BY LICENSOR UNDER
32 * A VALID CURRENT LICENSE. EXCEPT AS EXPRESSLY STATED IN THE IMMEDIATELY
33 * PRECEDING SENTENCE, THE SOFTWARE IS PROVIDED BY THE LICENSOR, CONTRIBUTORS
34 * AND COPYRIGHT OWNERS "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
37 * LICENSOR, CONTRIBUTORS OR COPYRIGHT OWNERS BE LIABLE FOR ANY CLAIM, DAMAGES
38 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
39 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE.
40 *
41 * This license is Copyright (C) 2002 Lawrence E. Rosen. All rights reserved.
42 * Permission is hereby granted to copy and distribute this license without
43 * modification. This license may not be modified without the express written
44 * permission of its copyright owner.
45 */
46
47 /* =====
48 *
49 * $Header: /ebxml/staff/cecid/cvs_repository/pki/src/hk/hku/cecid/phoenix/pki/ApacheXMLDSigner.java,v 1.8 2002/12/13 03:59:13 kcyee Exp $
50 *
51 * Code authored by:
52 *
53 * kcyee [2002-05-16]
54 *
55 * Code reviewed by:
56 *
57 * username [YYYY-MM-DD]
58 *
59 * Remarks:
60 *
61 * =====
62 */
63
64 package hk.hku.cecid.phoenix.pki;
65
66 import java.io.InputStream;
67 import java.io.IOException;
68 import java.security.*;
69 import java.security.cert.*;
70 import java.util.ArrayList;
71 import org.apache.xml.security.exceptions.XMLSecurityException;
72 import org.apache.xml.security.keys.KeyInfo;
73 import org.apache.xml.security.keys.keyresolver.KeyResolverException;
74 import org.apache.xml.security.signature.XMLSignature;
75 import org.apache.xml.security.signature.XMLSignatureException;
76 import org.apache.xml.security.transforms.Transforms;
77 import org.apache.xml.security.transforms.TransformationException;
78 import org.w3c.dom.Document;
79 import org.w3c.dom.Element;
80 import org.w3c.dom.Node;
81 import org.w3c.dom.NodeList;
82 import hk.hku.cecid.phoenix.common.util.Logger;
83 import hk.hku.cecid.phoenix.common.util.Version;
84
85 /**
86 * This class hides the details for digital signature. The digital signature
87 * routines are provided by the Apache XML Security library. We defined a
88 * standard way to have the document signed as interface. Different classes
89 * will implement the interface using different library behind.
90 *
91 * @author kcyee
92 * @version $Revision: 1.8 $
93 */
94 public class ApacheXMLDSigner implements XMLDSigner {
95
96 /**
97 * Logger
98 */
99 protected static Logger logger = Logger.getLogger(
100 ApacheXMLDSigner.class.getName());
101
102 /**
103 * The prefix of XML digital signature element.
104 */
105 protected static final String DSIG_URI =
106 "http://www.w3.org/2000/09/xmldsig#";
107
108 static {
109 org.apache.xml.security.Init.init();
110 }
111
112 /**
113 * Internal variable for holding the envelope of the signature.
114 */
115 protected Document envelope;
116
117 /**
118 * Internal variable for holding the documents needed to be referred
119 * in the signature.
120 */
121 protected ArrayList documents;
122
123 /**
124 * Internal variable for holding the trusted anchor for certificate
125 * path verification.
126 */
127 protected CompositeKeyStore trusted;
128
129 /**
130 * Internal variable of the Apache XML Security library signature object
131 * for doing the actual signing/verifying algorithm.
132 */
133 protected XMLSignature signature;
134
135 /**
136 * Default constructor to initialize the internal variables.
137 */
138 public ApacheXMLDSigner() {
139 documents = new ArrayList();
140 envelope = null;
141 signature = null;
142 trusted = null;
143 }
144
145 /**
146 * Set the envelope to host the Signature element. That is the
147 * XML document where the Signature element to be added. The
148 * digital signature here will always be an enveloped signature.
149 * The envelope will be included in the process of signing.
150 *
151 * @param doc the XML document to host the Signature element
152 * @param algo the algorithm used for digital signature. Currently, only
153 * two values are tested: <code>dsa-sha1</code> and
154 * <code>rsa-sha1</code>.
155 * @throws SignException internal exception when doing initialization
156 * on Apache XML Security library
157 */
158 public void setEnvelope(Document doc, String algo) throws SignException {
159 envelope = doc;
160 try {
161 signature = new XMLSignature(envelope, DSIG_URI, DSIG_URI + algo);
162 }
163 catch (XMLSecurityException e) {
164 throw new SignException("Cannot create XMLSignature object:\n"
165 + e.getMessage());
166 }
167 logger.debug("setEnvelope, using algorithm: " + algo);
168 }
169
170 /**
171 * Set the envelope to host the Signature element. That is the
172 * XML document where the Signature element to be added. The
173 * digital signature here will always be an enveloped signature.
174 * The envelope will be included in the process of signing.
175 *
176 * @param doc the XML document to host the Signature element
177 * @throws SignException internal exception when doing initialization
178 * on Apache XML Security library
179 */
180 public void setEnvelope(Document doc) throws SignException {
181 setEnvelope(doc, "dsa-sha1");
182 }
183
184 /**
185 * Adds a reference to a document attachment to the signature.
186 *
187 * @param uri the URI of the document attachment
188 * @param is the input stream of the content of the document
189 * @param contentType the content type of the document
190 */
191 public void addDocument(String uri, InputStream is, String contentType) {
192 DocumentDetail dd = new DocumentDetail();
193 dd.uri = uri;
194 dd.stream = is;
195 dd.contentType = contentType;
196 documents.add(dd);
197 logger.debug(
198 "addDocument URI: " + uri + ", contentType: " + contentType);
199 }
200
201 /**
202 * Signs the envelope and documents by using the specified key
203 * in the keystore.
204 *
205 * @param ks the keystore holding the key for signing
206 * @param alias the alias of the key for signing
207 * @param password the password for accessing the key for signing
208 * @throws SignException when there is any error in the processing of
209 * signing
210 */
211 public void sign(CompositeKeyStore ks, String alias, char[] password)
212 throws SignException {
213 logger.debug("start signing...");
214
215 if (envelope == null) {
216 throw new SignException("Envelope element not set!");
217 }
218
219 DocumentDetail[] doc_array = new DocumentDetail[documents.size()];
220 for (int i=0 ; i<doc_array.length ; i++) {
221 doc_array[i] = (DocumentDetail) documents.get(i);
222 }
223 DocumentResolver resolver = new DocumentResolver(doc_array);
224 signature.addResourceResolver(resolver);
225 logger.debug("created DocumentResolver");
226
227 Transforms transforms = new Transforms(envelope);
228 try {
229 transforms.addTransform(DSIG_URI + "enveloped-signature");
230 Element xpath = envelope.createElementNS(DSIG_URI, "XPath");
231 xpath.setAttribute(
232 "xmlns:soap-env", "http://schemas.xmlsoap.org/soap/envelope/");
233 xpath.appendChild(envelope.createTextNode(
234 "not(ancestor-or-self::node()[@soap-env:actor=\""
235 + "urn:oasis:names:tc:ebxml-msg:actor:nextMSH\"] | "
236 + "ancestor-or-self::node()[@soap-env:actor=\""
237 + "http://schemas.xmlsoap.org/soap/actor/next\"])"));
238 xpath.setPrefix("ds");
239 transforms.addTransform(
240 "http://www.w3.org/TR/1999/REC-xpath-19991116", xpath);
241 // add canonicalization transform
242 transforms.addTransform(
243 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
244 }
245 catch (TransformationException e) {
246 throw new SignException("Cannot add tranform:\n" + e.getMessage());
247 }
248 logger.debug("created Transform");
249
250 try {
251 signature.addDocument("", transforms, DSIG_URI + "sha1");
252 }
253 catch (XMLSignatureException e) {
254 throw new SignException("Cannot add envelope document:\n"
255 + e.getMessage());
256 }
257 logger.debug("added main document (envelope)");
258
259 for (int i=0 ; i<documents.size() ; i++) {
260 DocumentDetail dd = (DocumentDetail) documents.get(i);
261 try {
262 signature.addDocument(dd.uri);
263 }
264 catch (XMLSignatureException e) {
265 throw new SignException("Cannot add document " + dd.uri + ":\n"
266 + e.getMessage());
267 }
268 }
269 logger.debug("added " + documents.size() + " attachment documents");
270
271 java.security.cert.Certificate[] certificates;
272 try {
273 certificates = ks.getCertificateChain(alias);
274 if (certificates == null) {
275 throw new SignException("Cannot get certificates path - "
276 + alias);
277 }
278 }
279 catch (KeyStoreException e) {
280 throw new SignException("Cannot get certificates path - " + alias
281 + ":\n" + e.getMessage());
282 }
283 logger.debug("got the certificate chain from keystore");
284
285 for (int i=0 ; i<certificates.length ; i++) {
286 try {
287 signature.addKeyInfo((X509Certificate) certificates[i]);
288 }
289 catch (XMLSecurityException e) {
290 throw new SignException("Cannot add key info:\n"
291 + e.getMessage());
292 }
293 }
294 logger.debug("added the certificate chain to signature");
295
296 PrivateKey pk;
297 try {
298 pk = (PrivateKey) ks.getKey(alias, password);
299 }
300 catch (Exception e) {
301 throw new SignException("Cannot get private key - " + alias + ":\n"
302 + e.getMessage());
303 }
304 logger.debug("got private key from keystore");
305
306 try {
307 signature.sign(pk);
308 }
309 catch (XMLSignatureException e) {
310 e.printStackTrace();
311 throw new SignException("Cannot sign:\n"
312 + e.getMessage());
313 }
314 logger.debug("signed");
315 }
316
317 /**
318 * Sets the trust anchor for verfication of certificate path.
319 *
320 * @param ks the keystore providing the trusted certificates
321 */
322 public void setTrustAnchor(CompositeKeyStore ks) {
323 trusted = ks;
324 }
325
326 /**
327 * Verifies the signature in the envelope passed in, which may reference
328 * the documents specified using the addDocument method.
329 *
330 * @return true if the signature can be verified successfully, false
331 * if otherwise.
332 * @throws VerifyException when there is any error in the processing of
333 * verification
334 */
335 public boolean verify() throws VerifyException {
336 logger.debug("start verifying...");
337
338 if (envelope == null) {
339 throw new VerifyException("Envelope element not set!");
340 }
341
342 NodeList nodeList = envelope.getElementsByTagNameNS(
343 DSIG_URI, "Signature");
344
345 if (nodeList.getLength() == 0) {
346 throw new VerifyException("No <ds:Signature> found!");
347 }
348 Element signatureElement = (Element) nodeList.item(0);
349 String baseUri = DSIG_URI;
350 logger.debug("got the signature element");
351
352 try {
353 signature = new XMLSignature(signatureElement, baseUri);
354 }
355 catch (XMLSecurityException e) {
356 throw new VerifyException("Cannot create XMLSignature object:\n"
357 + e.getMessage());
358 }
359 catch (IOException e) {
360 throw new VerifyException("Cannot create XMLSignature object:\n"
361 + e.getMessage());
362 }
363 logger.debug("created signature object");
364
365 DocumentDetail[] doc_array = new DocumentDetail[documents.size()];
366 for (int i=0 ; i<doc_array.length ; i++) {
367 doc_array[i] = (DocumentDetail) documents.get(i);
368 }
369 DocumentResolver resolver = new DocumentResolver(doc_array);
370 signature.addResourceResolver(resolver);
371 logger.debug("created document resolver");
372
373 PublicKey publicKey = null;
374 KeyInfo keyInfo = signature.getKeyInfo();
375 java.security.cert.Certificate[] certs = null;
376 if (keyInfo != null) {
377 try {
378 int certPathLen = keyInfo.lengthX509Data();
379 if (certPathLen > 0) {
380 certs = new java.security.cert.Certificate[certPathLen];
381 for (int i=0 ; i<certPathLen ; i++) {
382 try {
383 certs[i] = keyInfo.itemX509Data(i)
384 .itemCertificate(0)
385 .getX509Certificate();
386 }
387 catch (XMLSecurityException e) {
388 throw new VerifyException(
389 "Cannot get keys from <ds:KeyInfo>:\n"
390 + e.getMessage());
391 }
392 }
393 }
394 X509Certificate certificate = keyInfo.getX509Certificate();
395 if (certificate != null) {
396 publicKey = certificate.getPublicKey();
397 }
398 }
399 catch (KeyResolverException e) {
400 throw new VerifyException("Cannot extract key info:\n"
401 + e.getMessage());
402 }
403 }
404 if (publicKey == null) {
405 throw new VerifyException("No PublicKey can be found!");
406 }
407 logger.debug("got public key and certificate chain from signature");
408
409 boolean ret = false;
410 try {
411 ret = signature.checkSignatureValue(publicKey);
412 }
413 catch (XMLSignatureException e) {
414 throw new VerifyException("Cannot verify:\n" + e.getMessage());
415 }
416 logger.debug("checked signature value, result: " + ret);
417
418 double javaVersion = Version.getJDKVersion();
419
420 if (ret == true && trusted != null && certs != null
421 && certs.length > 1 && javaVersion >= 1.4) {
422
423 logger.debug("start verifying cert path...");
424 CertPathVerifier.verify(certs, trusted);
425 logger.debug("verified, result: " + ret);
426 }
427 else {
428 logger.debug("verification of cert path skipped...");
429 }
430
431 return ret;
432 }
433
434 /**
435 * Gets the DOM element of the signature generated.
436 *
437 * @return the DOM element of the signature
438 */
439 public Element getElement() {
440 if (signature != null) {
441 return signature.getElement();
442 }
443 else {
444 return null;
445 }
446 }
447 }