View Javadoc
1   /*
2    * Copyright (c) 1997, 2012, 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       */
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</code> object is an
242      * <code>instanceof</code> <code>CRLExtensions</code>, 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 <tt>CRLExtensions</tt> object
290      * in the form of a set of entries, enclosed in braces and separated
291      * by the ASCII characters "<tt>,&nbsp;</tt>" (comma and space).
292      * <p>Overrides to <tt>toString</tt> method of <tt>Object</tt>.
293      *
294      * @return  a string representation of this CRLExtensions.
295      */
296     public String toString() {
297         return map.toString();
298     }
299 }