1/*
2 * Copyright (c) 1997, 2013, 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.x509;
27
28import java.io.IOException;
29import java.io.OutputStream;
30import java.lang.reflect.Constructor;
31import java.lang.reflect.InvocationTargetException;
32import java.security.cert.CRLException;
33import java.security.cert.CertificateException;
34import java.util.Collection;
35import java.util.Collections;
36import java.util.Enumeration;
37import java.util.Map;
38import java.util.TreeMap;
39
40import sun.security.util.*;
41
42/**
43 * This class defines the CRL Extensions.
44 * It is used for both CRL Extensions and CRL Entry Extensions,
45 * which are defined are follows:
46 * <pre>
47 * TBSCertList  ::=  SEQUENCE  {
48 *    version              Version OPTIONAL,   -- if present, must be v2
49 *    signature            AlgorithmIdentifier,
50 *    issuer               Name,
51 *    thisUpdate           Time,
52 *    nextUpdate           Time  OPTIONAL,
53 *    revokedCertificates  SEQUENCE OF SEQUENCE  {
54 *        userCertificate         CertificateSerialNumber,
55 *        revocationDate          Time,
56 *        crlEntryExtensions      Extensions OPTIONAL  -- if present, must be v2
57 *    }  OPTIONAL,
58 *    crlExtensions        [0] EXPLICIT Extensions OPTIONAL  -- if present, must be v2
59 * }
60 * </pre>
61 *
62 * @author Hemma Prafullchandra
63 */
64public class CRLExtensions {
65
66    private Map<String,Extension> map = Collections.synchronizedMap(
67            new TreeMap<String,Extension>());
68    private boolean unsupportedCritExt = false;
69
70    /**
71     * Default constructor.
72     */
73    public CRLExtensions() { }
74
75    /**
76     * Create the object, decoding the values from the passed DER stream.
77     *
78     * @param in the DerInputStream to read the Extension from, i.e. the
79     *        sequence of extensions.
80     * @exception CRLException on decoding errors.
81     */
82    public CRLExtensions(DerInputStream in) throws CRLException {
83        init(in);
84    }
85
86    // helper routine
87    private void init(DerInputStream derStrm) throws CRLException {
88        try {
89            DerInputStream str = derStrm;
90
91            byte nextByte = (byte)derStrm.peekByte();
92            // check for context specific byte 0; skip it
93            if (((nextByte & 0x0c0) == 0x080) &&
94                ((nextByte & 0x01f) == 0x000)) {
95                DerValue val = str.getDerValue();
96                str = val.data;
97            }
98
99            DerValue[] exts = str.getSequence(5);
100            for (int i = 0; i < exts.length; i++) {
101                Extension ext = new Extension(exts[i]);
102                parseExtension(ext);
103            }
104        } catch (IOException e) {
105            throw new CRLException("Parsing error: " + e.toString());
106        }
107    }
108
109    private static final Class<?>[] PARAMS = {Boolean.class, Object.class};
110
111    // Parse the encoded extension
112    private void parseExtension(Extension ext) throws CRLException {
113        try {
114            Class<?> extClass = OIDMap.getClass(ext.getExtensionId());
115            if (extClass == null) {   // Unsupported extension
116                if (ext.isCritical())
117                    unsupportedCritExt = true;
118                if (map.put(ext.getExtensionId().toString(), ext) != null)
119                    throw new CRLException("Duplicate extensions not allowed");
120                return;
121            }
122            Constructor<?> cons = extClass.getConstructor(PARAMS);
123            Object[] passed = new Object[] {Boolean.valueOf(ext.isCritical()),
124                                            ext.getExtensionValue()};
125            CertAttrSet<?> crlExt = (CertAttrSet<?>)cons.newInstance(passed);
126            if (map.put(crlExt.getName(), (Extension)crlExt) != null) {
127                throw new CRLException("Duplicate extensions not allowed");
128            }
129        } catch (InvocationTargetException invk) {
130            throw new CRLException(invk.getTargetException().getMessage());
131        } catch (Exception e) {
132            throw new CRLException(e.toString());
133        }
134    }
135
136    /**
137     * Encode the extensions in DER form to the stream.
138     *
139     * @param out the DerOutputStream to marshal the contents to.
140     * @param isExplicit the tag indicating whether this is an entry
141     * extension (false) or a CRL extension (true).
142     * @exception CRLException on encoding errors.
143     */
144    public void encode(OutputStream out, boolean isExplicit)
145    throws CRLException {
146        try {
147            DerOutputStream extOut = new DerOutputStream();
148            Collection<Extension> allExts = map.values();
149            Object[] objs = allExts.toArray();
150
151            for (int i = 0; i < objs.length; i++) {
152                if (objs[i] instanceof CertAttrSet)
153                    ((CertAttrSet)objs[i]).encode(extOut);
154                else if (objs[i] instanceof Extension)
155                    ((Extension)objs[i]).encode(extOut);
156                else
157                    throw new CRLException("Illegal extension object");
158            }
159
160            DerOutputStream seq = new DerOutputStream();
161            seq.write(DerValue.tag_Sequence, extOut);
162
163            DerOutputStream tmp = new DerOutputStream();
164            if (isExplicit)
165                tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT,
166                                             true, (byte)0), seq);
167            else
168                tmp = seq;
169
170            out.write(tmp.toByteArray());
171        } catch (IOException e) {
172            throw new CRLException("Encoding error: " + e.toString());
173        } catch (CertificateException e) {
174            throw new CRLException("Encoding error: " + e.toString());
175        }
176    }
177
178    /**
179     * Get the extension with this alias.
180     *
181     * @param alias the identifier string for the extension to retrieve.
182     */
183    public Extension get(String alias) {
184        X509AttributeName attr = new X509AttributeName(alias);
185        String name;
186        String id = attr.getPrefix();
187        if (id.equalsIgnoreCase(X509CertImpl.NAME)) { // fully qualified
188            int index = alias.lastIndexOf('.');
189            name = alias.substring(index + 1);
190        } else
191            name = alias;
192        return map.get(name);
193    }
194
195    /**
196     * Set the extension value with this alias.
197     *
198     * @param alias the identifier string for the extension to set.
199     * @param obj the Object to set the extension identified by the
200     *        alias.
201     */
202    public void set(String alias, Object obj) {
203        map.put(alias, (Extension)obj);
204    }
205
206    /**
207     * Delete the extension value with this alias.
208     *
209     * @param alias the identifier string for the extension to delete.
210     */
211    public void delete(String alias) {
212        map.remove(alias);
213    }
214
215    /**
216     * Return an enumeration of the extensions.
217     * @return an enumeration of the extensions in this CRL.
218     */
219    public Enumeration<Extension> getElements() {
220        return Collections.enumeration(map.values());
221    }
222
223    /**
224     * Return a collection view of the extensions.
225     * @return a collection view of the extensions in this CRL.
226     */
227    public Collection<Extension> getAllExtensions() {
228        return map.values();
229    }
230
231    /**
232     * Return true if a critical extension is found that is
233     * not supported, otherwise return false.
234     */
235    public boolean hasUnsupportedCriticalExtension() {
236        return unsupportedCritExt;
237    }
238
239    /**
240     * Compares this CRLExtensions for equality with the specified
241     * object. If the {@code other} object is an
242     * {@code instanceof} {@code CRLExtensions}, then
243     * all the entries are compared with the entries from this.
244     *
245     * @param other the object to test for equality with this CRLExtensions.
246     * @return true iff all the entries match that of the Other,
247     * false otherwise.
248     */
249    public boolean equals(Object other) {
250        if (this == other)
251            return true;
252        if (!(other instanceof CRLExtensions))
253            return false;
254        Collection<Extension> otherC =
255                        ((CRLExtensions)other).getAllExtensions();
256        Object[] objs = otherC.toArray();
257
258        int len = objs.length;
259        if (len != map.size())
260            return false;
261
262        Extension otherExt, thisExt;
263        String key = null;
264        for (int i = 0; i < len; i++) {
265            if (objs[i] instanceof CertAttrSet)
266                key = ((CertAttrSet)objs[i]).getName();
267            otherExt = (Extension)objs[i];
268            if (key == null)
269                key = otherExt.getExtensionId().toString();
270            thisExt = map.get(key);
271            if (thisExt == null)
272                return false;
273            if (! thisExt.equals(otherExt))
274                return false;
275        }
276        return true;
277    }
278
279    /**
280     * Returns a hashcode value for this CRLExtensions.
281     *
282     * @return the hashcode value.
283     */
284    public int hashCode() {
285        return map.hashCode();
286    }
287
288    /**
289     * Returns a string representation of this {@code CRLExtensions} object
290     * in the form of a set of entries, enclosed in braces and separated
291     * by the ASCII characters "<code>,&nbsp;</code>" (comma and space).
292     * <p>Overrides to {@code toString} method of {@code Object}.
293     *
294     * @return  a string representation of this CRLExtensions.
295     */
296    public String toString() {
297        return map.toString();
298    }
299}
300