1 /* 2 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.provider; 27 28 import java.security.cert.*; 29 import sun.security.x509.X509CertImpl; 30 import sun.security.x509.X509CRLImpl; 31 import sun.security.util.Cache; 32 33 /** 34 * This class defines a certificate factory for X.509 v3 certificates & 35 * certification paths, and X.509 v2 certificate revocation lists (CRLs). 36 * 37 * @author Jan Luehe 38 * @author Hemma Prafullchandra 39 * @author Sean Mullan 40 * 41 * 42 * @see java.security.cert.CertificateFactorySpi 43 * @see java.security.cert.Certificate 44 * @see java.security.cert.CertPath 45 * @see java.security.cert.CRL 46 * @see java.security.cert.X509Certificate 47 * @see java.security.cert.X509CRL 48 * @see sun.security.x509.X509CertImpl 49 * @see sun.security.x509.X509CRLImpl 50 */ 51 52 // Android-changed 53 // public class X509Factory extends CertificateFactorySpi { 54 public class X509Factory { 55 56 // BEGIN Android-removed 57 // public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; 58 // public static final String END_CERT = "-----END CERTIFICATE-----"; 59 // END Android-removed 60 61 private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX 62 63 private static final Cache<Object, X509CertImpl> certCache 64 = Cache.newSoftMemoryCache(750); 65 private static final Cache<Object, X509CRLImpl> crlCache 66 = Cache.newSoftMemoryCache(750); 67 68 // BEGIN Android-removed 69 /* 70 /** 71 * Generates an X.509 certificate object and initializes it with 72 * the data read from the input stream <code>is</code>. 73 * 74 * @param is an input stream with the certificate data. 75 * 76 * @return an X.509 certificate object initialized with the data 77 * from the input stream. 78 * 79 * @exception CertificateException on parsing errors. 80 * 81 @Override 82 public Certificate engineGenerateCertificate(InputStream is) 83 throws CertificateException 84 { 85 if (is == null) { 86 // clear the caches (for debugging) 87 certCache.clear(); 88 X509CertificatePair.clearCache(); 89 throw new CertificateException("Missing input stream"); 90 } 91 try { 92 byte[] encoding = readOneBlock(is); 93 if (encoding != null) { 94 X509CertImpl cert = getFromCache(certCache, encoding); 95 if (cert != null) { 96 return cert; 97 } 98 cert = new X509CertImpl(encoding); 99 addToCache(certCache, cert.getEncodedInternal(), cert); 100 return cert; 101 } else { 102 throw new IOException("Empty input"); 103 } 104 } catch (IOException ioe) { 105 throw new CertificateException("Could not parse certificate: " + 106 ioe.toString(), ioe); 107 } 108 } 109 110 /** 111 * Read from the stream until length bytes have been read or EOF has 112 * been reached. Return the number of bytes actually read. 113 * 114 private static int readFully(InputStream in, ByteArrayOutputStream bout, 115 int length) throws IOException { 116 int read = 0; 117 byte[] buffer = new byte[2048]; 118 while (length > 0) { 119 int n = in.read(buffer, 0, length<2048?length:2048); 120 if (n <= 0) { 121 break; 122 } 123 bout.write(buffer, 0, n); 124 read += n; 125 length -= n; 126 } 127 return read; 128 } 129 */ 130 // END Android-removed 131 132 /** 133 * Return an interned X509CertImpl for the given certificate. 134 * If the given X509Certificate or X509CertImpl is already present 135 * in the cert cache, the cached object is returned. Otherwise, 136 * if it is a X509Certificate, it is first converted to a X509CertImpl. 137 * Then the X509CertImpl is added to the cache and returned. 138 * 139 * Note that all certificates created via generateCertificate(InputStream) 140 * are already interned and this method does not need to be called. 141 * It is useful for certificates that cannot be created via 142 * generateCertificate() and for converting other X509Certificate 143 * implementations to an X509CertImpl. 144 * 145 * @param c The source X509Certificate 146 * @return An X509CertImpl object that is either a cached certificate or a 147 * newly built X509CertImpl from the provided X509Certificate 148 * @throws CertificateException if failures occur while obtaining the DER 149 * encoding for certificate data. 150 */ intern(X509Certificate c)151 public static synchronized X509CertImpl intern(X509Certificate c) 152 throws CertificateException { 153 if (c == null) { 154 return null; 155 } 156 boolean isImpl = c instanceof X509CertImpl; 157 byte[] encoding; 158 if (isImpl) { 159 encoding = ((X509CertImpl)c).getEncodedInternal(); 160 } else { 161 encoding = c.getEncoded(); 162 } 163 X509CertImpl newC = getFromCache(certCache, encoding); 164 if (newC != null) { 165 return newC; 166 } 167 if (isImpl) { 168 newC = (X509CertImpl)c; 169 } else { 170 newC = new X509CertImpl(encoding); 171 encoding = newC.getEncodedInternal(); 172 } 173 addToCache(certCache, encoding, newC); 174 return newC; 175 } 176 177 /** 178 * Return an interned X509CRLImpl for the given certificate. 179 * For more information, see intern(X509Certificate). 180 * 181 * @param c The source X509CRL 182 * @return An X509CRLImpl object that is either a cached CRL or a 183 * newly built X509CRLImpl from the provided X509CRL 184 * @throws CRLException if failures occur while obtaining the DER 185 * encoding for CRL data. 186 */ intern(X509CRL c)187 public static synchronized X509CRLImpl intern(X509CRL c) 188 throws CRLException { 189 if (c == null) { 190 return null; 191 } 192 boolean isImpl = c instanceof X509CRLImpl; 193 byte[] encoding; 194 if (isImpl) { 195 encoding = ((X509CRLImpl)c).getEncodedInternal(); 196 } else { 197 encoding = c.getEncoded(); 198 } 199 X509CRLImpl newC = getFromCache(crlCache, encoding); 200 if (newC != null) { 201 return newC; 202 } 203 if (isImpl) { 204 newC = (X509CRLImpl)c; 205 } else { 206 newC = new X509CRLImpl(encoding); 207 encoding = newC.getEncodedInternal(); 208 } 209 addToCache(crlCache, encoding, newC); 210 return newC; 211 } 212 213 /** 214 * Get the X509CertImpl or X509CRLImpl from the cache. 215 */ getFromCache(Cache<K,V> cache, byte[] encoding)216 private static synchronized <K,V> V getFromCache(Cache<K,V> cache, 217 byte[] encoding) { 218 Object key = new Cache.EqualByteArray(encoding); 219 return cache.get(key); 220 } 221 222 /** 223 * Add the X509CertImpl or X509CRLImpl to the cache. 224 */ addToCache(Cache<Object, V> cache, byte[] encoding, V value)225 private static synchronized <V> void addToCache(Cache<Object, V> cache, 226 byte[] encoding, V value) { 227 if (encoding.length > ENC_MAX_LENGTH) { 228 return; 229 } 230 Object key = new Cache.EqualByteArray(encoding); 231 cache.put(key, value); 232 } 233 234 // BEGIN Android-removed 235 /* 236 /** 237 * Generates a <code>CertPath</code> object and initializes it with 238 * the data read from the <code>InputStream</code> inStream. The data 239 * is assumed to be in the default encoding. 240 * 241 * @param inStream an <code>InputStream</code> containing the data 242 * @return a <code>CertPath</code> initialized with the data from the 243 * <code>InputStream</code> 244 * @exception CertificateException if an exception occurs while decoding 245 * @since 1.4 246 * 247 @Override 248 public CertPath engineGenerateCertPath(InputStream inStream) 249 throws CertificateException 250 { 251 if (inStream == null) { 252 throw new CertificateException("Missing input stream"); 253 } 254 try { 255 byte[] encoding = readOneBlock(inStream); 256 if (encoding != null) { 257 return new X509CertPath(new ByteArrayInputStream(encoding)); 258 } else { 259 throw new IOException("Empty input"); 260 } 261 } catch (IOException ioe) { 262 throw new CertificateException(ioe.getMessage()); 263 } 264 } 265 266 /** 267 * Generates a <code>CertPath</code> object and initializes it with 268 * the data read from the <code>InputStream</code> inStream. The data 269 * is assumed to be in the specified encoding. 270 * 271 * @param inStream an <code>InputStream</code> containing the data 272 * @param encoding the encoding used for the data 273 * @return a <code>CertPath</code> initialized with the data from the 274 * <code>InputStream</code> 275 * @exception CertificateException if an exception occurs while decoding or 276 * the encoding requested is not supported 277 * @since 1.4 278 * 279 @Override 280 public CertPath engineGenerateCertPath(InputStream inStream, 281 String encoding) throws CertificateException 282 { 283 if (inStream == null) { 284 throw new CertificateException("Missing input stream"); 285 } 286 try { 287 byte[] data = readOneBlock(inStream); 288 if (data != null) { 289 return new X509CertPath(new ByteArrayInputStream(data), encoding); 290 } else { 291 throw new IOException("Empty input"); 292 } 293 } catch (IOException ioe) { 294 throw new CertificateException(ioe.getMessage()); 295 } 296 } 297 298 /** 299 * Generates a <code>CertPath</code> object and initializes it with 300 * a <code>List</code> of <code>Certificate</code>s. 301 * <p> 302 * The certificates supplied must be of a type supported by the 303 * <code>CertificateFactory</code>. They will be copied out of the supplied 304 * <code>List</code> object. 305 * 306 * @param certificates a <code>List</code> of <code>Certificate</code>s 307 * @return a <code>CertPath</code> initialized with the supplied list of 308 * certificates 309 * @exception CertificateException if an exception occurs 310 * @since 1.4 311 * 312 @Override 313 public CertPath 314 engineGenerateCertPath(List<? extends Certificate> certificates) 315 throws CertificateException 316 { 317 return(new X509CertPath(certificates)); 318 } 319 320 /** 321 * Returns an iteration of the <code>CertPath</code> encodings supported 322 * by this certificate factory, with the default encoding first. 323 * <p> 324 * Attempts to modify the returned <code>Iterator</code> via its 325 * <code>remove</code> method result in an 326 * <code>UnsupportedOperationException</code>. 327 * 328 * @return an <code>Iterator</code> over the names of the supported 329 * <code>CertPath</code> encodings (as <code>String</code>s) 330 * @since 1.4 331 * 332 @Override 333 public Iterator<String> engineGetCertPathEncodings() { 334 return(X509CertPath.getEncodingsStatic()); 335 } 336 337 /** 338 * Returns a (possibly empty) collection view of X.509 certificates read 339 * from the given input stream <code>is</code>. 340 * 341 * @param is the input stream with the certificates. 342 * 343 * @return a (possibly empty) collection view of X.509 certificate objects 344 * initialized with the data from the input stream. 345 * 346 * @exception CertificateException on parsing errors. 347 * 348 @Override 349 public Collection<? extends java.security.cert.Certificate> 350 engineGenerateCertificates(InputStream is) 351 throws CertificateException { 352 if (is == null) { 353 throw new CertificateException("Missing input stream"); 354 } 355 try { 356 return parseX509orPKCS7Cert(is); 357 } catch (IOException ioe) { 358 throw new CertificateException(ioe); 359 } 360 } 361 362 /** 363 * Generates an X.509 certificate revocation list (CRL) object and 364 * initializes it with the data read from the given input stream 365 * <code>is</code>. 366 * 367 * @param is an input stream with the CRL data. 368 * 369 * @return an X.509 CRL object initialized with the data 370 * from the input stream. 371 * 372 * @exception CRLException on parsing errors. 373 * 374 @Override 375 public CRL engineGenerateCRL(InputStream is) 376 throws CRLException 377 { 378 if (is == null) { 379 // clear the cache (for debugging) 380 crlCache.clear(); 381 throw new CRLException("Missing input stream"); 382 } 383 try { 384 byte[] encoding = readOneBlock(is); 385 if (encoding != null) { 386 X509CRLImpl crl = getFromCache(crlCache, encoding); 387 if (crl != null) { 388 return crl; 389 } 390 crl = new X509CRLImpl(encoding); 391 addToCache(crlCache, crl.getEncodedInternal(), crl); 392 return crl; 393 } else { 394 throw new IOException("Empty input"); 395 } 396 } catch (IOException ioe) { 397 throw new CRLException(ioe.getMessage()); 398 } 399 } 400 401 /** 402 * Returns a (possibly empty) collection view of X.509 CRLs read 403 * from the given input stream <code>is</code>. 404 * 405 * @param is the input stream with the CRLs. 406 * 407 * @return a (possibly empty) collection view of X.509 CRL objects 408 * initialized with the data from the input stream. 409 * 410 * @exception CRLException on parsing errors. 411 * 412 @Override 413 public Collection<? extends java.security.cert.CRL> engineGenerateCRLs( 414 InputStream is) throws CRLException 415 { 416 if (is == null) { 417 throw new CRLException("Missing input stream"); 418 } 419 try { 420 return parseX509orPKCS7CRL(is); 421 } catch (IOException ioe) { 422 throw new CRLException(ioe.getMessage()); 423 } 424 } 425 426 /* 427 * Parses the data in the given input stream as a sequence of DER 428 * encoded X.509 certificates (in binary or base 64 encoded format) OR 429 * as a single PKCS#7 encoded blob (in binary or base64 encoded format). 430 * 431 private Collection<? extends java.security.cert.Certificate> 432 parseX509orPKCS7Cert(InputStream is) 433 throws CertificateException, IOException 434 { 435 int peekByte; 436 byte[] data; 437 PushbackInputStream pbis = new PushbackInputStream(is); 438 Collection<X509CertImpl> coll = new ArrayList<>(); 439 440 // Test the InputStream for end-of-stream. If the stream's 441 // initial state is already at end-of-stream then return 442 // an empty collection. Otherwise, push the byte back into the 443 // stream and let readOneBlock look for the first certificate. 444 peekByte = pbis.read(); 445 if (peekByte == -1) { 446 return new ArrayList<>(0); 447 } else { 448 pbis.unread(peekByte); 449 data = readOneBlock(pbis); 450 } 451 452 // If we end up with a null value after reading the first block 453 // then we know the end-of-stream has been reached and no certificate 454 // data has been found. 455 if (data == null) { 456 throw new CertificateException("No certificate data found"); 457 } 458 459 try { 460 PKCS7 pkcs7 = new PKCS7(data); 461 X509Certificate[] certs = pkcs7.getCertificates(); 462 // certs are optional in PKCS #7 463 if (certs != null) { 464 return Arrays.asList(certs); 465 } else { 466 // no certificates provided 467 return new ArrayList<>(0); 468 } 469 } catch (ParsingException e) { 470 while (data != null) { 471 coll.add(new X509CertImpl(data)); 472 data = readOneBlock(pbis); 473 } 474 } 475 return coll; 476 } 477 478 /* 479 * Parses the data in the given input stream as a sequence of DER encoded 480 * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7 481 * encoded blob (in binary or base 64 encoded format). 482 * 483 private Collection<? extends java.security.cert.CRL> 484 parseX509orPKCS7CRL(InputStream is) 485 throws CRLException, IOException 486 { 487 int peekByte; 488 byte[] data; 489 PushbackInputStream pbis = new PushbackInputStream(is); 490 Collection<X509CRLImpl> coll = new ArrayList<>(); 491 492 // Test the InputStream for end-of-stream. If the stream's 493 // initial state is already at end-of-stream then return 494 // an empty collection. Otherwise, push the byte back into the 495 // stream and let readOneBlock look for the first CRL. 496 peekByte = pbis.read(); 497 if (peekByte == -1) { 498 return new ArrayList<>(0); 499 } else { 500 pbis.unread(peekByte); 501 data = readOneBlock(pbis); 502 } 503 504 // If we end up with a null value after reading the first block 505 // then we know the end-of-stream has been reached and no CRL 506 // data has been found. 507 if (data == null) { 508 throw new CRLException("No CRL data found"); 509 } 510 511 try { 512 PKCS7 pkcs7 = new PKCS7(data); 513 X509CRL[] crls = pkcs7.getCRLs(); 514 // CRLs are optional in PKCS #7 515 if (crls != null) { 516 return Arrays.asList(crls); 517 } else { 518 // no crls provided 519 return new ArrayList<>(0); 520 } 521 } catch (ParsingException e) { 522 while (data != null) { 523 coll.add(new X509CRLImpl(data)); 524 data = readOneBlock(pbis); 525 } 526 } 527 return coll; 528 } 529 530 /** 531 * Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded 532 * binary block or a PEM-style BASE64-encoded ASCII data. In the latter 533 * case, it's de-BASE64'ed before return. 534 * 535 * After the reading, the input stream pointer is after the BER block, or 536 * after the newline character after the -----END SOMETHING----- line. 537 * 538 * @param is the InputStream 539 * @returns byte block or null if end of stream 540 * @throws IOException If any parsing error 541 * 542 private static byte[] readOneBlock(InputStream is) throws IOException { 543 544 // The first character of a BLOCK. 545 int c = is.read(); 546 if (c == -1) { 547 return null; 548 } 549 if (c == DerValue.tag_Sequence) { 550 ByteArrayOutputStream bout = new ByteArrayOutputStream(2048); 551 bout.write(c); 552 readBERInternal(is, bout, c); 553 return bout.toByteArray(); 554 } else { 555 // Read BASE64 encoded data, might skip info at the beginning 556 char[] data = new char[2048]; 557 int pos = 0; 558 559 // Step 1: Read until header is found 560 int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens 561 int last = (c=='-') ? -1: c; // the char before hyphen 562 while (true) { 563 int next = is.read(); 564 if (next == -1) { 565 // We accept useless data after the last block, 566 // say, empty lines. 567 return null; 568 } 569 if (next == '-') { 570 hyphen++; 571 } else { 572 hyphen = 0; 573 last = next; 574 } 575 if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) { 576 break; 577 } 578 } 579 580 // Step 2: Read the rest of header, determine the line end 581 int end; 582 StringBuilder header = new StringBuilder("-----"); 583 while (true) { 584 int next = is.read(); 585 if (next == -1) { 586 throw new IOException("Incomplete data"); 587 } 588 if (next == '\n') { 589 end = '\n'; 590 break; 591 } 592 if (next == '\r') { 593 next = is.read(); 594 if (next == -1) { 595 throw new IOException("Incomplete data"); 596 } 597 if (next == '\n') { 598 end = '\n'; 599 } else { 600 end = '\r'; 601 data[pos++] = (char)next; 602 } 603 break; 604 } 605 header.append((char)next); 606 } 607 608 // Step 3: Read the data 609 while (true) { 610 int next = is.read(); 611 if (next == -1) { 612 throw new IOException("Incomplete data"); 613 } 614 if (next != '-') { 615 data[pos++] = (char)next; 616 if (pos >= data.length) { 617 data = Arrays.copyOf(data, data.length+1024); 618 } 619 } else { 620 break; 621 } 622 } 623 624 // Step 4: Consume the footer 625 StringBuilder footer = new StringBuilder("-"); 626 while (true) { 627 int next = is.read(); 628 // Add next == '\n' for maximum safety, in case endline 629 // is not consistent. 630 if (next == -1 || next == end || next == '\n') { 631 break; 632 } 633 if (next != '\r') footer.append((char)next); 634 } 635 636 checkHeaderFooter(header.toString(), footer.toString()); 637 638 return Base64.getMimeDecoder().decode(new String(data, 0, pos)); 639 } 640 } 641 642 private static void checkHeaderFooter(String header, 643 String footer) throws IOException { 644 if (header.length() < 16 || !header.startsWith("-----BEGIN ") || 645 !header.endsWith("-----")) { 646 throw new IOException("Illegal header: " + header); 647 } 648 if (footer.length() < 14 || !footer.startsWith("-----END ") || 649 !footer.endsWith("-----")) { 650 throw new IOException("Illegal footer: " + footer); 651 } 652 String headerType = header.substring(11, header.length()-5); 653 String footerType = footer.substring(9, footer.length()-5); 654 if (!headerType.equals(footerType)) { 655 throw new IOException("Header and footer do not match: " + 656 header + " " + footer); 657 } 658 } 659 660 /** 661 * Read one BER data block. This method is aware of indefinite-length BER 662 * encoding and will read all of the sub-sections in a recursive way 663 * 664 * @param is Read from this InputStream 665 * @param bout Write into this OutputStream 666 * @param tag Tag already read (-1 mean not read) 667 * @returns The current tag, used to check EOC in indefinite-length BER 668 * @throws IOException Any parsing error 669 * 670 private static int readBERInternal(InputStream is, 671 ByteArrayOutputStream bout, int tag) throws IOException { 672 673 if (tag == -1) { // Not read before the call, read now 674 tag = is.read(); 675 if (tag == -1) { 676 throw new IOException("BER/DER tag info absent"); 677 } 678 if ((tag & 0x1f) == 0x1f) { 679 throw new IOException("Multi octets tag not supported"); 680 } 681 bout.write(tag); 682 } 683 684 int n = is.read(); 685 if (n == -1) { 686 throw new IOException("BER/DER length info absent"); 687 } 688 bout.write(n); 689 690 int length; 691 692 if (n == 0x80) { // Indefinite-length encoding 693 if ((tag & 0x20) != 0x20) { 694 throw new IOException( 695 "Non constructed encoding must have definite length"); 696 } 697 while (true) { 698 int subTag = readBERInternal(is, bout, -1); 699 if (subTag == 0) { // EOC, end of indefinite-length section 700 break; 701 } 702 } 703 } else { 704 if (n < 0x80) { 705 length = n; 706 } else if (n == 0x81) { 707 length = is.read(); 708 if (length == -1) { 709 throw new IOException("Incomplete BER/DER length info"); 710 } 711 bout.write(length); 712 } else if (n == 0x82) { 713 int highByte = is.read(); 714 int lowByte = is.read(); 715 if (lowByte == -1) { 716 throw new IOException("Incomplete BER/DER length info"); 717 } 718 bout.write(highByte); 719 bout.write(lowByte); 720 length = (highByte << 8) | lowByte; 721 } else if (n == 0x83) { 722 int highByte = is.read(); 723 int midByte = is.read(); 724 int lowByte = is.read(); 725 if (lowByte == -1) { 726 throw new IOException("Incomplete BER/DER length info"); 727 } 728 bout.write(highByte); 729 bout.write(midByte); 730 bout.write(lowByte); 731 length = (highByte << 16) | (midByte << 8) | lowByte; 732 } else if (n == 0x84) { 733 int highByte = is.read(); 734 int nextByte = is.read(); 735 int midByte = is.read(); 736 int lowByte = is.read(); 737 if (lowByte == -1) { 738 throw new IOException("Incomplete BER/DER length info"); 739 } 740 if (highByte > 127) { 741 throw new IOException("Invalid BER/DER data (a little huge?)"); 742 } 743 bout.write(highByte); 744 bout.write(nextByte); 745 bout.write(midByte); 746 bout.write(lowByte); 747 length = (highByte << 24 ) | (nextByte << 16) | 748 (midByte << 8) | lowByte; 749 } else { // ignore longer length forms 750 throw new IOException("Invalid BER/DER data (too huge?)"); 751 } 752 if (readFully(is, bout, length) != length) { 753 throw new IOException("Incomplete BER/DER data"); 754 } 755 } 756 return tag; 757 } 758 */ 759 // END Android-removed 760 } 761