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
23package org.apache.harmony.security.provider.cert;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.nio.charset.StandardCharsets;
28import java.security.cert.CRL;
29import java.security.cert.CRLException;
30import java.security.cert.CertPath;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateException;
33import java.security.cert.CertificateFactorySpi;
34import java.security.cert.X509CRL;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.Iterator;
38import java.util.List;
39import libcore.io.Base64;
40import libcore.io.Streams;
41import org.apache.harmony.security.asn1.ASN1Constants;
42import org.apache.harmony.security.asn1.BerInputStream;
43import org.apache.harmony.security.pkcs7.ContentInfo;
44import org.apache.harmony.security.pkcs7.SignedData;
45import 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 */
56public 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