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