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