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 * Copyright (c) 2005, 2016, 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 1334007 2012-05-04 14:59:46Z coheigea $
35 */
36package org.jcp.xml.dsig.internal.dom;
37
38import javax.xml.crypto.*;
39import javax.xml.crypto.dsig.*;
40import javax.xml.crypto.dom.DOMCryptoContext;
41import javax.xml.crypto.dom.DOMURIReference;
42
43import java.io.*;
44import java.net.URI;
45import java.net.URISyntaxException;
46import java.security.*;
47import java.util.*;
48import org.w3c.dom.Attr;
49import org.w3c.dom.Document;
50import org.w3c.dom.Element;
51import org.w3c.dom.Node;
52
53import org.jcp.xml.dsig.internal.DigesterOutputStream;
54import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
55import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput;
56import com.sun.org.apache.xml.internal.security.utils.Base64;
57import com.sun.org.apache.xml.internal.security.utils.UnsyncBufferedOutputStream;
58
59/**
60 * DOM-based implementation of Reference.
61 *
62 * @author Sean Mullan
63 * @author Joyce Leung
64 */
65public final class DOMReference extends DOMStructure
66    implements Reference, DOMURIReference {
67
68   /**
69    * Look up useC14N11 system property. If true, an explicit C14N11 transform
70    * will be added if necessary when generating the signature. See section
71    * 3.1.1 of http://www.w3.org/2007/xmlsec/Drafts/xmldsig-core/ for more info.
72    *
73    * If true, overrides the same property if set in the XMLSignContext.
74    */
75    private static boolean useC14N11 =
76        AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
77            public Boolean run() {
78                return Boolean.valueOf(Boolean.getBoolean
79                    ("com.sun.org.apache.xml.internal.security.useC14N11"));
80            }
81        });
82
83    private static java.util.logging.Logger log =
84        java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
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</code> 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</code> or empty.
114     * @param id the reference ID (may be <code>null</code>)
115     * @throws NullPointerException if <code>dm</code> is <code>null</code>
116     * @throws ClassCastException if any of the <code>transforms</code> are
117     *    not of type <code>Transform</code>
118     */
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
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
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        List<Transform> tempList =
144            Collections.checkedList(new ArrayList<Transform>(),
145                                    Transform.class);
146        if (appliedTransforms != null) {
147            tempList.addAll(appliedTransforms);
148        }
149        List<Transform> tempList2 =
150            Collections.checkedList(new ArrayList<Transform>(),
151                                    Transform.class);
152        if (transforms != null) {
153            tempList.addAll(transforms);
154            tempList2.addAll(transforms);
155        }
156        this.allTransforms = Collections.unmodifiableList(tempList);
157        this.transforms = tempList2;
158        this.digestMethod = dm;
159        this.uri = uri;
160        if ((uri != null) && (!uri.equals(""))) {
161            try {
162                new URI(uri);
163            } catch (URISyntaxException e) {
164                throw new IllegalArgumentException(e.getMessage());
165            }
166        }
167        this.type = type;
168        this.id = id;
169        if (digestValue != null) {
170            this.digestValue = digestValue.clone();
171            this.digested = true;
172        }
173        this.appliedTransformData = result;
174        this.provider = provider;
175    }
176
177    /**
178     * Creates a <code>DOMReference</code> from an element.
179     *
180     * @param refElem a Reference element
181     */
182    public DOMReference(Element refElem, XMLCryptoContext context,
183                        Provider provider)
184        throws MarshalException
185    {
186        boolean secVal = Utils.secureValidation(context);
187
188        // unmarshal Transforms, if specified
189        Element nextSibling = DOMUtils.getFirstChildElement(refElem);
190        List<Transform> transforms = new ArrayList<Transform>(5);
191        if (nextSibling.getLocalName().equals("Transforms")) {
192            Element transformElem = DOMUtils.getFirstChildElement(nextSibling,
193                                                                  "Transform");
194            transforms.add(new DOMTransform(transformElem, context, provider));
195            transformElem = DOMUtils.getNextSiblingElement(transformElem);
196            while (transformElem != null) {
197                String localName = transformElem.getLocalName();
198                if (!localName.equals("Transform")) {
199                    throw new MarshalException(
200                        "Invalid element name: " + localName +
201                        ", expected Transform");
202                }
203                transforms.add
204                    (new DOMTransform(transformElem, context, provider));
205                if (secVal && Policy.restrictNumTransforms(transforms.size())) {
206                    String error = "A maximum of " + Policy.maxTransforms()
207                        + " transforms per Reference are allowed when"
208                        + " secure validation is enabled";
209                    throw new MarshalException(error);
210                }
211                transformElem = DOMUtils.getNextSiblingElement(transformElem);
212            }
213            nextSibling = DOMUtils.getNextSiblingElement(nextSibling);
214        }
215        if (!nextSibling.getLocalName().equals("DigestMethod")) {
216            throw new MarshalException("Invalid element name: " +
217                                       nextSibling.getLocalName() +
218                                       ", expected DigestMethod");
219        }
220
221        // unmarshal DigestMethod
222        Element dmElem = nextSibling;
223        this.digestMethod = DOMDigestMethod.unmarshal(dmElem);
224        String digestMethodAlgorithm = this.digestMethod.getAlgorithm();
225        if (secVal && Policy.restrictAlg(digestMethodAlgorithm)) {
226            throw new MarshalException(
227                "It is forbidden to use algorithm " + digestMethodAlgorithm +
228                " when secure validation is enabled"
229            );
230        }
231
232        // unmarshal DigestValue
233        Element dvElem = DOMUtils.getNextSiblingElement(dmElem, "DigestValue");
234        try {
235            this.digestValue = Base64.decode(dvElem);
236        } catch (Base64DecodingException bde) {
237            throw new MarshalException(bde);
238        }
239
240        // check for extra elements
241        if (DOMUtils.getNextSiblingElement(dvElem) != null) {
242            throw new MarshalException(
243                "Unexpected element after DigestValue element");
244        }
245
246        // unmarshal attributes
247        this.uri = DOMUtils.getAttributeValue(refElem, "URI");
248
249        Attr attr = refElem.getAttributeNodeNS(null, "Id");
250        if (attr != null) {
251            this.id = attr.getValue();
252            refElem.setIdAttributeNode(attr, true);
253        } else {
254            this.id = null;
255        }
256
257        this.type = DOMUtils.getAttributeValue(refElem, "Type");
258        this.here = refElem.getAttributeNodeNS(null, "URI");
259        this.refElem = refElem;
260        this.transforms = transforms;
261        this.allTransforms = transforms;
262        this.appliedTransformData = null;
263        this.provider = provider;
264    }
265
266    public DigestMethod getDigestMethod() {
267        return digestMethod;
268    }
269
270    public String getId() {
271        return id;
272    }
273
274    public String getURI() {
275        return uri;
276    }
277
278    public String getType() {
279        return type;
280    }
281
282    public List<Transform> getTransforms() {
283        return Collections.unmodifiableList(allTransforms);
284    }
285
286    public byte[] getDigestValue() {
287        return (digestValue == null ? null : digestValue.clone());
288    }
289
290    public byte[] getCalculatedDigestValue() {
291        return (calcDigestValue == null ? null
292                                        : calcDigestValue.clone());
293    }
294
295    public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
296        throws MarshalException
297    {
298        if (log.isLoggable(java.util.logging.Level.FINE)) {
299            log.log(java.util.logging.Level.FINE, "Marshalling Reference");
300        }
301        Document ownerDoc = DOMUtils.getOwnerDocument(parent);
302
303        refElem = DOMUtils.createElement(ownerDoc, "Reference",
304                                         XMLSignature.XMLNS, dsPrefix);
305
306        // set attributes
307        DOMUtils.setAttributeID(refElem, "Id", id);
308        DOMUtils.setAttribute(refElem, "URI", uri);
309        DOMUtils.setAttribute(refElem, "Type", type);
310
311        // create and append Transforms element
312        if (!allTransforms.isEmpty()) {
313            Element transformsElem = DOMUtils.createElement(ownerDoc,
314                                                            "Transforms",
315                                                            XMLSignature.XMLNS,
316                                                            dsPrefix);
317            refElem.appendChild(transformsElem);
318            for (Transform transform : allTransforms) {
319                ((DOMStructure)transform).marshal(transformsElem,
320                                                  dsPrefix, context);
321            }
322        }
323
324        // create and append DigestMethod element
325        ((DOMDigestMethod)digestMethod).marshal(refElem, dsPrefix, context);
326
327        // create and append DigestValue element
328        if (log.isLoggable(java.util.logging.Level.FINE)) {
329            log.log(java.util.logging.Level.FINE, "Adding digestValueElem");
330        }
331        Element digestValueElem = DOMUtils.createElement(ownerDoc,
332                                                         "DigestValue",
333                                                         XMLSignature.XMLNS,
334                                                         dsPrefix);
335        if (digestValue != null) {
336            digestValueElem.appendChild
337                (ownerDoc.createTextNode(Base64.encode(digestValue)));
338        }
339        refElem.appendChild(digestValueElem);
340
341        parent.appendChild(refElem);
342        here = refElem.getAttributeNodeNS(null, "URI");
343    }
344
345    public void digest(XMLSignContext signContext)
346        throws XMLSignatureException
347    {
348        Data data = null;
349        if (appliedTransformData == null) {
350            data = dereference(signContext);
351        } else {
352            data = appliedTransformData;
353        }
354        digestValue = transform(data, signContext);
355
356        // insert digestValue into DigestValue element
357        String encodedDV = Base64.encode(digestValue);
358        if (log.isLoggable(java.util.logging.Level.FINE)) {
359            log.log(java.util.logging.Level.FINE, "Reference object uri = " + uri);
360        }
361        Element digestElem = DOMUtils.getLastChildElement(refElem);
362        if (digestElem == null) {
363            throw new XMLSignatureException("DigestValue element expected");
364        }
365        DOMUtils.removeAllChildren(digestElem);
366        digestElem.appendChild
367            (refElem.getOwnerDocument().createTextNode(encodedDV));
368
369        digested = true;
370        if (log.isLoggable(java.util.logging.Level.FINE)) {
371            log.log(java.util.logging.Level.FINE, "Reference digesting completed");
372        }
373    }
374
375    public boolean validate(XMLValidateContext validateContext)
376        throws XMLSignatureException
377    {
378        if (validateContext == null) {
379            throw new NullPointerException("validateContext cannot be null");
380        }
381        if (validated) {
382            return validationStatus;
383        }
384        Data data = dereference(validateContext);
385        calcDigestValue = transform(data, validateContext);
386
387        if (log.isLoggable(java.util.logging.Level.FINE)) {
388            log.log(java.util.logging.Level.FINE, "Expected digest: " + Base64.encode(digestValue));
389            log.log(java.util.logging.Level.FINE, "Actual digest: " + Base64.encode(calcDigestValue));
390        }
391
392        validationStatus = Arrays.equals(digestValue, calcDigestValue);
393        validated = true;
394        return validationStatus;
395    }
396
397    public Data getDereferencedData() {
398        return derefData;
399    }
400
401    public InputStream getDigestInputStream() {
402        return dis;
403    }
404
405    private Data dereference(XMLCryptoContext context)
406        throws XMLSignatureException
407    {
408        Data data = null;
409
410        // use user-specified URIDereferencer if specified; otherwise use deflt
411        URIDereferencer deref = context.getURIDereferencer();
412        if (deref == null) {
413            deref = DOMURIDereferencer.INSTANCE;
414        }
415        try {
416            data = deref.dereference(this, context);
417            if (log.isLoggable(java.util.logging.Level.FINE)) {
418                log.log(java.util.logging.Level.FINE, "URIDereferencer class name: " + deref.getClass().getName());
419                log.log(java.util.logging.Level.FINE, "Data class name: " + data.getClass().getName());
420            }
421        } catch (URIReferenceException ure) {
422            throw new XMLSignatureException(ure);
423        }
424
425        return data;
426    }
427
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.booleanValue()) {
445            this.derefData = copyDerefData(dereferencedData);
446            dos = new DigesterOutputStream(md, true);
447        } else {
448            dos = new DigesterOutputStream(md);
449        }
450        OutputStream os = null;
451        Data data = dereferencedData;
452        try {
453            os = new UnsyncBufferedOutputStream(dos);
454            for (int i = 0, size = transforms.size(); i < size; i++) {
455                DOMTransform transform = (DOMTransform)transforms.get(i);
456                if (i < size - 1) {
457                    data = transform.transform(data, context);
458                } else {
459                    data = transform.transform(data, context, os);
460                }
461            }
462
463            if (data != null) {
464                XMLSignatureInput xi;
465                // explicitly use C14N 1.1 when generating signature
466                // first check system property, then context property
467                boolean c14n11 = useC14N11;
468                String c14nalg = CanonicalizationMethod.INCLUSIVE;
469                if (context instanceof XMLSignContext) {
470                    if (!c14n11) {
471                        Boolean prop = (Boolean)context.getProperty
472                            ("com.sun.org.apache.xml.internal.security.useC14N11");
473                        c14n11 = (prop != null && prop.booleanValue());
474                        if (c14n11) {
475                            c14nalg = "http://www.w3.org/2006/12/xml-c14n11";
476                        }
477                    } else {
478                        c14nalg = "http://www.w3.org/2006/12/xml-c14n11";
479                    }
480                }
481                if (data instanceof ApacheData) {
482                    xi = ((ApacheData)data).getXMLSignatureInput();
483                } else if (data instanceof OctetStreamData) {
484                    xi = new XMLSignatureInput
485                        (((OctetStreamData)data).getOctetStream());
486                } else if (data instanceof NodeSetData) {
487                    TransformService spi = null;
488                    if (provider == null) {
489                        spi = TransformService.getInstance(c14nalg, "DOM");
490                    } else {
491                        try {
492                            spi = TransformService.getInstance(c14nalg, "DOM", provider);
493                        } catch (NoSuchAlgorithmException nsae) {
494                            spi = TransformService.getInstance(c14nalg, "DOM");
495                        }
496                    }
497                    data = spi.transform(data, context);
498                    xi = new XMLSignatureInput
499                        (((OctetStreamData)data).getOctetStream());
500                } else {
501                    throw new XMLSignatureException("unrecognized Data type");
502                }
503                if (context instanceof XMLSignContext && c14n11
504                    && !xi.isOctetStream() && !xi.isOutputStreamSet()) {
505                    TransformService spi = null;
506                    if (provider == null) {
507                        spi = TransformService.getInstance(c14nalg, "DOM");
508                    } else {
509                        try {
510                            spi = TransformService.getInstance(c14nalg, "DOM", provider);
511                        } catch (NoSuchAlgorithmException nsae) {
512                            spi = TransformService.getInstance(c14nalg, "DOM");
513                        }
514                    }
515
516                    DOMTransform t = new DOMTransform(spi);
517                    Element transformsElem = null;
518                    String dsPrefix = DOMUtils.getSignaturePrefix(context);
519                    if (allTransforms.isEmpty()) {
520                        transformsElem = DOMUtils.createElement(
521                            refElem.getOwnerDocument(),
522                            "Transforms", XMLSignature.XMLNS, dsPrefix);
523                        refElem.insertBefore(transformsElem,
524                            DOMUtils.getFirstChildElement(refElem));
525                    } else {
526                        transformsElem = DOMUtils.getFirstChildElement(refElem);
527                    }
528                    t.marshal(transformsElem, dsPrefix,
529                              (DOMCryptoContext)context);
530                    allTransforms.add(t);
531                    xi.updateOutputStream(os, true);
532                } else {
533                    xi.updateOutputStream(os);
534                }
535            }
536            os.flush();
537            if (cache != null && cache.booleanValue()) {
538                this.dis = dos.getInputStream();
539            }
540            return dos.getDigestValue();
541        } catch (NoSuchAlgorithmException e) {
542            throw new XMLSignatureException(e);
543        } catch (TransformException e) {
544            throw new XMLSignatureException(e);
545        } catch (MarshalException e) {
546            throw new XMLSignatureException(e);
547        } catch (IOException e) {
548            throw new XMLSignatureException(e);
549        } catch (com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException e) {
550            throw new XMLSignatureException(e);
551        } finally {
552            if (os != null) {
553                try {
554                    os.close();
555                } catch (IOException e) {
556                    throw new XMLSignatureException(e);
557                }
558            }
559            if (dos != null) {
560                try {
561                    dos.close();
562                } catch (IOException e) {
563                    throw new XMLSignatureException(e);
564                }
565            }
566        }
567    }
568
569    public Node getHere() {
570        return here;
571    }
572
573    @Override
574    public boolean equals(Object o) {
575        if (this == o) {
576            return true;
577        }
578
579        if (!(o instanceof Reference)) {
580            return false;
581        }
582        Reference oref = (Reference)o;
583
584        boolean idsEqual = (id == null ? oref.getId() == null
585                                       : id.equals(oref.getId()));
586        boolean urisEqual = (uri == null ? oref.getURI() == null
587                                         : uri.equals(oref.getURI()));
588        boolean typesEqual = (type == null ? oref.getType() == null
589                                           : type.equals(oref.getType()));
590        boolean digestValuesEqual =
591            Arrays.equals(digestValue, oref.getDigestValue());
592
593        return digestMethod.equals(oref.getDigestMethod()) && idsEqual &&
594            urisEqual && typesEqual &&
595            allTransforms.equals(oref.getTransforms()) && digestValuesEqual;
596    }
597
598    @Override
599    public int hashCode() {
600        int result = 17;
601        if (id != null) {
602            result = 31 * result + id.hashCode();
603        }
604        if (uri != null) {
605            result = 31 * result + uri.hashCode();
606        }
607        if (type != null) {
608            result = 31 * result + type.hashCode();
609        }
610        if (digestValue != null) {
611            result = 31 * result + Arrays.hashCode(digestValue);
612        }
613        result = 31 * result + digestMethod.hashCode();
614        result = 31 * result + allTransforms.hashCode();
615
616        return result;
617    }
618
619    boolean isDigested() {
620        return digested;
621    }
622
623    private static Data copyDerefData(Data dereferencedData) {
624        if (dereferencedData instanceof ApacheData) {
625            // need to make a copy of the Data
626            ApacheData ad = (ApacheData)dereferencedData;
627            XMLSignatureInput xsi = ad.getXMLSignatureInput();
628            if (xsi.isNodeSet()) {
629                try {
630                    final Set<Node> s = xsi.getNodeSet();
631                    return new NodeSetData<Node>() {
632                        public Iterator<Node> iterator() { return s.iterator(); }
633                    };
634                } catch (Exception e) {
635                    // log a warning
636                    log.log(java.util.logging.Level.WARNING, "cannot cache dereferenced data: " + e);
637                    return null;
638                }
639            } else if (xsi.isElement()) {
640                return new DOMSubTreeData
641                    (xsi.getSubNode(), xsi.isExcludeComments());
642            } else if (xsi.isOctetStream() || xsi.isByteArray()) {
643                try {
644                    return new OctetStreamData
645                        (xsi.getOctetStream(), xsi.getSourceURI(),
646                         xsi.getMIMEType());
647                } catch (IOException ioe) {
648                    // log a warning
649                    log.log(java.util.logging.Level.WARNING, "cannot cache dereferenced data: " + ioe);
650                    return null;
651                }
652            }
653        }
654        return dereferencedData;
655    }
656}
657