Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/provider/X509Factory.java
38830 views
/*1* Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.provider;2627import java.io.*;28import java.util.*;29import java.security.cert.*;3031import sun.security.util.Pem;32import sun.security.x509.X509CertImpl;33import sun.security.x509.X509CRLImpl;34import sun.security.pkcs.PKCS7;35import sun.security.provider.certpath.X509CertPath;36import sun.security.provider.certpath.X509CertificatePair;37import sun.security.util.DerValue;38import sun.security.util.Cache;39import java.util.Base64;40import sun.security.pkcs.ParsingException;4142/**43* This class defines a certificate factory for X.509 v3 certificates &44* certification paths, and X.509 v2 certificate revocation lists (CRLs).45*46* @author Jan Luehe47* @author Hemma Prafullchandra48* @author Sean Mullan49*50*51* @see java.security.cert.CertificateFactorySpi52* @see java.security.cert.Certificate53* @see java.security.cert.CertPath54* @see java.security.cert.CRL55* @see java.security.cert.X509Certificate56* @see java.security.cert.X509CRL57* @see sun.security.x509.X509CertImpl58* @see sun.security.x509.X509CRLImpl59*/6061public class X509Factory extends CertificateFactorySpi {6263public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";64public static final String END_CERT = "-----END CERTIFICATE-----";6566private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX6768private static final Cache<Object, X509CertImpl> certCache69= Cache.newSoftMemoryCache(750);70private static final Cache<Object, X509CRLImpl> crlCache71= Cache.newSoftMemoryCache(750);7273/**74* Generates an X.509 certificate object and initializes it with75* the data read from the input stream <code>is</code>.76*77* @param is an input stream with the certificate data.78*79* @return an X.509 certificate object initialized with the data80* from the input stream.81*82* @exception CertificateException on parsing errors.83*/84@Override85public Certificate engineGenerateCertificate(InputStream is)86throws CertificateException87{88if (is == null) {89// clear the caches (for debugging)90certCache.clear();91X509CertificatePair.clearCache();92throw new CertificateException("Missing input stream");93}94try {95byte[] encoding = readOneBlock(is);96if (encoding != null) {97X509CertImpl cert = getFromCache(certCache, encoding);98if (cert != null) {99return cert;100}101cert = new X509CertImpl(encoding);102addToCache(certCache, cert.getEncodedInternal(), cert);103return cert;104} else {105throw new IOException("Empty input");106}107} catch (IOException ioe) {108throw new CertificateException("Could not parse certificate: " +109ioe.toString(), ioe);110}111}112113/**114* Read from the stream until length bytes have been read or EOF has115* been reached. Return the number of bytes actually read.116*/117private static int readFully(InputStream in, ByteArrayOutputStream bout,118int length) throws IOException {119int read = 0;120byte[] buffer = new byte[2048];121while (length > 0) {122int n = in.read(buffer, 0, length<2048?length:2048);123if (n <= 0) {124break;125}126bout.write(buffer, 0, n);127read += n;128length -= n;129}130return read;131}132133/**134* Return an interned X509CertImpl for the given certificate.135* If the given X509Certificate or X509CertImpl is already present136* in the cert cache, the cached object is returned. Otherwise,137* if it is a X509Certificate, it is first converted to a X509CertImpl.138* Then the X509CertImpl is added to the cache and returned.139*140* Note that all certificates created via generateCertificate(InputStream)141* are already interned and this method does not need to be called.142* It is useful for certificates that cannot be created via143* generateCertificate() and for converting other X509Certificate144* implementations to an X509CertImpl.145*146* @param c The source X509Certificate147* @return An X509CertImpl object that is either a cached certificate or a148* newly built X509CertImpl from the provided X509Certificate149* @throws CertificateException if failures occur while obtaining the DER150* encoding for certificate data.151*/152public static synchronized X509CertImpl intern(X509Certificate c)153throws CertificateException {154if (c == null) {155return null;156}157boolean isImpl = c instanceof X509CertImpl;158byte[] encoding;159if (isImpl) {160encoding = ((X509CertImpl)c).getEncodedInternal();161} else {162encoding = c.getEncoded();163}164X509CertImpl newC = getFromCache(certCache, encoding);165if (newC != null) {166return newC;167}168if (isImpl) {169newC = (X509CertImpl)c;170} else {171newC = new X509CertImpl(encoding);172encoding = newC.getEncodedInternal();173}174addToCache(certCache, encoding, newC);175return newC;176}177178/**179* Return an interned X509CRLImpl for the given certificate.180* For more information, see intern(X509Certificate).181*182* @param c The source X509CRL183* @return An X509CRLImpl object that is either a cached CRL or a184* newly built X509CRLImpl from the provided X509CRL185* @throws CRLException if failures occur while obtaining the DER186* encoding for CRL data.187*/188public static synchronized X509CRLImpl intern(X509CRL c)189throws CRLException {190if (c == null) {191return null;192}193boolean isImpl = c instanceof X509CRLImpl;194byte[] encoding;195if (isImpl) {196encoding = ((X509CRLImpl)c).getEncodedInternal();197} else {198encoding = c.getEncoded();199}200X509CRLImpl newC = getFromCache(crlCache, encoding);201if (newC != null) {202return newC;203}204if (isImpl) {205newC = (X509CRLImpl)c;206} else {207newC = new X509CRLImpl(encoding);208encoding = newC.getEncodedInternal();209}210addToCache(crlCache, encoding, newC);211return newC;212}213214/**215* Get the X509CertImpl or X509CRLImpl from the cache.216*/217private static synchronized <K,V> V getFromCache(Cache<K,V> cache,218byte[] encoding) {219Object key = new Cache.EqualByteArray(encoding);220return cache.get(key);221}222223/**224* Add the X509CertImpl or X509CRLImpl to the cache.225*/226private static synchronized <V> void addToCache(Cache<Object, V> cache,227byte[] encoding, V value) {228if (encoding.length > ENC_MAX_LENGTH) {229return;230}231Object key = new Cache.EqualByteArray(encoding);232cache.put(key, value);233}234235/**236* Generates a <code>CertPath</code> object and initializes it with237* the data read from the <code>InputStream</code> inStream. The data238* is assumed to be in the default encoding.239*240* @param inStream an <code>InputStream</code> containing the data241* @return a <code>CertPath</code> initialized with the data from the242* <code>InputStream</code>243* @exception CertificateException if an exception occurs while decoding244* @since 1.4245*/246@Override247public CertPath engineGenerateCertPath(InputStream inStream)248throws CertificateException249{250if (inStream == null) {251throw new CertificateException("Missing input stream");252}253try {254byte[] encoding = readOneBlock(inStream);255if (encoding != null) {256return new X509CertPath(new ByteArrayInputStream(encoding));257} else {258throw new IOException("Empty input");259}260} catch (IOException ioe) {261throw new CertificateException(ioe.getMessage());262}263}264265/**266* Generates a <code>CertPath</code> object and initializes it with267* the data read from the <code>InputStream</code> inStream. The data268* is assumed to be in the specified encoding.269*270* @param inStream an <code>InputStream</code> containing the data271* @param encoding the encoding used for the data272* @return a <code>CertPath</code> initialized with the data from the273* <code>InputStream</code>274* @exception CertificateException if an exception occurs while decoding or275* the encoding requested is not supported276* @since 1.4277*/278@Override279public CertPath engineGenerateCertPath(InputStream inStream,280String encoding) throws CertificateException281{282if (inStream == null) {283throw new CertificateException("Missing input stream");284}285try {286byte[] data = readOneBlock(inStream);287if (data != null) {288return new X509CertPath(new ByteArrayInputStream(data), encoding);289} else {290throw new IOException("Empty input");291}292} catch (IOException ioe) {293throw new CertificateException(ioe.getMessage());294}295}296297/**298* Generates a <code>CertPath</code> object and initializes it with299* a <code>List</code> of <code>Certificate</code>s.300* <p>301* The certificates supplied must be of a type supported by the302* <code>CertificateFactory</code>. They will be copied out of the supplied303* <code>List</code> object.304*305* @param certificates a <code>List</code> of <code>Certificate</code>s306* @return a <code>CertPath</code> initialized with the supplied list of307* certificates308* @exception CertificateException if an exception occurs309* @since 1.4310*/311@Override312public CertPath313engineGenerateCertPath(List<? extends Certificate> certificates)314throws CertificateException315{316return(new X509CertPath(certificates));317}318319/**320* Returns an iteration of the <code>CertPath</code> encodings supported321* by this certificate factory, with the default encoding first.322* <p>323* Attempts to modify the returned <code>Iterator</code> via its324* <code>remove</code> method result in an325* <code>UnsupportedOperationException</code>.326*327* @return an <code>Iterator</code> over the names of the supported328* <code>CertPath</code> encodings (as <code>String</code>s)329* @since 1.4330*/331@Override332public Iterator<String> engineGetCertPathEncodings() {333return(X509CertPath.getEncodingsStatic());334}335336/**337* Returns a (possibly empty) collection view of X.509 certificates read338* from the given input stream <code>is</code>.339*340* @param is the input stream with the certificates.341*342* @return a (possibly empty) collection view of X.509 certificate objects343* initialized with the data from the input stream.344*345* @exception CertificateException on parsing errors.346*/347@Override348public Collection<? extends java.security.cert.Certificate>349engineGenerateCertificates(InputStream is)350throws CertificateException {351if (is == null) {352throw new CertificateException("Missing input stream");353}354try {355return parseX509orPKCS7Cert(is);356} catch (IOException ioe) {357throw new CertificateException(ioe);358}359}360361/**362* Generates an X.509 certificate revocation list (CRL) object and363* initializes it with the data read from the given input stream364* <code>is</code>.365*366* @param is an input stream with the CRL data.367*368* @return an X.509 CRL object initialized with the data369* from the input stream.370*371* @exception CRLException on parsing errors.372*/373@Override374public CRL engineGenerateCRL(InputStream is)375throws CRLException376{377if (is == null) {378// clear the cache (for debugging)379crlCache.clear();380throw new CRLException("Missing input stream");381}382try {383byte[] encoding = readOneBlock(is);384if (encoding != null) {385X509CRLImpl crl = getFromCache(crlCache, encoding);386if (crl != null) {387return crl;388}389crl = new X509CRLImpl(encoding);390addToCache(crlCache, crl.getEncodedInternal(), crl);391return crl;392} else {393throw new IOException("Empty input");394}395} catch (IOException ioe) {396throw new CRLException(ioe.getMessage());397}398}399400/**401* Returns a (possibly empty) collection view of X.509 CRLs read402* from the given input stream <code>is</code>.403*404* @param is the input stream with the CRLs.405*406* @return a (possibly empty) collection view of X.509 CRL objects407* initialized with the data from the input stream.408*409* @exception CRLException on parsing errors.410*/411@Override412public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(413InputStream is) throws CRLException414{415if (is == null) {416throw new CRLException("Missing input stream");417}418try {419return parseX509orPKCS7CRL(is);420} catch (IOException ioe) {421throw new CRLException(ioe.getMessage());422}423}424425/*426* Parses the data in the given input stream as a sequence of DER427* encoded X.509 certificates (in binary or base 64 encoded format) OR428* as a single PKCS#7 encoded blob (in binary or base64 encoded format).429*/430private Collection<? extends java.security.cert.Certificate>431parseX509orPKCS7Cert(InputStream is)432throws CertificateException, IOException433{434int peekByte;435byte[] data;436PushbackInputStream pbis = new PushbackInputStream(is);437Collection<X509CertImpl> coll = new ArrayList<>();438439// Test the InputStream for end-of-stream. If the stream's440// initial state is already at end-of-stream then return441// an empty collection. Otherwise, push the byte back into the442// stream and let readOneBlock look for the first certificate.443peekByte = pbis.read();444if (peekByte == -1) {445return new ArrayList<>(0);446} else {447pbis.unread(peekByte);448data = readOneBlock(pbis);449}450451// If we end up with a null value after reading the first block452// then we know the end-of-stream has been reached and no certificate453// data has been found.454if (data == null) {455throw new CertificateException("No certificate data found");456}457458try {459PKCS7 pkcs7 = new PKCS7(data);460X509Certificate[] certs = pkcs7.getCertificates();461// certs are optional in PKCS #7462if (certs != null) {463return Arrays.asList(certs);464} else {465// no certificates provided466return new ArrayList<>(0);467}468} catch (ParsingException e) {469while (data != null) {470coll.add(new X509CertImpl(data));471data = readOneBlock(pbis);472}473}474return coll;475}476477/*478* Parses the data in the given input stream as a sequence of DER encoded479* X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7480* encoded blob (in binary or base 64 encoded format).481*/482private Collection<? extends java.security.cert.CRL>483parseX509orPKCS7CRL(InputStream is)484throws CRLException, IOException485{486int peekByte;487byte[] data;488PushbackInputStream pbis = new PushbackInputStream(is);489Collection<X509CRLImpl> coll = new ArrayList<>();490491// Test the InputStream for end-of-stream. If the stream's492// initial state is already at end-of-stream then return493// an empty collection. Otherwise, push the byte back into the494// stream and let readOneBlock look for the first CRL.495peekByte = pbis.read();496if (peekByte == -1) {497return new ArrayList<>(0);498} else {499pbis.unread(peekByte);500data = readOneBlock(pbis);501}502503// If we end up with a null value after reading the first block504// then we know the end-of-stream has been reached and no CRL505// data has been found.506if (data == null) {507throw new CRLException("No CRL data found");508}509510try {511PKCS7 pkcs7 = new PKCS7(data);512X509CRL[] crls = pkcs7.getCRLs();513// CRLs are optional in PKCS #7514if (crls != null) {515return Arrays.asList(crls);516} else {517// no crls provided518return new ArrayList<>(0);519}520} catch (ParsingException e) {521while (data != null) {522coll.add(new X509CRLImpl(data));523data = readOneBlock(pbis);524}525}526return coll;527}528529/**530* Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded531* binary block or a PEM-style BASE64-encoded ASCII data. In the latter532* case, it's de-BASE64'ed before return.533*534* After the reading, the input stream pointer is after the BER block, or535* after the newline character after the -----END SOMETHING----- line.536*537* @param is the InputStream538* @returns byte block or null if end of stream539* @throws IOException If any parsing error540*/541private static byte[] readOneBlock(InputStream is) throws IOException {542543// The first character of a BLOCK.544int c = is.read();545if (c == -1) {546return null;547}548if (c == DerValue.tag_Sequence) {549ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);550bout.write(c);551readBERInternal(is, bout, c);552return bout.toByteArray();553} else {554// Read BASE64 encoded data, might skip info at the beginning555char[] data = new char[2048];556int pos = 0;557558// Step 1: Read until header is found559int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens560int last = (c=='-') ? -1: c; // the char before hyphen561while (true) {562int next = is.read();563if (next == -1) {564// We accept useless data after the last block,565// say, empty lines.566return null;567}568if (next == '-') {569hyphen++;570} else {571hyphen = 0;572last = next;573}574if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) {575break;576}577}578579// Step 2: Read the rest of header, determine the line end580int end;581StringBuilder header = new StringBuilder("-----");582while (true) {583int next = is.read();584if (next == -1) {585throw new IOException("Incomplete data");586}587if (next == '\n') {588end = '\n';589break;590}591if (next == '\r') {592next = is.read();593if (next == -1) {594throw new IOException("Incomplete data");595}596if (next == '\n') {597end = '\n';598} else {599end = '\r';600data[pos++] = (char)next;601}602break;603}604header.append((char)next);605}606607// Step 3: Read the data608while (true) {609int next = is.read();610if (next == -1) {611throw new IOException("Incomplete data");612}613if (next != '-') {614data[pos++] = (char)next;615if (pos >= data.length) {616data = Arrays.copyOf(data, data.length+1024);617}618} else {619break;620}621}622623// Step 4: Consume the footer624StringBuilder footer = new StringBuilder("-");625while (true) {626int next = is.read();627// Add next == '\n' for maximum safety, in case endline628// is not consistent.629if (next == -1 || next == end || next == '\n') {630break;631}632if (next != '\r') footer.append((char)next);633}634635checkHeaderFooter(header.toString(), footer.toString());636637return Pem.decode(new String(data, 0, pos));638}639}640641private static void checkHeaderFooter(String header,642String footer) throws IOException {643if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||644!header.endsWith("-----")) {645throw new IOException("Illegal header: " + header);646}647if (footer.length() < 14 || !footer.startsWith("-----END ") ||648!footer.endsWith("-----")) {649throw new IOException("Illegal footer: " + footer);650}651String headerType = header.substring(11, header.length()-5);652String footerType = footer.substring(9, footer.length()-5);653if (!headerType.equals(footerType)) {654throw new IOException("Header and footer do not match: " +655header + " " + footer);656}657}658659/**660* Read one BER data block. This method is aware of indefinite-length BER661* encoding and will read all of the sub-sections in a recursive way662*663* @param is Read from this InputStream664* @param bout Write into this OutputStream665* @param tag Tag already read (-1 mean not read)666* @returns The current tag, used to check EOC in indefinite-length BER667* @throws IOException Any parsing error668*/669private static int readBERInternal(InputStream is,670ByteArrayOutputStream bout, int tag) throws IOException {671672if (tag == -1) { // Not read before the call, read now673tag = is.read();674if (tag == -1) {675throw new IOException("BER/DER tag info absent");676}677if ((tag & 0x1f) == 0x1f) {678throw new IOException("Multi octets tag not supported");679}680bout.write(tag);681}682683int n = is.read();684if (n == -1) {685throw new IOException("BER/DER length info absent");686}687bout.write(n);688689int length;690691if (n == 0x80) { // Indefinite-length encoding692if ((tag & 0x20) != 0x20) {693throw new IOException(694"Non constructed encoding must have definite length");695}696while (true) {697int subTag = readBERInternal(is, bout, -1);698if (subTag == 0) { // EOC, end of indefinite-length section699break;700}701}702} else {703if (n < 0x80) {704length = n;705} else if (n == 0x81) {706length = is.read();707if (length == -1) {708throw new IOException("Incomplete BER/DER length info");709}710bout.write(length);711} else if (n == 0x82) {712int highByte = is.read();713int lowByte = is.read();714if (lowByte == -1) {715throw new IOException("Incomplete BER/DER length info");716}717bout.write(highByte);718bout.write(lowByte);719length = (highByte << 8) | lowByte;720} else if (n == 0x83) {721int highByte = is.read();722int midByte = is.read();723int lowByte = is.read();724if (lowByte == -1) {725throw new IOException("Incomplete BER/DER length info");726}727bout.write(highByte);728bout.write(midByte);729bout.write(lowByte);730length = (highByte << 16) | (midByte << 8) | lowByte;731} else if (n == 0x84) {732int highByte = is.read();733int nextByte = is.read();734int midByte = is.read();735int lowByte = is.read();736if (lowByte == -1) {737throw new IOException("Incomplete BER/DER length info");738}739if (highByte > 127) {740throw new IOException("Invalid BER/DER data (a little huge?)");741}742bout.write(highByte);743bout.write(nextByte);744bout.write(midByte);745bout.write(lowByte);746length = (highByte << 24 ) | (nextByte << 16) |747(midByte << 8) | lowByte;748} else { // ignore longer length forms749throw new IOException("Invalid BER/DER data (too huge?)");750}751if (readFully(is, bout, length) != length) {752throw new IOException("Incomplete BER/DER data");753}754}755return tag;756}757}758759760