Home | History | Annotate | Download | only in conscrypt
      1 /*
      2  * Copyright (C) 2012 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.CRL;
     23 import java.security.cert.CRLException;
     24 import java.security.cert.CertPath;
     25 import java.security.cert.Certificate;
     26 import java.security.cert.CertificateException;
     27 import java.security.cert.CertificateFactorySpi;
     28 import java.security.cert.X509Certificate;
     29 import java.util.ArrayList;
     30 import java.util.Arrays;
     31 import java.util.Collection;
     32 import java.util.Collections;
     33 import java.util.Iterator;
     34 import java.util.List;
     35 
     36 /**
     37  * An implementation of {@link java.security.cert.CertificateFactory} based on BoringSSL.
     38  *
     39  * @hide
     40  */
     41 @Internal
     42 public class OpenSSLX509CertificateFactory extends CertificateFactorySpi {
     43     private static final byte[] PKCS7_MARKER = new byte[] {
     44             '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'K', 'C', 'S', '7'
     45     };
     46 
     47     private static final int PUSHBACK_SIZE = 64;
     48 
     49     static class ParsingException extends Exception {
     50         private static final long serialVersionUID = 8390802697728301325L;
     51 
     52         ParsingException(String message) {
     53             super(message);
     54         }
     55 
     56         ParsingException(Exception cause) {
     57             super(cause);
     58         }
     59 
     60         ParsingException(String message, Exception cause) {
     61             super(message, cause);
     62         }
     63     }
     64 
     65     /**
     66      * The code for X509 Certificates and CRL is pretty much the same. We use
     67      * this abstract class to share the code between them. This makes it ugly,
     68      * but it's already written in this language anyway.
     69      */
     70     private static abstract class Parser<T> {
     71         T generateItem(InputStream inStream) throws ParsingException {
     72             if (inStream == null) {
     73                 throw new ParsingException("inStream == null");
     74             }
     75 
     76             final boolean markable = inStream.markSupported();
     77             if (markable) {
     78                 inStream.mark(PKCS7_MARKER.length);
     79             }
     80 
     81             final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
     82             try {
     83                 final byte[] buffer = new byte[PKCS7_MARKER.length];
     84 
     85                 final int len = pbis.read(buffer);
     86                 if (len < 0) {
     87                     /* No need to reset here. The stream was empty or EOF. */
     88                     throw new ParsingException("inStream is empty");
     89                 }
     90                 pbis.unread(buffer, 0, len);
     91 
     92                 if (buffer[0] == '-') {
     93                     if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
     94                         List<? extends T> items = fromPkcs7PemInputStream(pbis);
     95                         if (items.size() == 0) {
     96                             return null;
     97                         }
     98                         items.get(0);
     99                     } else {
    100                         return fromX509PemInputStream(pbis);
    101                     }
    102                 }
    103 
    104                 /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
    105                 if (buffer[4] == 0x06) {
    106                     List<? extends T> certs = fromPkcs7DerInputStream(pbis);
    107                     if (certs.size() == 0) {
    108                         return null;
    109                     }
    110                     return certs.get(0);
    111                 } else {
    112                     return fromX509DerInputStream(pbis);
    113                 }
    114             } catch (Exception e) {
    115                 if (markable) {
    116                     try {
    117                         inStream.reset();
    118                     } catch (IOException ignored) {
    119                     }
    120                 }
    121                 throw new ParsingException(e);
    122             }
    123         }
    124 
    125         Collection<? extends T> generateItems(InputStream inStream)
    126                 throws ParsingException {
    127             if (inStream == null) {
    128                 throw new ParsingException("inStream == null");
    129             }
    130             try {
    131                 if (inStream.available() == 0) {
    132                     return Collections.emptyList();
    133                 }
    134             } catch (IOException e) {
    135                 throw new ParsingException("Problem reading input stream", e);
    136             }
    137 
    138             final boolean markable = inStream.markSupported();
    139             if (markable) {
    140                 inStream.mark(PUSHBACK_SIZE);
    141             }
    142 
    143             /* Attempt to see if this is a PKCS#7 bag. */
    144             final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE);
    145             try {
    146                 final byte[] buffer = new byte[PKCS7_MARKER.length];
    147 
    148                 final int len = pbis.read(buffer);
    149                 if (len < 0) {
    150                     /* No need to reset here. The stream was empty or EOF. */
    151                     throw new ParsingException("inStream is empty");
    152                 }
    153                 pbis.unread(buffer, 0, len);
    154 
    155                 if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
    156                     return fromPkcs7PemInputStream(pbis);
    157                 }
    158 
    159                 /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
    160                 if (buffer[4] == 0x06) {
    161                     return fromPkcs7DerInputStream(pbis);
    162                 }
    163             } catch (Exception e) {
    164                 if (markable) {
    165                     try {
    166                         inStream.reset();
    167                     } catch (IOException ignored) {
    168                     }
    169                 }
    170                 throw new ParsingException(e);
    171             }
    172 
    173             /*
    174              * It wasn't, so just try to keep grabbing certificates until we
    175              * can't anymore.
    176              */
    177             final List<T> coll = new ArrayList<T>();
    178             T c;
    179             do {
    180                 /*
    181                  * If this stream supports marking, try to mark here in case
    182                  * there is an error during certificate generation.
    183                  */
    184                 if (markable) {
    185                     inStream.mark(PUSHBACK_SIZE);
    186                 }
    187 
    188                 try {
    189                     c = generateItem(pbis);
    190                     coll.add(c);
    191                 } catch (ParsingException e) {
    192                     /*
    193                      * If this stream supports marking, attempt to reset it to
    194                      * the mark before the failure.
    195                      */
    196                     if (markable) {
    197                         try {
    198                             inStream.reset();
    199                         } catch (IOException ignored) {
    200                         }
    201                     }
    202 
    203                     c = null;
    204                 }
    205             } while (c != null);
    206 
    207             return coll;
    208         }
    209 
    210         protected abstract T fromX509PemInputStream(InputStream pbis) throws ParsingException;
    211 
    212         protected abstract T fromX509DerInputStream(InputStream pbis) throws ParsingException;
    213 
    214         protected abstract List<? extends T> fromPkcs7PemInputStream(InputStream is)
    215                 throws ParsingException;
    216 
    217         protected abstract List<? extends T> fromPkcs7DerInputStream(InputStream is)
    218                 throws ParsingException;
    219     }
    220 
    221     private Parser<OpenSSLX509Certificate> certificateParser =
    222             new Parser<OpenSSLX509Certificate>() {
    223                 @Override
    224                 public OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
    225                         throws ParsingException {
    226                     return OpenSSLX509Certificate.fromX509PemInputStream(is);
    227                 }
    228 
    229                 @Override
    230                 public OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
    231                         throws ParsingException {
    232                     return OpenSSLX509Certificate.fromX509DerInputStream(is);
    233                 }
    234 
    235                 @Override
    236                 public List<? extends OpenSSLX509Certificate>
    237                         fromPkcs7PemInputStream(InputStream is) throws ParsingException {
    238                     return OpenSSLX509Certificate.fromPkcs7PemInputStream(is);
    239                 }
    240 
    241                 @Override
    242                 public List<? extends OpenSSLX509Certificate>
    243                         fromPkcs7DerInputStream(InputStream is) throws ParsingException {
    244                     return OpenSSLX509Certificate.fromPkcs7DerInputStream(is);
    245                 }
    246             };
    247 
    248     private Parser<OpenSSLX509CRL> crlParser =
    249             new Parser<OpenSSLX509CRL>() {
    250                 @Override
    251                 public OpenSSLX509CRL fromX509PemInputStream(InputStream is)
    252                         throws ParsingException {
    253                     return OpenSSLX509CRL.fromX509PemInputStream(is);
    254                 }
    255 
    256                 @Override
    257                 public OpenSSLX509CRL fromX509DerInputStream(InputStream is)
    258                         throws ParsingException {
    259                     return OpenSSLX509CRL.fromX509DerInputStream(is);
    260                 }
    261 
    262                 @Override
    263                 public List<? extends OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is)
    264                         throws ParsingException {
    265                     return OpenSSLX509CRL.fromPkcs7PemInputStream(is);
    266                 }
    267 
    268                 @Override
    269                 public List<? extends OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is)
    270                         throws ParsingException {
    271                     return OpenSSLX509CRL.fromPkcs7DerInputStream(is);
    272                 }
    273             };
    274 
    275     @Override
    276     public Certificate engineGenerateCertificate(InputStream inStream) throws CertificateException {
    277         try {
    278             return certificateParser.generateItem(inStream);
    279         } catch (ParsingException e) {
    280             throw new CertificateException(e);
    281         }
    282     }
    283 
    284     @Override
    285     public Collection<? extends Certificate> engineGenerateCertificates(
    286             InputStream inStream) throws CertificateException {
    287         try {
    288             return certificateParser.generateItems(inStream);
    289         } catch (ParsingException e) {
    290             throw new CertificateException(e);
    291         }
    292     }
    293 
    294     @Override
    295     public CRL engineGenerateCRL(InputStream inStream) throws CRLException {
    296         try {
    297             return crlParser.generateItem(inStream);
    298         } catch (ParsingException e) {
    299             throw new CRLException(e);
    300         }
    301     }
    302 
    303     @Override
    304     public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) throws CRLException {
    305         if (inStream == null) {
    306             return Collections.emptyList();
    307         }
    308 
    309         try {
    310             return crlParser.generateItems(inStream);
    311         } catch (ParsingException e) {
    312             throw new CRLException(e);
    313         }
    314     }
    315 
    316     @Override
    317     public Iterator<String> engineGetCertPathEncodings() {
    318         return OpenSSLX509CertPath.getEncodingsIterator();
    319     }
    320 
    321     @Override
    322     public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException {
    323         return OpenSSLX509CertPath.fromEncoding(inStream);
    324     }
    325 
    326     @Override
    327     public CertPath engineGenerateCertPath(InputStream inStream, String encoding)
    328             throws CertificateException {
    329         return OpenSSLX509CertPath.fromEncoding(inStream, encoding);
    330     }
    331 
    332     @Override
    333     public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)
    334             throws CertificateException {
    335         final List<X509Certificate> filtered = new ArrayList<X509Certificate>(certificates.size());
    336         for (int i = 0; i < certificates.size(); i++) {
    337             final Certificate c = certificates.get(i);
    338 
    339             if (!(c instanceof X509Certificate)) {
    340                 throw new CertificateException("Certificate not X.509 type at index " + i);
    341             }
    342 
    343             filtered.add((X509Certificate) c);
    344         }
    345 
    346         return new OpenSSLX509CertPath(filtered);
    347     }
    348 }
    349