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 25import java.io.IOException; 26import java.io.InputStream; 27import java.nio.charset.StandardCharsets; 28import 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; 39import libcore.io.Base64; 40import libcore.io.Streams; 41import org.apache.harmony.security.asn1.ASN1Constants; 42import org.apache.harmony.security.asn1.BerInputStream; 43import 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 59 private static final int CERT_CACHE_SEED_LENGTH = 28; 60 // certificate cache 61 private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH); 62 // number of leading/trailing bytes used for crl hash computation 63 private static final int CRL_CACHE_SEED_LENGTH = 24; 64 // crl cache 65 private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH); 66 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) { 92 throw new CertificateException("inStream == null"); 93 } 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) { 126 throw new CertificateException("inStream == null"); 127 } 128 ArrayList<Certificate> result = new ArrayList<Certificate>(); 129 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 138 // recognizing the data format 139 // (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) { 156 throw new CertificateException("Unsupported encoding"); 157 } 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) { 177 // there were not read X.509 Certificates, so 178 // break the cycle and check 179 // 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) { 200 /* No data in the stream, so return the empty collection. */ 201 return result; 202 } 203 // else: check if it is PKCS7 204 if (second_asn1_tag == ASN1Constants.TAG_OID) { 205 // it is PKCS7 ContentInfo structure, so decode it 206 ContentInfo info = (ContentInfo) 207 ((encoding != null) 208 ? ContentInfo.ASN1.decode(encoding) 209 : ContentInfo.ASN1.decode(inStream)); 210 // retrieve SignedData 211 SignedData data = info.getSignedData(); 212 if (data == null) { 213 throw new CertificateException("Invalid PKCS7 data provided"); 214 } 215 List<org.apache.harmony.security.x509.Certificate> certs = data.getCertificates(); 216 if (certs != null) { 217 for (org.apache.harmony.security.x509.Certificate cert : certs) { 218 result.add(new X509CertImpl(cert)); 219 } 220 } 221 return result; 222 } 223 // else: Unknown data format 224 throw new CertificateException("Unsupported encoding"); 225 } catch (IOException e) { 226 throw new CertificateException(e); 227 } 228 } 229 230 /** 231 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream) 232 * method documentation for more info 233 */ 234 public CRL engineGenerateCRL(InputStream inStream) 235 throws CRLException { 236 if (inStream == null) { 237 throw new CRLException("inStream == null"); 238 } 239 try { 240 if (!inStream.markSupported()) { 241 // Create the mark supporting wrapper 242 // Mark is needed to recognize the format 243 // of provided encoding form (ASN.1 or PEM) 244 inStream = new RestoringInputStream(inStream); 245 } 246 inStream.mark(1); 247 // check whether the provided crl is in PEM encoded form 248 if (inStream.read() == '-') { 249 // decode PEM, retrieve CRL 250 return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX)); 251 } else { 252 inStream.reset(); 253 // retrieve CRL 254 return getCRL(inStream); 255 } 256 } catch (IOException e) { 257 throw new CRLException(e); 258 } 259 } 260 261 /** 262 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream) 263 * method documentation for more info 264 */ 265 public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream) 266 throws CRLException { 267 if (inStream == null) { 268 throw new CRLException("inStream == null"); 269 } 270 ArrayList<CRL> result = new ArrayList<CRL>(); 271 try { 272 if (!inStream.markSupported()) { 273 inStream = new RestoringInputStream(inStream); 274 } 275 // if it is PEM encoded form this array will contain the encoding 276 // so ((it is PEM) <-> (encoding != null)) 277 byte[] encoding = null; 278 // The following by SEQUENCE ASN.1 tag, used for 279 // recognizing the data format 280 // (is it PKCS7 ContentInfo structure, X.509 CRL, or 281 // unsupported encoding) 282 int second_asn1_tag = -1; 283 inStream.mark(1); 284 int ch; 285 while ((ch = inStream.read()) != -1) { 286 // check if it is PEM encoded form 287 if (ch == '-') { // beginning of PEM encoding ('-' char) 288 // decode PEM chunk and store its content (ASN.1 encoding) 289 encoding = decodePEM(inStream, FREE_BOUND_SUFFIX); 290 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30) 291 encoding = null; 292 inStream.reset(); 293 // prepare for data format determination 294 inStream.mark(CRL_CACHE_SEED_LENGTH); 295 } else { // unsupported data 296 if (result.size() == 0) { 297 throw new CRLException("Unsupported encoding"); 298 } else { 299 // it can be trailing user data, 300 // so keep it in the stream 301 inStream.reset(); 302 return result; 303 } 304 } 305 // Check the data format 306 BerInputStream in = (encoding == null) 307 ? new BerInputStream(inStream) 308 : new BerInputStream(encoding); 309 // read the next ASN.1 tag 310 second_asn1_tag = in.next(); 311 if (encoding == null) { 312 // keep whole structure in the stream 313 inStream.reset(); 314 } 315 // check if it is a TBSCertList structure 316 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) { 317 if (result.size() == 0) { 318 // there were not read X.509 CRLs, so 319 // break the cycle and check 320 // whether it is PKCS7 structure 321 break; 322 } else { 323 // it can be trailing user data, 324 // so return what we already read 325 return result; 326 } 327 } else { 328 if (encoding == null) { 329 result.add(getCRL(inStream)); 330 } else { 331 result.add(getCRL(encoding)); 332 } 333 } 334 inStream.mark(1); 335 } 336 if (result.size() != 0) { 337 // the stream was read out 338 return result; 339 } else if (ch == -1) { 340 throw new CRLException("There is no data in the stream"); 341 } 342 // else: check if it is PKCS7 343 if (second_asn1_tag == ASN1Constants.TAG_OID) { 344 // it is PKCS7 ContentInfo structure, so decode it 345 ContentInfo info = (ContentInfo) 346 ((encoding != null) 347 ? ContentInfo.ASN1.decode(encoding) 348 : ContentInfo.ASN1.decode(inStream)); 349 // retrieve SignedData 350 SignedData data = info.getSignedData(); 351 if (data == null) { 352 throw new CRLException("Invalid PKCS7 data provided"); 353 } 354 List<CertificateList> crls = data.getCRLs(); 355 if (crls != null) { 356 for (CertificateList crl : crls) { 357 result.add(new X509CRLImpl(crl)); 358 } 359 } 360 return result; 361 } 362 // else: Unknown data format 363 throw new CRLException("Unsupported encoding"); 364 } 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) { 376 throw new CertificateException("inStream == null"); 377 } 378 return engineGenerateCertPath(inStream, "PkiPath"); 379 } 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) { 388 throw new CertificateException("inStream == null"); 389 } 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 { 407 throw new CertificateException("Unsupported encoding"); 408 } 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 */ 418 public CertPath engineGenerateCertPath(List<? extends Certificate> certificates) 419 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 435 private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(StandardCharsets.UTF_8); 436 private static final byte[] PEM_END = "-----END".getBytes(StandardCharsets.UTF_8); 437 /** 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 */ 442 private static final byte[] FREE_BOUND_SUFFIX = null; 443 /** 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 */ 448 private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(StandardCharsets.UTF_8); 449 450 /** 451 * Method retrieves the PEM encoded data from the stream 452 * 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 + 464 * "-----END" + boundary_suffix. If boundary_suffix is null, 465 * 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. 469 * @param boundary_suffix the suffix of expected PEM multipart 470 * boundary delimiter.<br> 471 * If it is null, that any character sequences are accepted. 472 * @throws IOException If PEM boundary delimiter does not comply 473 * with expected or some I/O or decoding problems occur. 474 */ 475 private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix) 476 throws IOException { 477 int ch; // the char to be read 478 // check and skip opening boundary delimiter 479 // (first '-' is supposed as already read) 480 for (int i = 1; i < PEM_BEGIN.length; ++i) { 481 if (PEM_BEGIN[i] != (ch = inStream.read())) { 482 throw new IOException( 483 "Incorrect PEM encoding: '-----BEGIN" 484 + ((boundary_suffix == null) 485 ? "" : new String(boundary_suffix)) 486 + "' is expected as opening delimiter boundary."); 487 } 488 } 489 if (boundary_suffix == null) { 490 // read (skip) the trailing characters of 491 // the beginning PEM boundary delimiter 492 while ((ch = inStream.read()) != '\n') { 493 if (ch == -1) { 494 throw new IOException("Incorrect PEM encoding: EOF before content"); 495 } 496 } 497 } else { 498 for (int i=0; i<boundary_suffix.length; i++) { 499 if (boundary_suffix[i] != inStream.read()) { 500 throw new IOException("Incorrect PEM encoding: '-----BEGIN" + 501 new String(boundary_suffix) + "' is expected as opening delimiter boundary."); 502 } 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') { 510 throw new IOException("Incorrect PEM encoding: newline expected after " + 511 "opening delimiter boundary"); 512 } 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) { 520 throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter"); 521 } 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') { 532 throw new IOException("Incorrect Base64 encoding: newline expected before " + 533 "closing boundary delimiter"); 534 } 535 // check and skip closing boundary delimiter prefix 536 // (first '-' was read) 537 for (int i = 1; i < PEM_END.length; ++i) { 538 if (PEM_END[i] != inStream.read()) { 539 throw badEnd(boundary_suffix); 540 } 541 } 542 if (boundary_suffix == null) { 543 // read (skip) the trailing characters of 544 // the closing PEM boundary delimiter 545 while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) { 546 } 547 } else { 548 for (int i=0; i<boundary_suffix.length; i++) { 549 if (boundary_suffix[i] != inStream.read()) { 550 throw badEnd(boundary_suffix); 551 } 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) { 562 throw new IOException("Incorrect Base64 encoding"); 563 } 564 return buff; 565 } 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 } 571 572 /** 573 * Reads the data of specified length from source 574 * and returns it as an array. 575 * @return the byte array contained read data or 576 * null if the stream contains not enough data 577 * @throws IOException if some I/O error has been occurred. 578 */ 579 private static byte[] readBytes(InputStream source, int length) 580 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. 594 * Resulting object is retrieved from the cache 595 * if it contains such correspondence 596 * and is constructed on the base of encoding 597 * and stored in the cache otherwise. 598 * @throws IOException if some decoding errors occur 599 * (in the case of cache miss). 600 */ 601 private static Certificate getCertificate(byte[] encoding) 602 throws CertificateException, IOException { 603 if (encoding.length < CERT_CACHE_SEED_LENGTH) { 604 throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH"); 605 } 606 synchronized (CERT_CACHE) { 607 long hash = CERT_CACHE.getHash(encoding); 608 if (CERT_CACHE.contains(hash)) { 609 Certificate res = 610 (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. 624 * Resulting object is retrieved from the cache 625 * if it contains such correspondence 626 * and is constructed on the base of encoding 627 * and stored in the cache otherwise. 628 * @throws IOException if some decoding errors occur 629 * (in the case of cache miss). 630 */ 631 private static Certificate getCertificate(InputStream inStream) 632 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) { 639 throw new CertificateException("InputStream doesn't contain enough data"); 640 } 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) { 645 throw new CertificateException("Bad Certificate encoding"); 646 } 647 Streams.readFully(inStream, encoding); 648 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. 666 * Resulting object is retrieved from the cache 667 * if it contains such correspondence 668 * and is constructed on the base of encoding 669 * and stored in the cache otherwise. 670 * @throws IOException if some decoding errors occur 671 * (in the case of cache miss). 672 */ 673 private static CRL getCRL(byte[] encoding) 674 throws CRLException, IOException { 675 if (encoding.length < CRL_CACHE_SEED_LENGTH) { 676 throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH"); 677 } 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. 695 * Resulting object is retrieved from the cache 696 * if it contains such correspondence 697 * and is constructed on the base of encoding 698 * and stored in the cache otherwise. 699 * @throws IOException if some decoding errors occur 700 * (in the case of cache miss). 701 */ 702 private static CRL getCRL(InputStream inStream) 703 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) { 710 throw new CRLException("InputStream doesn't contain enough data"); 711 } 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) { 716 throw new CRLException("Bad CRL encoding"); 717 } 718 Streams.readFully(inStream, encoding); 719 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 766 @Override 767 public int available() throws IOException { 768 return (bar - pos) + inStream.available(); 769 } 770 771 @Override 772 public void close() throws IOException { 773 inStream.close(); 774 } 775 776 @Override 777 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 787 @Override 788 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 835 @Override 836 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 848 @Override 849 public void reset() throws IOException { 850 if (pos >= 0) { 851 pos = (end + 1) % BUFF_SIZE; 852 } else { 853 throw new IOException("Could not reset the stream: " + 854 "position became invalid or stream has not been marked"); 855 } 856 } 857 } 858} 859