blob: 4b3cd0ec3d1e59e267d506cf2528207c861c2237 [file] [log] [blame]
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001/*
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
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080025import java.io.IOException;
26import java.io.InputStream;
Elliott Hughese810d3b2010-06-14 15:32:16 -070027import java.nio.charset.Charsets;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080028import java.security.cert.CRL;
29import java.security.cert.CRLException;
30import java.security.cert.CertPath;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateException;
33import java.security.cert.CertificateFactorySpi;
34import java.security.cert.X509CRL;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.Iterator;
38import java.util.List;
Elliott Hughesf5309a32011-02-24 18:51:21 -080039import libcore.io.Streams;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080040import org.apache.harmony.luni.util.Base64;
41import org.apache.harmony.security.asn1.ASN1Constants;
42import org.apache.harmony.security.asn1.BerInputStream;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080043import org.apache.harmony.security.pkcs7.ContentInfo;
44import org.apache.harmony.security.pkcs7.SignedData;
45import org.apache.harmony.security.x509.CertificateList;
46
47/**
48 * X509 Certificate Factory Service Provider Interface Implementation.
49 * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form,
50 * and Certification Paths in PkiPath and PKCS7 formats.
51 * For Certificates and CRLs factory maintains the caching
52 * mechanisms allowing to speed up repeated Certificate/CRL
53 * generation.
54 * @see Cache
55 */
56public class X509CertFactoryImpl extends CertificateFactorySpi {
57
58 // number of leading/trailing bytes used for cert hash computation
Elliott Hughese810d3b2010-06-14 15:32:16 -070059 private static final int CERT_CACHE_SEED_LENGTH = 28;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080060 // certificate cache
Elliott Hughese810d3b2010-06-14 15:32:16 -070061 private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080062 // number of leading/trailing bytes used for crl hash computation
Elliott Hughese810d3b2010-06-14 15:32:16 -070063 private static final int CRL_CACHE_SEED_LENGTH = 24;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080064 // crl cache
Elliott Hughese810d3b2010-06-14 15:32:16 -070065 private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080066
67 /**
68 * Default constructor.
69 * Creates the instance of Certificate Factory SPI ready for use.
70 */
71 public X509CertFactoryImpl() { }
72
73 /**
74 * Generates the X.509 certificate from the data in the stream.
75 * The data in the stream can be either in ASN.1 DER encoded X.509
76 * certificate, or PEM (Base64 encoding bounded by
77 * <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and
78 * <code>"-----END CERTIFICATE-----"</code> at the end) representation
79 * of the former encoded form.
80 *
81 * Before the generation the encoded form is looked up in
82 * the cache. If the cache contains the certificate with requested encoded
83 * form it is returned from it, otherwise it is generated by ASN.1
84 * decoder.
85 *
86 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream)
87 * method documentation for more info
88 */
89 public Certificate engineGenerateCertificate(InputStream inStream)
90 throws CertificateException {
91 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -070092 throw new CertificateException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080093 }
94 try {
95 if (!inStream.markSupported()) {
96 // create the mark supporting wrapper
97 inStream = new RestoringInputStream(inStream);
98 }
99 // mark is needed to recognize the format of the provided encoding
100 // (ASN.1 or PEM)
101 inStream.mark(1);
102 // check whether the provided certificate is in PEM encoded form
103 if (inStream.read() == '-') {
104 // decode PEM, retrieve CRL
105 return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX));
106 } else {
107 inStream.reset();
108 // retrieve CRL
109 return getCertificate(inStream);
110 }
111 } catch (IOException e) {
112 throw new CertificateException(e);
113 }
114 }
115
116 /**
117 * Generates the collection of the certificates on the base of provided
118 * via input stream encodings.
119 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream)
120 * method documentation for more info
121 */
122 public Collection<? extends Certificate>
123 engineGenerateCertificates(InputStream inStream)
124 throws CertificateException {
125 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700126 throw new CertificateException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800127 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800128 ArrayList<Certificate> result = new ArrayList<Certificate>();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800129 try {
130 if (!inStream.markSupported()) {
131 // create the mark supporting wrapper
132 inStream = new RestoringInputStream(inStream);
133 }
134 // if it is PEM encoded form this array will contain the encoding
135 // so ((it is PEM) <-> (encoding != null))
136 byte[] encoding = null;
137 // The following by SEQUENCE ASN.1 tag, used for
Elliott Hughesf33eae72010-05-13 12:36:25 -0700138 // recognizing the data format
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800139 // (is it PKCS7 ContentInfo structure, X.509 Certificate, or
140 // unsupported encoding)
141 int second_asn1_tag = -1;
142 inStream.mark(1);
143 int ch;
144 while ((ch = inStream.read()) != -1) {
145 // check if it is PEM encoded form
146 if (ch == '-') { // beginning of PEM encoding ('-' char)
147 // decode PEM chunk and store its content (ASN.1 encoding)
148 encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
149 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
150 encoding = null;
151 inStream.reset();
152 // prepare for data format determination
153 inStream.mark(CERT_CACHE_SEED_LENGTH);
154 } else { // unsupported data
155 if (result.size() == 0) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700156 throw new CertificateException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800157 } else {
158 // it can be trailing user data,
159 // so keep it in the stream
160 inStream.reset();
161 return result;
162 }
163 }
164 // Check the data format
165 BerInputStream in = (encoding == null)
166 ? new BerInputStream(inStream)
167 : new BerInputStream(encoding);
168 // read the next ASN.1 tag
169 second_asn1_tag = in.next(); // inStream position changed
170 if (encoding == null) {
171 // keep whole structure in the stream
172 inStream.reset();
173 }
174 // check if it is a TBSCertificate structure
175 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
176 if (result.size() == 0) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700177 // there were not read X.509 Certificates, so
178 // break the cycle and check
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800179 // whether it is PKCS7 structure
180 break;
181 } else {
182 // it can be trailing user data,
183 // so return what we already read
184 return result;
185 }
186 } else {
187 if (encoding == null) {
188 result.add(getCertificate(inStream));
189 } else {
190 result.add(getCertificate(encoding));
191 }
192 }
193 // mark for the next iteration
194 inStream.mark(1);
195 }
196 if (result.size() != 0) {
197 // some Certificates have been read
198 return result;
199 } else if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700200 throw new CertificateException("There is no data in the stream");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800201 }
202 // else: check if it is PKCS7
203 if (second_asn1_tag == ASN1Constants.TAG_OID) {
204 // it is PKCS7 ContentInfo structure, so decode it
Elliott Hughesf33eae72010-05-13 12:36:25 -0700205 ContentInfo info = (ContentInfo)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800206 ((encoding != null)
207 ? ContentInfo.ASN1.decode(encoding)
208 : ContentInfo.ASN1.decode(inStream));
209 // retrieve SignedData
210 SignedData data = info.getSignedData();
211 if (data == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700212 throw new CertificateException("Invalid PKCS7 data provided");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800213 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800214 List<org.apache.harmony.security.x509.Certificate> certs = data.getCertificates();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800215 if (certs != null) {
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800216 for (org.apache.harmony.security.x509.Certificate cert : certs) {
217 result.add(new X509CertImpl(cert));
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800218 }
219 }
220 return result;
221 }
222 // else: Unknown data format
Elliott Hughes897538a2010-05-28 20:00:47 -0700223 throw new CertificateException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800224 } catch (IOException e) {
225 throw new CertificateException(e);
226 }
227 }
228
229 /**
230 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
231 * method documentation for more info
232 */
233 public CRL engineGenerateCRL(InputStream inStream)
234 throws CRLException {
235 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700236 throw new CRLException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800237 }
238 try {
239 if (!inStream.markSupported()) {
240 // Create the mark supporting wrapper
Elliott Hughesf33eae72010-05-13 12:36:25 -0700241 // Mark is needed to recognize the format
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800242 // of provided encoding form (ASN.1 or PEM)
243 inStream = new RestoringInputStream(inStream);
244 }
245 inStream.mark(1);
246 // check whether the provided crl is in PEM encoded form
247 if (inStream.read() == '-') {
248 // decode PEM, retrieve CRL
249 return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
250 } else {
251 inStream.reset();
252 // retrieve CRL
253 return getCRL(inStream);
254 }
255 } catch (IOException e) {
256 throw new CRLException(e);
257 }
258 }
259
260 /**
261 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
262 * method documentation for more info
263 */
264 public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
265 throws CRLException {
266 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700267 throw new CRLException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800268 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800269 ArrayList<CRL> result = new ArrayList<CRL>();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800270 try {
271 if (!inStream.markSupported()) {
272 inStream = new RestoringInputStream(inStream);
273 }
274 // if it is PEM encoded form this array will contain the encoding
275 // so ((it is PEM) <-> (encoding != null))
276 byte[] encoding = null;
277 // The following by SEQUENCE ASN.1 tag, used for
Elliott Hughesf33eae72010-05-13 12:36:25 -0700278 // recognizing the data format
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800279 // (is it PKCS7 ContentInfo structure, X.509 CRL, or
280 // unsupported encoding)
281 int second_asn1_tag = -1;
282 inStream.mark(1);
283 int ch;
284 while ((ch = inStream.read()) != -1) {
285 // check if it is PEM encoded form
286 if (ch == '-') { // beginning of PEM encoding ('-' char)
287 // decode PEM chunk and store its content (ASN.1 encoding)
288 encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
289 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
290 encoding = null;
291 inStream.reset();
292 // prepare for data format determination
293 inStream.mark(CRL_CACHE_SEED_LENGTH);
294 } else { // unsupported data
295 if (result.size() == 0) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700296 throw new CRLException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800297 } else {
298 // it can be trailing user data,
299 // so keep it in the stream
300 inStream.reset();
301 return result;
302 }
303 }
304 // Check the data format
305 BerInputStream in = (encoding == null)
306 ? new BerInputStream(inStream)
307 : new BerInputStream(encoding);
308 // read the next ASN.1 tag
309 second_asn1_tag = in.next();
310 if (encoding == null) {
311 // keep whole structure in the stream
312 inStream.reset();
313 }
314 // check if it is a TBSCertList structure
315 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
316 if (result.size() == 0) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700317 // there were not read X.509 CRLs, so
318 // break the cycle and check
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800319 // whether it is PKCS7 structure
320 break;
321 } else {
322 // it can be trailing user data,
323 // so return what we already read
324 return result;
325 }
326 } else {
327 if (encoding == null) {
328 result.add(getCRL(inStream));
329 } else {
330 result.add(getCRL(encoding));
331 }
332 }
333 inStream.mark(1);
334 }
335 if (result.size() != 0) {
336 // the stream was read out
337 return result;
338 } else if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700339 throw new CRLException("There is no data in the stream");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800340 }
341 // else: check if it is PKCS7
342 if (second_asn1_tag == ASN1Constants.TAG_OID) {
343 // it is PKCS7 ContentInfo structure, so decode it
Elliott Hughesf33eae72010-05-13 12:36:25 -0700344 ContentInfo info = (ContentInfo)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800345 ((encoding != null)
346 ? ContentInfo.ASN1.decode(encoding)
347 : ContentInfo.ASN1.decode(inStream));
348 // retrieve SignedData
349 SignedData data = info.getSignedData();
350 if (data == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700351 throw new CRLException("Invalid PKCS7 data provided");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800352 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800353 List<CertificateList> crls = data.getCRLs();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800354 if (crls != null) {
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800355 for (CertificateList crl : crls) {
356 result.add(new X509CRLImpl(crl));
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800357 }
358 }
359 return result;
360 }
361 // else: Unknown data format
Elliott Hughes897538a2010-05-28 20:00:47 -0700362 throw new CRLException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800363 } catch (IOException e) {
364 throw new CRLException(e);
365 }
366 }
367
368 /**
369 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
370 * method documentation for more info
371 */
372 public CertPath engineGenerateCertPath(InputStream inStream)
373 throws CertificateException {
374 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700375 throw new CertificateException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800376 }
Elliott Hughesf33eae72010-05-13 12:36:25 -0700377 return engineGenerateCertPath(inStream, "PkiPath");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800378 }
379
380 /**
381 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
382 * method documentation for more info
383 */
384 public CertPath engineGenerateCertPath(
385 InputStream inStream, String encoding) throws CertificateException {
386 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700387 throw new CertificateException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800388 }
389 if (!inStream.markSupported()) {
390 inStream = new RestoringInputStream(inStream);
391 }
392 try {
393 inStream.mark(1);
394 int ch;
395
396 // check if it is PEM encoded form
397 if ((ch = inStream.read()) == '-') {
398 // decode PEM chunk into ASN.1 form and decode CertPath object
399 return X509CertPathImpl.getInstance(
400 decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
401 } else if (ch == 0x30) { // ASN.1 Sequence
402 inStream.reset();
403 // decode ASN.1 form
404 return X509CertPathImpl.getInstance(inStream, encoding);
405 } else {
Elliott Hughes897538a2010-05-28 20:00:47 -0700406 throw new CertificateException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800407 }
408 } catch (IOException e) {
409 throw new CertificateException(e);
410 }
411 }
412
413 /**
414 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
415 * method documentation for more info
416 */
417 public CertPath engineGenerateCertPath(List certificates)
418 throws CertificateException {
419 return new X509CertPathImpl(certificates);
420 }
421
422 /**
423 * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
424 * method documentation for more info
425 */
426 public Iterator<String> engineGetCertPathEncodings() {
427 return X509CertPathImpl.encodings.iterator();
428 }
429
430 // ---------------------------------------------------------------------
431 // ------------------------ Staff methods ------------------------------
432 // ---------------------------------------------------------------------
433
Elliott Hughese810d3b2010-06-14 15:32:16 -0700434 private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(Charsets.UTF_8);
435 private static final byte[] PEM_END = "-----END".getBytes(Charsets.UTF_8);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800436 /**
437 * Code describing free format for PEM boundary suffix:
438 * "^-----BEGIN.*\n" at the beginning, and<br>
439 * "\n-----END.*(EOF|\n)$" at the end.
440 */
Elliott Hughese810d3b2010-06-14 15:32:16 -0700441 private static final byte[] FREE_BOUND_SUFFIX = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800442 /**
443 * Code describing PEM boundary suffix for X.509 certificate:
444 * "^-----BEGIN CERTIFICATE-----\n" at the beginning, and<br>
445 * "\n-----END CERTIFICATE-----" at the end.
446 */
Elliott Hughese810d3b2010-06-14 15:32:16 -0700447 private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(Charsets.UTF_8);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800448
449 /**
Elliott Hughesf33eae72010-05-13 12:36:25 -0700450 * Method retrieves the PEM encoded data from the stream
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800451 * and returns its decoded representation.
452 * Method checks correctness of PEM boundaries. It supposes that
453 * the first '-' of the opening boundary has already been read from
454 * the stream. So first of all it checks that the leading bytes
455 * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
456 * is not null, it checks that next bytes equal to boundary_suffix
457 * + new line char[s] ([CR]LF).
458 * If boundary_suffix parameter is null, method supposes free suffix
459 * format and skips any bytes until the new line.<br>
460 * After the opening boundary has been read and checked, the method
461 * read Base64 encoded data until closing PEM boundary is not reached.<br>
462 * Than it checks closing boundary - it should start with new line +
Elliott Hughesf33eae72010-05-13 12:36:25 -0700463 * "-----END" + boundary_suffix. If boundary_suffix is null,
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800464 * any characters are skipped until the new line.<br>
465 * After this any trailing new line characters are skipped from the stream,
466 * Base64 encoding is decoded and returned.
467 * @param inStream the stream containing the PEM encoding.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700468 * @param boundary_suffix the suffix of expected PEM multipart
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800469 * boundary delimiter.<br>
470 * If it is null, that any character sequences are accepted.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700471 * @throws IOException If PEM boundary delimiter does not comply
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800472 * with expected or some I/O or decoding problems occur.
473 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700474 private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800475 throws IOException {
476 int ch; // the char to be read
Elliott Hughesf33eae72010-05-13 12:36:25 -0700477 // check and skip opening boundary delimiter
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800478 // (first '-' is supposed as already read)
Elliott Hughese810d3b2010-06-14 15:32:16 -0700479 for (int i = 1; i < PEM_BEGIN.length; ++i) {
480 if (PEM_BEGIN[i] != (ch = inStream.read())) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800481 throw new IOException(
482 "Incorrect PEM encoding: '-----BEGIN"
Elliott Hughesf33eae72010-05-13 12:36:25 -0700483 + ((boundary_suffix == null)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800484 ? "" : new String(boundary_suffix))
485 + "' is expected as opening delimiter boundary.");
486 }
487 }
488 if (boundary_suffix == null) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700489 // read (skip) the trailing characters of
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800490 // the beginning PEM boundary delimiter
491 while ((ch = inStream.read()) != '\n') {
492 if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700493 throw new IOException("Incorrect PEM encoding: EOF before content");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800494 }
495 }
496 } else {
497 for (int i=0; i<boundary_suffix.length; i++) {
498 if (boundary_suffix[i] != inStream.read()) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700499 throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
500 new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800501 }
502 }
503 // read new line characters
504 if ((ch = inStream.read()) == '\r') {
505 // CR has been read, now read LF character
506 ch = inStream.read();
507 }
508 if (ch != '\n') {
Elliott Hughes897538a2010-05-28 20:00:47 -0700509 throw new IOException("Incorrect PEM encoding: newline expected after " +
510 "opening delimiter boundary");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800511 }
512 }
513 int size = 1024; // the size of the buffer containing Base64 data
514 byte[] buff = new byte[size];
515 int index = 0;
516 // read bytes while ending boundary delimiter is not reached
517 while ((ch = inStream.read()) != '-') {
518 if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700519 throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800520 }
521 buff[index++] = (byte) ch;
522 if (index == size) {
523 // enlarge the buffer
524 byte[] newbuff = new byte[size+1024];
525 System.arraycopy(buff, 0, newbuff, 0, size);
526 buff = newbuff;
527 size += 1024;
528 }
529 }
530 if (buff[index-1] != '\n') {
Elliott Hughes897538a2010-05-28 20:00:47 -0700531 throw new IOException("Incorrect Base64 encoding: newline expected before " +
532 "closing boundary delimiter");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800533 }
534 // check and skip closing boundary delimiter prefix
535 // (first '-' was read)
Elliott Hughese810d3b2010-06-14 15:32:16 -0700536 for (int i = 1; i < PEM_END.length; ++i) {
537 if (PEM_END[i] != inStream.read()) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700538 throw badEnd(boundary_suffix);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800539 }
540 }
541 if (boundary_suffix == null) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700542 // read (skip) the trailing characters of
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800543 // the closing PEM boundary delimiter
Elliott Hughes897538a2010-05-28 20:00:47 -0700544 while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800545 }
546 } else {
547 for (int i=0; i<boundary_suffix.length; i++) {
548 if (boundary_suffix[i] != inStream.read()) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700549 throw badEnd(boundary_suffix);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800550 }
551 }
552 }
553 // skip trailing line breaks
554 inStream.mark(1);
555 while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
556 inStream.mark(1);
557 }
558 inStream.reset();
559 buff = Base64.decode(buff, index);
560 if (buff == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700561 throw new IOException("Incorrect Base64 encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800562 }
563 return buff;
Elliott Hughes897538a2010-05-28 20:00:47 -0700564 }
565
566 private IOException badEnd(byte[] boundary_suffix) throws IOException {
567 String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
568 throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
569 }
Elliott Hughesf33eae72010-05-13 12:36:25 -0700570
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800571 /**
Elliott Hughesf33eae72010-05-13 12:36:25 -0700572 * Reads the data of specified length from source
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800573 * and returns it as an array.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700574 * @return the byte array contained read data or
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800575 * null if the stream contains not enough data
576 * @throws IOException if some I/O error has been occurred.
577 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700578 private static byte[] readBytes(InputStream source, int length)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800579 throws IOException {
580 byte[] result = new byte[length];
581 for (int i=0; i<length; i++) {
582 int bytik = source.read();
583 if (bytik == -1) {
584 return null;
585 }
586 result[i] = (byte) bytik;
587 }
588 return result;
589 }
590
591 /**
592 * Returns the Certificate object corresponding to the provided encoding.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700593 * Resulting object is retrieved from the cache
594 * if it contains such correspondence
595 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800596 * and stored in the cache otherwise.
597 * @throws IOException if some decoding errors occur
598 * (in the case of cache miss).
599 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700600 private static Certificate getCertificate(byte[] encoding)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800601 throws CertificateException, IOException {
602 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700603 throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800604 }
605 synchronized (CERT_CACHE) {
606 long hash = CERT_CACHE.getHash(encoding);
607 if (CERT_CACHE.contains(hash)) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700608 Certificate res =
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800609 (Certificate) CERT_CACHE.get(hash, encoding);
610 if (res != null) {
611 return res;
612 }
613 }
614 Certificate res = new X509CertImpl(encoding);
615 CERT_CACHE.put(hash, encoding, res);
616 return res;
617 }
618 }
619
620 /**
621 * Returns the Certificate object corresponding to the encoding provided
622 * by the stream.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700623 * Resulting object is retrieved from the cache
624 * if it contains such correspondence
625 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800626 * and stored in the cache otherwise.
627 * @throws IOException if some decoding errors occur
628 * (in the case of cache miss).
629 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700630 private static Certificate getCertificate(InputStream inStream)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800631 throws CertificateException, IOException {
632 synchronized (CERT_CACHE) {
633 inStream.mark(CERT_CACHE_SEED_LENGTH);
634 // read the prefix of the encoding
635 byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
636 inStream.reset();
637 if (buff == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700638 throw new CertificateException("InputStream doesn't contain enough data");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800639 }
640 long hash = CERT_CACHE.getHash(buff);
641 if (CERT_CACHE.contains(hash)) {
642 byte[] encoding = new byte[BerInputStream.getLength(buff)];
643 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700644 throw new CertificateException("Bad Certificate encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800645 }
Elliott Hughesf5309a32011-02-24 18:51:21 -0800646 Streams.readFully(inStream, encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800647 Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
648 if (res != null) {
649 return res;
650 }
651 res = new X509CertImpl(encoding);
652 CERT_CACHE.put(hash, encoding, res);
653 return res;
654 } else {
655 inStream.reset();
656 Certificate res = new X509CertImpl(inStream);
657 CERT_CACHE.put(hash, res.getEncoded(), res);
658 return res;
659 }
660 }
661 }
662
663 /**
664 * Returns the CRL object corresponding to the provided encoding.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700665 * Resulting object is retrieved from the cache
666 * if it contains such correspondence
667 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800668 * and stored in the cache otherwise.
669 * @throws IOException if some decoding errors occur
670 * (in the case of cache miss).
671 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700672 private static CRL getCRL(byte[] encoding)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800673 throws CRLException, IOException {
674 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700675 throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800676 }
677 synchronized (CRL_CACHE) {
678 long hash = CRL_CACHE.getHash(encoding);
679 if (CRL_CACHE.contains(hash)) {
680 X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
681 if (res != null) {
682 return res;
683 }
684 }
685 X509CRL res = new X509CRLImpl(encoding);
686 CRL_CACHE.put(hash, encoding, res);
687 return res;
688 }
689 }
690
691 /**
692 * Returns the CRL object corresponding to the encoding provided
693 * by the stream.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700694 * Resulting object is retrieved from the cache
695 * if it contains such correspondence
696 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800697 * and stored in the cache otherwise.
698 * @throws IOException if some decoding errors occur
699 * (in the case of cache miss).
700 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700701 private static CRL getCRL(InputStream inStream)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800702 throws CRLException, IOException {
703 synchronized (CRL_CACHE) {
704 inStream.mark(CRL_CACHE_SEED_LENGTH);
705 byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
706 // read the prefix of the encoding
707 inStream.reset();
708 if (buff == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700709 throw new CRLException("InputStream doesn't contain enough data");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800710 }
711 long hash = CRL_CACHE.getHash(buff);
712 if (CRL_CACHE.contains(hash)) {
713 byte[] encoding = new byte[BerInputStream.getLength(buff)];
714 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700715 throw new CRLException("Bad CRL encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800716 }
Elliott Hughesf5309a32011-02-24 18:51:21 -0800717 Streams.readFully(inStream, encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800718 CRL res = (CRL) CRL_CACHE.get(hash, encoding);
719 if (res != null) {
720 return res;
721 }
722 res = new X509CRLImpl(encoding);
723 CRL_CACHE.put(hash, encoding, res);
724 return res;
725 } else {
726 X509CRL res = new X509CRLImpl(inStream);
727 CRL_CACHE.put(hash, res.getEncoded(), res);
728 return res;
729 }
730 }
731 }
732
733 /*
734 * This class extends any existing input stream with
735 * mark functionality. It acts as a wrapper over the
736 * stream and supports reset to the
737 * marked state with readlimit no more than BUFF_SIZE.
738 */
739 private static class RestoringInputStream extends InputStream {
740
741 // wrapped input stream
742 private final InputStream inStream;
743 // specifies how much of the read data is buffered
744 // after the mark has been set up
745 private static final int BUFF_SIZE = 32;
746 // buffer to keep the bytes read after the mark has been set up
747 private final int[] buff = new int[BUFF_SIZE*2];
748 // position of the next byte to read,
749 // the value of -1 indicates that the buffer is not used
750 // (mark was not set up or was invalidated, or reset to the marked
751 // position has been done and all the buffered data was read out)
752 private int pos = -1;
753 // position of the last buffered byte
754 private int bar = 0;
755 // position in the buffer where the mark becomes invalidated
756 private int end = 0;
757
758 /**
759 * Creates the mark supporting wrapper over the stream.
760 */
761 public RestoringInputStream(InputStream inStream) {
762 this.inStream = inStream;
763 }
764
Elliott Hughes582d9262010-04-05 11:33:58 -0700765 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800766 public int available() throws IOException {
767 return (bar - pos) + inStream.available();
768 }
769
Elliott Hughes582d9262010-04-05 11:33:58 -0700770 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800771 public void close() throws IOException {
772 inStream.close();
773 }
774
Elliott Hughes582d9262010-04-05 11:33:58 -0700775 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800776 public void mark(int readlimit) {
777 if (pos < 0) {
778 pos = 0;
779 bar = 0;
780 end = BUFF_SIZE - 1;
781 } else {
782 end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
783 }
784 }
785
Elliott Hughes582d9262010-04-05 11:33:58 -0700786 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800787 public boolean markSupported() {
788 return true;
789 }
790
791 /**
792 * Reads the byte from the stream. If mark has been set up
793 * and was not invalidated byte is read from the underlying
794 * stream and saved into the buffer. If the current read position
795 * has been reset to the marked position and there are remaining
796 * bytes in the buffer, the byte is taken from it. In the other cases
797 * (if mark has been invalidated, or there are no buffered bytes)
798 * the byte is taken directly from the underlying stream and it is
799 * returned without saving to the buffer.
800 *
801 * @see java.io.InputStream#read()
802 * method documentation for more info
803 */
804 public int read() throws IOException {
805 // if buffer is currently used
806 if (pos >= 0) {
807 // current position in the buffer
808 int cur = pos % BUFF_SIZE;
809 // check whether the buffer contains the data to be read
810 if (cur < bar) {
811 // return the data from the buffer
812 pos++;
813 return buff[cur];
814 }
815 // check whether buffer has free space
816 if (cur != end) {
817 // it has, so read the data from the wrapped stream
818 // and place it in the buffer
819 buff[cur] = inStream.read();
820 bar = cur+1;
821 pos++;
822 return buff[cur];
823 } else {
824 // buffer if full and can not operate
825 // any more, so invalidate the mark position
826 // and turn off the using of buffer
827 pos = -1;
828 }
829 }
830 // buffer is not used, so return the data from the wrapped stream
831 return inStream.read();
832 }
833
Elliott Hughes582d9262010-04-05 11:33:58 -0700834 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800835 public int read(byte[] b) throws IOException {
836 return read(b, 0, b.length);
837 }
838
Elliott Hughes582d9262010-04-05 11:33:58 -0700839 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800840 public int read(byte[] b, int off, int len) throws IOException {
841 int read_b;
842 int i;
843 for (i=0; i<len; i++) {
844 if ((read_b = read()) == -1) {
845 return (i == 0) ? -1 : i;
846 }
847 b[off+i] = (byte) read_b;
848 }
849 return i;
850 }
851
Elliott Hughes582d9262010-04-05 11:33:58 -0700852 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800853 public void reset() throws IOException {
854 if (pos >= 0) {
855 pos = (end + 1) % BUFF_SIZE;
856 } else {
Elliott Hughes897538a2010-05-28 20:00:47 -0700857 throw new IOException("Could not reset the stream: " +
858 "position became invalid or stream has not been marked");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800859 }
860 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800861 }
862}