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      */
  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 }