1 /*
2  * reserved comment block
3  * DO NOT REMOVE OR ALTER!
4  */
5 /**
6  * Licensed to the Apache Software Foundation (ASF) under one
7  * or more contributor license agreements. See the NOTICE file
8  * distributed with this work for additional information
9  * regarding copyright ownership. The ASF licenses this file
10  * to you under the Apache License, Version 2.0 (the
11  * "License"); you may not use this file except in compliance
12  * with the License. You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing,
17  * software distributed under the License is distributed on an
18  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  * KIND, either express or implied. See the License for the
20  * specific language governing permissions and limitations
21  * under the License.
22  */
23 /*
24  * Portions copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
25  */
26 /*
27  * ===========================================================================
28  *
29  * (C) Copyright IBM Corp. 2003 All Rights Reserved.
30  *
31  * ===========================================================================
32  */
33 /*
34  * $Id: DOMReference.java 1854026 2019-02-21 09:30:01Z coheigea $
35  */
36 package org.jcp.xml.dsig.internal.dom;
37 
38 import javax.xml.crypto.*;
39 import javax.xml.crypto.dsig.*;
40 import javax.xml.crypto.dom.DOMCryptoContext;
41 import javax.xml.crypto.dom.DOMURIReference;
42 
43 import java.io.*;
44 import java.net.URI;
45 import java.net.URISyntaxException;
46 import java.security.*;
47 import java.util.*;
48 
49 import org.w3c.dom.Attr;
50 import org.w3c.dom.Document;
51 import org.w3c.dom.Element;
52 import org.w3c.dom.Node;
53 
54 import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
55 
56 import org.jcp.xml.dsig.internal.DigesterOutputStream;
57 import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput;
58 import com.sun.org.apache.xml.internal.security.utils.UnsyncBufferedOutputStream;
59 
60 /**
61  * DOM-based implementation of Reference.
62  *
63  */
64 public final class DOMReference extends DOMStructure
65     implements Reference, DOMURIReference {
66 
67    /**
68     * The maximum number of transforms per reference, if secure validation is enabled.
69     */
70    public static final int MAXIMUM_TRANSFORM_COUNT = 5;
71 
72    /**
73     * Look up useC14N11 system property. If true, an explicit C14N11 transform
74     * will be added if necessary when generating the signature. See section
75     * 3.1.1 of http://www.w3.org/2007/xmlsec/Drafts/xmldsig-core/ for more info.
76     *
77     * If true, overrides the same property if set in the XMLSignContext.
78     */
79     private static boolean useC14N11 =
80         AccessController.doPrivileged((PrivilegedAction<Boolean>)
81             () -> Boolean.getBoolean("com.sun.org.apache.xml.internal.security.useC14N11"));
82 
83     private static final com.sun.org.slf4j.internal.Logger LOG =
84         com.sun.org.slf4j.internal.LoggerFactory.getLogger(DOMReference.class);
85 
86     private final DigestMethod digestMethod;
87     private final String id;
88     private final List<Transform> transforms;
89     private List<Transform> allTransforms;
90     private final Data appliedTransformData;
91     private Attr here;
92     private final String uri;
93     private final String type;
94     private byte[] digestValue;
95     private byte[] calcDigestValue;
96     private Element refElem;
97     private boolean digested = false;
98     private boolean validated = false;
99     private boolean validationStatus;
100     private Data derefData;
101     private InputStream dis;
102     private MessageDigest md;
103     private Provider provider;
104 
105     /**
106      * Creates a {@code Reference} from the specified parameters.
107      *
108      * @param uri the URI (may be null)
109      * @param type the type (may be null)
110      * @param dm the digest method
111      * @param transforms a list of {@link Transform}s. The list
112      *    is defensively copied to protect against subsequent modification.
113      *    May be {@code null} or empty.
114      * @param id the reference ID (may be {@code null})
115      * @throws NullPointerException if {@code dm} is {@code null}
116      * @throws ClassCastException if any of the {@code transforms} are
117      *    not of type {@code Transform}
118      */
DOMReference(String uri, String type, DigestMethod dm, List<? extends Transform> transforms, String id, Provider provider)119     public DOMReference(String uri, String type, DigestMethod dm,
120                         List<? extends Transform> transforms, String id,
121                         Provider provider)
122     {
123         this(uri, type, dm, null, null, transforms, id, null, provider);
124     }
125 
DOMReference(String uri, String type, DigestMethod dm, List<? extends Transform> appliedTransforms, Data result, List<? extends Transform> transforms, String id, Provider provider)126     public DOMReference(String uri, String type, DigestMethod dm,
127                         List<? extends Transform> appliedTransforms,
128                         Data result, List<? extends Transform> transforms,
129                         String id, Provider provider)
130     {
131         this(uri, type, dm, appliedTransforms,
132              result, transforms, id, null, provider);
133     }
134 
DOMReference(String uri, String type, DigestMethod dm, List<? extends Transform> appliedTransforms, Data result, List<? extends Transform> transforms, String id, byte[] digestValue, Provider provider)135     public DOMReference(String uri, String type, DigestMethod dm,
136                         List<? extends Transform> appliedTransforms,
137                         Data result, List<? extends Transform> transforms,
138                         String id, byte[] digestValue, Provider provider)
139     {
140         if (dm == null) {
141             throw new NullPointerException("DigestMethod must be non-null");
142         }
143         if (appliedTransforms == null) {
144             this.allTransforms = new ArrayList<>();
145         } else {
146             this.allTransforms = new ArrayList<>(appliedTransforms);
147             for (int i = 0, size = this.allTransforms.size(); i < size; i++) {
148                 if (!(this.allTransforms.get(i) instanceof Transform)) {
149                     throw new ClassCastException
150                         ("appliedTransforms["+i+"] is not a valid type");
151                 }
152             }
153         }
154         if (transforms == null) {
155             this.transforms = Collections.emptyList();
156         } else {
157             this.transforms = new ArrayList<>(transforms);
158             for (int i = 0, size = this.transforms.size(); i < size; i++) {
159                 if (!(this.transforms.get(i) instanceof Transform)) {
160                     throw new ClassCastException
161                         ("transforms["+i+"] is not a valid type");
162                 }
163             }
164             this.allTransforms.addAll(this.transforms);
165         }
166         this.digestMethod = dm;
167         this.uri = uri;
168         if (uri != null && !uri.equals("")) {
169             try {
170                 new URI(uri);
171             } catch (URISyntaxException e) {
172                 throw new IllegalArgumentException(e.getMessage());
173             }
174         }
175         this.type = type;
176         this.id = id;
177         if (digestValue != null) {
178             this.digestValue = digestValue.clone();
179             this.digested = true;
180         }
181         this.appliedTransformData = result;
182         this.provider = provider;
183     }
184 
185     /**
186      * Creates a {@code DOMReference} from an element.
187      *
188      * @param refElem a Reference element
189      */
DOMReference(Element refElem, XMLCryptoContext context, Provider provider)190     public DOMReference(Element refElem, XMLCryptoContext context,
191                         Provider provider)
192         throws MarshalException
193     {
194         boolean secVal = Utils.secureValidation(context);
195 
196         // unmarshal Transforms, if specified
197         Element nextSibling = DOMUtils.getFirstChildElement(refElem);
198         List<Transform> newTransforms = new ArrayList<>(MAXIMUM_TRANSFORM_COUNT);
199         if (nextSibling.getLocalName().equals("Transforms")
200             && XMLSignature.XMLNS.equals(nextSibling.getNamespaceURI())) {
201             Element transformElem = DOMUtils.getFirstChildElement(nextSibling,
202                                                                   "Transform",
203                                                                   XMLSignature.XMLNS);
204             newTransforms.add(new DOMTransform(transformElem, context, provider));
205             transformElem = DOMUtils.getNextSiblingElement(transformElem);
206             while (transformElem != null) {
207                 String localName = transformElem.getLocalName();
208                 String namespace = transformElem.getNamespaceURI();
209                 if (!"Transform".equals(localName) || !XMLSignature.XMLNS.equals(namespace)) {
210                     throw new MarshalException(
211                         "Invalid element name: " + localName +
212                         ", expected Transform");
213                 }
214                 newTransforms.add
215                     (new DOMTransform(transformElem, context, provider));
216                 if (secVal && Policy.restrictNumTransforms(newTransforms.size())) {
217                     String error = "A maximum of " + Policy.maxTransforms()
218                         + " transforms per Reference are allowed when"
219                         + " secure validation is enabled";
220                     throw new MarshalException(error);
221                 }
222                 transformElem = DOMUtils.getNextSiblingElement(transformElem);
223             }
224             nextSibling = DOMUtils.getNextSiblingElement(nextSibling);
225         }
226         if (!nextSibling.getLocalName().equals("DigestMethod")
227             && XMLSignature.XMLNS.equals(nextSibling.getNamespaceURI())) {
228             throw new MarshalException("Invalid element name: " +
229                                        nextSibling.getLocalName() +
230                                        ", expected DigestMethod");
231         }
232 
233         // unmarshal DigestMethod
234         Element dmElem = nextSibling;
235         this.digestMethod = DOMDigestMethod.unmarshal(dmElem);
236         String digestMethodAlgorithm = this.digestMethod.getAlgorithm();
237         if (secVal && Policy.restrictAlg(digestMethodAlgorithm)) {
238             throw new MarshalException(
239                 "It is forbidden to use algorithm " + digestMethodAlgorithm +
240                 " when secure validation is enabled"
241             );
242         }
243 
244         // unmarshal DigestValue
245         Element dvElem = DOMUtils.getNextSiblingElement(dmElem, "DigestValue", XMLSignature.XMLNS);
246         String content = XMLUtils.getFullTextChildrenFromNode(dvElem);
247         this.digestValue = XMLUtils.decode(content);
248 
249         // check for extra elements
250         if (DOMUtils.getNextSiblingElement(dvElem) != null) {
251             throw new MarshalException(
252                 "Unexpected element after DigestValue element");
253         }
254 
255         // unmarshal attributes
256         this.uri = DOMUtils.getAttributeValue(refElem, "URI");
257 
258         Attr attr = refElem.getAttributeNodeNS(null, "Id");
259         if (attr != null) {
260             this.id = attr.getValue();
261             refElem.setIdAttributeNode(attr, true);
262         } else {
263             this.id = null;
264         }
265 
266         this.type = DOMUtils.getAttributeValue(refElem, "Type");
267         this.here = refElem.getAttributeNodeNS(null, "URI");
268         this.refElem = refElem;
269         this.transforms = newTransforms;
270         this.allTransforms = transforms;
271         this.appliedTransformData = null;
272         this.provider = provider;
273     }
274 
getDigestMethod()275     public DigestMethod getDigestMethod() {
276         return digestMethod;
277     }
278 
getId()279     public String getId() {
280         return id;
281     }
282 
getURI()283     public String getURI() {
284         return uri;
285     }
286 
getType()287     public String getType() {
288         return type;
289     }
290 
getTransforms()291     public List<Transform> getTransforms() {
292         return Collections.unmodifiableList(allTransforms);
293     }
294 
getDigestValue()295     public byte[] getDigestValue() {
296         return digestValue == null ? null : digestValue.clone();
297     }
298 
getCalculatedDigestValue()299     public byte[] getCalculatedDigestValue() {
300         return calcDigestValue == null ? null
301                                         : calcDigestValue.clone();
302     }
303 
304     @Override
marshal(Node parent, String dsPrefix, DOMCryptoContext context)305     public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
306         throws MarshalException
307     {
308         LOG.debug("Marshalling Reference");
309         Document ownerDoc = DOMUtils.getOwnerDocument(parent);
310 
311         refElem = DOMUtils.createElement(ownerDoc, "Reference",
312                                          XMLSignature.XMLNS, dsPrefix);
313 
314         // set attributes
315         DOMUtils.setAttributeID(refElem, "Id", id);
316         DOMUtils.setAttribute(refElem, "URI", uri);
317         DOMUtils.setAttribute(refElem, "Type", type);
318 
319         // create and append Transforms element
320         if (!allTransforms.isEmpty()) {
321             Element transformsElem = DOMUtils.createElement(ownerDoc,
322                                                             "Transforms",
323                                                             XMLSignature.XMLNS,
324                                                             dsPrefix);
325             refElem.appendChild(transformsElem);
326             for (Transform transform : allTransforms) {
327                 ((DOMStructure)transform).marshal(transformsElem,
328                                                   dsPrefix, context);
329             }
330         }
331 
332         // create and append DigestMethod element
333         ((DOMDigestMethod)digestMethod).marshal(refElem, dsPrefix, context);
334 
335         // create and append DigestValue element
336         LOG.debug("Adding digestValueElem");
337         Element digestValueElem = DOMUtils.createElement(ownerDoc,
338                                                          "DigestValue",
339                                                          XMLSignature.XMLNS,
340                                                          dsPrefix);
341         if (digestValue != null) {
342             digestValueElem.appendChild
343                 (ownerDoc.createTextNode(XMLUtils.encodeToString(digestValue)));
344         }
345         refElem.appendChild(digestValueElem);
346 
347         parent.appendChild(refElem);
348         here = refElem.getAttributeNodeNS(null, "URI");
349     }
350 
digest(XMLSignContext signContext)351     public void digest(XMLSignContext signContext)
352         throws XMLSignatureException
353     {
354         Data data = null;
355         if (appliedTransformData == null) {
356             data = dereference(signContext);
357         } else {
358             data = appliedTransformData;
359         }
360         digestValue = transform(data, signContext);
361 
362         // insert digestValue into DigestValue element
363         String encodedDV = XMLUtils.encodeToString(digestValue);
364         LOG.debug("Reference object uri = {}", uri);
365         Element digestElem = DOMUtils.getLastChildElement(refElem);
366         if (digestElem == null) {
367             throw new XMLSignatureException("DigestValue element expected");
368         }
369         DOMUtils.removeAllChildren(digestElem);
370         digestElem.appendChild
371             (refElem.getOwnerDocument().createTextNode(encodedDV));
372 
373         digested = true;
374         LOG.debug("Reference digesting completed");
375     }
376 
validate(XMLValidateContext validateContext)377     public boolean validate(XMLValidateContext validateContext)
378         throws XMLSignatureException
379     {
380         if (validateContext == null) {
381             throw new NullPointerException("validateContext cannot be null");
382         }
383         if (validated) {
384             return validationStatus;
385         }
386         Data data = dereference(validateContext);
387         calcDigestValue = transform(data, validateContext);
388 
389         if (LOG.isDebugEnabled()) {
390             LOG.debug("Expected digest: " + XMLUtils.encodeToString(digestValue));
391             LOG.debug("Actual digest: " + XMLUtils.encodeToString(calcDigestValue));
392         }
393 
394         validationStatus = Arrays.equals(digestValue, calcDigestValue);
395         validated = true;
396         return validationStatus;
397     }
398 
getDereferencedData()399     public Data getDereferencedData() {
400         return derefData;
401     }
402 
getDigestInputStream()403     public InputStream getDigestInputStream() {
404         return dis;
405     }
406 
dereference(XMLCryptoContext context)407     private Data dereference(XMLCryptoContext context)
408         throws XMLSignatureException
409     {
410         Data data = null;
411 
412         // use user-specified URIDereferencer if specified; otherwise use deflt
413         URIDereferencer deref = context.getURIDereferencer();
414         if (deref == null) {
415             deref = DOMURIDereferencer.INSTANCE;
416         }
417         try {
418             data = deref.dereference(this, context);
419             LOG.debug("URIDereferencer class name: {}", deref.getClass().getName());
420             LOG.debug("Data class name: {}", data.getClass().getName());
421         } catch (URIReferenceException ure) {
422             throw new XMLSignatureException(ure);
423         }
424 
425         return data;
426     }
427 
transform(Data dereferencedData, XMLCryptoContext context)428     private byte[] transform(Data dereferencedData,
429                              XMLCryptoContext context)
430         throws XMLSignatureException
431     {
432         if (md == null) {
433             try {
434                 md = MessageDigest.getInstance
435                     (((DOMDigestMethod)digestMethod).getMessageDigestAlgorithm());
436             } catch (NoSuchAlgorithmException nsae) {
437                 throw new XMLSignatureException(nsae);
438             }
439         }
440         md.reset();
441         DigesterOutputStream dos;
442         Boolean cache = (Boolean)
443             context.getProperty("javax.xml.crypto.dsig.cacheReference");
444         if (cache != null && cache) {
445             this.derefData = copyDerefData(dereferencedData);
446             dos = new DigesterOutputStream(md, true);
447         } else {
448             dos = new DigesterOutputStream(md);
449         }
450         Data data = dereferencedData;
451         try (OutputStream os = new UnsyncBufferedOutputStream(dos)) {
452             for (int i = 0, size = transforms.size(); i < size; i++) {
453                 DOMTransform transform = (DOMTransform)transforms.get(i);
454                 if (i < size - 1) {
455                     data = transform.transform(data, context);
456                 } else {
457                     data = transform.transform(data, context, os);
458                 }
459             }
460 
461             if (data != null) {
462                 XMLSignatureInput xi;
463                 // explicitly use C14N 1.1 when generating signature
464                 // first check system property, then context property
465                 boolean c14n11 = useC14N11;
466                 String c14nalg = CanonicalizationMethod.INCLUSIVE;
467                 if (context instanceof XMLSignContext) {
468                     if (!c14n11) {
469                         Boolean prop = (Boolean)context.getProperty
470                             ("com.sun.org.apache.xml.internal.security.useC14N11");
471                         c14n11 = prop != null && prop;
472                         if (c14n11) {
473                             c14nalg = "http://www.w3.org/2006/12/xml-c14n11";
474                         }
475                     } else {
476                         c14nalg = "http://www.w3.org/2006/12/xml-c14n11";
477                     }
478                 }
479                 if (data instanceof ApacheData) {
480                     xi = ((ApacheData)data).getXMLSignatureInput();
481                 } else if (data instanceof OctetStreamData) {
482                     xi = new XMLSignatureInput
483                         (((OctetStreamData)data).getOctetStream());
484                 } else if (data instanceof NodeSetData) {
485                     TransformService spi = null;
486                     if (provider == null) {
487                         spi = TransformService.getInstance(c14nalg, "DOM");
488                     } else {
489                         try {
490                             spi = TransformService.getInstance(c14nalg, "DOM", provider);
491                         } catch (NoSuchAlgorithmException nsae) {
492                             spi = TransformService.getInstance(c14nalg, "DOM");
493                         }
494                     }
495                     data = spi.transform(data, context);
496                     xi = new XMLSignatureInput
497                         (((OctetStreamData)data).getOctetStream());
498                 } else {
499                     throw new XMLSignatureException("unrecognized Data type");
500                 }
501 
502                 boolean secVal = Utils.secureValidation(context);
503                 xi.setSecureValidation(secVal);
504                 if (context instanceof XMLSignContext && c14n11
505                     && !xi.isOctetStream() && !xi.isOutputStreamSet()) {
506                     TransformService spi = null;
507                     if (provider == null) {
508                         spi = TransformService.getInstance(c14nalg, "DOM");
509                     } else {
510                         try {
511                             spi = TransformService.getInstance(c14nalg, "DOM", provider);
512                         } catch (NoSuchAlgorithmException nsae) {
513                             spi = TransformService.getInstance(c14nalg, "DOM");
514                         }
515                     }
516 
517                     DOMTransform t = new DOMTransform(spi);
518                     Element transformsElem = null;
519                     String dsPrefix = DOMUtils.getSignaturePrefix(context);
520                     if (allTransforms.isEmpty()) {
521                         transformsElem = DOMUtils.createElement(
522                             refElem.getOwnerDocument(),
523                             "Transforms", XMLSignature.XMLNS, dsPrefix);
524                         refElem.insertBefore(transformsElem,
525                             DOMUtils.getFirstChildElement(refElem));
526                     } else {
527                         transformsElem = DOMUtils.getFirstChildElement(refElem);
528                     }
529                     t.marshal(transformsElem, dsPrefix,
530                               (DOMCryptoContext)context);
531                     allTransforms.add(t);
532                     xi.updateOutputStream(os, true);
533                 } else {
534                     xi.updateOutputStream(os);
535                 }
536             }
537             os.flush();
538             if (cache != null && cache) {
539                 this.dis = dos.getInputStream();
540             }
541             return dos.getDigestValue();
542         } catch (NoSuchAlgorithmException e) {
543             throw new XMLSignatureException(e);
544         } catch (TransformException e) {
545             throw new XMLSignatureException(e);
546         } catch (MarshalException e) {
547             throw new XMLSignatureException(e);
548         } catch (IOException e) {
549             throw new XMLSignatureException(e);
550         } catch (com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException e) {
551             throw new XMLSignatureException(e);
552         } finally {
553             if (dos != null) {
554                 try {
555                     dos.close();
556                 } catch (IOException e) {
557                     throw new XMLSignatureException(e);
558                 }
559             }
560         }
561     }
562 
getHere()563     public Node getHere() {
564         return here;
565     }
566 
567     @Override
equals(Object o)568     public boolean equals(Object o) {
569         if (this == o) {
570             return true;
571         }
572 
573         if (!(o instanceof Reference)) {
574             return false;
575         }
576         Reference oref = (Reference)o;
577 
578         boolean idsEqual = id == null ? oref.getId() == null
579                                        : id.equals(oref.getId());
580         boolean urisEqual = uri == null ? oref.getURI() == null
581                                          : uri.equals(oref.getURI());
582         boolean typesEqual = type == null ? oref.getType() == null
583                                            : type.equals(oref.getType());
584         boolean digestValuesEqual =
585             Arrays.equals(digestValue, oref.getDigestValue());
586 
587         return digestMethod.equals(oref.getDigestMethod()) && idsEqual &&
588             urisEqual && typesEqual &&
589             allTransforms.equals(oref.getTransforms()) && digestValuesEqual;
590     }
591 
592     @Override
hashCode()593     public int hashCode() {
594         int result = 17;
595         if (id != null) {
596             result = 31 * result + id.hashCode();
597         }
598         if (uri != null) {
599             result = 31 * result + uri.hashCode();
600         }
601         if (type != null) {
602             result = 31 * result + type.hashCode();
603         }
604         if (digestValue != null) {
605             result = 31 * result + Arrays.hashCode(digestValue);
606         }
607         result = 31 * result + digestMethod.hashCode();
608         result = 31 * result + allTransforms.hashCode();
609 
610         return result;
611     }
612 
isDigested()613     boolean isDigested() {
614         return digested;
615     }
616 
copyDerefData(Data dereferencedData)617     private static Data copyDerefData(Data dereferencedData) {
618         if (dereferencedData instanceof ApacheData) {
619             // need to make a copy of the Data
620             ApacheData ad = (ApacheData)dereferencedData;
621             XMLSignatureInput xsi = ad.getXMLSignatureInput();
622             if (xsi.isNodeSet()) {
623                 try {
624                     final Set<Node> s = xsi.getNodeSet();
625                     return new NodeSetData<Node>() {
626                         public Iterator<Node> iterator() { return s.iterator(); }
627                     };
628                 } catch (Exception e) {
629                     // LOG a warning
630                     LOG.warn("cannot cache dereferenced data: " + e);
631                     return null;
632                 }
633             } else if (xsi.isElement()) {
634                 return new DOMSubTreeData
635                     (xsi.getSubNode(), xsi.isExcludeComments());
636             } else if (xsi.isOctetStream() || xsi.isByteArray()) {
637                 try {
638                     return new OctetStreamData
639                         (xsi.getOctetStream(), xsi.getSourceURI(),
640                          xsi.getMIMEType());
641                 } catch (IOException ioe) {
642                     // LOG a warning
643                     LOG.warn("cannot cache dereferenced data: " + ioe);
644                     return null;
645                 }
646             }
647         }
648         return dereferencedData;
649     }
650 }
651