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