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 certCache = Cache.newSoftMemoryCache(750); 68 private static final Cache crlCache = Cache.newSoftMemoryCache(750); 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 */ engineGenerateCertificate(InputStream is)81 public Certificate engineGenerateCertificate(InputStream is) 82 throws CertificateException 83 { 84 if (is == null) { 85 // clear the caches (for debugging) 86 certCache.clear(); 87 X509CertificatePair.clearCache(); 88 throw new CertificateException("Missing input stream"); 89 } 90 try { 91 byte[] encoding = readOneBlock(is); 92 if (encoding != null) { 93 X509CertImpl cert = (X509CertImpl)getFromCache(certCache, encoding); 94 if (cert != null) { 95 return cert; 96 } 97 cert = new X509CertImpl(encoding); 98 addToCache(certCache, cert.getEncodedInternal(), cert); 99 return cert; 100 } else { 101 throw new IOException("Empty input"); 102 } 103 } catch (IOException ioe) { 104 throw (CertificateException)new CertificateException 105 ("Could not parse certificate: " + ioe.toString()).initCause(ioe); 106 } 107 } 108 109 /** 110 * Read from the stream until length bytes have been read or EOF has 111 * been reached. Return the number of bytes actually read. 112 */ readFully(InputStream in, ByteArrayOutputStream bout, int length)113 private static int readFully(InputStream in, ByteArrayOutputStream bout, 114 int length) throws IOException { 115 int read = 0; 116 byte[] buffer = new byte[2048]; 117 while (length > 0) { 118 int n = in.read(buffer, 0, length<2048?length:2048); 119 if (n <= 0) { 120 break; 121 } 122 bout.write(buffer, 0, n); 123 read += n; 124 length -= n; 125 } 126 return read; 127 } 128 129 /** 130 * Return an interned X509CertImpl for the given certificate. 131 * If the given X509Certificate or X509CertImpl is already present 132 * in the cert cache, the cached object is returned. Otherwise, 133 * if it is a X509Certificate, it is first converted to a X509CertImpl. 134 * Then the X509CertImpl is added to the cache and returned. 135 * 136 * Note that all certificates created via generateCertificate(InputStream) 137 * are already interned and this method does not need to be called. 138 * It is useful for certificates that cannot be created via 139 * generateCertificate() and for converting other X509Certificate 140 * implementations to an X509CertImpl. 141 */ intern(X509Certificate c)142 public static synchronized X509CertImpl intern(X509Certificate c) 143 throws CertificateException { 144 if (c == null) { 145 return null; 146 } 147 boolean isImpl = c instanceof X509CertImpl; 148 byte[] encoding; 149 if (isImpl) { 150 encoding = ((X509CertImpl)c).getEncodedInternal(); 151 } else { 152 encoding = c.getEncoded(); 153 } 154 X509CertImpl newC = (X509CertImpl)getFromCache(certCache, encoding); 155 if (newC != null) { 156 return newC; 157 } 158 if (isImpl) { 159 newC = (X509CertImpl)c; 160 } else { 161 newC = new X509CertImpl(encoding); 162 encoding = newC.getEncodedInternal(); 163 } 164 addToCache(certCache, encoding, newC); 165 return newC; 166 } 167 168 /** 169 * Return an interned X509CRLImpl for the given certificate. 170 * For more information, see intern(X509Certificate). 171 */ intern(X509CRL c)172 public static synchronized X509CRLImpl intern(X509CRL c) 173 throws CRLException { 174 if (c == null) { 175 return null; 176 } 177 boolean isImpl = c instanceof X509CRLImpl; 178 byte[] encoding; 179 if (isImpl) { 180 encoding = ((X509CRLImpl)c).getEncodedInternal(); 181 } else { 182 encoding = c.getEncoded(); 183 } 184 X509CRLImpl newC = (X509CRLImpl)getFromCache(crlCache, encoding); 185 if (newC != null) { 186 return newC; 187 } 188 if (isImpl) { 189 newC = (X509CRLImpl)c; 190 } else { 191 newC = new X509CRLImpl(encoding); 192 encoding = newC.getEncodedInternal(); 193 } 194 addToCache(crlCache, encoding, newC); 195 return newC; 196 } 197 198 /** 199 * Get the X509CertImpl or X509CRLImpl from the cache. 200 */ getFromCache(Cache cache, byte[] encoding)201 private static synchronized Object getFromCache(Cache cache, 202 byte[] encoding) { 203 Object key = new Cache.EqualByteArray(encoding); 204 Object value = cache.get(key); 205 return value; 206 } 207 208 /** 209 * Add the X509CertImpl or X509CRLImpl to the cache. 210 */ addToCache(Cache cache, byte[] encoding, Object value)211 private static synchronized void addToCache(Cache cache, byte[] encoding, 212 Object value) { 213 if (encoding.length > ENC_MAX_LENGTH) { 214 return; 215 } 216 Object key = new Cache.EqualByteArray(encoding); 217 cache.put(key, value); 218 } 219 220 /** 221 * Generates a <code>CertPath</code> object and initializes it with 222 * the data read from the <code>InputStream</code> inStream. The data 223 * is assumed to be in the default encoding. 224 * 225 * @param inStream an <code>InputStream</code> containing the data 226 * @return a <code>CertPath</code> initialized with the data from the 227 * <code>InputStream</code> 228 * @exception CertificateException if an exception occurs while decoding 229 * @since 1.4 230 */ engineGenerateCertPath(InputStream inStream)231 public CertPath engineGenerateCertPath(InputStream inStream) 232 throws CertificateException 233 { 234 if (inStream == null) { 235 throw new CertificateException("Missing input stream"); 236 } 237 try { 238 byte[] encoding = readOneBlock(inStream); 239 if (encoding != null) { 240 return new X509CertPath(new ByteArrayInputStream(encoding)); 241 } else { 242 throw new IOException("Empty input"); 243 } 244 } catch (IOException ioe) { 245 throw new CertificateException(ioe.getMessage()); 246 } 247 } 248 249 /** 250 * Generates a <code>CertPath</code> object and initializes it with 251 * the data read from the <code>InputStream</code> inStream. The data 252 * is assumed to be in the specified encoding. 253 * 254 * @param inStream an <code>InputStream</code> containing the data 255 * @param encoding the encoding used for the data 256 * @return a <code>CertPath</code> initialized with the data from the 257 * <code>InputStream</code> 258 * @exception CertificateException if an exception occurs while decoding or 259 * the encoding requested is not supported 260 * @since 1.4 261 */ engineGenerateCertPath(InputStream inStream, String encoding)262 public CertPath engineGenerateCertPath(InputStream inStream, 263 String encoding) throws CertificateException 264 { 265 if (inStream == null) { 266 throw new CertificateException("Missing input stream"); 267 } 268 try { 269 byte[] data = readOneBlock(inStream); 270 if (data != null) { 271 return new X509CertPath(new ByteArrayInputStream(data), encoding); 272 } else { 273 throw new IOException("Empty input"); 274 } 275 } catch (IOException ioe) { 276 throw new CertificateException(ioe.getMessage()); 277 } 278 } 279 280 /** 281 * Generates a <code>CertPath</code> object and initializes it with 282 * a <code>List</code> of <code>Certificate</code>s. 283 * <p> 284 * The certificates supplied must be of a type supported by the 285 * <code>CertificateFactory</code>. They will be copied out of the supplied 286 * <code>List</code> object. 287 * 288 * @param certificates a <code>List</code> of <code>Certificate</code>s 289 * @return a <code>CertPath</code> initialized with the supplied list of 290 * certificates 291 * @exception CertificateException if an exception occurs 292 * @since 1.4 293 */ 294 public CertPath engineGenerateCertPath(List<? extends Certificate> certificates)295 engineGenerateCertPath(List<? extends Certificate> certificates) 296 throws CertificateException 297 { 298 return(new X509CertPath(certificates)); 299 } 300 301 /** 302 * Returns an iteration of the <code>CertPath</code> encodings supported 303 * by this certificate factory, with the default encoding first. 304 * <p> 305 * Attempts to modify the returned <code>Iterator</code> via its 306 * <code>remove</code> method result in an 307 * <code>UnsupportedOperationException</code>. 308 * 309 * @return an <code>Iterator</code> over the names of the supported 310 * <code>CertPath</code> encodings (as <code>String</code>s) 311 * @since 1.4 312 */ engineGetCertPathEncodings()313 public Iterator<String> engineGetCertPathEncodings() { 314 return(X509CertPath.getEncodingsStatic()); 315 } 316 317 /** 318 * Returns a (possibly empty) collection view of X.509 certificates read 319 * from the given input stream <code>is</code>. 320 * 321 * @param is the input stream with the certificates. 322 * 323 * @return a (possibly empty) collection view of X.509 certificate objects 324 * initialized with the data from the input stream. 325 * 326 * @exception CertificateException on parsing errors. 327 */ 328 public Collection<? extends java.security.cert.Certificate> engineGenerateCertificates(InputStream is)329 engineGenerateCertificates(InputStream is) 330 throws CertificateException { 331 if (is == null) { 332 throw new CertificateException("Missing input stream"); 333 } 334 try { 335 return parseX509orPKCS7Cert(is); 336 } catch (IOException ioe) { 337 throw new CertificateException(ioe); 338 } 339 } 340 341 /** 342 * Generates an X.509 certificate revocation list (CRL) object and 343 * initializes it with the data read from the given input stream 344 * <code>is</code>. 345 * 346 * @param is an input stream with the CRL data. 347 * 348 * @return an X.509 CRL object initialized with the data 349 * from the input stream. 350 * 351 * @exception CRLException on parsing errors. 352 */ engineGenerateCRL(InputStream is)353 public CRL engineGenerateCRL(InputStream is) 354 throws CRLException 355 { 356 if (is == null) { 357 // clear the cache (for debugging) 358 crlCache.clear(); 359 throw new CRLException("Missing input stream"); 360 } 361 try { 362 byte[] encoding = readOneBlock(is); 363 if (encoding != null) { 364 X509CRLImpl crl = (X509CRLImpl)getFromCache(crlCache, encoding); 365 if (crl != null) { 366 return crl; 367 } 368 crl = new X509CRLImpl(encoding); 369 addToCache(crlCache, crl.getEncodedInternal(), crl); 370 return crl; 371 } else { 372 throw new IOException("Empty input"); 373 } 374 } catch (IOException ioe) { 375 throw new CRLException(ioe.getMessage()); 376 } 377 } 378 379 /** 380 * Returns a (possibly empty) collection view of X.509 CRLs read 381 * from the given input stream <code>is</code>. 382 * 383 * @param is the input stream with the CRLs. 384 * 385 * @return a (possibly empty) collection view of X.509 CRL objects 386 * initialized with the data from the input stream. 387 * 388 * @exception CRLException on parsing errors. 389 */ engineGenerateCRLs( InputStream is)390 public Collection<? extends java.security.cert.CRL> engineGenerateCRLs( 391 InputStream is) throws CRLException 392 { 393 if (is == null) { 394 throw new CRLException("Missing input stream"); 395 } 396 try { 397 return parseX509orPKCS7CRL(is); 398 } catch (IOException ioe) { 399 throw new CRLException(ioe.getMessage()); 400 } 401 } 402 403 /* 404 * Parses the data in the given input stream as a sequence of DER 405 * encoded X.509 certificates (in binary or base 64 encoded format) OR 406 * as a single PKCS#7 encoded blob (in binary or base64 encoded format). 407 */ 408 private Collection<? extends java.security.cert.Certificate> parseX509orPKCS7Cert(InputStream is)409 parseX509orPKCS7Cert(InputStream is) 410 throws CertificateException, IOException 411 { 412 Collection<X509CertImpl> coll = new ArrayList<>(); 413 byte[] data = readOneBlock(is); 414 if (data == null) { 415 return new ArrayList<>(0); 416 } 417 try { 418 PKCS7 pkcs7 = new PKCS7(data); 419 X509Certificate[] certs = pkcs7.getCertificates(); 420 // certs are optional in PKCS #7 421 if (certs != null) { 422 return Arrays.asList(certs); 423 } else { 424 // no crls provided 425 return new ArrayList<>(0); 426 } 427 } catch (ParsingException e) { 428 while (data != null) { 429 coll.add(new X509CertImpl(data)); 430 data = readOneBlock(is); 431 } 432 } 433 return coll; 434 } 435 436 /* 437 * Parses the data in the given input stream as a sequence of DER encoded 438 * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7 439 * encoded blob (in binary or base 64 encoded format). 440 */ 441 private Collection<? extends java.security.cert.CRL> parseX509orPKCS7CRL(InputStream is)442 parseX509orPKCS7CRL(InputStream is) 443 throws CRLException, IOException 444 { 445 Collection<X509CRLImpl> coll = new ArrayList<>(); 446 byte[] data = readOneBlock(is); 447 if (data == null) { 448 return new ArrayList<>(0); 449 } 450 try { 451 PKCS7 pkcs7 = new PKCS7(data); 452 X509CRL[] crls = pkcs7.getCRLs(); 453 // CRLs are optional in PKCS #7 454 if (crls != null) { 455 return Arrays.asList(crls); 456 } else { 457 // no crls provided 458 return new ArrayList<>(0); 459 } 460 } catch (ParsingException e) { 461 while (data != null) { 462 coll.add(new X509CRLImpl(data)); 463 data = readOneBlock(is); 464 } 465 } 466 return coll; 467 } 468 469 /** 470 * Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded 471 * binary block or a PEM-style BASE64-encoded ASCII data. In the latter 472 * case, it's de-BASE64'ed before return. 473 * 474 * After the reading, the input stream pointer is after the BER block, or 475 * after the newline character after the -----END SOMETHING----- line. 476 * 477 * @param is the InputStream 478 * @returns byte block or null if end of stream 479 * @throws IOException If any parsing error 480 */ readOneBlock(InputStream is)481 private static byte[] readOneBlock(InputStream is) throws IOException { 482 483 // The first character of a BLOCK. 484 int c = is.read(); 485 if (c == -1) { 486 return null; 487 } 488 if (c == DerValue.tag_Sequence) { 489 ByteArrayOutputStream bout = new ByteArrayOutputStream(2048); 490 bout.write(c); 491 readBERInternal(is, bout, c); 492 return bout.toByteArray(); 493 } else { 494 // Read BASE64 encoded data, might skip info at the beginning 495 char[] data = new char[2048]; 496 int pos = 0; 497 498 // Step 1: Read until header is found 499 int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens 500 int last = (c=='-') ? -1: c; // the char before hyphen 501 while (true) { 502 int next = is.read(); 503 if (next == -1) { 504 // We accept useless data after the last block, 505 // say, empty lines. 506 return null; 507 } 508 if (next == '-') { 509 hyphen++; 510 } else { 511 hyphen = 0; 512 last = next; 513 } 514 if (hyphen == 5 && (last==-1 || last=='\r' || last=='\n')) { 515 break; 516 } 517 } 518 519 // Step 2: Read the rest of header, determine the line end 520 int end; 521 StringBuffer header = new StringBuffer("-----"); 522 while (true) { 523 int next = is.read(); 524 if (next == -1) { 525 throw new IOException("Incomplete data"); 526 } 527 if (next == '\n') { 528 end = '\n'; 529 break; 530 } 531 if (next == '\r') { 532 next = is.read(); 533 if (next == -1) { 534 throw new IOException("Incomplete data"); 535 } 536 if (next == '\n') { 537 end = '\n'; 538 } else { 539 end = '\r'; 540 data[pos++] = (char)next; 541 } 542 break; 543 } 544 header.append((char)next); 545 } 546 547 // Step 3: Read the data 548 while (true) { 549 int next = is.read(); 550 if (next == -1) { 551 throw new IOException("Incomplete data"); 552 } 553 if (next != '-') { 554 data[pos++] = (char)next; 555 if (pos >= data.length) { 556 data = Arrays.copyOf(data, data.length+1024); 557 } 558 } else { 559 break; 560 } 561 } 562 563 // Step 4: Consume the footer 564 StringBuffer footer = new StringBuffer("-"); 565 while (true) { 566 int next = is.read(); 567 // Add next == '\n' for maximum safety, in case endline 568 // is not consistent. 569 if (next == -1 || next == end || next == '\n') { 570 break; 571 } 572 if (next != '\r') footer.append((char)next); 573 } 574 575 checkHeaderFooter(header.toString(), footer.toString()); 576 577 BASE64Decoder decoder = new BASE64Decoder(); 578 return decoder.decodeBuffer(new String(data, 0, pos)); 579 } 580 } 581 checkHeaderFooter(String header, String footer)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 */ readBERInternal(InputStream is, ByteArrayOutputStream bout, int tag)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 } 699