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      */
PKCS9Attributes(ObjectIdentifier[] permittedAttributes, DerInputStream in)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      */
PKCS9Attributes(DerInputStream in)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      */
PKCS9Attributes(DerInputStream in, boolean ignoreUnsupportedAttributes)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      */
PKCS9Attributes(PKCS9Attribute[] attribs)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      */
decode(DerInputStream in)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      */
encode(byte tag, OutputStream out)237     public void encode(byte tag, OutputStream out) throws IOException {
238         out.write(tag);
239         out.write(derEncoding, 1, derEncoding.length -1);
240     }
241 
generateDerEncoding()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      */
getDerEncoding()255     public byte[] getDerEncoding() throws IOException {
256         return derEncoding.clone();
257 
258     }
259 
260     /**
261      * Get an attribute from this set.
262      */
getAttribute(ObjectIdentifier oid)263     public PKCS9Attribute getAttribute(ObjectIdentifier oid) {
264         return attributes.get(oid);
265     }
266 
267     /**
268      * Get an attribute from this set.
269      */
getAttribute(String name)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      */
getAttributes()278     public PKCS9Attribute[] getAttributes() {
279         PKCS9Attribute[] attribs = new PKCS9Attribute[attributes.size()];
280 
281         int j = 0;
282         for (int i=1; i < PKCS9Attribute.PKCS9_OIDS.length &&
283                       j < attribs.length; i++) {
284             if (PKCS9Attribute.PKCS9_OIDS[i] == null) {
285                 continue;
286             }
287             attribs[j] = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]);
288 
289             if (attribs[j] != null)
290                 j++;
291         }
292         return attribs;
293     }
294 
295     /**
296      * Get an attribute value by OID.
297      */
getAttributeValue(ObjectIdentifier oid)298     public Object getAttributeValue(ObjectIdentifier oid)
299     throws IOException {
300         try {
301             Object value = getAttribute(oid).getValue();
302             return value;
303         } catch (NullPointerException ex) {
304             throw new IOException("No value found for attribute " + oid);
305         }
306 
307     }
308 
309     /**
310      *  Get an attribute value by type name.
311      */
getAttributeValue(String name)312     public Object getAttributeValue(String name) throws IOException {
313         ObjectIdentifier oid = PKCS9Attribute.getOID(name);
314 
315         if (oid == null)
316             throw new IOException("Attribute name " + name +
317                                   " not recognized or not supported.");
318 
319         return getAttributeValue(oid);
320     }
321 
322 
323     /**
324      * Returns the PKCS9 block in a printable string form.
325      */
toString()326     public String toString() {
327         StringBuilder sb = new StringBuilder(200);
328         sb.append("PKCS9 Attributes: [\n\t");
329 
330         PKCS9Attribute value;
331 
332         boolean first = true;
333         for (int i = 1; i < PKCS9Attribute.PKCS9_OIDS.length; i++) {
334             if (PKCS9Attribute.PKCS9_OIDS[i] == null) {
335                 continue;
336             }
337             value = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]);
338 
339             if (value == null) continue;
340 
341             // we have a value; print it
342             if (first)
343                 first = false;
344             else
345                 sb.append(";\n\t");
346 
347             sb.append(value);
348         }
349 
350         sb.append("\n\t] (end PKCS9 Attributes)");
351 
352         return sb.toString();
353     }
354 
355     /**
356      * Cast an object array whose components are
357      * <code>DerEncoder</code>s to <code>DerEncoder[]</code>.
358      */
castToDerEncoder(Object[] objs)359     static DerEncoder[] castToDerEncoder(Object[] objs) {
360 
361         DerEncoder[] encoders = new DerEncoder[objs.length];
362 
363         for (int i=0; i < encoders.length; i++)
364             encoders[i] = (DerEncoder) objs[i];
365 
366         return encoders;
367     }
368 }
369