blob: 9129ec2f878bed3f916cb7373431b2e141fd024c [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 Hughes2a6f23f2013-06-28 15:57:53 -070027import java.nio.charset.StandardCharsets;
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;
Jesse Wilson32b2c952011-05-10 14:59:29 -070039import libcore.io.Base64;
Elliott Hughesf5309a32011-02-24 18:51:21 -080040import libcore.io.Streams;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080041import 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) {
Kenny Root3b0aee62013-01-07 13:20:29 -0800200 /* No data in the stream, so return the empty collection. */
201 return result;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800202 }
203 // else: check if it is PKCS7
204 if (second_asn1_tag == ASN1Constants.TAG_OID) {
205 // it is PKCS7 ContentInfo structure, so decode it
Elliott Hughesf33eae72010-05-13 12:36:25 -0700206 ContentInfo info = (ContentInfo)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800207 ((encoding != null)
208 ? ContentInfo.ASN1.decode(encoding)
209 : ContentInfo.ASN1.decode(inStream));
210 // retrieve SignedData
211 SignedData data = info.getSignedData();
212 if (data == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700213 throw new CertificateException("Invalid PKCS7 data provided");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800214 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800215 List<org.apache.harmony.security.x509.Certificate> certs = data.getCertificates();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800216 if (certs != null) {
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800217 for (org.apache.harmony.security.x509.Certificate cert : certs) {
218 result.add(new X509CertImpl(cert));
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800219 }
220 }
221 return result;
222 }
223 // else: Unknown data format
Elliott Hughes897538a2010-05-28 20:00:47 -0700224 throw new CertificateException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800225 } catch (IOException e) {
226 throw new CertificateException(e);
227 }
228 }
229
230 /**
231 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
232 * method documentation for more info
233 */
234 public CRL engineGenerateCRL(InputStream inStream)
235 throws CRLException {
236 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700237 throw new CRLException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800238 }
239 try {
240 if (!inStream.markSupported()) {
241 // Create the mark supporting wrapper
Elliott Hughesf33eae72010-05-13 12:36:25 -0700242 // Mark is needed to recognize the format
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800243 // of provided encoding form (ASN.1 or PEM)
244 inStream = new RestoringInputStream(inStream);
245 }
246 inStream.mark(1);
247 // check whether the provided crl is in PEM encoded form
248 if (inStream.read() == '-') {
249 // decode PEM, retrieve CRL
250 return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
251 } else {
252 inStream.reset();
253 // retrieve CRL
254 return getCRL(inStream);
255 }
256 } catch (IOException e) {
257 throw new CRLException(e);
258 }
259 }
260
261 /**
262 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
263 * method documentation for more info
264 */
265 public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
266 throws CRLException {
267 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700268 throw new CRLException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800269 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800270 ArrayList<CRL> result = new ArrayList<CRL>();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800271 try {
272 if (!inStream.markSupported()) {
273 inStream = new RestoringInputStream(inStream);
274 }
275 // if it is PEM encoded form this array will contain the encoding
276 // so ((it is PEM) <-> (encoding != null))
277 byte[] encoding = null;
278 // The following by SEQUENCE ASN.1 tag, used for
Elliott Hughesf33eae72010-05-13 12:36:25 -0700279 // recognizing the data format
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800280 // (is it PKCS7 ContentInfo structure, X.509 CRL, or
281 // unsupported encoding)
282 int second_asn1_tag = -1;
283 inStream.mark(1);
284 int ch;
285 while ((ch = inStream.read()) != -1) {
286 // check if it is PEM encoded form
287 if (ch == '-') { // beginning of PEM encoding ('-' char)
288 // decode PEM chunk and store its content (ASN.1 encoding)
289 encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
290 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
291 encoding = null;
292 inStream.reset();
293 // prepare for data format determination
294 inStream.mark(CRL_CACHE_SEED_LENGTH);
295 } else { // unsupported data
296 if (result.size() == 0) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700297 throw new CRLException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800298 } else {
299 // it can be trailing user data,
300 // so keep it in the stream
301 inStream.reset();
302 return result;
303 }
304 }
305 // Check the data format
306 BerInputStream in = (encoding == null)
307 ? new BerInputStream(inStream)
308 : new BerInputStream(encoding);
309 // read the next ASN.1 tag
310 second_asn1_tag = in.next();
311 if (encoding == null) {
312 // keep whole structure in the stream
313 inStream.reset();
314 }
315 // check if it is a TBSCertList structure
316 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
317 if (result.size() == 0) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700318 // there were not read X.509 CRLs, so
319 // break the cycle and check
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800320 // whether it is PKCS7 structure
321 break;
322 } else {
323 // it can be trailing user data,
324 // so return what we already read
325 return result;
326 }
327 } else {
328 if (encoding == null) {
329 result.add(getCRL(inStream));
330 } else {
331 result.add(getCRL(encoding));
332 }
333 }
334 inStream.mark(1);
335 }
336 if (result.size() != 0) {
337 // the stream was read out
338 return result;
339 } else if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700340 throw new CRLException("There is no data in the stream");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800341 }
342 // else: check if it is PKCS7
343 if (second_asn1_tag == ASN1Constants.TAG_OID) {
344 // it is PKCS7 ContentInfo structure, so decode it
Elliott Hughesf33eae72010-05-13 12:36:25 -0700345 ContentInfo info = (ContentInfo)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800346 ((encoding != null)
347 ? ContentInfo.ASN1.decode(encoding)
348 : ContentInfo.ASN1.decode(inStream));
349 // retrieve SignedData
350 SignedData data = info.getSignedData();
351 if (data == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700352 throw new CRLException("Invalid PKCS7 data provided");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800353 }
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800354 List<CertificateList> crls = data.getCRLs();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800355 if (crls != null) {
Jesse Wilson5c27fb82011-01-24 17:56:05 -0800356 for (CertificateList crl : crls) {
357 result.add(new X509CRLImpl(crl));
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800358 }
359 }
360 return result;
361 }
362 // else: Unknown data format
Elliott Hughes897538a2010-05-28 20:00:47 -0700363 throw new CRLException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800364 } catch (IOException e) {
365 throw new CRLException(e);
366 }
367 }
368
369 /**
370 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
371 * method documentation for more info
372 */
373 public CertPath engineGenerateCertPath(InputStream inStream)
374 throws CertificateException {
375 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700376 throw new CertificateException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800377 }
Elliott Hughesf33eae72010-05-13 12:36:25 -0700378 return engineGenerateCertPath(inStream, "PkiPath");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800379 }
380
381 /**
382 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
383 * method documentation for more info
384 */
385 public CertPath engineGenerateCertPath(
386 InputStream inStream, String encoding) throws CertificateException {
387 if (inStream == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700388 throw new CertificateException("inStream == null");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800389 }
390 if (!inStream.markSupported()) {
391 inStream = new RestoringInputStream(inStream);
392 }
393 try {
394 inStream.mark(1);
395 int ch;
396
397 // check if it is PEM encoded form
398 if ((ch = inStream.read()) == '-') {
399 // decode PEM chunk into ASN.1 form and decode CertPath object
400 return X509CertPathImpl.getInstance(
401 decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
402 } else if (ch == 0x30) { // ASN.1 Sequence
403 inStream.reset();
404 // decode ASN.1 form
405 return X509CertPathImpl.getInstance(inStream, encoding);
406 } else {
Elliott Hughes897538a2010-05-28 20:00:47 -0700407 throw new CertificateException("Unsupported encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800408 }
409 } catch (IOException e) {
410 throw new CertificateException(e);
411 }
412 }
413
414 /**
415 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
416 * method documentation for more info
417 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800418 public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800419 throws CertificateException {
420 return new X509CertPathImpl(certificates);
421 }
422
423 /**
424 * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
425 * method documentation for more info
426 */
427 public Iterator<String> engineGetCertPathEncodings() {
428 return X509CertPathImpl.encodings.iterator();
429 }
430
431 // ---------------------------------------------------------------------
432 // ------------------------ Staff methods ------------------------------
433 // ---------------------------------------------------------------------
434
Elliott Hughes2a6f23f2013-06-28 15:57:53 -0700435 private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(StandardCharsets.UTF_8);
436 private static final byte[] PEM_END = "-----END".getBytes(StandardCharsets.UTF_8);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800437 /**
438 * Code describing free format for PEM boundary suffix:
439 * "^-----BEGIN.*\n" at the beginning, and<br>
440 * "\n-----END.*(EOF|\n)$" at the end.
441 */
Elliott Hughese810d3b2010-06-14 15:32:16 -0700442 private static final byte[] FREE_BOUND_SUFFIX = null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800443 /**
444 * Code describing PEM boundary suffix for X.509 certificate:
445 * "^-----BEGIN CERTIFICATE-----\n" at the beginning, and<br>
446 * "\n-----END CERTIFICATE-----" at the end.
447 */
Elliott Hughes2a6f23f2013-06-28 15:57:53 -0700448 private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(StandardCharsets.UTF_8);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800449
450 /**
Elliott Hughesf33eae72010-05-13 12:36:25 -0700451 * Method retrieves the PEM encoded data from the stream
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800452 * and returns its decoded representation.
453 * Method checks correctness of PEM boundaries. It supposes that
454 * the first '-' of the opening boundary has already been read from
455 * the stream. So first of all it checks that the leading bytes
456 * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
457 * is not null, it checks that next bytes equal to boundary_suffix
458 * + new line char[s] ([CR]LF).
459 * If boundary_suffix parameter is null, method supposes free suffix
460 * format and skips any bytes until the new line.<br>
461 * After the opening boundary has been read and checked, the method
462 * read Base64 encoded data until closing PEM boundary is not reached.<br>
463 * Than it checks closing boundary - it should start with new line +
Elliott Hughesf33eae72010-05-13 12:36:25 -0700464 * "-----END" + boundary_suffix. If boundary_suffix is null,
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800465 * any characters are skipped until the new line.<br>
466 * After this any trailing new line characters are skipped from the stream,
467 * Base64 encoding is decoded and returned.
468 * @param inStream the stream containing the PEM encoding.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700469 * @param boundary_suffix the suffix of expected PEM multipart
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800470 * boundary delimiter.<br>
471 * If it is null, that any character sequences are accepted.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700472 * @throws IOException If PEM boundary delimiter does not comply
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800473 * with expected or some I/O or decoding problems occur.
474 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700475 private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800476 throws IOException {
477 int ch; // the char to be read
Elliott Hughesf33eae72010-05-13 12:36:25 -0700478 // check and skip opening boundary delimiter
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800479 // (first '-' is supposed as already read)
Elliott Hughese810d3b2010-06-14 15:32:16 -0700480 for (int i = 1; i < PEM_BEGIN.length; ++i) {
481 if (PEM_BEGIN[i] != (ch = inStream.read())) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800482 throw new IOException(
483 "Incorrect PEM encoding: '-----BEGIN"
Elliott Hughesf33eae72010-05-13 12:36:25 -0700484 + ((boundary_suffix == null)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800485 ? "" : new String(boundary_suffix))
486 + "' is expected as opening delimiter boundary.");
487 }
488 }
489 if (boundary_suffix == null) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700490 // read (skip) the trailing characters of
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800491 // the beginning PEM boundary delimiter
492 while ((ch = inStream.read()) != '\n') {
493 if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700494 throw new IOException("Incorrect PEM encoding: EOF before content");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800495 }
496 }
497 } else {
498 for (int i=0; i<boundary_suffix.length; i++) {
499 if (boundary_suffix[i] != inStream.read()) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700500 throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
501 new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800502 }
503 }
504 // read new line characters
505 if ((ch = inStream.read()) == '\r') {
506 // CR has been read, now read LF character
507 ch = inStream.read();
508 }
509 if (ch != '\n') {
Elliott Hughes897538a2010-05-28 20:00:47 -0700510 throw new IOException("Incorrect PEM encoding: newline expected after " +
511 "opening delimiter boundary");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800512 }
513 }
514 int size = 1024; // the size of the buffer containing Base64 data
515 byte[] buff = new byte[size];
516 int index = 0;
517 // read bytes while ending boundary delimiter is not reached
518 while ((ch = inStream.read()) != '-') {
519 if (ch == -1) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700520 throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800521 }
522 buff[index++] = (byte) ch;
523 if (index == size) {
524 // enlarge the buffer
525 byte[] newbuff = new byte[size+1024];
526 System.arraycopy(buff, 0, newbuff, 0, size);
527 buff = newbuff;
528 size += 1024;
529 }
530 }
531 if (buff[index-1] != '\n') {
Elliott Hughes897538a2010-05-28 20:00:47 -0700532 throw new IOException("Incorrect Base64 encoding: newline expected before " +
533 "closing boundary delimiter");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800534 }
535 // check and skip closing boundary delimiter prefix
536 // (first '-' was read)
Elliott Hughese810d3b2010-06-14 15:32:16 -0700537 for (int i = 1; i < PEM_END.length; ++i) {
538 if (PEM_END[i] != inStream.read()) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700539 throw badEnd(boundary_suffix);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800540 }
541 }
542 if (boundary_suffix == null) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700543 // read (skip) the trailing characters of
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800544 // the closing PEM boundary delimiter
Elliott Hughes897538a2010-05-28 20:00:47 -0700545 while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800546 }
547 } else {
548 for (int i=0; i<boundary_suffix.length; i++) {
549 if (boundary_suffix[i] != inStream.read()) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700550 throw badEnd(boundary_suffix);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800551 }
552 }
553 }
554 // skip trailing line breaks
555 inStream.mark(1);
556 while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
557 inStream.mark(1);
558 }
559 inStream.reset();
560 buff = Base64.decode(buff, index);
561 if (buff == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700562 throw new IOException("Incorrect Base64 encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800563 }
564 return buff;
Elliott Hughes897538a2010-05-28 20:00:47 -0700565 }
566
567 private IOException badEnd(byte[] boundary_suffix) throws IOException {
568 String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
569 throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
570 }
Elliott Hughesf33eae72010-05-13 12:36:25 -0700571
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800572 /**
Elliott Hughesf33eae72010-05-13 12:36:25 -0700573 * Reads the data of specified length from source
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800574 * and returns it as an array.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700575 * @return the byte array contained read data or
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800576 * null if the stream contains not enough data
577 * @throws IOException if some I/O error has been occurred.
578 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700579 private static byte[] readBytes(InputStream source, int length)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800580 throws IOException {
581 byte[] result = new byte[length];
582 for (int i=0; i<length; i++) {
583 int bytik = source.read();
584 if (bytik == -1) {
585 return null;
586 }
587 result[i] = (byte) bytik;
588 }
589 return result;
590 }
591
592 /**
593 * Returns the Certificate object corresponding to the provided encoding.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700594 * Resulting object is retrieved from the cache
595 * if it contains such correspondence
596 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800597 * and stored in the cache otherwise.
598 * @throws IOException if some decoding errors occur
599 * (in the case of cache miss).
600 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700601 private static Certificate getCertificate(byte[] encoding)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800602 throws CertificateException, IOException {
603 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700604 throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800605 }
606 synchronized (CERT_CACHE) {
607 long hash = CERT_CACHE.getHash(encoding);
608 if (CERT_CACHE.contains(hash)) {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700609 Certificate res =
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800610 (Certificate) CERT_CACHE.get(hash, encoding);
611 if (res != null) {
612 return res;
613 }
614 }
615 Certificate res = new X509CertImpl(encoding);
616 CERT_CACHE.put(hash, encoding, res);
617 return res;
618 }
619 }
620
621 /**
622 * Returns the Certificate object corresponding to the encoding provided
623 * by the stream.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700624 * Resulting object is retrieved from the cache
625 * if it contains such correspondence
626 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800627 * and stored in the cache otherwise.
628 * @throws IOException if some decoding errors occur
629 * (in the case of cache miss).
630 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700631 private static Certificate getCertificate(InputStream inStream)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800632 throws CertificateException, IOException {
633 synchronized (CERT_CACHE) {
634 inStream.mark(CERT_CACHE_SEED_LENGTH);
635 // read the prefix of the encoding
636 byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
637 inStream.reset();
638 if (buff == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700639 throw new CertificateException("InputStream doesn't contain enough data");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800640 }
641 long hash = CERT_CACHE.getHash(buff);
642 if (CERT_CACHE.contains(hash)) {
643 byte[] encoding = new byte[BerInputStream.getLength(buff)];
644 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700645 throw new CertificateException("Bad Certificate encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800646 }
Elliott Hughesf5309a32011-02-24 18:51:21 -0800647 Streams.readFully(inStream, encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800648 Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
649 if (res != null) {
650 return res;
651 }
652 res = new X509CertImpl(encoding);
653 CERT_CACHE.put(hash, encoding, res);
654 return res;
655 } else {
656 inStream.reset();
657 Certificate res = new X509CertImpl(inStream);
658 CERT_CACHE.put(hash, res.getEncoded(), res);
659 return res;
660 }
661 }
662 }
663
664 /**
665 * Returns the CRL object corresponding to the provided encoding.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700666 * Resulting object is retrieved from the cache
667 * if it contains such correspondence
668 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800669 * and stored in the cache otherwise.
670 * @throws IOException if some decoding errors occur
671 * (in the case of cache miss).
672 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700673 private static CRL getCRL(byte[] encoding)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800674 throws CRLException, IOException {
675 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700676 throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800677 }
678 synchronized (CRL_CACHE) {
679 long hash = CRL_CACHE.getHash(encoding);
680 if (CRL_CACHE.contains(hash)) {
681 X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
682 if (res != null) {
683 return res;
684 }
685 }
686 X509CRL res = new X509CRLImpl(encoding);
687 CRL_CACHE.put(hash, encoding, res);
688 return res;
689 }
690 }
691
692 /**
693 * Returns the CRL object corresponding to the encoding provided
694 * by the stream.
Elliott Hughesf33eae72010-05-13 12:36:25 -0700695 * Resulting object is retrieved from the cache
696 * if it contains such correspondence
697 * and is constructed on the base of encoding
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800698 * and stored in the cache otherwise.
699 * @throws IOException if some decoding errors occur
700 * (in the case of cache miss).
701 */
Elliott Hughesf33eae72010-05-13 12:36:25 -0700702 private static CRL getCRL(InputStream inStream)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800703 throws CRLException, IOException {
704 synchronized (CRL_CACHE) {
705 inStream.mark(CRL_CACHE_SEED_LENGTH);
706 byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
707 // read the prefix of the encoding
708 inStream.reset();
709 if (buff == null) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700710 throw new CRLException("InputStream doesn't contain enough data");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800711 }
712 long hash = CRL_CACHE.getHash(buff);
713 if (CRL_CACHE.contains(hash)) {
714 byte[] encoding = new byte[BerInputStream.getLength(buff)];
715 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
Elliott Hughes897538a2010-05-28 20:00:47 -0700716 throw new CRLException("Bad CRL encoding");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800717 }
Elliott Hughesf5309a32011-02-24 18:51:21 -0800718 Streams.readFully(inStream, encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800719 CRL res = (CRL) CRL_CACHE.get(hash, encoding);
720 if (res != null) {
721 return res;
722 }
723 res = new X509CRLImpl(encoding);
724 CRL_CACHE.put(hash, encoding, res);
725 return res;
726 } else {
727 X509CRL res = new X509CRLImpl(inStream);
728 CRL_CACHE.put(hash, res.getEncoded(), res);
729 return res;
730 }
731 }
732 }
733
734 /*
735 * This class extends any existing input stream with
736 * mark functionality. It acts as a wrapper over the
737 * stream and supports reset to the
738 * marked state with readlimit no more than BUFF_SIZE.
739 */
740 private static class RestoringInputStream extends InputStream {
741
742 // wrapped input stream
743 private final InputStream inStream;
744 // specifies how much of the read data is buffered
745 // after the mark has been set up
746 private static final int BUFF_SIZE = 32;
747 // buffer to keep the bytes read after the mark has been set up
748 private final int[] buff = new int[BUFF_SIZE*2];
749 // position of the next byte to read,
750 // the value of -1 indicates that the buffer is not used
751 // (mark was not set up or was invalidated, or reset to the marked
752 // position has been done and all the buffered data was read out)
753 private int pos = -1;
754 // position of the last buffered byte
755 private int bar = 0;
756 // position in the buffer where the mark becomes invalidated
757 private int end = 0;
758
759 /**
760 * Creates the mark supporting wrapper over the stream.
761 */
762 public RestoringInputStream(InputStream inStream) {
763 this.inStream = inStream;
764 }
765
Elliott Hughes582d9262010-04-05 11:33:58 -0700766 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800767 public int available() throws IOException {
768 return (bar - pos) + inStream.available();
769 }
770
Elliott Hughes582d9262010-04-05 11:33:58 -0700771 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800772 public void close() throws IOException {
773 inStream.close();
774 }
775
Elliott Hughes582d9262010-04-05 11:33:58 -0700776 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800777 public void mark(int readlimit) {
778 if (pos < 0) {
779 pos = 0;
780 bar = 0;
781 end = BUFF_SIZE - 1;
782 } else {
783 end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
784 }
785 }
786
Elliott Hughes582d9262010-04-05 11:33:58 -0700787 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800788 public boolean markSupported() {
789 return true;
790 }
791
792 /**
793 * Reads the byte from the stream. If mark has been set up
794 * and was not invalidated byte is read from the underlying
795 * stream and saved into the buffer. If the current read position
796 * has been reset to the marked position and there are remaining
797 * bytes in the buffer, the byte is taken from it. In the other cases
798 * (if mark has been invalidated, or there are no buffered bytes)
799 * the byte is taken directly from the underlying stream and it is
800 * returned without saving to the buffer.
801 *
802 * @see java.io.InputStream#read()
803 * method documentation for more info
804 */
805 public int read() throws IOException {
806 // if buffer is currently used
807 if (pos >= 0) {
808 // current position in the buffer
809 int cur = pos % BUFF_SIZE;
810 // check whether the buffer contains the data to be read
811 if (cur < bar) {
812 // return the data from the buffer
813 pos++;
814 return buff[cur];
815 }
816 // check whether buffer has free space
817 if (cur != end) {
818 // it has, so read the data from the wrapped stream
819 // and place it in the buffer
820 buff[cur] = inStream.read();
821 bar = cur+1;
822 pos++;
823 return buff[cur];
824 } else {
825 // buffer if full and can not operate
826 // any more, so invalidate the mark position
827 // and turn off the using of buffer
828 pos = -1;
829 }
830 }
831 // buffer is not used, so return the data from the wrapped stream
832 return inStream.read();
833 }
834
Elliott Hughes582d9262010-04-05 11:33:58 -0700835 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800836 public int read(byte[] b, int off, int len) throws IOException {
837 int read_b;
838 int i;
839 for (i=0; i<len; i++) {
840 if ((read_b = read()) == -1) {
841 return (i == 0) ? -1 : i;
842 }
843 b[off+i] = (byte) read_b;
844 }
845 return i;
846 }
847
Elliott Hughes582d9262010-04-05 11:33:58 -0700848 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800849 public void reset() throws IOException {
850 if (pos >= 0) {
851 pos = (end + 1) % BUFF_SIZE;
852 } else {
Elliott Hughes897538a2010-05-28 20:00:47 -0700853 throw new IOException("Could not reset the stream: " +
854 "position became invalid or stream has not been marked");
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800855 }
856 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800857 }
858}