blob: 1e31d60a0821b6203a3afab652a7ba140a4edc0c [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Portions Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26/*
27 * ===========================================================================
28 *
29 * (C) Copyright IBM Corp. 2003 All Rights Reserved.
30 *
31 * ===========================================================================
32 */
33/*
34 * $Id: DOMXMLSignature.java,v 1.42 2005/09/23 20:29:04 mullan Exp $
35 */
36package org.jcp.xml.dsig.internal.dom;
37
38import javax.xml.crypto.*;
39import javax.xml.crypto.dom.*;
40import javax.xml.crypto.dsig.*;
41import javax.xml.crypto.dsig.dom.DOMSignContext;
42import javax.xml.crypto.dsig.dom.DOMValidateContext;
43import javax.xml.crypto.dsig.keyinfo.KeyInfo;
44
45import java.io.*;
46import java.security.InvalidKeyException;
47import java.security.Key;
48import java.util.Collections;
49import java.util.ArrayList;
50import java.util.HashMap;
51import java.util.List;
52import java.util.logging.Level;
53import java.util.logging.Logger;
54import org.w3c.dom.Document;
55import org.w3c.dom.Element;
56import org.w3c.dom.Node;
57
58import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
59import com.sun.org.apache.xml.internal.security.utils.Base64;
60
61/**
62 * DOM-based implementation of XMLSignature.
63 *
64 * @author Sean Mullan
65 * @author Joyce Leung
66 */
67public final class DOMXMLSignature extends DOMStructure
68 implements XMLSignature {
69
70 private static Logger log = Logger.getLogger("org.jcp.xml.dsig.internal.dom");
71 private String id;
72 private SignatureValue sv;
73 private KeyInfo ki;
74 private List objects;
75 private SignedInfo si;
76 private Document ownerDoc = null;
77 private Element localSigElem = null;
78 private Element sigElem = null;
79 private boolean validationStatus;
80 private boolean validated = false;
81 private KeySelectorResult ksr;
82 private HashMap signatureIdMap;
83
84 static {
85 com.sun.org.apache.xml.internal.security.Init.init();
86 }
87
88 /**
89 * Creates a <code>DOMXMLSignature</code> from the specified components.
90 *
91 * @param si the <code>SignedInfo</code>
92 * @param ki the <code>KeyInfo</code>, or <code>null</code> if not specified
93 * @param objs a list of <code>XMLObject</code>s or <code>null</code>
94 * if not specified. The list is copied to protect against subsequent
95 * modification.
96 * @param id an optional id (specify <code>null</code> to omit)
97 * @param signatureValueId an optional id (specify <code>null</code> to
98 * omit)
99 * @throws NullPointerException if <code>si</code> is <code>null</code>
100 */
101 public DOMXMLSignature(SignedInfo si, KeyInfo ki, List objs, String id,
102 String signatureValueId)
103 {
104 if (si == null) {
105 throw new NullPointerException("signedInfo cannot be null");
106 }
107 this.si = si;
108 this.id = id;
109 this.sv = new DOMSignatureValue(signatureValueId);
110 if (objs == null) {
111 this.objects = Collections.EMPTY_LIST;
112 } else {
113 List objsCopy = new ArrayList(objs);
114 for (int i = 0, size = objsCopy.size(); i < size; i++) {
115 if (!(objsCopy.get(i) instanceof XMLObject)) {
116 throw new ClassCastException
117 ("objs["+i+"] is not an XMLObject");
118 }
119 }
120 this.objects = Collections.unmodifiableList(objsCopy);
121 }
122 this.ki = ki;
123 }
124
125 /**
126 * Creates a <code>DOMXMLSignature</code> from XML.
127 *
128 * @param sigElem Signature element
129 * @throws MarshalException if XMLSignature cannot be unmarshalled
130 */
131 public DOMXMLSignature(Element sigElem, XMLCryptoContext context)
132 throws MarshalException {
133 localSigElem = sigElem;
134 ownerDoc = localSigElem.getOwnerDocument();
135
136 // get Id attribute, if specified
137 id = DOMUtils.getAttributeValue(localSigElem, "Id");
138
139 // unmarshal SignedInfo
140 Element siElem = DOMUtils.getFirstChildElement(localSigElem);
141 si = new DOMSignedInfo(siElem, context);
142
143 // unmarshal SignatureValue
144 Element sigValElem = DOMUtils.getNextSiblingElement(siElem);
145 sv = new DOMSignatureValue(sigValElem);
146
147 // unmarshal KeyInfo, if specified
148 Element nextSibling = DOMUtils.getNextSiblingElement(sigValElem);
149 if (nextSibling != null && nextSibling.getLocalName().equals("KeyInfo")) {
150 ki = new DOMKeyInfo(nextSibling, context);
151 nextSibling = DOMUtils.getNextSiblingElement(nextSibling);
152 }
153
154 // unmarshal Objects, if specified
155 if (nextSibling == null) {
156 objects = Collections.EMPTY_LIST;
157 } else {
158 List tempObjects = new ArrayList();
159 while (nextSibling != null) {
160 tempObjects.add(new DOMXMLObject(nextSibling, context));
161 nextSibling = DOMUtils.getNextSiblingElement(nextSibling);
162 }
163 objects = Collections.unmodifiableList(tempObjects);
164 }
165 }
166
167 public String getId() {
168 return id;
169 }
170
171 public KeyInfo getKeyInfo() {
172 return ki;
173 }
174
175 public SignedInfo getSignedInfo() {
176 return si;
177 }
178
179 public List getObjects() {
180 return objects;
181 }
182
183 public SignatureValue getSignatureValue() {
184 return sv;
185 }
186
187 public KeySelectorResult getKeySelectorResult() {
188 return ksr;
189 }
190
191 public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
192 throws MarshalException {
193 marshal(parent, null, dsPrefix, context);
194 }
195
196 public void marshal(Node parent, Node nextSibling, String dsPrefix,
197 DOMCryptoContext context) throws MarshalException {
198 ownerDoc = DOMUtils.getOwnerDocument(parent);
199
200 sigElem = DOMUtils.createElement
201 (ownerDoc, "Signature", XMLSignature.XMLNS, dsPrefix);
202
203 // append xmlns attribute
204 //XXX I think this is supposed to be automatically inserted when
205 //XXX serializing a DOM2 tree, but doesn't seem to work with JAXP/Xalan
206 if (dsPrefix == null) {
207 sigElem.setAttributeNS
208 ("http://www.w3.org/2000/xmlns/", "xmlns", XMLSignature.XMLNS);
209 } else {
210 sigElem.setAttributeNS
211 ("http://www.w3.org/2000/xmlns/", "xmlns:" + dsPrefix,
212 XMLSignature.XMLNS);
213 }
214
215 // create and append SignedInfo element
216 ((DOMSignedInfo) si).marshal(sigElem, dsPrefix, context);
217
218 // create and append SignatureValue element
219 ((DOMSignatureValue) sv).marshal(sigElem, dsPrefix, context);
220
221 // create and append KeyInfo element if necessary
222 if (ki != null) {
223 ((DOMKeyInfo) ki).marshal(sigElem, null, dsPrefix, context);
224 }
225
226 // create and append Object elements if necessary
227 for (int i = 0, size = objects.size(); i < size; i++) {
228 ((DOMXMLObject) objects.get(i)).marshal(sigElem, dsPrefix, context);
229 }
230
231 // append Id attribute
232 DOMUtils.setAttributeID(sigElem, "Id", id);
233
234 parent.insertBefore(sigElem, nextSibling);
235 }
236
237 public boolean validate(XMLValidateContext vc)
238 throws XMLSignatureException {
239
240 if (vc == null) {
241 throw new NullPointerException("validateContext is null");
242 }
243
244 if (!(vc instanceof DOMValidateContext)) {
245 throw new ClassCastException
246 ("validateContext must be of type DOMValidateContext");
247 }
248
249 if (validated) {
250 return validationStatus;
251 }
252
253 // validate the signature
254 boolean sigValidity = sv.validate(vc);
255 if (!sigValidity) {
256 validationStatus = false;
257 validated = true;
258 return validationStatus;
259 }
260
261 // validate all References
262 List refs = this.si.getReferences();
263 boolean validateRefs = true;
264 for (int i = 0, size = refs.size(); validateRefs && i < size; i++) {
265 Reference ref = (Reference) refs.get(i);
266 boolean refValid = ref.validate(vc);
267 if (log.isLoggable(Level.FINE)) {
268 log.log(Level.FINE, "Reference[" + ref.getURI() + "] is valid: "
269 + refValid);
270 }
271 validateRefs &= refValid;
272 }
273 if (!validateRefs) {
274 if (log.isLoggable(Level.FINE)) {
275 log.log(Level.FINE, "Couldn't validate the References");
276 }
277 validationStatus = false;
278 validated = true;
279 return validationStatus;
280 }
281
282 // validate Manifests, if property set
283 boolean validateMans = true;
284 if (Boolean.TRUE.equals(vc.getProperty
285 ("org.jcp.xml.dsig.validateManifests"))) {
286
287 for (int i=0, size=objects.size(); validateMans && i < size; i++) {
288 XMLObject xo = (XMLObject) objects.get(i);
289 List content = xo.getContent();
290 int csize = content.size();
291 for (int j = 0; validateMans && j < csize; j++) {
292 XMLStructure xs = (XMLStructure) content.get(j);
293 if (xs instanceof Manifest) {
294 if (log.isLoggable(Level.FINE)) {
295 log.log(Level.FINE, "validating manifest");
296 }
297 Manifest man = (Manifest) xs;
298 List manRefs = man.getReferences();
299 int rsize = manRefs.size();
300 for (int k = 0; validateMans && k < rsize; k++) {
301 Reference ref = (Reference) manRefs.get(k);
302 boolean refValid = ref.validate(vc);
303 if (log.isLoggable(Level.FINE)) {
304 log.log(Level.FINE, "Manifest ref["
305 + ref.getURI() + "] is valid: " + refValid);
306 }
307 validateMans &= refValid;
308 }
309 }
310 }
311 }
312 }
313
314 validationStatus = validateMans;
315 validated = true;
316 return validationStatus;
317 }
318
319 public void sign(XMLSignContext signContext)
320 throws MarshalException, XMLSignatureException {
321 if (signContext == null) {
322 throw new NullPointerException("signContext cannot be null");
323 }
324 DOMSignContext context = (DOMSignContext) signContext;
325 if (context != null) {
326 marshal(context.getParent(), context.getNextSibling(),
327 DOMUtils.getSignaturePrefix(context), context);
328 }
329
330 // generate references and signature value
331 List allReferences = new ArrayList(si.getReferences());
332
333 // traverse the Signature and register all objects with IDs that
334 // may contain References
335 signatureIdMap = new HashMap();
336 signatureIdMap.put(id, this);
337 signatureIdMap.put(si.getId(), si);
338 List refs = si.getReferences();
339 for (int i = 0, size = refs.size(); i < size; i++) {
340 Reference ref = (Reference) refs.get(i);
341 signatureIdMap.put(ref.getId(), ref);
342 }
343 for (int i = 0, size = objects.size(); i < size; i++) {
344 XMLObject obj = (XMLObject) objects.get(i);
345 signatureIdMap.put(obj.getId(), obj);
346 List content = obj.getContent();
347 for (int j = 0, csize = content.size(); j < csize; j++) {
348 XMLStructure xs = (XMLStructure) content.get(j);
349 if (xs instanceof Manifest) {
350 Manifest man = (Manifest) xs;
351 signatureIdMap.put(man.getId(), man);
352 List manRefs = man.getReferences();
353 for (int k = 0, msize = manRefs.size(); k < msize; k++) {
354 Reference ref = (Reference) manRefs.get(k);
355 allReferences.add(ref);
356 signatureIdMap.put(ref.getId(), ref);
357 }
358 }
359 }
360 }
361
362 // generate/digest each reference
363 for (int i = 0, size = allReferences.size(); i < size; i++) {
364 DOMReference ref = (DOMReference) allReferences.get(i);
365 digestReference(ref, signContext);
366 }
367
368 // do final sweep to digest any references that were skipped or missed
369 for (int i = 0, size = allReferences.size(); i < size; i++) {
370 DOMReference ref = (DOMReference) allReferences.get(i);
371 if (ref.isDigested()) {
372 continue;
373 }
374 ref.digest(signContext);
375 }
376
377 Key signingKey = null;
378 KeySelectorResult ksr = null;
379 try {
380 ksr = signContext.getKeySelector().select
381 (ki, KeySelector.Purpose.SIGN,
382 si.getSignatureMethod(), signContext);
383 signingKey = ksr.getKey();
384 if (signingKey == null) {
385 throw new XMLSignatureException("the keySelector did not " +
386 "find a signing key");
387 }
388 } catch (KeySelectorException kse) {
389 throw new XMLSignatureException("cannot find signing key", kse);
390 }
391
392 // calculate signature value
393 byte[] val = null;
394 try {
395 val = ((DOMSignatureMethod) si.getSignatureMethod()).sign
396 (signingKey, (DOMSignedInfo) si, signContext);
397 } catch (InvalidKeyException ike) {
398 throw new XMLSignatureException(ike);
399 }
400
401 if (log.isLoggable(Level.FINE)) {
402 log.log(Level.FINE, "SignatureValue = " + val);
403 }
404 ((DOMSignatureValue) sv).setValue(val);
405
406 this.localSigElem = sigElem;
407 this.ksr = ksr;
408 }
409
410 public boolean equals(Object o) {
411 if (this == o) {
412 return true;
413 }
414
415 if (!(o instanceof XMLSignature)) {
416 return false;
417 }
418 XMLSignature osig = (XMLSignature) o;
419
420 boolean idEqual =
421 (id == null ? osig.getId() == null : id.equals(osig.getId()));
422 boolean keyInfoEqual =
423 (ki == null ? osig.getKeyInfo() == null :
424 ki.equals(osig.getKeyInfo()));
425
426 return (idEqual && keyInfoEqual &&
427 sv.equals(osig.getSignatureValue()) &&
428 si.equals(osig.getSignedInfo()) &&
429 objects.equals(osig.getObjects()));
430 }
431
432 private void digestReference(DOMReference ref, XMLSignContext signContext)
433 throws XMLSignatureException {
434 if (ref.isDigested()) {
435 return;
436 }
437 // check dependencies
438 String uri = ref.getURI();
439 if (Utils.sameDocumentURI(uri)) {
440 String id = Utils.parseIdFromSameDocumentURI(uri);
441 if (id != null && signatureIdMap.containsKey(id)) {
442 Object obj = signatureIdMap.get(id);
443 if (obj instanceof DOMReference) {
444 digestReference((DOMReference) obj, signContext);
445 } else if (obj instanceof Manifest) {
446 Manifest man = (Manifest) obj;
447 List manRefs = man.getReferences();
448 for (int i = 0, size = manRefs.size(); i < size; i++) {
449 digestReference
450 ((DOMReference) manRefs.get(i), signContext);
451 }
452 }
453 }
454 // if uri="" and there are XPath Transforms, there may be
455 // reference dependencies in the XPath Transform - so be on
456 // the safe side, and skip and do at end in the final sweep
457 if (uri.length() == 0) {
458 List transforms = ref.getTransforms();
459 for (int i = 0, size = transforms.size(); i < size; i++) {
460 Transform transform = (Transform) transforms.get(i);
461 String transformAlg = transform.getAlgorithm();
462 if (transformAlg.equals(Transform.XPATH) ||
463 transformAlg.equals(Transform.XPATH2)) {
464 return;
465 }
466 }
467 }
468 }
469 ref.digest(signContext);
470 }
471
472 public class DOMSignatureValue extends DOMStructure
473 implements SignatureValue {
474
475 private String id;
476 private byte[] value;
477 private String valueBase64;
478 private Element sigValueElem;
479 private boolean validated = false;
480 private boolean validationStatus;
481
482 DOMSignatureValue(String id) {
483 this.id = id;
484 }
485
486 DOMSignatureValue(Element sigValueElem) throws MarshalException {
487 try {
488 // base64 decode signatureValue
489 value = Base64.decode(sigValueElem);
490 } catch (Base64DecodingException bde) {
491 throw new MarshalException(bde);
492 }
493
494 id = DOMUtils.getAttributeValue(sigValueElem, "Id");
495 this.sigValueElem = sigValueElem;
496 }
497
498 public String getId() {
499 return id;
500 }
501
502 public byte[] getValue() {
503 return (value == null) ? null : (byte[]) value.clone();
504 }
505
506 public boolean validate(XMLValidateContext validateContext)
507 throws XMLSignatureException {
508
509 if (validateContext == null) {
510 throw new NullPointerException("context cannot be null");
511 }
512
513 if (validated) {
514 return validationStatus;
515 }
516
517 // get validating key
518 SignatureMethod sm = si.getSignatureMethod();
519 Key validationKey = null;
520 KeySelectorResult ksResult;
521 try {
522 ksResult = validateContext.getKeySelector().select
523 (ki, KeySelector.Purpose.VERIFY, sm, validateContext);
524 validationKey = ksResult.getKey();
525 if (validationKey == null) {
526 throw new XMLSignatureException("the keyselector did " +
527 "not find a validation key");
528 }
529 } catch (KeySelectorException kse) {
530 throw new XMLSignatureException("cannot find validation " +
531 "key", kse);
532 }
533
534 // canonicalize SignedInfo and verify signature
535 try {
536 validationStatus = ((DOMSignatureMethod) sm).verify
537 (validationKey, (DOMSignedInfo) si, value, validateContext);
538 } catch (Exception e) {
539 throw new XMLSignatureException(e);
540 }
541
542 validated = true;
543 ksr = ksResult;
544 return validationStatus;
545 }
546
547 public boolean equals(Object o) {
548 if (this == o) {
549 return true;
550 }
551
552 if (!(o instanceof SignatureValue)) {
553 return false;
554 }
555 SignatureValue osv = (SignatureValue) o;
556
557 boolean idEqual =
558 (id == null ? osv.getId() == null : id.equals(osv.getId()));
559
560 //XXX compare signature values?
561 return idEqual;
562 }
563
564 public void marshal(Node parent, String dsPrefix,
565 DOMCryptoContext context) throws MarshalException {
566
567 // create SignatureValue element
568 sigValueElem = DOMUtils.createElement
569 (ownerDoc, "SignatureValue", XMLSignature.XMLNS, dsPrefix);
570 if (valueBase64 != null) {
571 sigValueElem.appendChild(ownerDoc.createTextNode(valueBase64));
572 }
573
574 // append Id attribute, if specified
575 DOMUtils.setAttributeID(sigValueElem, "Id", id);
576 parent.appendChild(sigValueElem);
577 }
578
579 void setValue(byte[] value) {
580 this.value = value;
581 valueBase64 = Base64.encode(value);
582 sigValueElem.appendChild(ownerDoc.createTextNode(valueBase64));
583 }
584 }
585}