/*
 * Copyright (c) 1998, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.provider;

import java.io.*;
import java.util.Collection;
import java.util.*;
import java.security.cert.*;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CRLImpl;
import sun.security.pkcs.PKCS7;
import sun.security.provider.certpath.X509CertPath;
import sun.security.provider.certpath.X509CertificatePair;
import sun.security.util.DerValue;
import sun.security.util.Cache;
import sun.misc.BASE64Decoder;

This class defines a certificate factory for X.509 v3 certificates & certification paths, and X.509 v2 certificate revocation lists (CRLs).
Author:Jan Luehe, Hemma Prafullchandra, Sean Mullan
See Also:
/** * This class defines a certificate factory for X.509 v3 certificates & * certification paths, and X.509 v2 certificate revocation lists (CRLs). * * @author Jan Luehe * @author Hemma Prafullchandra * @author Sean Mullan * * * @see java.security.cert.CertificateFactorySpi * @see java.security.cert.Certificate * @see java.security.cert.CertPath * @see java.security.cert.CRL * @see java.security.cert.X509Certificate * @see java.security.cert.X509CRL * @see sun.security.x509.X509CertImpl * @see sun.security.x509.X509CRLImpl */
public class X509Factory extends CertificateFactorySpi { public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; public static final String END_CERT = "-----END CERTIFICATE-----"; private static final int defaultExpectedLineLength = 80; private static final char[] endBoundary = "-----END".toCharArray(); private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX private static final Cache certCache = Cache.newSoftMemoryCache(750); private static final Cache crlCache = Cache.newSoftMemoryCache(750);
Generates an X.509 certificate object and initializes it with the data read from the input stream is.
Params:
  • is – an input stream with the certificate data.
Throws:
Returns:an X.509 certificate object initialized with the data from the input stream.
/** * Generates an X.509 certificate object and initializes it with * the data read from the input stream <code>is</code>. * * @param is an input stream with the certificate data. * * @return an X.509 certificate object initialized with the data * from the input stream. * * @exception CertificateException on parsing errors. */
public Certificate engineGenerateCertificate(InputStream is) throws CertificateException { if (is == null) { // clear the caches (for debugging) certCache.clear(); X509CertificatePair.clearCache(); throw new CertificateException("Missing input stream"); } try { if (is.markSupported() == false) { // consume the entire input stream byte[] totalBytes; totalBytes = getTotalBytes(new BufferedInputStream(is)); is = new ByteArrayInputStream(totalBytes); } byte[] encoding = readSequence(is); if (encoding != null) { X509CertImpl cert = (X509CertImpl)getFromCache(certCache, encoding); if (cert != null) { return cert; } cert = new X509CertImpl(encoding); addToCache(certCache, cert.getEncodedInternal(), cert); return cert; } else { X509CertImpl cert; // determine if binary or Base64 encoding. If Base64 encoding, // the certificate must be bounded at the beginning by // "-----BEGIN". if (isBase64(is)) { // Base64 byte[] data = base64_to_binary(is); cert = new X509CertImpl(data); } else { // binary cert = new X509CertImpl(new DerValue(is)); } return intern(cert); } } catch (IOException ioe) { throw (CertificateException)new CertificateException ("Could not parse certificate: " + ioe.toString()).initCause(ioe); } }
Read a DER SEQUENCE from an InputStream and return the encoding. If data does not represent a SEQUENCE, it uses indefinite length encoding, or is longer than ENC_MAX_LENGTH, the stream is reset and this method returns null.
/** * Read a DER SEQUENCE from an InputStream and return the encoding. * If data does not represent a SEQUENCE, it uses indefinite length * encoding, or is longer than ENC_MAX_LENGTH, the stream is reset * and this method returns null. */
private static byte[] readSequence(InputStream in) throws IOException { in.mark(ENC_MAX_LENGTH); byte[] b = new byte[4]; int i = readFully(in, b, 0, b.length); if ((i != b.length) || (b[0] != 0x30)) { // first byte must be SEQUENCE in.reset(); return null; } i = b[1] & 0xff; int totalLength; if (i < 0x80) { int valueLength = i; totalLength = valueLength + 2; } else if (i == 0x81) { int valueLength = b[2] & 0xff; totalLength = valueLength + 3; } else if (i == 0x82) { int valueLength = ((b[2] & 0xff) << 8) | (b[3] & 0xff); totalLength = valueLength + 4; } else { // ignore longer length forms in.reset(); return null; } if (totalLength > ENC_MAX_LENGTH) { in.reset(); return null; } byte[] encoding = new byte[totalLength]; if( totalLength < b.length ) { in.reset(); i = readFully(in, encoding, 0, totalLength); if( i != totalLength ) { in.reset(); return null; } } else { System.arraycopy(b, 0, encoding, 0, b.length); int n = totalLength - b.length; i = readFully(in, encoding, b.length, n); if (i != n) { in.reset(); return null; } } return encoding; }
Read from the stream until length bytes have been read or EOF has been reached. Return the number of bytes actually read.
/** * Read from the stream until length bytes have been read or EOF has * been reached. Return the number of bytes actually read. */
private static int readFully(InputStream in, byte[] buffer, int offset, int length) throws IOException { int read = 0; while (length > 0) { int n = in.read(buffer, offset, length); if (n <= 0) { break; } read += n; length -= n; offset += n; } return read; }
Return an interned X509CertImpl for the given certificate. If the given X509Certificate or X509CertImpl is already present in the cert cache, the cached object is returned. Otherwise, if it is a X509Certificate, it is first converted to a X509CertImpl. Then the X509CertImpl is added to the cache and returned. Note that all certificates created via generateCertificate(InputStream) are already interned and this method does not need to be called. It is useful for certificates that cannot be created via generateCertificate() and for converting other X509Certificate implementations to an X509CertImpl.
/** * Return an interned X509CertImpl for the given certificate. * If the given X509Certificate or X509CertImpl is already present * in the cert cache, the cached object is returned. Otherwise, * if it is a X509Certificate, it is first converted to a X509CertImpl. * Then the X509CertImpl is added to the cache and returned. * * Note that all certificates created via generateCertificate(InputStream) * are already interned and this method does not need to be called. * It is useful for certificates that cannot be created via * generateCertificate() and for converting other X509Certificate * implementations to an X509CertImpl. */
public static synchronized X509CertImpl intern(X509Certificate c) throws CertificateException { if (c == null) { return null; } boolean isImpl = c instanceof X509CertImpl; byte[] encoding; if (isImpl) { encoding = ((X509CertImpl)c).getEncodedInternal(); } else { encoding = c.getEncoded(); } X509CertImpl newC = (X509CertImpl)getFromCache(certCache, encoding); if (newC != null) { return newC; } if (isImpl) { newC = (X509CertImpl)c; } else { newC = new X509CertImpl(encoding); encoding = newC.getEncodedInternal(); } addToCache(certCache, encoding, newC); return newC; }
Return an interned X509CRLImpl for the given certificate. For more information, see intern(X509Certificate).
/** * Return an interned X509CRLImpl for the given certificate. * For more information, see intern(X509Certificate). */
public static synchronized X509CRLImpl intern(X509CRL c) throws CRLException { if (c == null) { return null; } boolean isImpl = c instanceof X509CRLImpl; byte[] encoding; if (isImpl) { encoding = ((X509CRLImpl)c).getEncodedInternal(); } else { encoding = c.getEncoded(); } X509CRLImpl newC = (X509CRLImpl)getFromCache(crlCache, encoding); if (newC != null) { return newC; } if (isImpl) { newC = (X509CRLImpl)c; } else { newC = new X509CRLImpl(encoding); encoding = newC.getEncodedInternal(); } addToCache(crlCache, encoding, newC); return newC; }
Get the X509CertImpl or X509CRLImpl from the cache.
/** * Get the X509CertImpl or X509CRLImpl from the cache. */
private static synchronized Object getFromCache(Cache cache, byte[] encoding) { Object key = new Cache.EqualByteArray(encoding); Object value = cache.get(key); return value; }
Add the X509CertImpl or X509CRLImpl to the cache.
/** * Add the X509CertImpl or X509CRLImpl to the cache. */
private static synchronized void addToCache(Cache cache, byte[] encoding, Object value) { if (encoding.length > ENC_MAX_LENGTH) { return; } Object key = new Cache.EqualByteArray(encoding); cache.put(key, value); }
Generates a CertPath object and initializes it with the data read from the InputStream inStream. The data is assumed to be in the default encoding.
Params:
  • inStream – an InputStream containing the data
Throws:
Returns:a CertPath initialized with the data from the InputStream
Since:1.4
/** * Generates a <code>CertPath</code> object and initializes it with * the data read from the <code>InputStream</code> inStream. The data * is assumed to be in the default encoding. * * @param inStream an <code>InputStream</code> containing the data * @return a <code>CertPath</code> initialized with the data from the * <code>InputStream</code> * @exception CertificateException if an exception occurs while decoding * @since 1.4 */
public CertPath engineGenerateCertPath(InputStream inStream) throws CertificateException { if (inStream == null) { throw new CertificateException("Missing input stream"); } try { if (inStream.markSupported() == false) { // consume the entire input stream byte[] totalBytes; totalBytes = getTotalBytes(new BufferedInputStream(inStream)); inStream = new ByteArrayInputStream(totalBytes); } // determine if binary or Base64 encoding. If Base64 encoding, // each certificate must be bounded at the beginning by // "-----BEGIN". if (isBase64(inStream)) { // Base64 byte[] data = base64_to_binary(inStream); return new X509CertPath(new ByteArrayInputStream(data)); } else { return new X509CertPath(inStream); } } catch (IOException ioe) { throw new CertificateException(ioe.getMessage()); } }
Generates a CertPath object and initializes it with the data read from the InputStream inStream. The data is assumed to be in the specified encoding.
Params:
  • inStream – an InputStream containing the data
  • encoding – the encoding used for the data
Throws:
  • CertificateException – if an exception occurs while decoding or the encoding requested is not supported
Returns:a CertPath initialized with the data from the InputStream
Since:1.4
/** * Generates a <code>CertPath</code> object and initializes it with * the data read from the <code>InputStream</code> inStream. The data * is assumed to be in the specified encoding. * * @param inStream an <code>InputStream</code> containing the data * @param encoding the encoding used for the data * @return a <code>CertPath</code> initialized with the data from the * <code>InputStream</code> * @exception CertificateException if an exception occurs while decoding or * the encoding requested is not supported * @since 1.4 */
public CertPath engineGenerateCertPath(InputStream inStream, String encoding) throws CertificateException { if (inStream == null) { throw new CertificateException("Missing input stream"); } try { if (inStream.markSupported() == false) { // consume the entire input stream byte[] totalBytes; totalBytes = getTotalBytes(new BufferedInputStream(inStream)); inStream = new ByteArrayInputStream(totalBytes); } // determine if binary or Base64 encoding. If Base64 encoding, // each certificate must be bounded at the beginning by // "-----BEGIN". if (isBase64(inStream)) { // Base64 byte[] data = base64_to_binary(inStream); return new X509CertPath(new ByteArrayInputStream(data), encoding); } else { return(new X509CertPath(inStream, encoding)); } } catch (IOException ioe) { throw new CertificateException(ioe.getMessage()); } }
Generates a CertPath object and initializes it with a List of Certificates.

The certificates supplied must be of a type supported by the CertificateFactory. They will be copied out of the supplied List object.

Params:
  • certificates – a List of Certificates
Throws:
Returns:a CertPath initialized with the supplied list of certificates
Since:1.4
/** * Generates a <code>CertPath</code> object and initializes it with * a <code>List</code> of <code>Certificate</code>s. * <p> * The certificates supplied must be of a type supported by the * <code>CertificateFactory</code>. They will be copied out of the supplied * <code>List</code> object. * * @param certificates a <code>List</code> of <code>Certificate</code>s * @return a <code>CertPath</code> initialized with the supplied list of * certificates * @exception CertificateException if an exception occurs * @since 1.4 */
public CertPath engineGenerateCertPath(List<? extends Certificate> certificates) throws CertificateException { return(new X509CertPath(certificates)); }
Returns an iteration of the CertPath encodings supported by this certificate factory, with the default encoding first.

Attempts to modify the returned Iterator via its remove method result in an UnsupportedOperationException.

Returns:an Iterator over the names of the supported CertPath encodings (as Strings)
Since:1.4
/** * Returns an iteration of the <code>CertPath</code> encodings supported * by this certificate factory, with the default encoding first. * <p> * Attempts to modify the returned <code>Iterator</code> via its * <code>remove</code> method result in an * <code>UnsupportedOperationException</code>. * * @return an <code>Iterator</code> over the names of the supported * <code>CertPath</code> encodings (as <code>String</code>s) * @since 1.4 */
public Iterator<String> engineGetCertPathEncodings() { return(X509CertPath.getEncodingsStatic()); }
Returns a (possibly empty) collection view of X.509 certificates read from the given input stream is.
Params:
  • is – the input stream with the certificates.
Throws:
Returns:a (possibly empty) collection view of X.509 certificate objects initialized with the data from the input stream.
/** * Returns a (possibly empty) collection view of X.509 certificates read * from the given input stream <code>is</code>. * * @param is the input stream with the certificates. * * @return a (possibly empty) collection view of X.509 certificate objects * initialized with the data from the input stream. * * @exception CertificateException on parsing errors. */
public Collection<? extends java.security.cert.Certificate> engineGenerateCertificates(InputStream is) throws CertificateException { if (is == null) { throw new CertificateException("Missing input stream"); } try { if (is.markSupported() == false) { // consume the entire input stream is = new ByteArrayInputStream (getTotalBytes(new BufferedInputStream(is))); } return parseX509orPKCS7Cert(is); } catch (IOException ioe) { throw new CertificateException(ioe); } }
Generates an X.509 certificate revocation list (CRL) object and initializes it with the data read from the given input stream is.
Params:
  • is – an input stream with the CRL data.
Throws:
Returns:an X.509 CRL object initialized with the data from the input stream.
/** * Generates an X.509 certificate revocation list (CRL) object and * initializes it with the data read from the given input stream * <code>is</code>. * * @param is an input stream with the CRL data. * * @return an X.509 CRL object initialized with the data * from the input stream. * * @exception CRLException on parsing errors. */
public CRL engineGenerateCRL(InputStream is) throws CRLException { if (is == null) { // clear the cache (for debugging) crlCache.clear(); throw new CRLException("Missing input stream"); } try { if (is.markSupported() == false) { // consume the entire input stream byte[] totalBytes; totalBytes = getTotalBytes(new BufferedInputStream(is)); is = new ByteArrayInputStream(totalBytes); } byte[] encoding = readSequence(is); if (encoding != null) { X509CRLImpl crl = (X509CRLImpl)getFromCache(crlCache, encoding); if (crl != null) { return crl; } crl = new X509CRLImpl(encoding); addToCache(crlCache, crl.getEncodedInternal(), crl); return crl; } else { X509CRLImpl crl; // determine if binary or Base64 encoding. If Base64 encoding, // the CRL must be bounded at the beginning by // "-----BEGIN". if (isBase64(is)) { // Base64 byte[] data = base64_to_binary(is); crl = new X509CRLImpl(data); } else { // binary crl = new X509CRLImpl(new DerValue(is)); } return intern(crl); } } catch (IOException ioe) { throw new CRLException(ioe.getMessage()); } }
Returns a (possibly empty) collection view of X.509 CRLs read from the given input stream is.
Params:
  • is – the input stream with the CRLs.
Throws:
Returns:a (possibly empty) collection view of X.509 CRL objects initialized with the data from the input stream.
/** * Returns a (possibly empty) collection view of X.509 CRLs read * from the given input stream <code>is</code>. * * @param is the input stream with the CRLs. * * @return a (possibly empty) collection view of X.509 CRL objects * initialized with the data from the input stream. * * @exception CRLException on parsing errors. */
public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(InputStream is) throws CRLException { if (is == null) { throw new CRLException("Missing input stream"); } try { if (is.markSupported() == false) { // consume the entire input stream is = new ByteArrayInputStream (getTotalBytes(new BufferedInputStream(is))); } return parseX509orPKCS7CRL(is); } catch (IOException ioe) { throw new CRLException(ioe.getMessage()); } } /* * Parses the data in the given input stream as a sequence of DER * encoded X.509 certificates (in binary or base 64 encoded format) OR * as a single PKCS#7 encoded blob (in binary or base64 encoded format). */ private Collection<? extends java.security.cert.Certificate> parseX509orPKCS7Cert(InputStream is) throws CertificateException, IOException { Collection<X509CertImpl> coll = new ArrayList<X509CertImpl>(); boolean first = true; while (is.available() != 0) { // determine if binary or Base64 encoding. If Base64 encoding, // each certificate must be bounded at the beginning by // "-----BEGIN". InputStream is2 = is; if (isBase64(is2)) { // Base64 is2 = new ByteArrayInputStream(base64_to_binary(is2)); } if (first) is2.mark(is2.available()); try { // treat as X.509 cert coll.add(intern(new X509CertImpl(new DerValue(is2)))); } catch (CertificateException e) { Throwable cause = e.getCause(); // only treat as PKCS#7 if this is the first cert parsed // and the root cause of the decoding failure is an IOException if (first && cause != null && (cause instanceof IOException)) { // treat as PKCS#7 is2.reset(); PKCS7 pkcs7 = new PKCS7(is2); X509Certificate[] certs = pkcs7.getCertificates(); // certs are optional in PKCS #7 if (certs != null) { return Arrays.asList(certs); } else { // no certs provided return new ArrayList<X509Certificate>(0); } } else { throw e; } } first = false; } return coll; } /* * Parses the data in the given input stream as a sequence of DER encoded * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7 * encoded blob (in binary or base 64 encoded format). */ private Collection<? extends java.security.cert.CRL> parseX509orPKCS7CRL(InputStream is) throws CRLException, IOException { Collection<X509CRLImpl> coll = new ArrayList<X509CRLImpl>(); boolean first = true; while (is.available() != 0) { // determine if binary or Base64 encoding. If Base64 encoding, // the CRL must be bounded at the beginning by // "-----BEGIN". InputStream is2 = is; if (isBase64(is)) { // Base64 is2 = new ByteArrayInputStream(base64_to_binary(is2)); } if (first) is2.mark(is2.available()); try { // treat as X.509 CRL coll.add(new X509CRLImpl(is2)); } catch (CRLException e) { // only treat as PKCS#7 if this is the first CRL parsed if (first) { is2.reset(); PKCS7 pkcs7 = new PKCS7(is2); X509CRL[] crls = pkcs7.getCRLs(); // CRLs are optional in PKCS #7 if (crls != null) { return Arrays.asList(crls); } else { // no crls provided return new ArrayList<X509CRL>(0); } } } first = false; } return coll; } /* * Converts a Base64-encoded X.509 certificate or X.509 CRL or PKCS#7 data * to binary encoding. * In all cases, the data must be bounded at the beginning by * "-----BEGIN", and must be bounded at the end by "-----END". */ private byte[] base64_to_binary(InputStream is) throws IOException { long len = 0; // total length of base64 encoding, including boundaries is.mark(is.available()); BufferedInputStream bufin = new BufferedInputStream(is); BufferedReader br = new BufferedReader(new InputStreamReader(bufin, "ASCII")); // First read all of the data that is found between // the "-----BEGIN" and "-----END" boundaries into a buffer. String temp; while (true) { temp=readLine(br); if (temp == null) { throw new IOException("Unsupported encoding"); } len += temp.length(); if (temp.startsWith("-----BEGIN")) { break; } } StringBuffer strBuf = new StringBuffer(); while ((temp=readLine(br))!=null && !temp.startsWith("-----END")) { strBuf.append(temp); } if (temp == null) { throw new IOException("Unsupported encoding"); } else { len += temp.length(); } // consume only as much as was needed len += strBuf.length(); is.reset(); is.skip(len); // Now, that data is supposed to be a single X.509 certificate or // X.509 CRL or PKCS#7 formatted data... Base64 encoded. // Decode into binary and return the result. BASE64Decoder decoder = new BASE64Decoder(); return decoder.decodeBuffer(strBuf.toString()); } /* * Reads the entire input stream into a byte array. */ private byte[] getTotalBytes(InputStream is) throws IOException { byte[] buffer = new byte[8192]; ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); int n; baos.reset(); while ((n = is.read(buffer, 0, buffer.length)) != -1) { baos.write(buffer, 0, n); } return baos.toByteArray(); } /* * Determines if input is binary or Base64 encoded. */ private boolean isBase64(InputStream is) throws IOException { if (is.available() >= 1) { is.mark(1); int c1 = is.read(); is.reset(); if (c1 != DerValue.tag_Sequence) { return true; } else { return false; } } else { return false; } } /* * Read a line of text. A line is considered to be terminated by any one * of a line feed ('\n'), a carriage return ('\r'), a carriage return * followed immediately by a linefeed, or an end-of-certificate marker. * * @return A String containing the contents of the line, including * any line-termination characters, or null if the end of the * stream has been reached. */ private String readLine(BufferedReader br) throws IOException { int c; int i = 0; boolean isMatch = true; boolean matched = false; StringBuffer sb = new StringBuffer(defaultExpectedLineLength); do { c = br.read(); if (isMatch && (i < endBoundary.length)) { isMatch = ((char)c != endBoundary[i++]) ? false : true; } if (!matched) matched = (isMatch && (i == endBoundary.length)); sb.append((char)c); } while ((c != -1) && (c != '\n') && (c != '\r')); if (!matched && c == -1) { return null; } if (c == '\r') { br.mark(1); int c2 = br.read(); if (c2 == '\n') { sb.append((char)c); } else { br.reset(); } } return sb.toString(); } }