1 /*
   2  * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.pkcs;
  27 
  28 import java.io.IOException;
  29 import java.io.OutputStream;
  30 import java.util.Hashtable;
  31 import sun.security.util.DerEncoder;
  32 import sun.security.util.DerValue;
  33 import sun.security.util.DerInputStream;
  34 import sun.security.util.DerOutputStream;
  35 import sun.security.util.ObjectIdentifier;
  36 
  37 /**
  38  * A set of attributes of class PKCS9Attribute.
  39  *
  40  * @author Douglas Hoover
  41  */
  42 public class PKCS9Attributes {
  43     /**
  44      * Attributes in this set indexed by OID.
  45      */
  46     private final Hashtable<ObjectIdentifier, PKCS9Attribute> attributes =
  47         new Hashtable<ObjectIdentifier, PKCS9Attribute>(3);
  48 
  49     /**
  50      * The keys of this hashtable are the OIDs of permitted attributes.
  51      */
  52     private final Hashtable<ObjectIdentifier, ObjectIdentifier> permittedAttributes;
  53 
  54     /**
  55      * The DER encoding of this attribute set.  The tag byte must be
  56      * DerValue.tag_SetOf.
  57      */
  58     private final byte[] derEncoding;
  59 
  60     /*
  61      * Contols how attributes, which are not recognized by the PKCS9Attribute
  62      * class, are handled during parsing.
  63      */
  64     private boolean ignoreUnsupportedAttributes = false;
  65 
  66     /**
  67      * Construct a set of PKCS9 Attributes from its
  68      * DER encoding on a DerInputStream, accepting only attributes
  69      * with OIDs on the given
  70      * list.  If the array is null, accept all attributes supported by
  71      * class PKCS9Attribute.
  72      *
  73      * @param permittedAttributes
  74      * Array of attribute OIDs that will be accepted.
  75      * @param in
  76      * the contents of the DER encoding of the attribute set.
  77      *
  78      * @exception IOException
  79      * on i/o error, encoding syntax error, unacceptable or
  80      * unsupported attribute, or duplicate attribute.
  81      *
  82      * @see PKCS9Attribute
  83      */
  84     public PKCS9Attributes(ObjectIdentifier[] permittedAttributes,
  85                            DerInputStream in) throws IOException {
  86         if (permittedAttributes != null) {
  87             this.permittedAttributes =
  88                 new Hashtable<>(permittedAttributes.length);
  89 
  90             for (int i = 0; i < permittedAttributes.length; i++)
  91                 this.permittedAttributes.put(permittedAttributes[i],
  92                                              permittedAttributes[i]);
  93         } else {
  94             this.permittedAttributes = null;
  95         }
  96 
  97         // derEncoding initialized in <code>decode()</code>
  98         derEncoding = decode(in);
  99     }
 100 
 101     /**
 102      * Construct a set of PKCS9 Attributes from the contents of its
 103      * DER encoding on a DerInputStream.  Accept all attributes
 104      * supported by class PKCS9Attribute and reject any unsupported
 105      * attributes.
 106      *
 107      * @param in the contents of the DER encoding of the attribute set.
 108      * @exception IOException
 109      * on i/o error, encoding syntax error, or unsupported or
 110      * duplicate attribute.
 111      *
 112      * @see PKCS9Attribute
 113      */
 114     public PKCS9Attributes(DerInputStream in) throws IOException {
 115         this(in, false);
 116     }
 117 
 118     /**
 119      * Construct a set of PKCS9 Attributes from the contents of its
 120      * DER encoding on a DerInputStream.  Accept all attributes
 121      * supported by class PKCS9Attribute and ignore any unsupported
 122      * attributes, if directed.
 123      *
 124      * @param in the contents of the DER encoding of the attribute set.
 125      * @param ignoreUnsupportedAttributes If true then any attributes
 126      * not supported by the PKCS9Attribute class are ignored. Otherwise
 127      * unsupported attributes cause an exception to be thrown.
 128      * @exception IOException
 129      * on i/o error, encoding syntax error, or unsupported or
 130      * duplicate attribute.
 131      *
 132      * @see PKCS9Attribute
 133      */
 134     public PKCS9Attributes(DerInputStream in,
 135         boolean ignoreUnsupportedAttributes) throws IOException {
 136 
 137         this.ignoreUnsupportedAttributes = ignoreUnsupportedAttributes;
 138         // derEncoding initialized in <code>decode()</code>
 139         derEncoding = decode(in);
 140         permittedAttributes = null;
 141     }
 142 
 143     /**
 144      * Construct a set of PKCS9 Attributes from the given array of
 145      * PKCS9 attributes.
 146      * DER encoding on a DerInputStream.  All attributes in
 147      * <code>attribs</code> must be
 148      * supported by class PKCS9Attribute.
 149      *
 150      * @exception IOException
 151      * on i/o error, encoding syntax error, or unsupported or
 152      * duplicate attribute.
 153      *
 154      * @see PKCS9Attribute
 155      */
 156     public PKCS9Attributes(PKCS9Attribute[] attribs)
 157     throws IllegalArgumentException, IOException {
 158         ObjectIdentifier oid;
 159         for (int i=0; i < attribs.length; i++) {
 160             oid = attribs[i].getOID();
 161             if (attributes.containsKey(oid))
 162                 throw new IllegalArgumentException(
 163                           "PKCSAttribute " + attribs[i].getOID() +
 164                           " duplicated while constructing " +
 165                           "PKCS9Attributes.");
 166 
 167             attributes.put(oid, attribs[i]);
 168         }
 169         derEncoding = generateDerEncoding();
 170         permittedAttributes = null;
 171     }
 172 
 173 
 174     /**
 175      * Decode this set of PKCS9 attributes from the contents of its
 176      * DER encoding. Ignores unsupported attributes when directed.
 177      *
 178      * @param in
 179      * the contents of the DER encoding of the attribute set.
 180      *
 181      * @exception IOException
 182      * on i/o error, encoding syntax error, unacceptable or
 183      * unsupported attribute, or duplicate attribute.
 184      */
 185     private byte[] decode(DerInputStream in) throws IOException {
 186 
 187         DerValue val = in.getDerValue();
 188 
 189         // save the DER encoding with its proper tag byte.
 190         byte[] derEncoding = val.toByteArray();
 191         derEncoding[0] = DerValue.tag_SetOf;
 192 
 193         DerInputStream derIn = new DerInputStream(derEncoding);
 194         DerValue[] derVals = derIn.getSet(3,true);
 195 
 196         PKCS9Attribute attrib;
 197         ObjectIdentifier oid;
 198         boolean reuseEncoding = true;
 199 
 200         for (int i=0; i < derVals.length; i++) {
 201 
 202             try {
 203                 attrib = new PKCS9Attribute(derVals[i]);
 204 
 205             } catch (ParsingException e) {
 206                 if (ignoreUnsupportedAttributes) {
 207                     reuseEncoding = false; // cannot reuse supplied DER encoding
 208                     continue; // skip
 209                 } else {
 210                     throw e;
 211                 }
 212             }
 213             oid = attrib.getOID();
 214 
 215             if (attributes.get(oid) != null)
 216                 throw new IOException("Duplicate PKCS9 attribute: " + oid);
 217 
 218             if (permittedAttributes != null &&
 219                 !permittedAttributes.containsKey(oid))
 220                 throw new IOException("Attribute " + oid +
 221                                       " not permitted in this attribute set");
 222 
 223             attributes.put(oid, attrib);
 224         }
 225         return reuseEncoding ? derEncoding : generateDerEncoding();
 226     }
 227 
 228     /**
 229      * Put the DER encoding of this PKCS9 attribute set on an
 230      * DerOutputStream, tagged with the given implicit tag.
 231      *
 232      * @param tag the implicit tag to use in the DER encoding.
 233      * @param out the output stream on which to put the DER encoding.
 234      *
 235      * @exception IOException  on output error.
 236      */
 237     public void encode(byte tag, OutputStream out) throws IOException {
 238         out.write(tag);
 239         out.write(derEncoding, 1, derEncoding.length -1);
 240     }
 241 
 242     private byte[] generateDerEncoding() throws IOException {
 243         DerOutputStream out = new DerOutputStream();
 244         Object[] attribVals = attributes.values().toArray();
 245 
 246         out.putOrderedSetOf(DerValue.tag_SetOf,
 247                             castToDerEncoder(attribVals));
 248         return out.toByteArray();
 249     }
 250 
 251     /**
 252      * Return the DER encoding of this attribute set, tagged with
 253      * DerValue.tag_SetOf.
 254      */
 255     public byte[] getDerEncoding() throws IOException {
 256         return derEncoding.clone();
 257 
 258     }
 259 
 260     /**
 261      * Get an attribute from this set.
 262      */
 263     public PKCS9Attribute getAttribute(ObjectIdentifier oid) {
 264         return attributes.get(oid);
 265     }
 266 
 267     /**
 268      * Get an attribute from this set.
 269      */
 270     public PKCS9Attribute getAttribute(String name) {
 271         return attributes.get(PKCS9Attribute.getOID(name));
 272     }
 273 
 274 
 275     /**
 276      * Get an array of all attributes in this set, in order of OID.
 277      */
 278     public PKCS9Attribute[] getAttributes() {
 279         PKCS9Attribute[] attribs = new PKCS9Attribute[attributes.size()];
 280         ObjectIdentifier oid;
 281 
 282         int j = 0;
 283         for (int i=1; i < PKCS9Attribute.PKCS9_OIDS.length &&
 284                       j < attribs.length; i++) {
 285             attribs[j] = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]);
 286 
 287             if (attribs[j] != null)
 288                 j++;
 289         }
 290         return attribs;
 291     }
 292 
 293     /**
 294      * Get an attribute value by OID.
 295      */
 296     public Object getAttributeValue(ObjectIdentifier oid)
 297     throws IOException {
 298         try {
 299             Object value = getAttribute(oid).getValue();
 300             return value;
 301         } catch (NullPointerException ex) {
 302             throw new IOException("No value found for attribute " + oid);
 303         }
 304 
 305     }
 306 
 307     /**
 308      *  Get an attribute value by type name.
 309      */
 310     public Object getAttributeValue(String name) throws IOException {
 311         ObjectIdentifier oid = PKCS9Attribute.getOID(name);
 312 
 313         if (oid == null)
 314             throw new IOException("Attribute name " + name +
 315                                   " not recognized or not supported.");
 316 
 317         return getAttributeValue(oid);
 318     }
 319 
 320 
 321     /**
 322      * Returns the PKCS9 block in a printable string form.
 323      */
 324     public String toString() {
 325         StringBuilder sb = new StringBuilder(200);
 326         sb.append("PKCS9 Attributes: [\n\t");
 327 
 328         ObjectIdentifier oid;
 329         PKCS9Attribute value;
 330 
 331         boolean first = true;
 332         for (int i = 1; i < PKCS9Attribute.PKCS9_OIDS.length; i++) {
 333             value = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]);
 334 
 335             if (value == null) continue;
 336 
 337             // we have a value; print it
 338             if (first)
 339                 first = false;
 340             else
 341                 sb.append(";\n\t");
 342 
 343             sb.append(value.toString());
 344         }
 345 
 346         sb.append("\n\t] (end PKCS9 Attributes)");
 347 
 348         return sb.toString();
 349     }
 350 
 351     /**
 352      * Cast an object array whose components are
 353      * <code>DerEncoder</code>s to <code>DerEncoder[]</code>.
 354      */
 355     static DerEncoder[] castToDerEncoder(Object[] objs) {
 356 
 357         DerEncoder[] encoders = new DerEncoder[objs.length];
 358 
 359         for (int i=0; i < encoders.length; i++)
 360             encoders[i] = (DerEncoder) objs[i];
 361 
 362         return encoders;
 363     }
 364 }