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, 2014, Oracle and/or its affiliates. All rights reserved.
25 */
26/*
27 * $Id: DOMKeyInfo.java 1333869 2012-05-04 10:42:44Z coheigea $
28 */
29package org.jcp.xml.dsig.internal.dom;
30
31import javax.xml.crypto.*;
32import javax.xml.crypto.dsig.*;
33import javax.xml.crypto.dsig.keyinfo.KeyInfo;
34import javax.xml.crypto.dom.*;
35
36import java.security.Provider;
37import java.util.*;
38
39import org.w3c.dom.Attr;
40import org.w3c.dom.Document;
41import org.w3c.dom.Element;
42import org.w3c.dom.Node;
43import org.w3c.dom.NodeList;
44
45/**
46 * DOM-based implementation of KeyInfo.
47 *
48 * @author Sean Mullan
49 */
50public final class DOMKeyInfo extends DOMStructure implements KeyInfo {
51
52    private final String id;
53    private final List<XMLStructure> keyInfoTypes;
54
55    /**
56     * Creates a <code>DOMKeyInfo</code>.
57     *
58     * @param content a list of one or more {@link XMLStructure}s representing
59     *    key information types. The list is defensively copied to protect
60     *    against subsequent modification.
61     * @param id an ID attribute
62     * @throws NullPointerException if <code>content</code> is <code>null</code>
63     * @throws IllegalArgumentException if <code>content</code> is empty
64     * @throws ClassCastException if <code>content</code> contains any entries
65     *    that are not of type {@link XMLStructure}
66     */
67    public DOMKeyInfo(List<? extends XMLStructure> content, String id) {
68        if (content == null) {
69            throw new NullPointerException("content cannot be null");
70        }
71        List<XMLStructure> tempList =
72            Collections.checkedList(new ArrayList<XMLStructure>(),
73                                    XMLStructure.class);
74        tempList.addAll(content);
75        this.keyInfoTypes = Collections.unmodifiableList(tempList);
76        if (this.keyInfoTypes.isEmpty()) {
77            throw new IllegalArgumentException("content cannot be empty");
78        }
79        this.id = id;
80    }
81
82    /**
83     * Creates a <code>DOMKeyInfo</code> from XML.
84     *
85     * @param kiElem KeyInfo element
86     */
87    public DOMKeyInfo(Element kiElem, XMLCryptoContext context,
88                      Provider provider)
89        throws MarshalException
90    {
91        // get Id attribute, if specified
92        Attr attr = kiElem.getAttributeNodeNS(null, "Id");
93        if (attr != null) {
94            id = attr.getValue();
95            kiElem.setIdAttributeNode(attr, true);
96        } else {
97            id = null;
98        }
99
100        // get all children nodes
101        NodeList nl = kiElem.getChildNodes();
102        int length = nl.getLength();
103        if (length < 1) {
104            throw new MarshalException
105                ("KeyInfo must contain at least one type");
106        }
107        List<XMLStructure> content = new ArrayList<XMLStructure>(length);
108        for (int i = 0; i < length; i++) {
109            Node child = nl.item(i);
110            // ignore all non-Element nodes
111            if (child.getNodeType() != Node.ELEMENT_NODE) {
112                continue;
113            }
114            Element childElem = (Element)child;
115            String localName = childElem.getLocalName();
116            if (localName.equals("X509Data")) {
117                content.add(new DOMX509Data(childElem));
118            } else if (localName.equals("KeyName")) {
119                content.add(new DOMKeyName(childElem));
120            } else if (localName.equals("KeyValue")) {
121                content.add(DOMKeyValue.unmarshal(childElem));
122            } else if (localName.equals("RetrievalMethod")) {
123                content.add(new DOMRetrievalMethod(childElem,
124                                                   context, provider));
125            } else if (localName.equals("PGPData")) {
126                content.add(new DOMPGPData(childElem));
127            } else { //may be MgmtData, SPKIData or element from other namespace
128                content.add(new javax.xml.crypto.dom.DOMStructure((childElem)));
129            }
130        }
131        keyInfoTypes = Collections.unmodifiableList(content);
132    }
133
134    public String getId() {
135        return id;
136    }
137
138    public List<XMLStructure> getContent() {
139        return keyInfoTypes;
140    }
141
142    public void marshal(XMLStructure parent, XMLCryptoContext context)
143        throws MarshalException
144    {
145        if (parent == null) {
146            throw new NullPointerException("parent is null");
147        }
148        if (!(parent instanceof javax.xml.crypto.dom.DOMStructure)) {
149            throw new ClassCastException("parent must be of type DOMStructure");
150        }
151
152        Node pNode = ((javax.xml.crypto.dom.DOMStructure)parent).getNode();
153        String dsPrefix = DOMUtils.getSignaturePrefix(context);
154        Element kiElem = DOMUtils.createElement
155            (DOMUtils.getOwnerDocument(pNode), "KeyInfo",
156             XMLSignature.XMLNS, dsPrefix);
157        if (dsPrefix == null || dsPrefix.length() == 0) {
158            kiElem.setAttributeNS("http://www.w3.org/2000/xmlns/",
159                                  "xmlns", XMLSignature.XMLNS);
160        } else {
161            kiElem.setAttributeNS("http://www.w3.org/2000/xmlns/",
162                                  "xmlns:" + dsPrefix, XMLSignature.XMLNS);
163        }
164        marshal(pNode, kiElem, null, dsPrefix, (DOMCryptoContext)context);
165    }
166
167    public void marshal(Node parent, String dsPrefix,
168                        DOMCryptoContext context)
169        throws MarshalException
170    {
171        marshal(parent, null, dsPrefix, context);
172    }
173
174    public void marshal(Node parent, Node nextSibling, String dsPrefix,
175                        DOMCryptoContext context)
176        throws MarshalException
177    {
178        Document ownerDoc = DOMUtils.getOwnerDocument(parent);
179        Element kiElem = DOMUtils.createElement(ownerDoc, "KeyInfo",
180                                                XMLSignature.XMLNS, dsPrefix);
181        marshal(parent, kiElem, nextSibling, dsPrefix, context);
182    }
183
184    private void marshal(Node parent, Element kiElem, Node nextSibling,
185                         String dsPrefix, DOMCryptoContext context)
186        throws MarshalException
187    {
188        // create and append KeyInfoType elements
189        for (XMLStructure kiType : keyInfoTypes) {
190            if (kiType instanceof DOMStructure) {
191                ((DOMStructure)kiType).marshal(kiElem, dsPrefix, context);
192            } else {
193                DOMUtils.appendChild(kiElem,
194                    ((javax.xml.crypto.dom.DOMStructure)kiType).getNode());
195            }
196        }
197
198        // append id attribute
199        DOMUtils.setAttributeID(kiElem, "Id", id);
200
201        parent.insertBefore(kiElem, nextSibling);
202    }
203
204    @Override
205    public boolean equals(Object o) {
206        if (this == o) {
207            return true;
208        }
209
210        if (!(o instanceof KeyInfo)) {
211            return false;
212        }
213        KeyInfo oki = (KeyInfo)o;
214
215        boolean idsEqual = (id == null ? oki.getId() == null
216                                       : id.equals(oki.getId()));
217
218        return (keyInfoTypes.equals(oki.getContent()) && idsEqual);
219    }
220
221    @Override
222    public int hashCode() {
223        int result = 17;
224        if (id != null) {
225            result = 31 * result + id.hashCode();
226        }
227        result = 31 * result + keyInfoTypes.hashCode();
228
229        return result;
230    }
231}
232