All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.modules.java.base.classes.sun.security.provider.X509Factory Maven / Gradle / Ivy

There is a newer version: 2024-05-10
Show newest version
/*
 * Copyright (c) 1998, 2014, 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.*;
import java.security.cert.*;

import sun.security.util.Pem;
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 java.util.Base64;
import sun.security.pkcs.ParsingException;

/**
 * This class defines a certificate factory for X.509 v3 certificates {@literal &}
 * 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 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.
     *
     * @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.
     */
    @Override
    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 {
            byte[] encoding = readOneBlock(is);
            if (encoding != null) {
                X509CertImpl cert = getFromCache(certCache, encoding);
                if (cert != null) {
                    return cert;
                }
                cert = new X509CertImpl(encoding);
                addToCache(certCache, cert.getEncodedInternal(), cert);
                return cert;
            } else {
                throw new IOException("Empty input");
            }
        } catch (IOException ioe) {
            throw new CertificateException("Could not parse certificate: " +
                    ioe.toString(), ioe);
        }
    }

    /**
     * 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, ByteArrayOutputStream bout,
            int length) throws IOException {
        int read = 0;
        byte[] buffer = new byte[2048];
        while (length > 0) {
            int n = in.read(buffer, 0, length<2048?length:2048);
            if (n <= 0) {
                break;
            }
            bout.write(buffer, 0, n);
            read += n;
            length -= 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.
     *
     * @param c The source X509Certificate
     * @return An X509CertImpl object that is either a cached certificate or a
     *      newly built X509CertImpl from the provided X509Certificate
     * @throws CertificateException if failures occur while obtaining the DER
     *      encoding for certificate data.
     */
    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 = 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).
     *
     * @param c The source X509CRL
     * @return An X509CRLImpl object that is either a cached CRL or a
     *      newly built X509CRLImpl from the provided X509CRL
     * @throws CRLException if failures occur while obtaining the DER
     *      encoding for CRL data.
     */
    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 = 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  V getFromCache(Cache cache,
            byte[] encoding) {
        Object key = new Cache.EqualByteArray(encoding);
        return cache.get(key);
    }

    /**
     * Add the X509CertImpl or X509CRLImpl to the cache.
     */
    private static synchronized  void addToCache(Cache cache,
            byte[] encoding, V 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.
     *
     * @param inStream an InputStream containing the data
     * @return a CertPath initialized with the data from the
     *   InputStream
     * @exception CertificateException if an exception occurs while decoding
     * @since 1.4
     */
    @Override
    public CertPath engineGenerateCertPath(InputStream inStream)
        throws CertificateException
    {
        if (inStream == null) {
            throw new CertificateException("Missing input stream");
        }
        try {
            byte[] encoding = readOneBlock(inStream);
            if (encoding != null) {
                return new X509CertPath(new ByteArrayInputStream(encoding));
            } else {
                throw new IOException("Empty input");
            }
        } 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.
     *
     * @param inStream an InputStream containing the data
     * @param encoding the encoding used for the data
     * @return a CertPath initialized with the data from the
     *   InputStream
     * @exception CertificateException if an exception occurs while decoding or
     *   the encoding requested is not supported
     * @since 1.4
     */
    @Override
    public CertPath engineGenerateCertPath(InputStream inStream,
        String encoding) throws CertificateException
    {
        if (inStream == null) {
            throw new CertificateException("Missing input stream");
        }
        try {
            byte[] data = readOneBlock(inStream);
            if (data != null) {
                return new X509CertPath(new ByteArrayInputStream(data), encoding);
            } else {
                throw new IOException("Empty input");
            }
        } 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. * * @param certificates a List of Certificates * @return a CertPath initialized with the supplied list of * certificates * @exception CertificateException if an exception occurs * @since 1.4 */ @Override public CertPath engineGenerateCertPath(List 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. * * @return an Iterator over the names of the supported * CertPath encodings (as Strings) * @since 1.4 */ @Override public Iterator engineGetCertPathEncodings() { return(X509CertPath.getEncodingsStatic()); } /** * Returns a (possibly empty) collection view of X.509 certificates read * from the given input stream is. * * @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. */ @Override public Collection engineGenerateCertificates(InputStream is) throws CertificateException { if (is == null) { throw new CertificateException("Missing input stream"); } try { 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. * * @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. */ @Override public CRL engineGenerateCRL(InputStream is) throws CRLException { if (is == null) { // clear the cache (for debugging) crlCache.clear(); throw new CRLException("Missing input stream"); } try { byte[] encoding = readOneBlock(is); if (encoding != null) { X509CRLImpl crl = getFromCache(crlCache, encoding); if (crl != null) { return crl; } crl = new X509CRLImpl(encoding); addToCache(crlCache, crl.getEncodedInternal(), crl); return crl; } else { throw new IOException("Empty input"); } } 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. * * @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. */ @Override public Collection engineGenerateCRLs( InputStream is) throws CRLException { if (is == null) { throw new CRLException("Missing input stream"); } try { 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 parseX509orPKCS7Cert(InputStream is) throws CertificateException, IOException { int peekByte; byte[] data; PushbackInputStream pbis = new PushbackInputStream(is); Collection coll = new ArrayList<>(); // Test the InputStream for end-of-stream. If the stream's // initial state is already at end-of-stream then return // an empty collection. Otherwise, push the byte back into the // stream and let readOneBlock look for the first certificate. peekByte = pbis.read(); if (peekByte == -1) { return new ArrayList<>(0); } else { pbis.unread(peekByte); data = readOneBlock(pbis); } // If we end up with a null value after reading the first block // then we know the end-of-stream has been reached and no certificate // data has been found. if (data == null) { throw new CertificateException("No certificate data found"); } try { PKCS7 pkcs7 = new PKCS7(data); X509Certificate[] certs = pkcs7.getCertificates(); // certs are optional in PKCS #7 if (certs != null) { return Arrays.asList(certs); } else { // no certificates provided return new ArrayList<>(0); } } catch (ParsingException e) { while (data != null) { coll.add(new X509CertImpl(data)); data = readOneBlock(pbis); } } 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 parseX509orPKCS7CRL(InputStream is) throws CRLException, IOException { int peekByte; byte[] data; PushbackInputStream pbis = new PushbackInputStream(is); Collection coll = new ArrayList<>(); // Test the InputStream for end-of-stream. If the stream's // initial state is already at end-of-stream then return // an empty collection. Otherwise, push the byte back into the // stream and let readOneBlock look for the first CRL. peekByte = pbis.read(); if (peekByte == -1) { return new ArrayList<>(0); } else { pbis.unread(peekByte); data = readOneBlock(pbis); } // If we end up with a null value after reading the first block // then we know the end-of-stream has been reached and no CRL // data has been found. if (data == null) { throw new CRLException("No CRL data found"); } try { PKCS7 pkcs7 = new PKCS7(data); X509CRL[] crls = pkcs7.getCRLs(); // CRLs are optional in PKCS #7 if (crls != null) { return Arrays.asList(crls); } else { // no crls provided return new ArrayList<>(0); } } catch (ParsingException e) { while (data != null) { coll.add(new X509CRLImpl(data)); data = readOneBlock(pbis); } } return coll; } /** * Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded * binary block or a PEM-style BASE64-encoded ASCII data. In the latter * case, it's de-BASE64'ed before return. * * After the reading, the input stream pointer is after the BER block, or * after the newline character after the -----END SOMETHING----- line. * * @param is the InputStream * @return byte block or null if end of stream * @throws IOException If any parsing error */ private static byte[] readOneBlock(InputStream is) throws IOException { // The first character of a BLOCK. int c = is.read(); if (c == -1) { return null; } if (c == DerValue.tag_Sequence) { ByteArrayOutputStream bout = new ByteArrayOutputStream(2048); bout.write(c); readBERInternal(is, bout, c); return bout.toByteArray(); } else { // Read BASE64 encoded data, might skip info at the beginning ByteArrayOutputStream data = new ByteArrayOutputStream(); // Step 1: Read until header is found int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens int last = (c=='-') ? -1: c; // the char before hyphen while (true) { int next = is.read(); if (next == -1) { // We accept useless data after the last block, // say, empty lines. return null; } if (next == '-') { hyphen++; } else { hyphen = 0; last = next; } if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) { break; } } // Step 2: Read the rest of header, determine the line end int end; StringBuilder header = new StringBuilder("-----"); while (true) { int next = is.read(); if (next == -1) { throw new IOException("Incomplete data"); } if (next == '\n') { end = '\n'; break; } if (next == '\r') { next = is.read(); if (next == -1) { throw new IOException("Incomplete data"); } if (next == '\n') { end = '\n'; } else { end = '\r'; // Skip all white space chars if (next != 9 && next != 10 && next != 13 && next != 32) { data.write(next); } } break; } header.append((char)next); } // Step 3: Read the data while (true) { int next = is.read(); if (next == -1) { throw new IOException("Incomplete data"); } if (next != '-') { // Skip all white space chars if (next != 9 && next != 10 && next != 13 && next != 32) { data.write(next); } } else { break; } } // Step 4: Consume the footer StringBuilder footer = new StringBuilder("-"); while (true) { int next = is.read(); // Add next == '\n' for maximum safety, in case endline // is not consistent. if (next == -1 || next == end || next == '\n') { break; } if (next != '\r') footer.append((char)next); } checkHeaderFooter(header.toString(), footer.toString()); try { return Base64.getDecoder().decode(data.toByteArray()); } catch (IllegalArgumentException e) { throw new IOException(e); } } } private static void checkHeaderFooter(String header, String footer) throws IOException { if (header.length() < 16 || !header.startsWith("-----BEGIN ") || !header.endsWith("-----")) { throw new IOException("Illegal header: " + header); } if (footer.length() < 14 || !footer.startsWith("-----END ") || !footer.endsWith("-----")) { throw new IOException("Illegal footer: " + footer); } String headerType = header.substring(11, header.length()-5); String footerType = footer.substring(9, footer.length()-5); if (!headerType.equals(footerType)) { throw new IOException("Header and footer do not match: " + header + " " + footer); } } /** * Read one BER data block. This method is aware of indefinite-length BER * encoding and will read all of the sub-sections in a recursive way * * @param is Read from this InputStream * @param bout Write into this OutputStream * @param tag Tag already read (-1 mean not read) * @return The current tag, used to check EOC in indefinite-length BER * @throws IOException Any parsing error */ private static int readBERInternal(InputStream is, ByteArrayOutputStream bout, int tag) throws IOException { if (tag == -1) { // Not read before the call, read now tag = is.read(); if (tag == -1) { throw new IOException("BER/DER tag info absent"); } if ((tag & 0x1f) == 0x1f) { throw new IOException("Multi octets tag not supported"); } bout.write(tag); } int n = is.read(); if (n == -1) { throw new IOException("BER/DER length info absent"); } bout.write(n); int length; if (n == 0x80) { // Indefinite-length encoding if ((tag & 0x20) != 0x20) { throw new IOException( "Non constructed encoding must have definite length"); } while (true) { int subTag = readBERInternal(is, bout, -1); if (subTag == 0) { // EOC, end of indefinite-length section break; } } } else { if (n < 0x80) { length = n; } else if (n == 0x81) { length = is.read(); if (length == -1) { throw new IOException("Incomplete BER/DER length info"); } bout.write(length); } else if (n == 0x82) { int highByte = is.read(); int lowByte = is.read(); if (lowByte == -1) { throw new IOException("Incomplete BER/DER length info"); } bout.write(highByte); bout.write(lowByte); length = (highByte << 8) | lowByte; } else if (n == 0x83) { int highByte = is.read(); int midByte = is.read(); int lowByte = is.read(); if (lowByte == -1) { throw new IOException("Incomplete BER/DER length info"); } bout.write(highByte); bout.write(midByte); bout.write(lowByte); length = (highByte << 16) | (midByte << 8) | lowByte; } else if (n == 0x84) { int highByte = is.read(); int nextByte = is.read(); int midByte = is.read(); int lowByte = is.read(); if (lowByte == -1) { throw new IOException("Incomplete BER/DER length info"); } if (highByte > 127) { throw new IOException("Invalid BER/DER data (a little huge?)"); } bout.write(highByte); bout.write(nextByte); bout.write(midByte); bout.write(lowByte); length = (highByte << 24 ) | (nextByte << 16) | (midByte << 8) | lowByte; } else { // ignore longer length forms throw new IOException("Invalid BER/DER data (too huge?)"); } if (readFully(is, bout, length) != length) { throw new IOException("Incomplete BER/DER data"); } } return tag; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy