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 java.util.Base64; 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 return Base64.getMimeDecoder().decode(new String(data, 0, pos)); 579 } 580 } 581 582 private static void checkHeaderFooter(String header, 583 String footer) throws IOException { 584 if (header.length() < 16 || !header.startsWith("-----BEGIN ") || 585 !header.endsWith("-----")) { 586 throw new IOException("Illegal header: " + header); 587 } 588 if (footer.length() < 14 || !footer.startsWith("-----END ") || 589 !footer.endsWith("-----")) { 590 throw new IOException("Illegal footer: " + footer); 591 } 592 String headerType = header.substring(11, header.length()-5); 593 String footerType = footer.substring(9, footer.length()-5); 594 if (!headerType.equals(footerType)) { 595 throw new IOException("Header and footer do not match: " + 596 header + " " + footer); 597 } 598 } 599 600 /** 601 * Read one BER data block. This method is aware of indefinite-length BER 602 * encoding and will read all of the sub-sections in a recursive way 603 * 604 * @param is Read from this InputStream 605 * @param bout Write into this OutputStream 606 * @param tag Tag already read (-1 mean not read) 607 * @returns The current tag, used to check EOC in indefinite-length BER 608 * @throws IOException Any parsing error 609 */ 610 private static int readBERInternal(InputStream is, 611 ByteArrayOutputStream bout, int tag) throws IOException { 612 613 if (tag == -1) { // Not read before the call, read now 614 tag = is.read(); 615 if (tag == -1) { 616 throw new IOException("BER/DER tag info absent"); 617 } 618 if ((tag & 0x1f) == 0x1f) { 619 throw new IOException("Multi octets tag not supported"); 620 } 621 bout.write(tag); 622 } 623 624 int n = is.read(); 625 if (n == -1) { 626 throw new IOException("BER/DER length info ansent"); 627 } 628 bout.write(n); 629 630 int length; 631 632 if (n == 0x80) { // Indefinite-length encoding 633 if ((tag & 0x20) != 0x20) { 634 throw new IOException( 635 "Non constructed encoding must have definite length"); 636 } 637 while (true) { 638 int subTag = readBERInternal(is, bout, -1); 639 if (subTag == 0) { // EOC, end of indefinite-length section 640 break; 641 } 642 } 643 } else { 644 if (n < 0x80) { 645 length = n; 646 } else if (n == 0x81) { 647 length = is.read(); 648 if (length == -1) { 649 throw new IOException("Incomplete BER/DER length info"); 650 } 651 bout.write(length); 652 } else if (n == 0x82) { 653 int highByte = is.read(); 654 int lowByte = is.read(); 655 if (lowByte == -1) { 656 throw new IOException("Incomplete BER/DER length info"); 657 } 658 bout.write(highByte); 659 bout.write(lowByte); 660 length = (highByte << 8) | lowByte; 661 } else if (n == 0x83) { 662 int highByte = is.read(); 663 int midByte = is.read(); 664 int lowByte = is.read(); 665 if (lowByte == -1) { 666 throw new IOException("Incomplete BER/DER length info"); 667 } 668 bout.write(highByte); 669 bout.write(midByte); 670 bout.write(lowByte); 671 length = (highByte << 16) | (midByte << 8) | lowByte; 672 } else if (n == 0x84) { 673 int highByte = is.read(); 674 int nextByte = is.read(); 675 int midByte = is.read(); 676 int lowByte = is.read(); 677 if (lowByte == -1) { 678 throw new IOException("Incomplete BER/DER length info"); 679 } 680 if (highByte > 127) { 681 throw new IOException("Invalid BER/DER data (a little huge?)"); 682 } 683 bout.write(highByte); 684 bout.write(nextByte); 685 bout.write(midByte); 686 bout.write(lowByte); 687 length = (highByte << 24 ) | (nextByte << 16) | 688 (midByte << 8) | lowByte; 689 } else { // ignore longer length forms 690 throw new IOException("Invalid BER/DER data (too huge?)"); 691 } 692 if (readFully(is, bout, length) != length) { 693 throw new IOException("Incomplete BER/DER data"); 694 } 695 } 696 return tag; 697 } 698 }