summaryrefslogtreecommitdiffstats
path: root/luni/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
blob: 9129ec2f878bed3f916cb7373431b2e141fd024c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
/*
 *  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.StandardCharsets;
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(StandardCharsets.UTF_8);
    private static final byte[] PEM_END = "-----END".getBytes(StandardCharsets.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(StandardCharsets.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");
            }
        }
    }
}