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.Field;
  32 import java.lang.reflect.InvocationTargetException;
  33 import java.security.cert.CertificateException;
  34 import java.util.*;
  35 
  36 import sun.misc.HexDumpEncoder;
  37 
  38 import sun.security.util.*;
  39 
  40 /**
  41  * This class defines the Extensions attribute for the Certificate.
  42  *
  43  * @author Amit Kapoor
  44  * @author Hemma Prafullchandra
  45  * @see CertAttrSet
  46  */
  47 public class CertificateExtensions implements CertAttrSet<Extension> {
  48     /**
  49      * Identifier for this attribute, to be used with the
  50      * get, set, delete methods of Certificate, x509 type.
  51      */
  52     public static final String IDENT = "x509.info.extensions";
  53     /**
  54      * name
  55      */
  56     public static final String NAME = "extensions";
  57 
  58     private static final Debug debug = Debug.getInstance("x509");
  59 
  60     private Map<String,Extension> map = Collections.synchronizedMap(
  61             new TreeMap<String,Extension>());
  62     private boolean unsupportedCritExt = false;
  63 
  64     private Map<String,Extension> unparseableExtensions;
  65 
  66     /**
  67      * Default constructor.
  68      */
  69     public CertificateExtensions() { }
  70 
  71     /**
  72      * Create the object, decoding the values from the passed DER stream.
  73      *
  74      * @param in the DerInputStream to read the Extension from.
  75      * @exception IOException on decoding errors.
  76      */
  77     public CertificateExtensions(DerInputStream in) throws IOException {
  78         init(in);
  79     }
  80 
  81     // helper routine
  82     private void init(DerInputStream in) throws IOException {
  83 
  84         DerValue[] exts = in.getSequence(5);
  85 
  86         for (int i = 0; i < exts.length; i++) {
  87             Extension ext = new Extension(exts[i]);
  88             parseExtension(ext);
  89         }
  90     }
  91 
  92     private static Class[] PARAMS = {Boolean.class, Object.class};
  93 
  94     // Parse the encoded extension
  95     private void parseExtension(Extension ext) throws IOException {
  96         try {
  97             Class<?> extClass = OIDMap.getClass(ext.getExtensionId());
  98             if (extClass == null) {   // Unsupported extension
  99                 if (ext.isCritical()) {
 100                     unsupportedCritExt = true;
 101                 }
 102                 if (map.put(ext.getExtensionId().toString(), ext) == null) {
 103                     return;
 104                 } else {
 105                     throw new IOException("Duplicate extensions not allowed");
 106                 }
 107             }
 108             Constructor<?> cons = extClass.getConstructor(PARAMS);
 109 
 110             Object[] passed = new Object[] {Boolean.valueOf(ext.isCritical()),
 111                     ext.getExtensionValue()};
 112                     CertAttrSet<?> certExt = (CertAttrSet<?>)
 113                             cons.newInstance(passed);
 114                     if (map.put(certExt.getName(), (Extension)certExt) != null) {
 115                         throw new IOException("Duplicate extensions not allowed");
 116                     }
 117         } catch (InvocationTargetException invk) {
 118             Throwable e = invk.getTargetException();
 119             if (ext.isCritical() == false) {
 120                 // ignore errors parsing non-critical extensions
 121                 if (unparseableExtensions == null) {
 122                     unparseableExtensions = new TreeMap<String,Extension>();
 123                 }
 124                 unparseableExtensions.put(ext.getExtensionId().toString(),
 125                         new UnparseableExtension(ext, e));
 126                 if (debug != null) {
 127                     debug.println("Error parsing extension: " + ext);
 128                     e.printStackTrace();
 129                     HexDumpEncoder h = new HexDumpEncoder();
 130                     System.err.println(h.encodeBuffer(ext.getExtensionValue()));
 131                 }
 132                 return;
 133             }
 134             if (e instanceof IOException) {
 135                 throw (IOException)e;
 136             } else {
 137                 throw new IOException(e);
 138             }
 139         } catch (IOException e) {
 140             throw e;
 141         } catch (Exception e) {
 142             throw new IOException(e);
 143         }
 144     }
 145 
 146     /**
 147      * Encode the extensions in DER form to the stream, setting
 148      * the context specific tag as needed in the X.509 v3 certificate.
 149      *
 150      * @param out the DerOutputStream to marshal the contents to.
 151      * @exception CertificateException on encoding errors.
 152      * @exception IOException on errors.
 153      */
 154     public void encode(OutputStream out)
 155     throws CertificateException, IOException {
 156         encode(out, false);
 157     }
 158 
 159     /**
 160      * Encode the extensions in DER form to the stream.
 161      *
 162      * @param out the DerOutputStream to marshal the contents to.
 163      * @param isCertReq if true then no context specific tag is added.
 164      * @exception CertificateException on encoding errors.
 165      * @exception IOException on errors.
 166      */
 167     public void encode(OutputStream out, boolean isCertReq)
 168     throws CertificateException, IOException {
 169         DerOutputStream extOut = new DerOutputStream();
 170         Collection<Extension> allExts = map.values();
 171         Object[] objs = allExts.toArray();
 172 
 173         for (int i = 0; i < objs.length; i++) {
 174             if (objs[i] instanceof CertAttrSet)
 175                 ((CertAttrSet)objs[i]).encode(extOut);
 176             else if (objs[i] instanceof Extension)
 177                 ((Extension)objs[i]).encode(extOut);
 178             else
 179                 throw new CertificateException("Illegal extension object");
 180         }
 181 
 182         DerOutputStream seq = new DerOutputStream();
 183         seq.write(DerValue.tag_Sequence, extOut);
 184 
 185         DerOutputStream tmp;
 186         if (!isCertReq) { // certificate
 187             tmp = new DerOutputStream();
 188             tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)3),
 189                     seq);
 190         } else
 191             tmp = seq; // pkcs#10 certificateRequest
 192 
 193         out.write(tmp.toByteArray());
 194     }
 195 
 196     /**
 197      * Set the attribute value.
 198      * @param name the extension name used in the cache.
 199      * @param obj the object to set.
 200      * @exception IOException if the object could not be cached.
 201      */
 202     public void set(String name, Object obj) throws IOException {
 203         if (obj instanceof Extension) {
 204             map.put(name, (Extension)obj);
 205         } else {
 206             throw new IOException("Unknown extension type.");
 207         }
 208     }
 209 
 210     /**
 211      * Get the attribute value.
 212      * @param name the extension name used in the lookup.
 213      * @exception IOException if named extension is not found.
 214      */
 215     public Extension get(String name) throws IOException {
 216         Extension obj = map.get(name);
 217         if (obj == null) {
 218             throw new IOException("No extension found with name " + name);
 219         }
 220         return (obj);
 221     }
 222 
 223     // Similar to get(String), but throw no exception, might return null.
 224     // Used in X509CertImpl::getExtension(OID).
 225     Extension getExtension(String name) {
 226         return map.get(name);
 227     }
 228 
 229     /**
 230      * Delete the attribute value.
 231      * @param name the extension name used in the lookup.
 232      * @exception IOException if named extension is not found.
 233      */
 234     public void delete(String name) throws IOException {
 235         Object obj = map.get(name);
 236         if (obj == null) {
 237             throw new IOException("No extension found with name " + name);
 238         }
 239         map.remove(name);
 240     }
 241 
 242     public String getNameByOid(ObjectIdentifier oid) throws IOException {
 243         for (String name: map.keySet()) {
 244             if (map.get(name).getExtensionId().equals((Object)oid)) {
 245                 return name;
 246             }
 247         }
 248         return null;
 249     }
 250 
 251     /**
 252      * Return an enumeration of names of attributes existing within this
 253      * attribute.
 254      */
 255     public Enumeration<Extension> getElements() {
 256         return Collections.enumeration(map.values());
 257     }
 258 
 259     /**
 260      * Return a collection view of the extensions.
 261      * @return a collection view of the extensions in this Certificate.
 262      */
 263     public Collection<Extension> getAllExtensions() {
 264         return map.values();
 265     }
 266 
 267     public Map<String,Extension> getUnparseableExtensions() {
 268         if (unparseableExtensions == null) {
 269             return Collections.emptyMap();
 270         } else {
 271             return unparseableExtensions;
 272         }
 273     }
 274 
 275     /**
 276      * Return the name of this attribute.
 277      */
 278     public String getName() {
 279         return NAME;
 280     }
 281 
 282     /**
 283      * Return true if a critical extension is found that is
 284      * not supported, otherwise return false.
 285      */
 286     public boolean hasUnsupportedCriticalExtension() {
 287         return unsupportedCritExt;
 288     }
 289 
 290     /**
 291      * Compares this CertificateExtensions for equality with the specified
 292      * object. If the <code>other</code> object is an
 293      * <code>instanceof</code> <code>CertificateExtensions</code>, then
 294      * all the entries are compared with the entries from this.
 295      *
 296      * @param other the object to test for equality with this
 297      * CertificateExtensions.
 298      * @return true iff all the entries match that of the Other,
 299      * false otherwise.
 300      */
 301     public boolean equals(Object other) {
 302         if (this == other)
 303             return true;
 304         if (!(other instanceof CertificateExtensions))
 305             return false;
 306         Collection<Extension> otherC =
 307                 ((CertificateExtensions)other).getAllExtensions();
 308         Object[] objs = otherC.toArray();
 309 
 310         int len = objs.length;
 311         if (len != map.size())
 312             return false;
 313 
 314         Extension otherExt, thisExt;
 315         String key = null;
 316         for (int i = 0; i < len; i++) {
 317             if (objs[i] instanceof CertAttrSet)
 318                 key = ((CertAttrSet)objs[i]).getName();
 319             otherExt = (Extension)objs[i];
 320             if (key == null)
 321                 key = otherExt.getExtensionId().toString();
 322             thisExt = map.get(key);
 323             if (thisExt == null)
 324                 return false;
 325             if (! thisExt.equals(otherExt))
 326                 return false;
 327         }
 328         return this.getUnparseableExtensions().equals(
 329                 ((CertificateExtensions)other).getUnparseableExtensions());
 330     }
 331 
 332     /**
 333      * Returns a hashcode value for this CertificateExtensions.
 334      *
 335      * @return the hashcode value.
 336      */
 337     public int hashCode() {
 338         return map.hashCode() + getUnparseableExtensions().hashCode();
 339     }
 340 
 341     /**
 342      * Returns a string representation of this <tt>CertificateExtensions</tt>
 343      * object in the form of a set of entries, enclosed in braces and separated
 344      * by the ASCII characters "<tt>,&nbsp;</tt>" (comma and space).
 345      * <p>Overrides to <tt>toString</tt> method of <tt>Object</tt>.
 346      *
 347      * @return  a string representation of this CertificateExtensions.
 348      */
 349     public String toString() {
 350         return map.toString();
 351     }
 352 
 353 }
 354 
 355 class UnparseableExtension extends Extension {
 356     private String name;
 357     private Throwable why;
 358 
 359     public UnparseableExtension(Extension ext, Throwable why) {
 360         super(ext);
 361 
 362         name = "";
 363         try {
 364             Class<?> extClass = OIDMap.getClass(ext.getExtensionId());
 365             if (extClass != null) {
 366                 Field field = extClass.getDeclaredField("NAME");
 367                 name = (String)(field.get(null)) + " ";
 368             }
 369         } catch (Exception e) {
 370             // If we cannot find the name, just ignore it
 371         }
 372 
 373         this.why = why;
 374     }
 375 
 376     @Override public String toString() {
 377         return super.toString() +
 378                 "Unparseable " + name + "extension due to\n" + why + "\n\n" +
 379                 new sun.misc.HexDumpEncoder().encodeBuffer(getExtensionValue());
 380     }
 381 }