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