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