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 
26 package sun.security.x509;
27 
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.InvocationTargetException;
32 import java.security.cert.CRLException;
33 import java.security.cert.CertificateException;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.Enumeration;
37 import java.util.Map;
38 import java.util.TreeMap;
39 
40 import 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  */
64 public 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      */
CRLExtensions()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      */
CRLExtensions(DerInputStream in)82     public CRLExtensions(DerInputStream in) throws CRLException {
83         init(in);
84     }
85 
86     // helper routine
init(DerInputStream derStrm)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
parseExtension(Extension ext)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      */
encode(OutputStream out, boolean isExplicit)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      */
get(String alias)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      */
set(String alias, Object obj)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      */
delete(String alias)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      */
getElements()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      */
getAllExtensions()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      */
hasUnsupportedCriticalExtension()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      */
equals(Object other)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      */
hashCode()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      */
toString()296     public String toString() {
297         return map.toString();
298     }
299 }
300