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
26package sun.security.pkcs;
27
28import java.io.IOException;
29import java.io.OutputStream;
30import java.util.Hashtable;
31import sun.security.util.DerEncoder;
32import sun.security.util.DerValue;
33import sun.security.util.DerInputStream;
34import sun.security.util.DerOutputStream;
35import sun.security.util.ObjectIdentifier;
36
37/**
38 * A set of attributes of class PKCS9Attribute.
39 *
40 * @author Douglas Hoover
41 */
42public 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<ObjectIdentifier, ObjectIdentifier>(
89                                                permittedAttributes.length);
90
91            for (int i = 0; i < permittedAttributes.length; i++)
92                this.permittedAttributes.put(permittedAttributes[i],
93                                             permittedAttributes[i]);
94        } else {
95            this.permittedAttributes = null;
96        }
97
98        // derEncoding initialized in <code>decode()</code>
99        derEncoding = decode(in);
100    }
101
102    /**
103     * Construct a set of PKCS9 Attributes from the contents of its
104     * DER encoding on a DerInputStream.  Accept all attributes
105     * supported by class PKCS9Attribute and reject any unsupported
106     * attributes.
107     *
108     * @param in the contents of the DER encoding of the attribute set.
109     * @exception IOException
110     * on i/o error, encoding syntax error, or unsupported or
111     * duplicate attribute.
112     *
113     * @see PKCS9Attribute
114     */
115    public PKCS9Attributes(DerInputStream in) throws IOException {
116        this(in, false);
117    }
118
119    /**
120     * Construct a set of PKCS9 Attributes from the contents of its
121     * DER encoding on a DerInputStream.  Accept all attributes
122     * supported by class PKCS9Attribute and ignore any unsupported
123     * attributes, if directed.
124     *
125     * @param in the contents of the DER encoding of the attribute set.
126     * @param ignoreUnsupportedAttributes If true then any attributes
127     * not supported by the PKCS9Attribute class are ignored. Otherwise
128     * unsupported attributes cause an exception to be thrown.
129     * @exception IOException
130     * on i/o error, encoding syntax error, or unsupported or
131     * duplicate attribute.
132     *
133     * @see PKCS9Attribute
134     */
135    public PKCS9Attributes(DerInputStream in,
136        boolean ignoreUnsupportedAttributes) throws IOException {
137
138        this.ignoreUnsupportedAttributes = ignoreUnsupportedAttributes;
139        // derEncoding initialized in <code>decode()</code>
140        derEncoding = decode(in);
141        permittedAttributes = null;
142    }
143
144    /**
145     * Construct a set of PKCS9 Attributes from the given array of
146     * PKCS9 attributes.
147     * DER encoding on a DerInputStream.  All attributes in
148     * <code>attribs</code> must be
149     * supported by class PKCS9Attribute.
150     *
151     * @exception IOException
152     * on i/o error, encoding syntax error, or unsupported or
153     * duplicate attribute.
154     *
155     * @see PKCS9Attribute
156     */
157    public PKCS9Attributes(PKCS9Attribute[] attribs)
158    throws IllegalArgumentException, IOException {
159        ObjectIdentifier oid;
160        for (int i=0; i < attribs.length; i++) {
161            oid = attribs[i].getOID();
162            if (attributes.containsKey(oid))
163                throw new IllegalArgumentException(
164                          "PKCSAttribute " + attribs[i].getOID() +
165                          " duplicated while constructing " +
166                          "PKCS9Attributes.");
167
168            attributes.put(oid, attribs[i]);
169        }
170        derEncoding = generateDerEncoding();
171        permittedAttributes = null;
172    }
173
174
175    /**
176     * Decode this set of PKCS9 attributes from the contents of its
177     * DER encoding. Ignores unsupported attributes when directed.
178     *
179     * @param in
180     * the contents of the DER encoding of the attribute set.
181     *
182     * @exception IOException
183     * on i/o error, encoding syntax error, unacceptable or
184     * unsupported attribute, or duplicate attribute.
185     */
186    private byte[] decode(DerInputStream in) throws IOException {
187
188        DerValue val = in.getDerValue();
189
190        // save the DER encoding with its proper tag byte.
191        byte[] derEncoding = val.toByteArray();
192        derEncoding[0] = DerValue.tag_SetOf;
193
194        DerInputStream derIn = new DerInputStream(derEncoding);
195        DerValue[] derVals = derIn.getSet(3,true);
196
197        PKCS9Attribute attrib;
198        ObjectIdentifier oid;
199        boolean reuseEncoding = true;
200
201        for (int i=0; i < derVals.length; i++) {
202
203            try {
204                attrib = new PKCS9Attribute(derVals[i]);
205
206            } catch (ParsingException e) {
207                if (ignoreUnsupportedAttributes) {
208                    reuseEncoding = false; // cannot reuse supplied DER encoding
209                    continue; // skip
210                } else {
211                    throw e;
212                }
213            }
214            oid = attrib.getOID();
215
216            if (attributes.get(oid) != null)
217                throw new IOException("Duplicate PKCS9 attribute: " + oid);
218
219            if (permittedAttributes != null &&
220                !permittedAttributes.containsKey(oid))
221                throw new IOException("Attribute " + oid +
222                                      " not permitted in this attribute set");
223
224            attributes.put(oid, attrib);
225        }
226        return reuseEncoding ? derEncoding : generateDerEncoding();
227    }
228
229    /**
230     * Put the DER encoding of this PKCS9 attribute set on an
231     * DerOutputStream, tagged with the given implicit tag.
232     *
233     * @param tag the implicit tag to use in the DER encoding.
234     * @param out the output stream on which to put the DER encoding.
235     *
236     * @exception IOException  on output error.
237     */
238    public void encode(byte tag, OutputStream out) throws IOException {
239        out.write(tag);
240        out.write(derEncoding, 1, derEncoding.length -1);
241    }
242
243    private byte[] generateDerEncoding() throws IOException {
244        DerOutputStream out = new DerOutputStream();
245        Object[] attribVals = attributes.values().toArray();
246
247        out.putOrderedSetOf(DerValue.tag_SetOf,
248                            castToDerEncoder(attribVals));
249        return out.toByteArray();
250    }
251
252    /**
253     * Return the DER encoding of this attribute set, tagged with
254     * DerValue.tag_SetOf.
255     */
256    public byte[] getDerEncoding() throws IOException {
257        return derEncoding.clone();
258
259    }
260
261    /**
262     * Get an attribute from this set.
263     */
264    public PKCS9Attribute getAttribute(ObjectIdentifier oid) {
265        return attributes.get(oid);
266    }
267
268    /**
269     * Get an attribute from this set.
270     */
271    public PKCS9Attribute getAttribute(String name) {
272        return attributes.get(PKCS9Attribute.getOID(name));
273    }
274
275
276    /**
277     * Get an array of all attributes in this set, in order of OID.
278     */
279    public PKCS9Attribute[] getAttributes() {
280        PKCS9Attribute[] attribs = new PKCS9Attribute[attributes.size()];
281        ObjectIdentifier oid;
282
283        int j = 0;
284        for (int i=1; i < PKCS9Attribute.PKCS9_OIDS.length &&
285                      j < attribs.length; i++) {
286            attribs[j] = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]);
287
288            if (attribs[j] != null)
289                j++;
290        }
291        return attribs;
292    }
293
294    /**
295     * Get an attribute value by OID.
296     */
297    public Object getAttributeValue(ObjectIdentifier oid)
298    throws IOException {
299        try {
300            Object value = getAttribute(oid).getValue();
301            return value;
302        } catch (NullPointerException ex) {
303            throw new IOException("No value found for attribute " + oid);
304        }
305
306    }
307
308    /**
309     *  Get an attribute value by type name.
310     */
311    public Object getAttributeValue(String name) throws IOException {
312        ObjectIdentifier oid = PKCS9Attribute.getOID(name);
313
314        if (oid == null)
315            throw new IOException("Attribute name " + name +
316                                  " not recognized or not supported.");
317
318        return getAttributeValue(oid);
319    }
320
321
322    /**
323     * Returns the PKCS9 block in a printable string form.
324     */
325    public String toString() {
326        StringBuffer buf = new StringBuffer(200);
327        buf.append("PKCS9 Attributes: [\n\t");
328
329        ObjectIdentifier oid;
330        PKCS9Attribute value;
331
332        boolean first = true;
333        for (int i = 1; i < PKCS9Attribute.PKCS9_OIDS.length; i++) {
334            value = getAttribute(PKCS9Attribute.PKCS9_OIDS[i]);
335
336            if (value == null) continue;
337
338            // we have a value; print it
339            if (first)
340                first = false;
341            else
342                buf.append(";\n\t");
343
344            buf.append(value.toString());
345        }
346
347        buf.append("\n\t] (end PKCS9 Attributes)");
348
349        return buf.toString();
350    }
351
352    /**
353     * Cast an object array whose components are
354     * <code>DerEncoder</code>s to <code>DerEncoder[]</code>.
355     */
356    static DerEncoder[] castToDerEncoder(Object[] objs) {
357
358        DerEncoder[] encoders = new DerEncoder[objs.length];
359
360        for (int i=0; i < encoders.length; i++)
361            encoders[i] = (DerEncoder) objs[i];
362
363        return encoders;
364    }
365}
366