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