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