Home | History | Annotate | Download | only in cert
      1 /*
      2  *  Licensed to the Apache Software Foundation (ASF) under one or more
      3  *  contributor license agreements.  See the NOTICE file distributed with
      4  *  this work for additional information regarding copyright ownership.
      5  *  The ASF licenses this file to You under the Apache License, Version 2.0
      6  *  (the "License"); you may not use this file except in compliance with
      7  *  the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *  See the License for the specific language governing permissions and
     15  *  limitations under the License.
     16  */
     17 
     18 /**
     19 * @author Alexander Y. Kleymenov
     20 * @version $Revision$
     21 */
     22 
     23 package org.apache.harmony.security.provider.cert;
     24 
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.nio.charset.Charsets;
     28 import java.security.cert.CRL;
     29 import java.security.cert.CRLException;
     30 import java.security.cert.CertPath;
     31 import java.security.cert.Certificate;
     32 import java.security.cert.CertificateException;
     33 import java.security.cert.CertificateFactorySpi;
     34 import java.security.cert.X509CRL;
     35 import java.util.ArrayList;
     36 import java.util.Collection;
     37 import java.util.Iterator;
     38 import java.util.List;
     39 import org.apache.harmony.luni.util.Base64;
     40 import org.apache.harmony.security.asn1.ASN1Constants;
     41 import org.apache.harmony.security.asn1.BerInputStream;
     42 import org.apache.harmony.security.pkcs7.ContentInfo;
     43 import org.apache.harmony.security.pkcs7.SignedData;
     44 import org.apache.harmony.security.x509.CertificateList;
     45 
     46 /**
     47  * X509 Certificate Factory Service Provider Interface Implementation.
     48  * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form,
     49  * and Certification Paths in PkiPath and PKCS7 formats.
     50  * For Certificates and CRLs factory maintains the caching
     51  * mechanisms allowing to speed up repeated Certificate/CRL
     52  * generation.
     53  * @see Cache
     54  */
     55 public class X509CertFactoryImpl extends CertificateFactorySpi {
     56 
     57     // number of leading/trailing bytes used for cert hash computation
     58     private static final int CERT_CACHE_SEED_LENGTH = 28;
     59     // certificate cache
     60     private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH);
     61     // number of leading/trailing bytes used for crl hash computation
     62     private static final int CRL_CACHE_SEED_LENGTH = 24;
     63     // crl cache
     64     private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH);
     65 
     66     /**
     67      * Default constructor.
     68      * Creates the instance of Certificate Factory SPI ready for use.
     69      */
     70     public X509CertFactoryImpl() { }
     71 
     72     /**
     73      * Generates the X.509 certificate from the data in the stream.
     74      * The data in the stream can be either in ASN.1 DER encoded X.509
     75      * certificate, or PEM (Base64 encoding bounded by
     76      * <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and
     77      * <code>"-----END CERTIFICATE-----"</code> at the end) representation
     78      * of the former encoded form.
     79      *
     80      * Before the generation the encoded form is looked up in
     81      * the cache. If the cache contains the certificate with requested encoded
     82      * form it is returned from it, otherwise it is generated by ASN.1
     83      * decoder.
     84      *
     85      * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream)
     86      * method documentation for more info
     87      */
     88     public Certificate engineGenerateCertificate(InputStream inStream)
     89             throws CertificateException {
     90         if (inStream == null) {
     91             throw new CertificateException("inStream == null");
     92         }
     93         try {
     94             if (!inStream.markSupported()) {
     95                 // create the mark supporting wrapper
     96                 inStream = new RestoringInputStream(inStream);
     97             }
     98             // mark is needed to recognize the format of the provided encoding
     99             // (ASN.1 or PEM)
    100             inStream.mark(1);
    101             // check whether the provided certificate is in PEM encoded form
    102             if (inStream.read() == '-') {
    103                 // decode PEM, retrieve CRL
    104                 return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX));
    105             } else {
    106                 inStream.reset();
    107                 // retrieve CRL
    108                 return getCertificate(inStream);
    109             }
    110         } catch (IOException e) {
    111             throw new CertificateException(e);
    112         }
    113     }
    114 
    115     /**
    116      * Generates the collection of the certificates on the base of provided
    117      * via input stream encodings.
    118      * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream)
    119      * method documentation for more info
    120      */
    121     public Collection<? extends Certificate>
    122             engineGenerateCertificates(InputStream inStream)
    123                 throws CertificateException {
    124         if (inStream == null) {
    125             throw new CertificateException("inStream == null");
    126         }
    127         ArrayList result = new ArrayList();
    128         try {
    129             if (!inStream.markSupported()) {
    130                 // create the mark supporting wrapper
    131                 inStream = new RestoringInputStream(inStream);
    132             }
    133             // if it is PEM encoded form this array will contain the encoding
    134             // so ((it is PEM) <-> (encoding != null))
    135             byte[] encoding = null;
    136             // The following by SEQUENCE ASN.1 tag, used for
    137             // recognizing the data format
    138             // (is it PKCS7 ContentInfo structure, X.509 Certificate, or
    139             // unsupported encoding)
    140             int second_asn1_tag = -1;
    141             inStream.mark(1);
    142             int ch;
    143             while ((ch = inStream.read()) != -1) {
    144                 // check if it is PEM encoded form
    145                 if (ch == '-') { // beginning of PEM encoding ('-' char)
    146                     // decode PEM chunk and store its content (ASN.1 encoding)
    147                     encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
    148                 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
    149                     encoding = null;
    150                     inStream.reset();
    151                     // prepare for data format determination
    152                     inStream.mark(CERT_CACHE_SEED_LENGTH);
    153                 } else { // unsupported data
    154                     if (result.size() == 0) {
    155                         throw new CertificateException("Unsupported encoding");
    156                     } else {
    157                         // it can be trailing user data,
    158                         // so keep it in the stream
    159                         inStream.reset();
    160                         return result;
    161                     }
    162                 }
    163                 // Check the data format
    164                 BerInputStream in = (encoding == null)
    165                                         ? new BerInputStream(inStream)
    166                                         : new BerInputStream(encoding);
    167                 // read the next ASN.1 tag
    168                 second_asn1_tag = in.next(); // inStream position changed
    169                 if (encoding == null) {
    170                     // keep whole structure in the stream
    171                     inStream.reset();
    172                 }
    173                 // check if it is a TBSCertificate structure
    174                 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
    175                     if (result.size() == 0) {
    176                         // there were not read X.509 Certificates, so
    177                         // break the cycle and check
    178                         // whether it is PKCS7 structure
    179                         break;
    180                     } else {
    181                         // it can be trailing user data,
    182                         // so return what we already read
    183                         return result;
    184                     }
    185                 } else {
    186                     if (encoding == null) {
    187                         result.add(getCertificate(inStream));
    188                     } else {
    189                         result.add(getCertificate(encoding));
    190                     }
    191                 }
    192                 // mark for the next iteration
    193                 inStream.mark(1);
    194             }
    195             if (result.size() != 0) {
    196                 // some Certificates have been read
    197                 return result;
    198             } else if (ch == -1) {
    199                 throw new CertificateException("There is no data in the stream");
    200             }
    201             // else: check if it is PKCS7
    202             if (second_asn1_tag == ASN1Constants.TAG_OID) {
    203                 // it is PKCS7 ContentInfo structure, so decode it
    204                 ContentInfo info = (ContentInfo)
    205                     ((encoding != null)
    206                         ? ContentInfo.ASN1.decode(encoding)
    207                         : ContentInfo.ASN1.decode(inStream));
    208                 // retrieve SignedData
    209                 SignedData data = info.getSignedData();
    210                 if (data == null) {
    211                     throw new CertificateException("Invalid PKCS7 data provided");
    212                 }
    213                 List certs = data.getCertificates();
    214                 if (certs != null) {
    215                     for (int i = 0; i < certs.size(); i++) {
    216                         result.add(new X509CertImpl(
    217                             (org.apache.harmony.security.x509.Certificate)
    218                                 certs.get(i)));
    219                     }
    220                 }
    221                 return result;
    222             }
    223             // else: Unknown data format
    224             throw new CertificateException("Unsupported encoding");
    225         } catch (IOException e) {
    226             throw new CertificateException(e);
    227         }
    228     }
    229 
    230     /**
    231      * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
    232      * method documentation for more info
    233      */
    234     public CRL engineGenerateCRL(InputStream inStream)
    235             throws CRLException {
    236         if (inStream == null) {
    237             throw new CRLException("inStream == null");
    238         }
    239         try {
    240             if (!inStream.markSupported()) {
    241                 // Create the mark supporting wrapper
    242                 // Mark is needed to recognize the format
    243                 // of provided encoding form (ASN.1 or PEM)
    244                 inStream = new RestoringInputStream(inStream);
    245             }
    246             inStream.mark(1);
    247             // check whether the provided crl is in PEM encoded form
    248             if (inStream.read() == '-') {
    249                 // decode PEM, retrieve CRL
    250                 return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
    251             } else {
    252                 inStream.reset();
    253                 // retrieve CRL
    254                 return getCRL(inStream);
    255             }
    256         } catch (IOException e) {
    257             throw new CRLException(e);
    258         }
    259     }
    260 
    261     /**
    262      * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
    263      * method documentation for more info
    264      */
    265     public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
    266             throws CRLException {
    267         if (inStream == null) {
    268             throw new CRLException("inStream == null");
    269         }
    270         ArrayList result = new ArrayList();
    271         try {
    272             if (!inStream.markSupported()) {
    273                 inStream = new RestoringInputStream(inStream);
    274             }
    275             // if it is PEM encoded form this array will contain the encoding
    276             // so ((it is PEM) <-> (encoding != null))
    277             byte[] encoding = null;
    278             // The following by SEQUENCE ASN.1 tag, used for
    279             // recognizing the data format
    280             // (is it PKCS7 ContentInfo structure, X.509 CRL, or
    281             // unsupported encoding)
    282             int second_asn1_tag = -1;
    283             inStream.mark(1);
    284             int ch;
    285             while ((ch = inStream.read()) != -1) {
    286                 // check if it is PEM encoded form
    287                 if (ch == '-') { // beginning of PEM encoding ('-' char)
    288                     // decode PEM chunk and store its content (ASN.1 encoding)
    289                     encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
    290                 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
    291                     encoding = null;
    292                     inStream.reset();
    293                     // prepare for data format determination
    294                     inStream.mark(CRL_CACHE_SEED_LENGTH);
    295                 } else { // unsupported data
    296                     if (result.size() == 0) {
    297                         throw new CRLException("Unsupported encoding");
    298                     } else {
    299                         // it can be trailing user data,
    300                         // so keep it in the stream
    301                         inStream.reset();
    302                         return result;
    303                     }
    304                 }
    305                 // Check the data format
    306                 BerInputStream in = (encoding == null)
    307                                         ? new BerInputStream(inStream)
    308                                         : new BerInputStream(encoding);
    309                 // read the next ASN.1 tag
    310                 second_asn1_tag = in.next();
    311                 if (encoding == null) {
    312                     // keep whole structure in the stream
    313                     inStream.reset();
    314                 }
    315                 // check if it is a TBSCertList structure
    316                 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
    317                     if (result.size() == 0) {
    318                         // there were not read X.509 CRLs, so
    319                         // break the cycle and check
    320                         // whether it is PKCS7 structure
    321                         break;
    322                     } else {
    323                         // it can be trailing user data,
    324                         // so return what we already read
    325                         return result;
    326                     }
    327                 } else {
    328                     if (encoding == null) {
    329                         result.add(getCRL(inStream));
    330                     } else {
    331                         result.add(getCRL(encoding));
    332                     }
    333                 }
    334                 inStream.mark(1);
    335             }
    336             if (result.size() != 0) {
    337                 // the stream was read out
    338                 return result;
    339             } else if (ch == -1) {
    340                 throw new CRLException("There is no data in the stream");
    341             }
    342             // else: check if it is PKCS7
    343             if (second_asn1_tag == ASN1Constants.TAG_OID) {
    344                 // it is PKCS7 ContentInfo structure, so decode it
    345                 ContentInfo info = (ContentInfo)
    346                     ((encoding != null)
    347                         ? ContentInfo.ASN1.decode(encoding)
    348                         : ContentInfo.ASN1.decode(inStream));
    349                 // retrieve SignedData
    350                 SignedData data = info.getSignedData();
    351                 if (data == null) {
    352                     throw new CRLException("Invalid PKCS7 data provided");
    353                 }
    354                 List crls = data.getCRLs();
    355                 if (crls != null) {
    356                     for (int i = 0; i < crls.size(); i++) {
    357                         result.add(new X509CRLImpl(
    358                             (CertificateList) crls.get(i)));
    359                     }
    360                 }
    361                 return result;
    362             }
    363             // else: Unknown data format
    364             throw new CRLException("Unsupported encoding");
    365         } catch (IOException e) {
    366             throw new CRLException(e);
    367         }
    368     }
    369 
    370     /**
    371      * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
    372      * method documentation for more info
    373      */
    374     public CertPath engineGenerateCertPath(InputStream inStream)
    375             throws CertificateException {
    376         if (inStream == null) {
    377             throw new CertificateException("inStream == null");
    378         }
    379         return engineGenerateCertPath(inStream, "PkiPath");
    380     }
    381 
    382     /**
    383      * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
    384      * method documentation for more info
    385      */
    386     public CertPath engineGenerateCertPath(
    387             InputStream inStream, String encoding) throws CertificateException {
    388         if (inStream == null) {
    389             throw new CertificateException("inStream == null");
    390         }
    391         if (!inStream.markSupported()) {
    392             inStream = new RestoringInputStream(inStream);
    393         }
    394         try {
    395             inStream.mark(1);
    396             int ch;
    397 
    398             // check if it is PEM encoded form
    399             if ((ch = inStream.read()) == '-') {
    400                 // decode PEM chunk into ASN.1 form and decode CertPath object
    401                 return X509CertPathImpl.getInstance(
    402                         decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
    403             } else if (ch == 0x30) { // ASN.1 Sequence
    404                 inStream.reset();
    405                 // decode ASN.1 form
    406                 return X509CertPathImpl.getInstance(inStream, encoding);
    407             } else {
    408                 throw new CertificateException("Unsupported encoding");
    409             }
    410         } catch (IOException e) {
    411             throw new CertificateException(e);
    412         }
    413     }
    414 
    415     /**
    416      * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
    417      * method documentation for more info
    418      */
    419     public CertPath engineGenerateCertPath(List certificates)
    420             throws CertificateException {
    421         return new X509CertPathImpl(certificates);
    422     }
    423 
    424     /**
    425      * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
    426      * method documentation for more info
    427      */
    428     public Iterator<String> engineGetCertPathEncodings() {
    429         return X509CertPathImpl.encodings.iterator();
    430     }
    431 
    432     // ---------------------------------------------------------------------
    433     // ------------------------ Staff methods ------------------------------
    434     // ---------------------------------------------------------------------
    435 
    436     private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(Charsets.UTF_8);
    437     private static final byte[] PEM_END = "-----END".getBytes(Charsets.UTF_8);
    438     /**
    439      * Code describing free format for PEM boundary suffix:
    440      * "^-----BEGIN.*\n"         at the beginning, and<br>
    441      * "\n-----END.*(EOF|\n)$"   at the end.
    442      */
    443     private static final byte[] FREE_BOUND_SUFFIX = null;
    444     /**
    445      * Code describing PEM boundary suffix for X.509 certificate:
    446      * "^-----BEGIN CERTIFICATE-----\n"   at the beginning, and<br>
    447      * "\n-----END CERTIFICATE-----"   at the end.
    448      */
    449     private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(Charsets.UTF_8);
    450 
    451     /**
    452      * Method retrieves the PEM encoded data from the stream
    453      * and returns its decoded representation.
    454      * Method checks correctness of PEM boundaries. It supposes that
    455      * the first '-' of the opening boundary has already been read from
    456      * the stream. So first of all it checks that the leading bytes
    457      * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
    458      * is not null, it checks that next bytes equal to boundary_suffix
    459      * + new line char[s] ([CR]LF).
    460      * If boundary_suffix parameter is null, method supposes free suffix
    461      * format and skips any bytes until the new line.<br>
    462      * After the opening boundary has been read and checked, the method
    463      * read Base64 encoded data until closing PEM boundary is not reached.<br>
    464      * Than it checks closing boundary - it should start with new line +
    465      * "-----END" + boundary_suffix. If boundary_suffix is null,
    466      * any characters are skipped until the new line.<br>
    467      * After this any trailing new line characters are skipped from the stream,
    468      * Base64 encoding is decoded and returned.
    469      * @param inStream the stream containing the PEM encoding.
    470      * @param boundary_suffix the suffix of expected PEM multipart
    471      * boundary delimiter.<br>
    472      * If it is null, that any character sequences are accepted.
    473      * @throws IOException If PEM boundary delimiter does not comply
    474      * with expected or some I/O or decoding problems occur.
    475      */
    476     private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
    477                                                         throws IOException {
    478         int ch; // the char to be read
    479         // check and skip opening boundary delimiter
    480         // (first '-' is supposed as already read)
    481         for (int i = 1; i < PEM_BEGIN.length; ++i) {
    482             if (PEM_BEGIN[i] != (ch = inStream.read())) {
    483                 throw new IOException(
    484                     "Incorrect PEM encoding: '-----BEGIN"
    485                     + ((boundary_suffix == null)
    486                         ? "" : new String(boundary_suffix))
    487                     + "' is expected as opening delimiter boundary.");
    488             }
    489         }
    490         if (boundary_suffix == null) {
    491             // read (skip) the trailing characters of
    492             // the beginning PEM boundary delimiter
    493             while ((ch = inStream.read()) != '\n') {
    494                 if (ch == -1) {
    495                     throw new IOException("Incorrect PEM encoding: EOF before content");
    496                 }
    497             }
    498         } else {
    499             for (int i=0; i<boundary_suffix.length; i++) {
    500                 if (boundary_suffix[i] != inStream.read()) {
    501                     throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
    502                             new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
    503                 }
    504             }
    505             // read new line characters
    506             if ((ch = inStream.read()) == '\r') {
    507                 // CR has been read, now read LF character
    508                 ch = inStream.read();
    509             }
    510             if (ch != '\n') {
    511                 throw new IOException("Incorrect PEM encoding: newline expected after " +
    512                         "opening delimiter boundary");
    513             }
    514         }
    515         int size = 1024; // the size of the buffer containing Base64 data
    516         byte[] buff = new byte[size];
    517         int index = 0;
    518         // read bytes while ending boundary delimiter is not reached
    519         while ((ch = inStream.read()) != '-') {
    520             if (ch == -1) {
    521                 throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
    522             }
    523             buff[index++] = (byte) ch;
    524             if (index == size) {
    525                 // enlarge the buffer
    526                 byte[] newbuff = new byte[size+1024];
    527                 System.arraycopy(buff, 0, newbuff, 0, size);
    528                 buff = newbuff;
    529                 size += 1024;
    530             }
    531         }
    532         if (buff[index-1] != '\n') {
    533             throw new IOException("Incorrect Base64 encoding: newline expected before " +
    534                     "closing boundary delimiter");
    535         }
    536         // check and skip closing boundary delimiter prefix
    537         // (first '-' was read)
    538         for (int i = 1; i < PEM_END.length; ++i) {
    539             if (PEM_END[i] != inStream.read()) {
    540                 throw badEnd(boundary_suffix);
    541             }
    542         }
    543         if (boundary_suffix == null) {
    544             // read (skip) the trailing characters of
    545             // the closing PEM boundary delimiter
    546             while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
    547             }
    548         } else {
    549             for (int i=0; i<boundary_suffix.length; i++) {
    550                 if (boundary_suffix[i] != inStream.read()) {
    551                     throw badEnd(boundary_suffix);
    552                 }
    553             }
    554         }
    555         // skip trailing line breaks
    556         inStream.mark(1);
    557         while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
    558             inStream.mark(1);
    559         }
    560         inStream.reset();
    561         buff = Base64.decode(buff, index);
    562         if (buff == null) {
    563             throw new IOException("Incorrect Base64 encoding");
    564         }
    565         return buff;
    566     }
    567 
    568     private IOException badEnd(byte[] boundary_suffix) throws IOException {
    569         String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
    570         throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
    571     }
    572 
    573     /**
    574      * Reads the data of specified length from source
    575      * and returns it as an array.
    576      * @return the byte array contained read data or
    577      * null if the stream contains not enough data
    578      * @throws IOException if some I/O error has been occurred.
    579      */
    580     private static byte[] readBytes(InputStream source, int length)
    581                                                             throws IOException {
    582         byte[] result = new byte[length];
    583         for (int i=0; i<length; i++) {
    584             int bytik = source.read();
    585             if (bytik == -1) {
    586                 return null;
    587             }
    588             result[i] = (byte) bytik;
    589         }
    590         return result;
    591     }
    592 
    593     /**
    594      * Returns the Certificate object corresponding to the provided encoding.
    595      * Resulting object is retrieved from the cache
    596      * if it contains such correspondence
    597      * and is constructed on the base of encoding
    598      * and stored in the cache otherwise.
    599      * @throws IOException if some decoding errors occur
    600      * (in the case of cache miss).
    601      */
    602     private static Certificate getCertificate(byte[] encoding)
    603                                     throws CertificateException, IOException {
    604         if (encoding.length < CERT_CACHE_SEED_LENGTH) {
    605             throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
    606         }
    607         synchronized (CERT_CACHE) {
    608             long hash = CERT_CACHE.getHash(encoding);
    609             if (CERT_CACHE.contains(hash)) {
    610                 Certificate res =
    611                     (Certificate) CERT_CACHE.get(hash, encoding);
    612                 if (res != null) {
    613                     return res;
    614                 }
    615             }
    616             Certificate res = new X509CertImpl(encoding);
    617             CERT_CACHE.put(hash, encoding, res);
    618             return res;
    619         }
    620     }
    621 
    622     /**
    623      * Returns the Certificate object corresponding to the encoding provided
    624      * by the stream.
    625      * Resulting object is retrieved from the cache
    626      * if it contains such correspondence
    627      * and is constructed on the base of encoding
    628      * and stored in the cache otherwise.
    629      * @throws IOException if some decoding errors occur
    630      * (in the case of cache miss).
    631      */
    632     private static Certificate getCertificate(InputStream inStream)
    633                                     throws CertificateException, IOException {
    634         synchronized (CERT_CACHE) {
    635             inStream.mark(CERT_CACHE_SEED_LENGTH);
    636             // read the prefix of the encoding
    637             byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
    638             inStream.reset();
    639             if (buff == null) {
    640                 throw new CertificateException("InputStream doesn't contain enough data");
    641             }
    642             long hash = CERT_CACHE.getHash(buff);
    643             if (CERT_CACHE.contains(hash)) {
    644                 byte[] encoding = new byte[BerInputStream.getLength(buff)];
    645                 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
    646                     throw new CertificateException("Bad Certificate encoding");
    647                 }
    648                 inStream.read(encoding);
    649                 Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
    650                 if (res != null) {
    651                     return res;
    652                 }
    653                 res = new X509CertImpl(encoding);
    654                 CERT_CACHE.put(hash, encoding, res);
    655                 return res;
    656             } else {
    657                 inStream.reset();
    658                 Certificate res = new X509CertImpl(inStream);
    659                 CERT_CACHE.put(hash, res.getEncoded(), res);
    660                 return res;
    661             }
    662         }
    663     }
    664 
    665     /**
    666      * Returns the CRL object corresponding to the provided encoding.
    667      * Resulting object is retrieved from the cache
    668      * if it contains such correspondence
    669      * and is constructed on the base of encoding
    670      * and stored in the cache otherwise.
    671      * @throws IOException if some decoding errors occur
    672      * (in the case of cache miss).
    673      */
    674     private static CRL getCRL(byte[] encoding)
    675                                             throws CRLException, IOException {
    676         if (encoding.length < CRL_CACHE_SEED_LENGTH) {
    677             throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
    678         }
    679         synchronized (CRL_CACHE) {
    680             long hash = CRL_CACHE.getHash(encoding);
    681             if (CRL_CACHE.contains(hash)) {
    682                 X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
    683                 if (res != null) {
    684                     return res;
    685                 }
    686             }
    687             X509CRL res = new X509CRLImpl(encoding);
    688             CRL_CACHE.put(hash, encoding, res);
    689             return res;
    690         }
    691     }
    692 
    693     /**
    694      * Returns the CRL object corresponding to the encoding provided
    695      * by the stream.
    696      * Resulting object is retrieved from the cache
    697      * if it contains such correspondence
    698      * and is constructed on the base of encoding
    699      * and stored in the cache otherwise.
    700      * @throws IOException if some decoding errors occur
    701      * (in the case of cache miss).
    702      */
    703     private static CRL getCRL(InputStream inStream)
    704                                             throws CRLException, IOException {
    705         synchronized (CRL_CACHE) {
    706             inStream.mark(CRL_CACHE_SEED_LENGTH);
    707             byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
    708             // read the prefix of the encoding
    709             inStream.reset();
    710             if (buff == null) {
    711                 throw new CRLException("InputStream doesn't contain enough data");
    712             }
    713             long hash = CRL_CACHE.getHash(buff);
    714             if (CRL_CACHE.contains(hash)) {
    715                 byte[] encoding = new byte[BerInputStream.getLength(buff)];
    716                 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
    717                     throw new CRLException("Bad CRL encoding");
    718                 }
    719                 inStream.read(encoding);
    720                 CRL res = (CRL) CRL_CACHE.get(hash, encoding);
    721                 if (res != null) {
    722                     return res;
    723                 }
    724                 res = new X509CRLImpl(encoding);
    725                 CRL_CACHE.put(hash, encoding, res);
    726                 return res;
    727             } else {
    728                 X509CRL res = new X509CRLImpl(inStream);
    729                 CRL_CACHE.put(hash, res.getEncoded(), res);
    730                 return res;
    731             }
    732         }
    733     }
    734 
    735     /*
    736      * This class extends any existing input stream with
    737      * mark functionality. It acts as a wrapper over the
    738      * stream and supports reset to the
    739      * marked state with readlimit no more than BUFF_SIZE.
    740      */
    741     private static class RestoringInputStream extends InputStream {
    742 
    743         // wrapped input stream
    744         private final InputStream inStream;
    745         // specifies how much of the read data is buffered
    746         // after the mark has been set up
    747         private static final int BUFF_SIZE = 32;
    748         // buffer to keep the bytes read after the mark has been set up
    749         private final int[] buff = new int[BUFF_SIZE*2];
    750         // position of the next byte to read,
    751         // the value of -1 indicates that the buffer is not used
    752         // (mark was not set up or was invalidated, or reset to the marked
    753         // position has been done and all the buffered data was read out)
    754         private int pos = -1;
    755         // position of the last buffered byte
    756         private int bar = 0;
    757         // position in the buffer where the mark becomes invalidated
    758         private int end = 0;
    759 
    760         /**
    761          * Creates the mark supporting wrapper over the stream.
    762          */
    763         public RestoringInputStream(InputStream inStream) {
    764             this.inStream = inStream;
    765         }
    766 
    767         @Override
    768         public int available() throws IOException {
    769             return (bar - pos) + inStream.available();
    770         }
    771 
    772         @Override
    773         public void close() throws IOException {
    774             inStream.close();
    775         }
    776 
    777         @Override
    778         public void mark(int readlimit) {
    779             if (pos < 0) {
    780                 pos = 0;
    781                 bar = 0;
    782                 end = BUFF_SIZE - 1;
    783             } else {
    784                 end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
    785             }
    786         }
    787 
    788         @Override
    789         public boolean markSupported() {
    790             return true;
    791         }
    792 
    793         /**
    794          * Reads the byte from the stream. If mark has been set up
    795          * and was not invalidated byte is read from the underlying
    796          * stream and saved into the buffer. If the current read position
    797          * has been reset to the marked position and there are remaining
    798          * bytes in the buffer, the byte is taken from it. In the other cases
    799          * (if mark has been invalidated, or there are no buffered bytes)
    800          * the byte is taken directly from the underlying stream and it is
    801          * returned without saving to the buffer.
    802          *
    803          * @see java.io.InputStream#read()
    804          * method documentation for more info
    805          */
    806         public int read() throws IOException {
    807             // if buffer is currently used
    808             if (pos >= 0) {
    809                 // current position in the buffer
    810                 int cur = pos % BUFF_SIZE;
    811                 // check whether the buffer contains the data to be read
    812                 if (cur < bar) {
    813                     // return the data from the buffer
    814                     pos++;
    815                     return buff[cur];
    816                 }
    817                 // check whether buffer has free space
    818                 if (cur != end) {
    819                     // it has, so read the data from the wrapped stream
    820                     // and place it in the buffer
    821                     buff[cur] = inStream.read();
    822                     bar = cur+1;
    823                     pos++;
    824                     return buff[cur];
    825                 } else {
    826                     // buffer if full and can not operate
    827                     // any more, so invalidate the mark position
    828                     // and turn off the using of buffer
    829                     pos = -1;
    830                 }
    831             }
    832             // buffer is not used, so return the data from the wrapped stream
    833             return inStream.read();
    834         }
    835 
    836         @Override
    837         public int read(byte[] b) throws IOException {
    838             return read(b, 0, b.length);
    839         }
    840 
    841         @Override
    842         public int read(byte[] b, int off, int len) throws IOException {
    843             int read_b;
    844             int i;
    845             for (i=0; i<len; i++) {
    846                 if ((read_b = read()) == -1) {
    847                     return (i == 0) ? -1 : i;
    848                 }
    849                 b[off+i] = (byte) read_b;
    850             }
    851             return i;
    852         }
    853 
    854         @Override
    855         public void reset() throws IOException {
    856             if (pos >= 0) {
    857                 pos = (end + 1) % BUFF_SIZE;
    858             } else {
    859                 throw new IOException("Could not reset the stream: " +
    860                         "position became invalid or stream has not been marked");
    861             }
    862         }
    863 
    864         @Override
    865         public long skip(long n) throws IOException {
    866             if (pos >= 0) {
    867                 long i = 0;
    868                 int av = available();
    869                 if (av < n) {
    870                     n = av;
    871                 }
    872                 while ((i < n) && (read() != -1)) {
    873                     i++;
    874                 }
    875                 return i;
    876             } else {
    877                 return inStream.skip(n);
    878             }
    879         }
    880     }
    881 }
    882