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