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<>(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}
365