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 * $Id: DOMSignedInfo.java 1333415 2012-05-03 12:03:51Z coheigea $
28 */
29package org.jcp.xml.dsig.internal.dom;
30
31import javax.xml.crypto.*;
32import javax.xml.crypto.dom.DOMCryptoContext;
33import javax.xml.crypto.dsig.*;
34
35import java.io.ByteArrayInputStream;
36import java.io.ByteArrayOutputStream;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.io.IOException;
40import java.security.Provider;
41import java.util.*;
42
43import org.w3c.dom.Document;
44import org.w3c.dom.Element;
45import org.w3c.dom.Node;
46
47import com.sun.org.apache.xml.internal.security.utils.Base64;
48import com.sun.org.apache.xml.internal.security.utils.UnsyncBufferedOutputStream;
49
50/**
51 * DOM-based implementation of SignedInfo.
52 *
53 * @author Sean Mullan
54 */
55public final class DOMSignedInfo extends DOMStructure implements SignedInfo {
56
57    private static java.util.logging.Logger log =
58        java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom");
59
60    private List<Reference> references;
61    private CanonicalizationMethod canonicalizationMethod;
62    private SignatureMethod signatureMethod;
63    private String id;
64    private Document ownerDoc;
65    private Element localSiElem;
66    private InputStream canonData;
67
68    /**
69     * Creates a <code>DOMSignedInfo</code> from the specified parameters. Use
70     * this constructor when the <code>Id</code> is not specified.
71     *
72     * @param cm the canonicalization method
73     * @param sm the signature method
74     * @param references the list of references. The list is copied.
75     * @throws NullPointerException if
76     *    <code>cm</code>, <code>sm</code>, or <code>references</code> is
77     *    <code>null</code>
78     * @throws IllegalArgumentException if <code>references</code> is empty
79     * @throws ClassCastException if any of the references are not of
80     *    type <code>Reference</code>
81     */
82    public DOMSignedInfo(CanonicalizationMethod cm, SignatureMethod sm,
83                         List<? extends Reference> references) {
84        if (cm == null || sm == null || references == null) {
85            throw new NullPointerException();
86        }
87        this.canonicalizationMethod = cm;
88        this.signatureMethod = sm;
89        List<Reference> tempList =
90            Collections.checkedList(new ArrayList<Reference>(),
91                                    Reference.class);
92        tempList.addAll(references);
93        if (tempList.isEmpty()) {
94            throw new IllegalArgumentException("references cannot be empty");
95        }
96        this.references = Collections.unmodifiableList(tempList);
97    }
98
99    /**
100     * Creates a <code>DOMSignedInfo</code> from the specified parameters.
101     *
102     * @param cm the canonicalization method
103     * @param sm the signature method
104     * @param references the list of references. The list is copied.
105     * @param id an optional identifer that will allow this
106     *    <code>SignedInfo</code> to be referenced by other signatures and
107     *    objects
108     * @throws NullPointerException if <code>cm</code>, <code>sm</code>,
109     *    or <code>references</code> is <code>null</code>
110     * @throws IllegalArgumentException if <code>references</code> is empty
111     * @throws ClassCastException if any of the references are not of
112     *    type <code>Reference</code>
113     */
114    public DOMSignedInfo(CanonicalizationMethod cm, SignatureMethod sm,
115                         List<? extends Reference> references, String id) {
116        this(cm, sm, references);
117        this.id = id;
118    }
119
120    /**
121     * Creates a <code>DOMSignedInfo</code> from an element.
122     *
123     * @param siElem a SignedInfo element
124     */
125    public DOMSignedInfo(Element siElem, XMLCryptoContext context, Provider provider)
126        throws MarshalException {
127        localSiElem = siElem;
128        ownerDoc = siElem.getOwnerDocument();
129
130        // get Id attribute, if specified
131        id = DOMUtils.getAttributeValue(siElem, "Id");
132
133        // unmarshal CanonicalizationMethod
134        Element cmElem = DOMUtils.getFirstChildElement(siElem,
135                                                       "CanonicalizationMethod");
136        canonicalizationMethod = new DOMCanonicalizationMethod(cmElem, context,
137                                                               provider);
138
139        // unmarshal SignatureMethod
140        Element smElem = DOMUtils.getNextSiblingElement(cmElem,
141                                                        "SignatureMethod");
142        signatureMethod = DOMSignatureMethod.unmarshal(smElem);
143
144        boolean secVal = Utils.secureValidation(context);
145
146        String signatureMethodAlgorithm = signatureMethod.getAlgorithm();
147        if (secVal && Policy.restrictAlg(signatureMethodAlgorithm)) {
148            throw new MarshalException(
149                "It is forbidden to use algorithm " + signatureMethodAlgorithm +
150                " when secure validation is enabled"
151            );
152        }
153
154        // unmarshal References
155        ArrayList<Reference> refList = new ArrayList<Reference>(5);
156        Element refElem = DOMUtils.getNextSiblingElement(smElem, "Reference");
157        refList.add(new DOMReference(refElem, context, provider));
158
159        refElem = DOMUtils.getNextSiblingElement(refElem);
160        while (refElem != null) {
161            String name = refElem.getLocalName();
162            if (!name.equals("Reference")) {
163                throw new MarshalException("Invalid element name: " +
164                                           name + ", expected Reference");
165            }
166            refList.add(new DOMReference(refElem, context, provider));
167
168            if (secVal && Policy.restrictNumReferences(refList.size())) {
169                String error = "A maximum of " + Policy.maxReferences()
170                    + " references per Manifest are allowed when"
171                    + " secure validation is enabled";
172                throw new MarshalException(error);
173            }
174            refElem = DOMUtils.getNextSiblingElement(refElem);
175        }
176        references = Collections.unmodifiableList(refList);
177    }
178
179    public CanonicalizationMethod getCanonicalizationMethod() {
180        return canonicalizationMethod;
181    }
182
183    public SignatureMethod getSignatureMethod() {
184        return signatureMethod;
185    }
186
187    public String getId() {
188        return id;
189    }
190
191    public List<Reference> getReferences() {
192        return references;
193    }
194
195    public InputStream getCanonicalizedData() {
196        return canonData;
197    }
198
199    public void canonicalize(XMLCryptoContext context, ByteArrayOutputStream bos)
200        throws XMLSignatureException {
201        if (context == null) {
202            throw new NullPointerException("context cannot be null");
203        }
204
205        OutputStream os = new UnsyncBufferedOutputStream(bos);
206
207        DOMSubTreeData subTree = new DOMSubTreeData(localSiElem, true);
208        try {
209            ((DOMCanonicalizationMethod)
210                canonicalizationMethod).canonicalize(subTree, context, os);
211        } catch (TransformException te) {
212            throw new XMLSignatureException(te);
213        }
214
215        try {
216            os.flush();
217        } catch (IOException e) {
218            if (log.isLoggable(java.util.logging.Level.FINE)) {
219                log.log(java.util.logging.Level.FINE, e.getMessage(), e);
220            }
221            // Impossible
222        }
223
224        byte[] signedInfoBytes = bos.toByteArray();
225
226        // this whole block should only be done if logging is enabled
227        if (log.isLoggable(java.util.logging.Level.FINE)) {
228            log.log(java.util.logging.Level.FINE, "Canonicalized SignedInfo:");
229            StringBuilder sb = new StringBuilder(signedInfoBytes.length);
230            for (int i = 0; i < signedInfoBytes.length; i++) {
231                sb.append((char)signedInfoBytes[i]);
232            }
233            log.log(java.util.logging.Level.FINE, sb.toString());
234            log.log(java.util.logging.Level.FINE, "Data to be signed/verified:" + Base64.encode(signedInfoBytes));
235        }
236
237        this.canonData = new ByteArrayInputStream(signedInfoBytes);
238
239        try {
240            os.close();
241        } catch (IOException e) {
242            if (log.isLoggable(java.util.logging.Level.FINE)) {
243                log.log(java.util.logging.Level.FINE, e.getMessage(), e);
244            }
245            // Impossible
246        }
247    }
248
249    public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
250        throws MarshalException
251    {
252        ownerDoc = DOMUtils.getOwnerDocument(parent);
253        Element siElem = DOMUtils.createElement(ownerDoc, "SignedInfo",
254                                                XMLSignature.XMLNS, dsPrefix);
255
256        // create and append CanonicalizationMethod element
257        DOMCanonicalizationMethod dcm =
258            (DOMCanonicalizationMethod)canonicalizationMethod;
259        dcm.marshal(siElem, dsPrefix, context);
260
261        // create and append SignatureMethod element
262        ((DOMStructure)signatureMethod).marshal(siElem, dsPrefix, context);
263
264        // create and append Reference elements
265        for (Reference reference : references) {
266            ((DOMReference)reference).marshal(siElem, dsPrefix, context);
267        }
268
269        // append Id attribute
270        DOMUtils.setAttributeID(siElem, "Id", id);
271
272        parent.appendChild(siElem);
273        localSiElem = siElem;
274    }
275
276    @Override
277    public boolean equals(Object o) {
278        if (this == o) {
279            return true;
280        }
281
282        if (!(o instanceof SignedInfo)) {
283            return false;
284        }
285        SignedInfo osi = (SignedInfo)o;
286
287        boolean idEqual = (id == null ? osi.getId() == null
288                                      : id.equals(osi.getId()));
289
290        return (canonicalizationMethod.equals(osi.getCanonicalizationMethod())
291                && signatureMethod.equals(osi.getSignatureMethod()) &&
292                references.equals(osi.getReferences()) && idEqual);
293    }
294
295    @Override
296    public int hashCode() {
297        int result = 17;
298        if (id != null) {
299            result = 31 * result + id.hashCode();
300        }
301        result = 31 * result + canonicalizationMethod.hashCode();
302        result = 31 * result + signatureMethod.hashCode();
303        result = 31 * result + references.hashCode();
304
305        return result;
306    }
307}
308