content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You under the Apache License, Version 2.0
   *  (the "License"); you may not use this file except in compliance with
   *  the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  
  /**
  * @author Alexander Y. Kleymenov
  * @version $Revision$
  */
  
  package android.framework.org.apache.harmony.security_custom.provider.cert;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.charset.Charset;
  import java.security.cert.CRL;
  import java.security.cert.CRLException;
  import java.security.cert.CertPath;
  import java.security.cert.Certificate;
  import java.security.cert.CertificateException;
  import java.security.cert.CertificateFactorySpi;
  import java.security.cert.X509CRL;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Iterator;
  import java.util.List;
  import android.framework.org.apache.harmony.security_custom.asn1.ASN1Constants;
  import android.framework.org.apache.harmony.security_custom.asn1.BerInputStream;
  import android.framework.org.apache.harmony.security_custom.pkcs7.ContentInfo;
  import android.framework.org.apache.harmony.security_custom.pkcs7.SignedData;
  import android.framework.org.apache.harmony.security_custom.x509.CertificateList;
  
  /**
   * X509 Certificate Factory Service Provider Interface Implementation.
   * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form,
   * and Certification Paths in PkiPath and PKCS7 formats.
   * For Certificates and CRLs factory maintains the caching
   * mechanisms allowing to speed up repeated Certificate/CRL
   * generation.
   * @see Cache
   */
  public class X509CertFactoryImpl extends CertificateFactorySpi {
  
      // number of leading/trailing bytes used for cert hash computation
      private static final int CERT_CACHE_SEED_LENGTH = 28;
      // certificate cache
      private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH);
      // number of leading/trailing bytes used for crl hash computation
      private static final int CRL_CACHE_SEED_LENGTH = 24;
      // crl cache
      private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH);
  
      /**
       * Default constructor.
       * Creates the instance of Certificate Factory SPI ready for use.
       */
      public X509CertFactoryImpl() { }
  
      /**
       * Generates the X.509 certificate from the data in the stream.
       * The data in the stream can be either in ASN.1 DER encoded X.509
       * certificate, or PEM (Base64 encoding bounded by
       * <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and
       * <code>"-----END CERTIFICATE-----"</code> at the end) representation
       * of the former encoded form.
       *
       * Before the generation the encoded form is looked up in
       * the cache. If the cache contains the certificate with requested encoded
       * form it is returned from it, otherwise it is generated by ASN.1
       * decoder.
       *
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream)
       * method documentation for more info
       */
      public Certificate engineGenerateCertificate(InputStream inStream)
              throws CertificateException {
          if (inStream == null) {
              throw new CertificateException("inStream == null");
          }
          try {
              if (!inStream.markSupported()) {
                  // create the mark supporting wrapper
                  inStream = new RestoringInputStream(inStream);
              }
              // mark is needed to recognize the format of the provided encoding
              // (ASN.1 or PEM)
              inStream.mark(1);
              // check whether the provided certificate is in PEM encoded form
              if (inStream.read() == '-') {
                  // decode PEM, retrieve CRL
                  return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX));
              } else {
                  inStream.reset();
                  // retrieve CRL
                  return getCertificate(inStream);
              }
          } catch (IOException e) {
              throw new CertificateException(e);
          }
      }
  
      /**
       * Generates the collection of the certificates on the base of provided
       * via input stream encodings.
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream)
       * method documentation for more info
       */
      public Collection<? extends Certificate>
              engineGenerateCertificates(InputStream inStream)
                  throws CertificateException {
          if (inStream == null) {
              throw new CertificateException("inStream == null");
          }
          ArrayList<Certificate> result = new ArrayList<>();
          try {
              if (!inStream.markSupported()) {
                  // create the mark supporting wrapper
                  inStream = new RestoringInputStream(inStream);
              }
              // if it is PEM encoded form this array will contain the encoding
              // so ((it is PEM) <-> (encoding != null))
              byte[] encoding = null;
              // The following by SEQUENCE ASN.1 tag, used for
              // recognizing the data format
              // (is it PKCS7 ContentInfo structure, X.509 Certificate, or
              // unsupported encoding)
              int second_asn1_tag = -1;
              inStream.mark(1);
              int ch;
              while ((ch = inStream.read()) != -1) {
                  // check if it is PEM encoded form
                  if (ch == '-') { // beginning of PEM encoding ('-' char)
                      // decode PEM chunk and store its content (ASN.1 encoding)
                      encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
                  } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
                      encoding = null;
                      inStream.reset();
                      // prepare for data format determination
                      inStream.mark(CERT_CACHE_SEED_LENGTH);
                  } else { // unsupported data
                      if (result.size() == 0) {
                          throw new CertificateException("Unsupported encoding");
                      } else {
                          // it can be trailing user data,
                          // so keep it in the stream
                          inStream.reset();
                          return result;
                      }
                  }
                  // Check the data format
                  BerInputStream in = (encoding == null)
                                          ? new BerInputStream(inStream)
                                          : new BerInputStream(encoding);
                  // read the next ASN.1 tag
                  second_asn1_tag = in.next(); // inStream position changed
                  if (encoding == null) {
                      // keep whole structure in the stream
                      inStream.reset();
                  }
                  // check if it is a TBSCertificate structure
                  if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
                      if (result.size() == 0) {
                          // there were not read X.509 Certificates, so
                          // break the cycle and check
                          // whether it is PKCS7 structure
                          break;
                      } else {
                          // it can be trailing user data,
                          // so return what we already read
                          return result;
                      }
                  } else {
                      if (encoding == null) {
                          result.add(getCertificate(inStream));
                      } else {
                          result.add(getCertificate(encoding));
                      }
                  }
                  // mark for the next iteration
                  inStream.mark(1);
              }
              if (result.size() != 0) {
                  // some Certificates have been read
                  return result;
              } else if (ch == -1) {
                  throw new CertificateException("There is no data in the stream");
              }
              // else: check if it is PKCS7
              if (second_asn1_tag == ASN1Constants.TAG_OID) {
                  // it is PKCS7 ContentInfo structure, so decode it
                  ContentInfo info = (ContentInfo)
                      ((encoding != null)
                          ? ContentInfo.ASN1.decode(encoding)
                          : ContentInfo.ASN1.decode(inStream));
                  // retrieve SignedData
                  SignedData data = info.getSignedData();
                  if (data == null) {
                      throw new CertificateException("Invalid PKCS7 data provided");
                  }
                  List<android.framework.org.apache.harmony.security_custom.x509.Certificate> certs = data.getCertificates();
                  if (certs != null) {
                      for (android.framework.org.apache.harmony.security_custom.x509.Certificate cert : certs) {
                          result.add(new X509CertImpl(cert));
                      }
                  }
                  return result;
              }
              // else: Unknown data format
              throw new CertificateException("Unsupported encoding");
          } catch (IOException e) {
              throw new CertificateException(e);
          }
      }
  
      /**
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
       * method documentation for more info
       */
      public CRL engineGenerateCRL(InputStream inStream)
              throws CRLException {
          if (inStream == null) {
              throw new CRLException("inStream == null");
          }
          try {
              if (!inStream.markSupported()) {
                  // Create the mark supporting wrapper
                  // Mark is needed to recognize the format
                  // of provided encoding form (ASN.1 or PEM)
                  inStream = new RestoringInputStream(inStream);
              }
              inStream.mark(1);
              // check whether the provided crl is in PEM encoded form
              if (inStream.read() == '-') {
                  // decode PEM, retrieve CRL
                  return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
              } else {
                  inStream.reset();
                  // retrieve CRL
                  return getCRL(inStream);
              }
          } catch (IOException e) {
              throw new CRLException(e);
          }
      }
  
      /**
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
       * method documentation for more info
       */
      public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
              throws CRLException {
          if (inStream == null) {
              throw new CRLException("inStream == null");
          }
          ArrayList<CRL> result = new ArrayList<>();
          try {
              if (!inStream.markSupported()) {
                  inStream = new RestoringInputStream(inStream);
              }
              // if it is PEM encoded form this array will contain the encoding
              // so ((it is PEM) <-> (encoding != null))
              byte[] encoding = null;
              // The following by SEQUENCE ASN.1 tag, used for
              // recognizing the data format
              // (is it PKCS7 ContentInfo structure, X.509 CRL, or
              // unsupported encoding)
              int second_asn1_tag = -1;
              inStream.mark(1);
              int ch;
              while ((ch = inStream.read()) != -1) {
                  // check if it is PEM encoded form
                  if (ch == '-') { // beginning of PEM encoding ('-' char)
                      // decode PEM chunk and store its content (ASN.1 encoding)
                      encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
                  } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
                      encoding = null;
                      inStream.reset();
                      // prepare for data format determination
                      inStream.mark(CRL_CACHE_SEED_LENGTH);
                  } else { // unsupported data
                      if (result.size() == 0) {
                          throw new CRLException("Unsupported encoding");
                      } else {
                          // it can be trailing user data,
                          // so keep it in the stream
                          inStream.reset();
                          return result;
                      }
                  }
                  // Check the data format
                  BerInputStream in = (encoding == null)
                                          ? new BerInputStream(inStream)
                                          : new BerInputStream(encoding);
                  // read the next ASN.1 tag
                  second_asn1_tag = in.next();
                  if (encoding == null) {
                      // keep whole structure in the stream
                      inStream.reset();
                  }
                  // check if it is a TBSCertList structure
                  if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
                      if (result.size() == 0) {
                          // there were not read X.509 CRLs, so
                          // break the cycle and check
                          // whether it is PKCS7 structure
                          break;
                      } else {
                          // it can be trailing user data,
                          // so return what we already read
                          return result;
                      }
                  } else {
                      if (encoding == null) {
                          result.add(getCRL(inStream));
                      } else {
                          result.add(getCRL(encoding));
                      }
                  }
                  inStream.mark(1);
              }
              if (result.size() != 0) {
                  // the stream was read out
                  return result;
              } else if (ch == -1) {
                  throw new CRLException("There is no data in the stream");
              }
              // else: check if it is PKCS7
              if (second_asn1_tag == ASN1Constants.TAG_OID) {
                  // it is PKCS7 ContentInfo structure, so decode it
                  ContentInfo info = (ContentInfo)
                      ((encoding != null)
                          ? ContentInfo.ASN1.decode(encoding)
                          : ContentInfo.ASN1.decode(inStream));
                  // retrieve SignedData
                  SignedData data = info.getSignedData();
                  if (data == null) {
                      throw new CRLException("Invalid PKCS7 data provided");
                  }
                  List<CertificateList> crls = data.getCRLs();
                  if (crls != null) {
                      for (CertificateList crl : crls) {
                          result.add(new X509CRLImpl(crl));
                      }
                  }
                  return result;
              }
              // else: Unknown data format
              throw new CRLException("Unsupported encoding");
          } catch (IOException e) {
              throw new CRLException(e);
          }
      }
  
      /**
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
       * method documentation for more info
       */
      public CertPath engineGenerateCertPath(InputStream inStream)
              throws CertificateException {
          if (inStream == null) {
              throw new CertificateException("inStream == null");
          }
          return engineGenerateCertPath(inStream, "PkiPath");
      }
  
      /**
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
       * method documentation for more info
       */
      public CertPath engineGenerateCertPath(
              InputStream inStream, String encoding) throws CertificateException {
          if (inStream == null) {
              throw new CertificateException("inStream == null");
          }
          if (!inStream.markSupported()) {
              inStream = new RestoringInputStream(inStream);
          }
          try {
              inStream.mark(1);
              int ch;
  
              // check if it is PEM encoded form
              if ((ch = inStream.read()) == '-') {
                  // decode PEM chunk into ASN.1 form and decode CertPath object
                  return X509CertPathImpl.getInstance(
                          decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
              } else if (ch == 0x30) { // ASN.1 Sequence
                  inStream.reset();
                  // decode ASN.1 form
                  return X509CertPathImpl.getInstance(inStream, encoding);
              } else {
                  throw new CertificateException("Unsupported encoding");
              }
          } catch (IOException e) {
              throw new CertificateException(e);
          }
      }
  
      /**
       * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
       * method documentation for more info
       */
      public CertPath engineGenerateCertPath(List certificates)
              throws CertificateException {
          return new X509CertPathImpl(certificates);
      }
  
      /**
       * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
       * method documentation for more info
       */
      public Iterator<String> engineGetCertPathEncodings() {
          return X509CertPathImpl.encodings.iterator();
      }
  
      // ---------------------------------------------------------------------
      // ------------------------ Staff methods ------------------------------
      // ---------------------------------------------------------------------
  
      private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(Charset.forName("UTF-8"));
      private static final byte[] PEM_END = "-----END".getBytes(Charset.forName("UTF-8"));
      /**
       * Code describing free format for PEM boundary suffix:
       * "^-----BEGIN.*\n"         at the beginning, and<br>
       * "\n-----END.*(EOF|\n)$"   at the end.
       */
      private static final byte[] FREE_BOUND_SUFFIX = null;
      /**
       * Code describing PEM boundary suffix for X.509 certificate:
       * "^-----BEGIN CERTIFICATE-----\n"   at the beginning, and<br>
       * "\n-----END CERTIFICATE-----"   at the end.
       */
      private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(Charset.forName("UTF-8"));
  
      /**
       * Method retrieves the PEM encoded data from the stream
       * and returns its decoded representation.
       * Method checks correctness of PEM boundaries. It supposes that
       * the first '-' of the opening boundary has already been read from
       * the stream. So first of all it checks that the leading bytes
       * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
       * is not null, it checks that next bytes equal to boundary_suffix
       * + new line char[s] ([CR]LF).
       * If boundary_suffix parameter is null, method supposes free suffix
       * format and skips any bytes until the new line.<br>
       * After the opening boundary has been read and checked, the method
       * read Base64 encoded data until closing PEM boundary is not reached.<br>
       * Than it checks closing boundary - it should start with new line +
       * "-----END" + boundary_suffix. If boundary_suffix is null,
       * any characters are skipped until the new line.<br>
       * After this any trailing new line characters are skipped from the stream,
       * Base64 encoding is decoded and returned.
       * @param inStream the stream containing the PEM encoding.
       * @param boundary_suffix the suffix of expected PEM multipart
       * boundary delimiter.<br>
       * If it is null, that any character sequences are accepted.
       * @throws IOException If PEM boundary delimiter does not comply
       * with expected or some I/O or decoding problems occur.
       */
      private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
                                                          throws IOException {
          int ch; // the char to be read
          // check and skip opening boundary delimiter
          // (first '-' is supposed as already read)
          for (int i = 1; i < PEM_BEGIN.length; ++i) {
              if (PEM_BEGIN[i] != (ch = inStream.read())) {
                  throw new IOException(
                      "Incorrect PEM encoding: '-----BEGIN"
                      + ((boundary_suffix == null)
                          ? "" : new String(boundary_suffix))
                      + "' is expected as opening delimiter boundary.");
              }
          }
          if (boundary_suffix == null) {
              // read (skip) the trailing characters of
              // the beginning PEM boundary delimiter
              while ((ch = inStream.read()) != '\n') {
                  if (ch == -1) {
                      throw new IOException("Incorrect PEM encoding: EOF before content");
                  }
              }
          } else {
              for (int i=0; i<boundary_suffix.length; i++) {
                  if (boundary_suffix[i] != inStream.read()) {
                      throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
                              new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
                  }
              }
              // read new line characters
              if ((ch = inStream.read()) == '\r') {
                  // CR has been read, now read LF character
                  ch = inStream.read();
              }
              if (ch != '\n') {
                  throw new IOException("Incorrect PEM encoding: newline expected after " +
                          "opening delimiter boundary");
              }
          }
          int size = 1024; // the size of the buffer containing Base64 data
          byte[] buff = new byte[size];
          int index = 0;
          // read bytes while ending boundary delimiter is not reached
          while ((ch = inStream.read()) != '-') {
              if (ch == -1) {
                  throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
              }
              buff[index++] = (byte) ch;
              if (index == size) {
                  // enlarge the buffer
                  byte[] newbuff = new byte[size+1024];
                  System.arraycopy(buff, 0, newbuff, 0, size);
                  buff = newbuff;
                  size += 1024;
              }
          }
          if (buff[index-1] != '\n') {
              throw new IOException("Incorrect Base64 encoding: newline expected before " +
                      "closing boundary delimiter");
          }
          // check and skip closing boundary delimiter prefix
          // (first '-' was read)
          for (int i = 1; i < PEM_END.length; ++i) {
              if (PEM_END[i] != inStream.read()) {
                  throw badEnd(boundary_suffix);
              }
          }
          if (boundary_suffix == null) {
              // read (skip) the trailing characters of
              // the closing PEM boundary delimiter
              while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
              }
          } else {
              for (int i=0; i<boundary_suffix.length; i++) {
                  if (boundary_suffix[i] != inStream.read()) {
                      throw badEnd(boundary_suffix);
                  }
              }
          }
          // skip trailing line breaks
          inStream.mark(1);
          while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
              inStream.mark(1);
          }
          inStream.reset();
          buff = android.util.Base64.decode(buff, index);
          if (buff == null) {
              throw new IOException("Incorrect Base64 encoding");
          }
          return buff;
      }
  
      private IOException badEnd(byte[] boundary_suffix) throws IOException {
          String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
          throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
      }
  
      /**
       * Reads the data of specified length from source
       * and returns it as an array.
       * @return the byte array contained read data or
       * null if the stream contains not enough data
       * @throws IOException if some I/O error has been occurred.
       */
      private static byte[] readBytes(InputStream source, int length)
                                                              throws IOException {
          byte[] result = new byte[length];
          for (int i=0; i<length; i++) {
              int bytik = source.read();
              if (bytik == -1) {
                  return null;
              }
              result[i] = (byte) bytik;
          }
          return result;
      }
  
      /**
       * Returns the Certificate object corresponding to the provided encoding.
       * Resulting object is retrieved from the cache
       * if it contains such correspondence
       * and is constructed on the base of encoding
       * and stored in the cache otherwise.
       * @throws IOException if some decoding errors occur
       * (in the case of cache miss).
       */
      private static Certificate getCertificate(byte[] encoding)
                                      throws CertificateException, IOException {
          if (encoding.length < CERT_CACHE_SEED_LENGTH) {
              throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
          }
          synchronized (CERT_CACHE) {
              long hash = CERT_CACHE.getHash(encoding);
              if (CERT_CACHE.contains(hash)) {
                  Certificate res =
                      (Certificate) CERT_CACHE.get(hash, encoding);
                  if (res != null) {
                      return res;
                  }
              }
              Certificate res = new X509CertImpl(encoding);
              CERT_CACHE.put(hash, encoding, res);
              return res;
          }
      }
  
      /**
       * Returns the Certificate object corresponding to the encoding provided
       * by the stream.
       * Resulting object is retrieved from the cache
       * if it contains such correspondence
       * and is constructed on the base of encoding
       * and stored in the cache otherwise.
       * @throws IOException if some decoding errors occur
       * (in the case of cache miss).
       */
      private static Certificate getCertificate(InputStream inStream)
                                      throws CertificateException, IOException {
          synchronized (CERT_CACHE) {
              inStream.mark(CERT_CACHE_SEED_LENGTH);
              // read the prefix of the encoding
              byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
              inStream.reset();
              if (buff == null) {
                  throw new CertificateException("InputStream doesn't contain enough data");
              }
              long hash = CERT_CACHE.getHash(buff);
              if (CERT_CACHE.contains(hash)) {
                  byte[] encoding = new byte[BerInputStream.getLength(buff)];
                  if (encoding.length < CERT_CACHE_SEED_LENGTH) {
                      throw new CertificateException("Bad Certificate encoding");
                  }
  
                  inStream.read(encoding);
  
                  Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
                  if (res != null) {
                      return res;
                  }
                  res = new X509CertImpl(encoding);
                  CERT_CACHE.put(hash, encoding, res);
                  return res;
              } else {
                  inStream.reset();
                  Certificate res = new X509CertImpl(inStream);
                  CERT_CACHE.put(hash, res.getEncoded(), res);
                  return res;
              }
          }
      }
  
      /**
       * Returns the CRL object corresponding to the provided encoding.
       * Resulting object is retrieved from the cache
       * if it contains such correspondence
       * and is constructed on the base of encoding
       * and stored in the cache otherwise.
       * @throws IOException if some decoding errors occur
       * (in the case of cache miss).
       */
      private static CRL getCRL(byte[] encoding)
                                              throws CRLException, IOException {
          if (encoding.length < CRL_CACHE_SEED_LENGTH) {
              throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
          }
          synchronized (CRL_CACHE) {
              long hash = CRL_CACHE.getHash(encoding);
              if (CRL_CACHE.contains(hash)) {
                  X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
                  if (res != null) {
                      return res;
                  }
              }
              X509CRL res = new X509CRLImpl(encoding);
              CRL_CACHE.put(hash, encoding, res);
              return res;
          }
      }
  
      /**
       * Returns the CRL object corresponding to the encoding provided
       * by the stream.
       * Resulting object is retrieved from the cache
       * if it contains such correspondence
       * and is constructed on the base of encoding
       * and stored in the cache otherwise.
       * @throws IOException if some decoding errors occur
       * (in the case of cache miss).
       */
      private static CRL getCRL(InputStream inStream)
                                              throws CRLException, IOException {
          synchronized (CRL_CACHE) {
              inStream.mark(CRL_CACHE_SEED_LENGTH);
              byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
              // read the prefix of the encoding
              inStream.reset();
              if (buff == null) {
                  throw new CRLException("InputStream doesn't contain enough data");
              }
              long hash = CRL_CACHE.getHash(buff);
              if (CRL_CACHE.contains(hash)) {
                  byte[] encoding = new byte[BerInputStream.getLength(buff)];
                  if (encoding.length < CRL_CACHE_SEED_LENGTH) {
                      throw new CRLException("Bad CRL encoding");
                  }
  
                  inStream.read(encoding);
  
                  CRL res = (CRL) CRL_CACHE.get(hash, encoding);
                  if (res != null) {
                      return res;
                  }
                  res = new X509CRLImpl(encoding);
                  CRL_CACHE.put(hash, encoding, res);
                  return res;
              } else {
                  X509CRL res = new X509CRLImpl(inStream);
                  CRL_CACHE.put(hash, res.getEncoded(), res);
                  return res;
              }
          }
      }
  
      /*
       * This class extends any existing input stream with
       * mark functionality. It acts as a wrapper over the
       * stream and supports reset to the
       * marked state with readlimit no more than BUFF_SIZE.
       */
      private static class RestoringInputStream extends InputStream {
  
          // wrapped input stream
          private final InputStream inStream;
          // specifies how much of the read data is buffered
          // after the mark has been set up
          private static final int BUFF_SIZE = 32;
          // buffer to keep the bytes read after the mark has been set up
          private final int[] buff = new int[BUFF_SIZE*2];
          // position of the next byte to read,
          // the value of -1 indicates that the buffer is not used
          // (mark was not set up or was invalidated, or reset to the marked
          // position has been done and all the buffered data was read out)
          private int pos = -1;
          // position of the last buffered byte
          private int bar = 0;
          // position in the buffer where the mark becomes invalidated
          private int end = 0;
  
          /**
           * Creates the mark supporting wrapper over the stream.
           */
          public RestoringInputStream(InputStream inStream) {
              this.inStream = inStream;
          }
  
          @Override
          public int available() throws IOException {
              return (bar - pos) + inStream.available();
          }
  
          @Override
          public void close() throws IOException {
              inStream.close();
          }
  
          @Override
          public void mark(int readlimit) {
              if (pos < 0) {
                  pos = 0;
                  bar = 0;
                  end = BUFF_SIZE - 1;
              } else {
                  end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
              }
          }
  
          @Override
          public boolean markSupported() {
              return true;
          }
  
          /**
           * Reads the byte from the stream. If mark has been set up
           * and was not invalidated byte is read from the underlying
           * stream and saved into the buffer. If the current read position
           * has been reset to the marked position and there are remaining
           * bytes in the buffer, the byte is taken from it. In the other cases
           * (if mark has been invalidated, or there are no buffered bytes)
           * the byte is taken directly from the underlying stream and it is
           * returned without saving to the buffer.
           *
           * @see java.io.InputStream#read()
           * method documentation for more info
           */
          public int read() throws IOException {
              // if buffer is currently used
              if (pos >= 0) {
                  // current position in the buffer
                  int cur = pos % BUFF_SIZE;
                  // check whether the buffer contains the data to be read
                  if (cur < bar) {
                      // return the data from the buffer
                      pos++;
                      return buff[cur];
                  }
                  // check whether buffer has free space
                  if (cur != end) {
                      // it has, so read the data from the wrapped stream
                      // and place it in the buffer
                      buff[cur] = inStream.read();
                      bar = cur+1;
                      pos++;
                      return buff[cur];
                  } else {
                      // buffer if full and can not operate
                      // any more, so invalidate the mark position
                      // and turn off the using of buffer
                      pos = -1;
                  }
              }
              // buffer is not used, so return the data from the wrapped stream
              return inStream.read();
          }
  
          @Override
          public int read(byte[] b, int off, int len) throws IOException {
              int read_b;
              int i;
              for (i=0; i<len; i++) {
                  if ((read_b = read()) == -1) {
                      return (i == 0) ? -1 : i;
                  }
                  b[off+i] = (byte) read_b;
              }
              return i;
          }
  
          @Override
          public void reset() throws IOException {
              if (pos >= 0) {
                  pos = (end + 1) % BUFF_SIZE;
              } else {
                  throw new IOException("Could not reset the stream: " +
                          "position became invalid or stream has not been marked");
              }
          }
      }
  }
  