Home | History | Annotate | Download | only in x509
      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 }
    382