| /* |
| * 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 org.apache.harmony.security.provider.cert; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.Charsets; |
| 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 libcore.io.Base64; |
| import libcore.io.Streams; |
| import org.apache.harmony.security.asn1.ASN1Constants; |
| import org.apache.harmony.security.asn1.BerInputStream; |
| import org.apache.harmony.security.pkcs7.ContentInfo; |
| import org.apache.harmony.security.pkcs7.SignedData; |
| import org.apache.harmony.security.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<Certificate>(); |
| 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) { |
| /* No data in the stream, so return the empty collection. */ |
| return result; |
| } |
| // 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<org.apache.harmony.security.x509.Certificate> certs = data.getCertificates(); |
| if (certs != null) { |
| for (org.apache.harmony.security.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<CRL>(); |
| 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<? extends Certificate> 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(Charsets.UTF_8); |
| private static final byte[] PEM_END = "-----END".getBytes(Charsets.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(Charsets.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 = 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"); |
| } |
| Streams.readFully(inStream, 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"); |
| } |
| Streams.readFully(inStream, 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"); |
| } |
| } |
| } |
| } |