Use Tree Navigation
public class

X509Factory

extends CertificateFactorySpi
/*
 * Copyright (c) 1998, 2006, 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
 * @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 <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.
     */

   
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.
     */

   
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.
     */

   
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).
     */

   
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.
     */

   
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.
     */

   
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 <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 <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 <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 <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 <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
     * <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 <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;
       
if ((temp=readLine(br))==null || !temp.startsWith("-----BEGIN")) {
           
throw new IOException("Unsupported encoding");
       
} else {
            len
+= temp.length();
       
}
       
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() >= 10) {
           
is.mark(10);
           
int c1 = is.read();
           
int c2 = is.read();
           
int c3 = is.read();
           
int c4 = is.read();
           
int c5 = is.read();
           
int c6 = is.read();
           
int c7 = is.read();
           
int c8 = is.read();
           
int c9 = is.read();
           
int c10 = is.read();
           
is.reset();
           
if (c1 == '-' && c2 == '-' && c3 == '-' && c4 == '-'
               
&& c5 == '-' && c6 == 'B' && c7 == 'E' && c8 == 'G'
               
&& c9 == 'I' && c10 == 'N') {
               
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();
   
}
}