OSDN Git Service

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