Home | History | Annotate | Download | only in certpath
      1 /*
      2  * Copyright (c) 2000, 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.provider.certpath;
     27 
     28 import java.io.ByteArrayInputStream;
     29 import java.io.ByteArrayOutputStream;
     30 import java.io.IOException;
     31 import java.io.InputStream;
     32 import java.security.cert.CertificateEncodingException;
     33 import java.security.cert.Certificate;
     34 import java.security.cert.CertificateException;
     35 import java.security.cert.CertificateFactory;
     36 import java.security.cert.CertPath;
     37 import java.security.cert.X509Certificate;
     38 import java.util.*;
     39 
     40 import sun.security.pkcs.ContentInfo;
     41 import sun.security.pkcs.PKCS7;
     42 import sun.security.pkcs.SignerInfo;
     43 import sun.security.x509.AlgorithmId;
     44 import sun.security.util.DerValue;
     45 import sun.security.util.DerOutputStream;
     46 import sun.security.util.DerInputStream;
     47 
     48 /**
     49  * A {@link java.security.cert.CertPath CertPath} (certification path)
     50  * consisting exclusively of
     51  * {@link java.security.cert.X509Certificate X509Certificate}s.
     52  * <p>
     53  * By convention, X.509 <code>CertPath</code>s are stored from target
     54  * to trust anchor.
     55  * That is, the issuer of one certificate is the subject of the following
     56  * one. However, unvalidated X.509 <code>CertPath</code>s may not follow
     57  * this convention. PKIX <code>CertPathValidator</code>s will detect any
     58  * departure from this convention and throw a
     59  * <code>CertPathValidatorException</code>.
     60  *
     61  * @author      Yassir Elley
     62  * @since       1.4
     63  */
     64 public class X509CertPath extends CertPath {
     65 
     66     private static final long serialVersionUID = 4989800333263052980L;
     67 
     68     /**
     69      * List of certificates in this chain
     70      */
     71     private List<X509Certificate> certs;
     72 
     73     /**
     74      * The names of our encodings.  PkiPath is the default.
     75      */
     76     private static final String COUNT_ENCODING = "count";
     77     private static final String PKCS7_ENCODING = "PKCS7";
     78     private static final String PKIPATH_ENCODING = "PkiPath";
     79 
     80     /**
     81      * List of supported encodings
     82      */
     83     private static final Collection<String> encodingList;
     84 
     85     static {
     86         List<String> list = new ArrayList<>(2);
     87         list.add(PKIPATH_ENCODING);
     88         list.add(PKCS7_ENCODING);
     89         encodingList = Collections.unmodifiableCollection(list);
     90     }
     91 
     92     /**
     93      * Creates an <code>X509CertPath</code> from a <code>List</code> of
     94      * <code>X509Certificate</code>s.
     95      * <p>
     96      * The certificates are copied out of the supplied <code>List</code>
     97      * object.
     98      *
     99      * @param certs a <code>List</code> of <code>X509Certificate</code>s
    100      * @exception CertificateException if <code>certs</code> contains an element
    101      *                      that is not an <code>X509Certificate</code>
    102      */
    103     @SuppressWarnings("unchecked")
    104     public X509CertPath(List<? extends Certificate> certs) throws CertificateException {
    105         super("X.509");
    106 
    107         // Ensure that the List contains only X509Certificates
    108         //
    109         // Note; The certs parameter is not necessarily to be of Certificate
    110         // for some old code. For compatibility, to make sure the exception
    111         // is CertificateException, rather than ClassCastException, please
    112         // don't use
    113         //     for (Certificate obj : certs)
    114         for (Object obj : certs) {
    115             if (obj instanceof X509Certificate == false) {
    116                 throw new CertificateException
    117                     ("List is not all X509Certificates: "
    118                     + obj.getClass().getName());
    119             }
    120         }
    121 
    122         // Assumes that the resulting List is thread-safe. This is true
    123         // because we ensure that it cannot be modified after construction
    124         // and the methods in the Sun JDK 1.4 implementation of ArrayList that
    125         // allow read-only access are thread-safe.
    126         this.certs = Collections.unmodifiableList(
    127                 new ArrayList<X509Certificate>((List<X509Certificate>)certs));
    128     }
    129 
    130     /**
    131      * Creates an <code>X509CertPath</code>, reading the encoded form
    132      * from an <code>InputStream</code>. The data is assumed to be in
    133      * the default encoding.
    134      *
    135      * @param is the <code>InputStream</code> to read the data from
    136      * @exception CertificateException if an exception occurs while decoding
    137      */
    138     public X509CertPath(InputStream is) throws CertificateException {
    139         this(is, PKIPATH_ENCODING);
    140     }
    141 
    142     /**
    143      * Creates an <code>X509CertPath</code>, reading the encoded form
    144      * from an InputStream. The data is assumed to be in the specified
    145      * encoding.
    146      *
    147      * @param is the <code>InputStream</code> to read the data from
    148      * @param encoding the encoding used
    149      * @exception CertificateException if an exception occurs while decoding or
    150      *   the encoding requested is not supported
    151      */
    152     public X509CertPath(InputStream is, String encoding)
    153             throws CertificateException {
    154         super("X.509");
    155 
    156         switch (encoding) {
    157             case PKIPATH_ENCODING:
    158                 certs = parsePKIPATH(is);
    159                 break;
    160             case PKCS7_ENCODING:
    161                 certs = parsePKCS7(is);
    162                 break;
    163             default:
    164                 throw new CertificateException("unsupported encoding");
    165         }
    166     }
    167 
    168     /**
    169      * Parse a PKIPATH format CertPath from an InputStream. Return an
    170      * unmodifiable List of the certificates.
    171      *
    172      * @param is the <code>InputStream</code> to read the data from
    173      * @return an unmodifiable List of the certificates
    174      * @exception CertificateException if an exception occurs
    175      */
    176     private static List<X509Certificate> parsePKIPATH(InputStream is)
    177             throws CertificateException {
    178         List<X509Certificate> certList = null;
    179         CertificateFactory certFac = null;
    180 
    181         if (is == null) {
    182             throw new CertificateException("input stream is null");
    183         }
    184 
    185         try {
    186             DerInputStream dis = new DerInputStream(readAllBytes(is));
    187             DerValue[] seq = dis.getSequence(3);
    188             if (seq.length == 0) {
    189                 return Collections.<X509Certificate>emptyList();
    190             }
    191 
    192             certFac = CertificateFactory.getInstance("X.509");
    193             certList = new ArrayList<X509Certificate>(seq.length);
    194 
    195             // append certs in reverse order (target to trust anchor)
    196             for (int i = seq.length-1; i >= 0; i--) {
    197                 certList.add((X509Certificate)certFac.generateCertificate
    198                     (new ByteArrayInputStream(seq[i].toByteArray())));
    199             }
    200 
    201             return Collections.unmodifiableList(certList);
    202 
    203         } catch (IOException ioe) {
    204             throw new CertificateException("IOException parsing PkiPath data: "
    205                     + ioe, ioe);
    206         }
    207     }
    208 
    209     /**
    210      * Parse a PKCS#7 format CertPath from an InputStream. Return an
    211      * unmodifiable List of the certificates.
    212      *
    213      * @param is the <code>InputStream</code> to read the data from
    214      * @return an unmodifiable List of the certificates
    215      * @exception CertificateException if an exception occurs
    216      */
    217     private static List<X509Certificate> parsePKCS7(InputStream is)
    218             throws CertificateException {
    219         List<X509Certificate> certList;
    220 
    221         if (is == null) {
    222             throw new CertificateException("input stream is null");
    223         }
    224 
    225         try {
    226             if (is.markSupported() == false) {
    227                 // Copy the entire input stream into an InputStream that does
    228                 // support mark
    229                 is = new ByteArrayInputStream(readAllBytes(is));
    230             }
    231             PKCS7 pkcs7 = new PKCS7(is);
    232 
    233             X509Certificate[] certArray = pkcs7.getCertificates();
    234             // certs are optional in PKCS #7
    235             if (certArray != null) {
    236                 certList = Arrays.asList(certArray);
    237             } else {
    238                 // no certs provided
    239                 certList = new ArrayList<X509Certificate>(0);
    240             }
    241         } catch (IOException ioe) {
    242             throw new CertificateException("IOException parsing PKCS7 data: " +
    243                                         ioe);
    244         }
    245         // Assumes that the resulting List is thread-safe. This is true
    246         // because we ensure that it cannot be modified after construction
    247         // and the methods in the Sun JDK 1.4 implementation of ArrayList that
    248         // allow read-only access are thread-safe.
    249         return Collections.unmodifiableList(certList);
    250     }
    251 
    252     /*
    253      * Reads the entire contents of an InputStream into a byte array.
    254      *
    255      * @param is the InputStream to read from
    256      * @return the bytes read from the InputStream
    257      */
    258     private static byte[] readAllBytes(InputStream is) throws IOException {
    259         byte[] buffer = new byte[8192];
    260         ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
    261         int n;
    262         while ((n = is.read(buffer)) != -1) {
    263             baos.write(buffer, 0, n);
    264         }
    265         return baos.toByteArray();
    266     }
    267 
    268     /**
    269      * Returns the encoded form of this certification path, using the
    270      * default encoding.
    271      *
    272      * @return the encoded bytes
    273      * @exception CertificateEncodingException if an encoding error occurs
    274      */
    275     @Override
    276     public byte[] getEncoded() throws CertificateEncodingException {
    277         // @@@ Should cache the encoded form
    278         return encodePKIPATH();
    279     }
    280 
    281     /**
    282      * Encode the CertPath using PKIPATH format.
    283      *
    284      * @return a byte array containing the binary encoding of the PkiPath object
    285      * @exception CertificateEncodingException if an exception occurs
    286      */
    287     private byte[] encodePKIPATH() throws CertificateEncodingException {
    288 
    289         ListIterator<X509Certificate> li = certs.listIterator(certs.size());
    290         try {
    291             DerOutputStream bytes = new DerOutputStream();
    292             // encode certs in reverse order (trust anchor to target)
    293             // according to PkiPath format
    294             while (li.hasPrevious()) {
    295                 X509Certificate cert = li.previous();
    296                 // check for duplicate cert
    297                 if (certs.lastIndexOf(cert) != certs.indexOf(cert)) {
    298                     throw new CertificateEncodingException
    299                         ("Duplicate Certificate");
    300                 }
    301                 // get encoded certificates
    302                 byte[] encoded = cert.getEncoded();
    303                 bytes.write(encoded);
    304             }
    305 
    306             // Wrap the data in a SEQUENCE
    307             DerOutputStream derout = new DerOutputStream();
    308             derout.write(DerValue.tag_SequenceOf, bytes);
    309             return derout.toByteArray();
    310 
    311         } catch (IOException ioe) {
    312            throw new CertificateEncodingException("IOException encoding " +
    313                    "PkiPath data: " + ioe, ioe);
    314         }
    315     }
    316 
    317     /**
    318      * Encode the CertPath using PKCS#7 format.
    319      *
    320      * @return a byte array containing the binary encoding of the PKCS#7 object
    321      * @exception CertificateEncodingException if an exception occurs
    322      */
    323     private byte[] encodePKCS7() throws CertificateEncodingException {
    324         PKCS7 p7 = new PKCS7(new AlgorithmId[0],
    325                              new ContentInfo(ContentInfo.DATA_OID, null),
    326                              certs.toArray(new X509Certificate[certs.size()]),
    327                              new SignerInfo[0]);
    328         DerOutputStream derout = new DerOutputStream();
    329         try {
    330             p7.encodeSignedData(derout);
    331         } catch (IOException ioe) {
    332             throw new CertificateEncodingException(ioe.getMessage());
    333         }
    334         return derout.toByteArray();
    335     }
    336 
    337     /**
    338      * Returns the encoded form of this certification path, using the
    339      * specified encoding.
    340      *
    341      * @param encoding the name of the encoding to use
    342      * @return the encoded bytes
    343      * @exception CertificateEncodingException if an encoding error occurs or
    344      *   the encoding requested is not supported
    345      */
    346     @Override
    347     public byte[] getEncoded(String encoding)
    348             throws CertificateEncodingException {
    349         switch (encoding) {
    350             case PKIPATH_ENCODING:
    351                 return encodePKIPATH();
    352             case PKCS7_ENCODING:
    353                 return encodePKCS7();
    354             default:
    355                 throw new CertificateEncodingException("unsupported encoding");
    356         }
    357     }
    358 
    359     /**
    360      * Returns the encodings supported by this certification path, with the
    361      * default encoding first.
    362      *
    363      * @return an <code>Iterator</code> over the names of the supported
    364      *         encodings (as Strings)
    365      */
    366     public static Iterator<String> getEncodingsStatic() {
    367         return encodingList.iterator();
    368     }
    369 
    370     /**
    371      * Returns an iteration of the encodings supported by this certification
    372      * path, with the default encoding first.
    373      * <p>
    374      * Attempts to modify the returned <code>Iterator</code> via its
    375      * <code>remove</code> method result in an
    376      * <code>UnsupportedOperationException</code>.
    377      *
    378      * @return an <code>Iterator</code> over the names of the supported
    379      *         encodings (as Strings)
    380      */
    381     @Override
    382     public Iterator<String> getEncodings() {
    383         return getEncodingsStatic();
    384     }
    385 
    386     /**
    387      * Returns the list of certificates in this certification path.
    388      * The <code>List</code> returned must be immutable and thread-safe.
    389      *
    390      * @return an immutable <code>List</code> of <code>X509Certificate</code>s
    391      *         (may be empty, but not null)
    392      */
    393     @Override
    394     public List<X509Certificate> getCertificates() {
    395         return certs;
    396     }
    397 }
    398