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: DOMPGPData.java 1203846 2011-11-18 21:18:17Z mullan $
28 */
29package org.jcp.xml.dsig.internal.dom;
30
31import java.util.*;
32import javax.xml.crypto.*;
33import javax.xml.crypto.dom.DOMCryptoContext;
34import javax.xml.crypto.dsig.*;
35import javax.xml.crypto.dsig.keyinfo.PGPData;
36import org.w3c.dom.Document;
37import org.w3c.dom.Element;
38import org.w3c.dom.Node;
39import org.w3c.dom.NodeList;
40
41import com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
42import com.sun.org.apache.xml.internal.security.utils.Base64;
43
44/**
45 * DOM-based implementation of PGPData.
46 *
47 * @author Sean Mullan
48 */
49public final class DOMPGPData extends DOMStructure implements PGPData {
50
51    private final byte[] keyId;
52    private final byte[] keyPacket;
53    private final List<XMLStructure> externalElements;
54
55    /**
56     * Creates a <code>DOMPGPData</code> containing the specified key packet.
57     * and optional list of external elements.
58     *
59     * @param keyPacket a PGP Key Material Packet as defined in section 5.5 of
60     *    <a href="http://www.ietf.org/rfc/rfc2440.txt">RFC 2440</a>. The
61     *    array is cloned to prevent subsequent modification.
62     * @param other a list of {@link XMLStructure}s representing elements from
63     *    an external namespace. The list is defensively copied to prevent
64     *    subsequent modification. May be <code>null</code> or empty.
65     * @throws NullPointerException if <code>keyPacket</code> is
66     *    <code>null</code>
67     * @throws IllegalArgumentException if the key packet is not in the
68     *    correct format
69     * @throws ClassCastException if <code>other</code> contains any
70     *    entries that are not of type {@link XMLStructure}
71     */
72    public DOMPGPData(byte[] keyPacket, List<? extends XMLStructure> other) {
73        if (keyPacket == null) {
74            throw new NullPointerException("keyPacket cannot be null");
75        }
76        List<XMLStructure> tempList =
77            Collections.checkedList(new ArrayList<XMLStructure>(),
78                                    XMLStructure.class);
79        if (other != null) {
80            tempList.addAll(other);
81        }
82        this.externalElements = Collections.unmodifiableList(tempList);
83        this.keyPacket = keyPacket.clone();
84        checkKeyPacket(keyPacket);
85        this.keyId = null;
86    }
87
88    /**
89     * Creates a <code>DOMPGPData</code> containing the specified key id and
90     * optional key packet and list of external elements.
91     *
92     * @param keyId a PGP public key id as defined in section 11.2 of
93     *    <a href="http://www.ietf.org/rfc/rfc2440.txt">RFC 2440</a>. The
94     *    array is cloned to prevent subsequent modification.
95     * @param keyPacket a PGP Key Material Packet as defined in section 5.5 of
96     *    <a href="http://www.ietf.org/rfc/rfc2440.txt">RFC 2440</a> (may
97     *    be <code>null</code>). The array is cloned to prevent subsequent
98     *    modification.
99     * @param other a list of {@link XMLStructure}s representing elements from
100     *    an external namespace. The list is defensively copied to prevent
101     *    subsequent modification. May be <code>null</code> or empty.
102     * @throws NullPointerException if <code>keyId</code> is <code>null</code>
103     * @throws IllegalArgumentException if the key id or packet is not in the
104     *    correct format
105     * @throws ClassCastException if <code>other</code> contains any
106     *    entries that are not of type {@link XMLStructure}
107     */
108    public DOMPGPData(byte[] keyId, byte[] keyPacket,
109                      List<? extends XMLStructure> other)
110    {
111        if (keyId == null) {
112            throw new NullPointerException("keyId cannot be null");
113        }
114        // key ids must be 8 bytes
115        if (keyId.length != 8) {
116            throw new IllegalArgumentException("keyId must be 8 bytes long");
117        }
118        List<XMLStructure> tempList =
119            Collections.checkedList(new ArrayList<XMLStructure>(),
120                                    XMLStructure.class);
121        if (other != null) {
122            tempList.addAll(other);
123        }
124        this.externalElements = Collections.unmodifiableList(tempList);
125        this.keyId = keyId.clone();
126        this.keyPacket = keyPacket == null ? null
127                                           : keyPacket.clone();
128        if (keyPacket != null) {
129            checkKeyPacket(keyPacket);
130        }
131    }
132
133    /**
134     * Creates a <code>DOMPGPData</code> from an element.
135     *
136     * @param pdElem a PGPData element
137     */
138    public DOMPGPData(Element pdElem) throws MarshalException {
139        // get all children nodes
140        byte[] keyId = null;
141        byte[] keyPacket = null;
142        NodeList nl = pdElem.getChildNodes();
143        int length = nl.getLength();
144        List<XMLStructure> other = new ArrayList<XMLStructure>(length);
145        for (int x = 0; x < length; x++) {
146            Node n = nl.item(x);
147            if (n.getNodeType() == Node.ELEMENT_NODE) {
148                Element childElem = (Element)n;
149                String localName = childElem.getLocalName();
150                try {
151                    if (localName.equals("PGPKeyID")) {
152                        keyId = Base64.decode(childElem);
153                    } else if (localName.equals("PGPKeyPacket")){
154                        keyPacket = Base64.decode(childElem);
155                    } else {
156                        other.add
157                            (new javax.xml.crypto.dom.DOMStructure(childElem));
158                    }
159                } catch (Base64DecodingException bde) {
160                    throw new MarshalException(bde);
161                }
162            }
163        }
164        this.keyId = keyId;
165        this.keyPacket = keyPacket;
166        this.externalElements = Collections.unmodifiableList(other);
167    }
168
169    public byte[] getKeyId() {
170        return (keyId == null ? null : keyId.clone());
171    }
172
173    public byte[] getKeyPacket() {
174        return (keyPacket == null ? null : keyPacket.clone());
175    }
176
177    public List<XMLStructure> getExternalElements() {
178        return externalElements;
179    }
180
181    public void marshal(Node parent, String dsPrefix, DOMCryptoContext context)
182        throws MarshalException
183    {
184        Document ownerDoc = DOMUtils.getOwnerDocument(parent);
185        Element pdElem = DOMUtils.createElement(ownerDoc, "PGPData",
186                                                XMLSignature.XMLNS, dsPrefix);
187
188        // create and append PGPKeyID element
189        if (keyId != null) {
190            Element keyIdElem = DOMUtils.createElement(ownerDoc, "PGPKeyID",
191                                                       XMLSignature.XMLNS,
192                                                       dsPrefix);
193            keyIdElem.appendChild
194                (ownerDoc.createTextNode(Base64.encode(keyId)));
195            pdElem.appendChild(keyIdElem);
196        }
197
198        // create and append PGPKeyPacket element
199        if (keyPacket != null) {
200            Element keyPktElem = DOMUtils.createElement(ownerDoc,
201                                                        "PGPKeyPacket",
202                                                        XMLSignature.XMLNS,
203                                                        dsPrefix);
204            keyPktElem.appendChild
205                (ownerDoc.createTextNode(Base64.encode(keyPacket)));
206            pdElem.appendChild(keyPktElem);
207        }
208
209        // create and append any elements
210        for (XMLStructure extElem : externalElements) {
211            DOMUtils.appendChild(pdElem, ((javax.xml.crypto.dom.DOMStructure)
212                extElem).getNode());
213        }
214
215        parent.appendChild(pdElem);
216    }
217
218    /**
219     * We assume packets use the new format packet syntax, as specified in
220     * section 4 of RFC 2440.
221     *
222     * This method only checks if the packet contains a valid tag. The
223     * contents of the packet should be checked by the application.
224     */
225    private void checkKeyPacket(byte[] keyPacket) {
226        // length must be at least 3 (one byte for tag, one byte for length,
227        // and minimally one byte of content
228        if (keyPacket.length < 3) {
229            throw new IllegalArgumentException("keypacket must be at least " +
230                                               "3 bytes long");
231        }
232
233        int tag = keyPacket[0];
234        // first bit must be set
235        if ((tag & 128) != 128) {
236            throw new IllegalArgumentException("keypacket tag is invalid: " +
237                                               "bit 7 is not set");
238        }
239        // make sure using new format
240        if ((tag & 64) != 64) {
241            throw new IllegalArgumentException("old keypacket tag format is " +
242                                               "unsupported");
243        }
244
245        // tag value must be 6, 14, 5 or 7
246        if (((tag & 6) != 6) && ((tag & 14) != 14) &&
247            ((tag & 5) != 5) && ((tag & 7) != 7)) {
248            throw new IllegalArgumentException("keypacket tag is invalid: " +
249                                               "must be 6, 14, 5, or 7");
250        }
251    }
252}
253