Home | History | Annotate | Download | only in conscrypt
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.conscrypt;
     18 
     19 import java.io.IOException;
     20 import java.io.InputStream;
     21 import java.io.PushbackInputStream;
     22 import java.security.cert.CertPath;
     23 import java.security.cert.Certificate;
     24 import java.security.cert.CertificateEncodingException;
     25 import java.security.cert.CertificateException;
     26 import java.security.cert.CertificateParsingException;
     27 import java.security.cert.X509Certificate;
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.Collections;
     31 import java.util.Iterator;
     32 import java.util.List;
     33 import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
     34 
     35 /**
     36  * An implementation of {@link CertPath} based on BoringSSL.
     37  */
     38 final class OpenSSLX509CertPath extends CertPath {
     39     private static final long serialVersionUID = -3249106005255170761L;
     40 
     41     private static final byte[] PKCS7_MARKER = new byte[] {
     42             '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'K', 'C', 'S', '7'
     43     };
     44 
     45     private static final int PUSHBACK_SIZE = 64;
     46 
     47     /**
     48      * Supported encoding types for CerthPath. Used by the various APIs that
     49      * encode this into bytes such as {@link #getEncoded()}.
     50      */
     51     private enum Encoding {
     52         PKI_PATH("PkiPath"),
     53         PKCS7("PKCS7");
     54 
     55         private final String apiName;
     56 
     57         Encoding(String apiName) {
     58             this.apiName = apiName;
     59         }
     60 
     61         static Encoding findByApiName(String apiName) throws CertificateEncodingException {
     62             for (Encoding element : values()) {
     63                 if (element.apiName.equals(apiName)) {
     64                     return element;
     65                 }
     66             }
     67 
     68             return null;
     69         }
     70     }
     71 
     72     /** Unmodifiable list of encodings for the API. */
     73     private static final List<String> ALL_ENCODINGS = Collections.unmodifiableList(Arrays
     74             .asList(new String[] {
     75                     Encoding.PKI_PATH.apiName,
     76                     Encoding.PKCS7.apiName,
     77             }));
     78 
     79     private static final Encoding DEFAULT_ENCODING = Encoding.PKI_PATH;
     80 
     81     private final List<? extends X509Certificate> mCertificates;
     82 
     83     static Iterator<String> getEncodingsIterator() {
     84         return ALL_ENCODINGS.iterator();
     85     }
     86 
     87     OpenSSLX509CertPath(List<? extends X509Certificate> certificates) {
     88         super("X.509");
     89 
     90         mCertificates = certificates;
     91     }
     92 
     93     @Override
     94     public List<? extends Certificate> getCertificates() {
     95         return Collections.unmodifiableList(mCertificates);
     96     }
     97 
     98     private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
     99         final OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[mCertificates.size()];
    100         final long[] certRefs = new long[certs.length];
    101 
    102         for (int i = 0, j = certs.length - 1; j >= 0; i++, j--) {
    103             final X509Certificate cert = mCertificates.get(i);
    104 
    105             if (cert instanceof OpenSSLX509Certificate) {
    106                 certs[j] = (OpenSSLX509Certificate) cert;
    107             } else {
    108                 certs[j] = OpenSSLX509Certificate.fromX509Der(cert.getEncoded());
    109             }
    110 
    111             certRefs[j] = certs[j].getContext();
    112         }
    113 
    114         switch (encoding) {
    115             case PKI_PATH:
    116                 return NativeCrypto.ASN1_seq_pack_X509(certRefs);
    117             case PKCS7:
    118                 return NativeCrypto.i2d_PKCS7(certRefs);
    119             default:
    120                 throw new CertificateEncodingException("Unknown encoding");
    121         }
    122     }
    123 
    124     @Override
    125     public byte[] getEncoded() throws CertificateEncodingException {
    126         return getEncoded(DEFAULT_ENCODING);
    127     }
    128 
    129     @Override
    130     public byte[] getEncoded(String encoding) throws CertificateEncodingException {
    131         Encoding enc = Encoding.findByApiName(encoding);
    132         if (enc == null) {
    133             throw new CertificateEncodingException("Invalid encoding: " + encoding);
    134         }
    135 
    136         return getEncoded(enc);
    137     }
    138 
    139     @Override
    140     public Iterator<String> getEncodings() {
    141         return getEncodingsIterator();
    142     }
    143 
    144     private static CertPath fromPkiPathEncoding(InputStream inStream) throws CertificateException {
    145         OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(inStream, true);
    146 
    147         final boolean markable = inStream.markSupported();
    148         if (markable) {
    149             inStream.mark(PUSHBACK_SIZE);
    150         }
    151 
    152         final long[] certRefs;
    153         try {
    154             certRefs = NativeCrypto.ASN1_seq_unpack_X509_bio(bis.getBioContext());
    155         } catch (Exception e) {
    156             if (markable) {
    157                 try {
    158                     inStream.reset();
    159                 } catch (IOException ignored) {
    160                 }
    161             }
    162             throw new CertificateException(e);
    163         } finally {
    164             bis.release();
    165         }
    166 
    167         if (certRefs == null) {
    168             return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
    169         }
    170 
    171         final List<OpenSSLX509Certificate> certs =
    172                 new ArrayList<OpenSSLX509Certificate>(certRefs.length);
    173         for (int i = certRefs.length - 1; i >= 0; i--) {
    174             if (certRefs[i] == 0) {
    175                 continue;
    176             }
    177             try {
    178                 certs.add(new OpenSSLX509Certificate(certRefs[i]));
    179             } catch (ParsingException e) {
    180                 throw new CertificateParsingException(e);
    181             }
    182         }
    183 
    184         return new OpenSSLX509CertPath(certs);
    185     }
    186 
    187     private static CertPath fromPkcs7Encoding(InputStream inStream) throws CertificateException {
    188         try {
    189             if (inStream == null || inStream.available() == 0) {
    190                 return new OpenSSLX509CertPath(Collections.<X509Certificate> emptyList());
    191             }
    192         } catch (IOException e) {
    193             throw new CertificateException("Problem reading input stream", e);
    194         }
    195 
    196         final boolean markable = inStream.markSupported();
    197         if (markable) {
    198             inStream.mark(PUSHBACK_SIZE);
    199         }
    200 
    201         /* Attempt to see if this is a PKCS#7 bag. */
    202         final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
    203         try {
    204             final byte[] buffer = new byte[PKCS7_MARKER.length];
    205 
    206             final int len = pbis.read(buffer);
    207             if (len < 0) {
    208                 /* No need to reset here. The stream was empty or EOF. */
    209                 throw new ParsingException("inStream is empty");
    210             }
    211             pbis.unread(buffer, 0, len);
    212 
    213             if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
    214                 return new OpenSSLX509CertPath(OpenSSLX509Certificate.fromPkcs7PemInputStream(pbis));
    215             }
    216 
    217             return new OpenSSLX509CertPath(OpenSSLX509Certificate.fromPkcs7DerInputStream(pbis));
    218         } catch (Exception e) {
    219             if (markable) {
    220                 try {
    221                     inStream.reset();
    222                 } catch (IOException ignored) {
    223                 }
    224             }
    225             throw new CertificateException(e);
    226         }
    227     }
    228 
    229     private static CertPath fromEncoding(InputStream inStream, Encoding encoding)
    230             throws CertificateException {
    231         switch (encoding) {
    232             case PKI_PATH:
    233                 return fromPkiPathEncoding(inStream);
    234             case PKCS7:
    235                 return fromPkcs7Encoding(inStream);
    236             default:
    237                 throw new CertificateEncodingException("Unknown encoding");
    238         }
    239     }
    240 
    241     static CertPath fromEncoding(InputStream inStream, String encoding)
    242             throws CertificateException {
    243         if (inStream == null) {
    244             throw new CertificateException("inStream == null");
    245         }
    246 
    247         Encoding enc = Encoding.findByApiName(encoding);
    248         if (enc == null) {
    249             throw new CertificateException("Invalid encoding: " + encoding);
    250         }
    251 
    252         return fromEncoding(inStream, enc);
    253     }
    254 
    255     static CertPath fromEncoding(InputStream inStream) throws CertificateException {
    256         if (inStream == null) {
    257             throw new CertificateException("inStream == null");
    258         }
    259 
    260         return fromEncoding(inStream, DEFAULT_ENCODING);
    261     }
    262 }
    263