Path: blob/master/src/java.base/share/classes/sun/security/provider/X509Factory.java
41159 views
/*1* Copyright (c) 1998, 2018, 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.security.PublicKey;29import java.util.*;30import java.security.cert.*;3132import jdk.internal.event.EventHelper;33import jdk.internal.event.X509CertificateEvent;34import sun.security.util.KeyUtil;35import sun.security.util.Pem;36import sun.security.x509.*;37import sun.security.pkcs.PKCS7;38import sun.security.provider.certpath.X509CertPath;39import sun.security.provider.certpath.X509CertificatePair;40import sun.security.util.DerValue;41import sun.security.util.Cache;42import java.util.Base64;43import sun.security.pkcs.ParsingException;4445/**46* This class defines a certificate factory for X.509 v3 certificates {@literal &}47* certification paths, and X.509 v2 certificate revocation lists (CRLs).48*49* @author Jan Luehe50* @author Hemma Prafullchandra51* @author Sean Mullan52*53*54* @see java.security.cert.CertificateFactorySpi55* @see java.security.cert.Certificate56* @see java.security.cert.CertPath57* @see java.security.cert.CRL58* @see java.security.cert.X509Certificate59* @see java.security.cert.X509CRL60* @see sun.security.x509.X509CertImpl61* @see sun.security.x509.X509CRLImpl62*/6364public class X509Factory extends CertificateFactorySpi {6566public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";67public static final String END_CERT = "-----END CERTIFICATE-----";6869private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX7071private static final Cache<Object, X509CertImpl> certCache72= Cache.newSoftMemoryCache(750);73private static final Cache<Object, X509CRLImpl> crlCache74= Cache.newSoftMemoryCache(750);7576/**77* Generates an X.509 certificate object and initializes it with78* the data read from the input stream <code>is</code>.79*80* @param is an input stream with the certificate data.81*82* @return an X.509 certificate object initialized with the data83* from the input stream.84*85* @exception CertificateException on parsing errors.86*/87@Override88public Certificate engineGenerateCertificate(InputStream is)89throws CertificateException90{91if (is == null) {92// clear the caches (for debugging)93certCache.clear();94X509CertificatePair.clearCache();95throw new CertificateException("Missing input stream");96}97try {98byte[] encoding = readOneBlock(is);99if (encoding != null) {100X509CertImpl cert = getFromCache(certCache, encoding);101if (cert != null) {102return cert;103}104cert = new X509CertImpl(encoding);105addToCache(certCache, cert.getEncodedInternal(), cert);106// record cert details if necessary107commitEvent(cert);108return cert;109} else {110throw new IOException("Empty input");111}112} catch (IOException ioe) {113throw new CertificateException("Could not parse certificate: " +114ioe.toString(), ioe);115}116}117118/**119* Read from the stream until length bytes have been read or EOF has120* been reached. Return the number of bytes actually read.121*/122private static int readFully(InputStream in, ByteArrayOutputStream bout,123int length) throws IOException {124int read = 0;125byte[] buffer = new byte[2048];126while (length > 0) {127int n = in.read(buffer, 0, length<2048?length:2048);128if (n <= 0) {129break;130}131bout.write(buffer, 0, n);132read += n;133length -= n;134}135return read;136}137138/**139* Return an interned X509CertImpl for the given certificate.140* If the given X509Certificate or X509CertImpl is already present141* in the cert cache, the cached object is returned. Otherwise,142* if it is a X509Certificate, it is first converted to a X509CertImpl.143* Then the X509CertImpl is added to the cache and returned.144*145* Note that all certificates created via generateCertificate(InputStream)146* are already interned and this method does not need to be called.147* It is useful for certificates that cannot be created via148* generateCertificate() and for converting other X509Certificate149* implementations to an X509CertImpl.150*151* @param c The source X509Certificate152* @return An X509CertImpl object that is either a cached certificate or a153* newly built X509CertImpl from the provided X509Certificate154* @throws CertificateException if failures occur while obtaining the DER155* encoding for certificate data.156*/157public static synchronized X509CertImpl intern(X509Certificate c)158throws CertificateException {159if (c == null) {160return null;161}162boolean isImpl = c instanceof X509CertImpl;163byte[] encoding;164if (isImpl) {165encoding = ((X509CertImpl)c).getEncodedInternal();166} else {167encoding = c.getEncoded();168}169X509CertImpl newC = getFromCache(certCache, encoding);170if (newC != null) {171return newC;172}173if (isImpl) {174newC = (X509CertImpl)c;175} else {176newC = new X509CertImpl(encoding);177encoding = newC.getEncodedInternal();178}179addToCache(certCache, encoding, newC);180return newC;181}182183/**184* Return an interned X509CRLImpl for the given certificate.185* For more information, see intern(X509Certificate).186*187* @param c The source X509CRL188* @return An X509CRLImpl object that is either a cached CRL or a189* newly built X509CRLImpl from the provided X509CRL190* @throws CRLException if failures occur while obtaining the DER191* encoding for CRL data.192*/193public static synchronized X509CRLImpl intern(X509CRL c)194throws CRLException {195if (c == null) {196return null;197}198boolean isImpl = c instanceof X509CRLImpl;199byte[] encoding;200if (isImpl) {201encoding = ((X509CRLImpl)c).getEncodedInternal();202} else {203encoding = c.getEncoded();204}205X509CRLImpl newC = getFromCache(crlCache, encoding);206if (newC != null) {207return newC;208}209if (isImpl) {210newC = (X509CRLImpl)c;211} else {212newC = new X509CRLImpl(encoding);213encoding = newC.getEncodedInternal();214}215addToCache(crlCache, encoding, newC);216return newC;217}218219/**220* Get the X509CertImpl or X509CRLImpl from the cache.221*/222private static synchronized <K,V> V getFromCache(Cache<K,V> cache,223byte[] encoding) {224Object key = new Cache.EqualByteArray(encoding);225return cache.get(key);226}227228/**229* Add the X509CertImpl or X509CRLImpl to the cache.230*/231private static synchronized <V> void addToCache(Cache<Object, V> cache,232byte[] encoding, V value) {233if (encoding.length > ENC_MAX_LENGTH) {234return;235}236Object key = new Cache.EqualByteArray(encoding);237cache.put(key, value);238}239240/**241* Generates a <code>CertPath</code> object and initializes it with242* the data read from the <code>InputStream</code> inStream. The data243* is assumed to be in the default encoding.244*245* @param inStream an <code>InputStream</code> containing the data246* @return a <code>CertPath</code> initialized with the data from the247* <code>InputStream</code>248* @exception CertificateException if an exception occurs while decoding249* @since 1.4250*/251@Override252public CertPath engineGenerateCertPath(InputStream inStream)253throws CertificateException254{255if (inStream == null) {256throw new CertificateException("Missing input stream");257}258try {259byte[] encoding = readOneBlock(inStream);260if (encoding != null) {261return new X509CertPath(new ByteArrayInputStream(encoding));262} else {263throw new IOException("Empty input");264}265} catch (IOException ioe) {266throw new CertificateException(ioe.getMessage());267}268}269270/**271* Generates a <code>CertPath</code> object and initializes it with272* the data read from the <code>InputStream</code> inStream. The data273* is assumed to be in the specified encoding.274*275* @param inStream an <code>InputStream</code> containing the data276* @param encoding the encoding used for the data277* @return a <code>CertPath</code> initialized with the data from the278* <code>InputStream</code>279* @exception CertificateException if an exception occurs while decoding or280* the encoding requested is not supported281* @since 1.4282*/283@Override284public CertPath engineGenerateCertPath(InputStream inStream,285String encoding) throws CertificateException286{287if (inStream == null) {288throw new CertificateException("Missing input stream");289}290try {291byte[] data = readOneBlock(inStream);292if (data != null) {293return new X509CertPath(new ByteArrayInputStream(data), encoding);294} else {295throw new IOException("Empty input");296}297} catch (IOException ioe) {298throw new CertificateException(ioe.getMessage());299}300}301302/**303* Generates a <code>CertPath</code> object and initializes it with304* a <code>List</code> of <code>Certificate</code>s.305* <p>306* The certificates supplied must be of a type supported by the307* <code>CertificateFactory</code>. They will be copied out of the supplied308* <code>List</code> object.309*310* @param certificates a <code>List</code> of <code>Certificate</code>s311* @return a <code>CertPath</code> initialized with the supplied list of312* certificates313* @exception CertificateException if an exception occurs314* @since 1.4315*/316@Override317public CertPath318engineGenerateCertPath(List<? extends Certificate> certificates)319throws CertificateException320{321return(new X509CertPath(certificates));322}323324/**325* Returns an iteration of the <code>CertPath</code> encodings supported326* by this certificate factory, with the default encoding first.327* <p>328* Attempts to modify the returned <code>Iterator</code> via its329* <code>remove</code> method result in an330* <code>UnsupportedOperationException</code>.331*332* @return an <code>Iterator</code> over the names of the supported333* <code>CertPath</code> encodings (as <code>String</code>s)334* @since 1.4335*/336@Override337public Iterator<String> engineGetCertPathEncodings() {338return(X509CertPath.getEncodingsStatic());339}340341/**342* Returns a (possibly empty) collection view of X.509 certificates read343* from the given input stream <code>is</code>.344*345* @param is the input stream with the certificates.346*347* @return a (possibly empty) collection view of X.509 certificate objects348* initialized with the data from the input stream.349*350* @exception CertificateException on parsing errors.351*/352@Override353public Collection<? extends java.security.cert.Certificate>354engineGenerateCertificates(InputStream is)355throws CertificateException {356if (is == null) {357throw new CertificateException("Missing input stream");358}359try {360return parseX509orPKCS7Cert(is);361} catch (IOException ioe) {362throw new CertificateException(ioe);363}364}365366/**367* Generates an X.509 certificate revocation list (CRL) object and368* initializes it with the data read from the given input stream369* <code>is</code>.370*371* @param is an input stream with the CRL data.372*373* @return an X.509 CRL object initialized with the data374* from the input stream.375*376* @exception CRLException on parsing errors.377*/378@Override379public CRL engineGenerateCRL(InputStream is)380throws CRLException381{382if (is == null) {383// clear the cache (for debugging)384crlCache.clear();385throw new CRLException("Missing input stream");386}387try {388byte[] encoding = readOneBlock(is);389if (encoding != null) {390X509CRLImpl crl = getFromCache(crlCache, encoding);391if (crl != null) {392return crl;393}394crl = new X509CRLImpl(encoding);395addToCache(crlCache, crl.getEncodedInternal(), crl);396return crl;397} else {398throw new IOException("Empty input");399}400} catch (IOException ioe) {401throw new CRLException(ioe.getMessage());402}403}404405/**406* Returns a (possibly empty) collection view of X.509 CRLs read407* from the given input stream <code>is</code>.408*409* @param is the input stream with the CRLs.410*411* @return a (possibly empty) collection view of X.509 CRL objects412* initialized with the data from the input stream.413*414* @exception CRLException on parsing errors.415*/416@Override417public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(418InputStream is) throws CRLException419{420if (is == null) {421throw new CRLException("Missing input stream");422}423try {424return parseX509orPKCS7CRL(is);425} catch (IOException ioe) {426throw new CRLException(ioe.getMessage());427}428}429430/*431* Parses the data in the given input stream as a sequence of DER432* encoded X.509 certificates (in binary or base 64 encoded format) OR433* as a single PKCS#7 encoded blob (in binary or base64 encoded format).434*/435private Collection<? extends java.security.cert.Certificate>436parseX509orPKCS7Cert(InputStream is)437throws CertificateException, IOException438{439int peekByte;440byte[] data;441PushbackInputStream pbis = new PushbackInputStream(is);442Collection<X509CertImpl> coll = new ArrayList<>();443444// Test the InputStream for end-of-stream. If the stream's445// initial state is already at end-of-stream then return446// an empty collection. Otherwise, push the byte back into the447// stream and let readOneBlock look for the first certificate.448peekByte = pbis.read();449if (peekByte == -1) {450return new ArrayList<>(0);451} else {452pbis.unread(peekByte);453data = readOneBlock(pbis);454}455456// If we end up with a null value after reading the first block457// then we know the end-of-stream has been reached and no certificate458// data has been found.459if (data == null) {460throw new CertificateException("No certificate data found");461}462463try {464PKCS7 pkcs7 = new PKCS7(data);465X509Certificate[] certs = pkcs7.getCertificates();466// certs are optional in PKCS #7467if (certs != null) {468return Arrays.asList(certs);469} else {470// no certificates provided471return new ArrayList<>(0);472}473} catch (ParsingException e) {474while (data != null) {475coll.add(new X509CertImpl(data));476data = readOneBlock(pbis);477}478}479return coll;480}481482/*483* Parses the data in the given input stream as a sequence of DER encoded484* X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7485* encoded blob (in binary or base 64 encoded format).486*/487private Collection<? extends java.security.cert.CRL>488parseX509orPKCS7CRL(InputStream is)489throws CRLException, IOException490{491int peekByte;492byte[] data;493PushbackInputStream pbis = new PushbackInputStream(is);494Collection<X509CRLImpl> coll = new ArrayList<>();495496// Test the InputStream for end-of-stream. If the stream's497// initial state is already at end-of-stream then return498// an empty collection. Otherwise, push the byte back into the499// stream and let readOneBlock look for the first CRL.500peekByte = pbis.read();501if (peekByte == -1) {502return new ArrayList<>(0);503} else {504pbis.unread(peekByte);505data = readOneBlock(pbis);506}507508// If we end up with a null value after reading the first block509// then we know the end-of-stream has been reached and no CRL510// data has been found.511if (data == null) {512throw new CRLException("No CRL data found");513}514515try {516PKCS7 pkcs7 = new PKCS7(data);517X509CRL[] crls = pkcs7.getCRLs();518// CRLs are optional in PKCS #7519if (crls != null) {520return Arrays.asList(crls);521} else {522// no crls provided523return new ArrayList<>(0);524}525} catch (ParsingException e) {526while (data != null) {527coll.add(new X509CRLImpl(data));528data = readOneBlock(pbis);529}530}531return coll;532}533534/**535* Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded536* binary block or a PEM-style BASE64-encoded ASCII data. In the latter537* case, it's de-BASE64'ed before return.538*539* After the reading, the input stream pointer is after the BER block, or540* after the newline character after the -----END SOMETHING----- line.541*542* @param is the InputStream543* @return byte block or null if end of stream544* @throws IOException If any parsing error545*/546private static byte[] readOneBlock(InputStream is) throws IOException {547548// The first character of a BLOCK.549int c = is.read();550if (c == -1) {551return null;552}553if (c == DerValue.tag_Sequence) {554ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);555bout.write(c);556readBERInternal(is, bout, c);557return bout.toByteArray();558} else {559// Read BASE64 encoded data, might skip info at the beginning560ByteArrayOutputStream data = new ByteArrayOutputStream();561562// Step 1: Read until header is found563int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens564int last = (c=='-') ? -1: c; // the char before hyphen565while (true) {566int next = is.read();567if (next == -1) {568// We accept useless data after the last block,569// say, empty lines.570return null;571}572if (next == '-') {573hyphen++;574} else {575hyphen = 0;576last = next;577}578if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) {579break;580}581}582583// Step 2: Read the rest of header, determine the line end584int end;585StringBuilder header = new StringBuilder("-----");586while (true) {587int next = is.read();588if (next == -1) {589throw new IOException("Incomplete data");590}591if (next == '\n') {592end = '\n';593break;594}595if (next == '\r') {596next = is.read();597if (next == -1) {598throw new IOException("Incomplete data");599}600if (next == '\n') {601end = '\n';602} else {603end = '\r';604// Skip all white space chars605if (next != 9 && next != 10 && next != 13 && next != 32) {606data.write(next);607}608}609break;610}611header.append((char)next);612}613614// Step 3: Read the data615while (true) {616int next = is.read();617if (next == -1) {618throw new IOException("Incomplete data");619}620if (next != '-') {621// Skip all white space chars622if (next != 9 && next != 10 && next != 13 && next != 32) {623data.write(next);624}625} else {626break;627}628}629630// Step 4: Consume the footer631StringBuilder footer = new StringBuilder("-");632while (true) {633int next = is.read();634// Add next == '\n' for maximum safety, in case endline635// is not consistent.636if (next == -1 || next == end || next == '\n') {637break;638}639if (next != '\r') footer.append((char)next);640}641642checkHeaderFooter(header.toString().stripTrailing(),643footer.toString().stripTrailing());644645try {646return Base64.getDecoder().decode(data.toByteArray());647} catch (IllegalArgumentException e) {648throw new IOException(e);649}650}651}652653private static void checkHeaderFooter(String header,654String footer) throws IOException {655if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||656!header.endsWith("-----")) {657throw new IOException("Illegal header: " + header);658}659if (footer.length() < 14 || !footer.startsWith("-----END ") ||660!footer.endsWith("-----")) {661throw new IOException("Illegal footer: " + footer);662}663String headerType = header.substring(11, header.length()-5);664String footerType = footer.substring(9, footer.length()-5);665if (!headerType.equals(footerType)) {666throw new IOException("Header and footer do not match: " +667header + " " + footer);668}669}670671/**672* Read one BER data block. This method is aware of indefinite-length BER673* encoding and will read all of the sub-sections in a recursive way674*675* @param is Read from this InputStream676* @param bout Write into this OutputStream677* @param tag Tag already read (-1 mean not read)678* @return The current tag, used to check EOC in indefinite-length BER679* @throws IOException Any parsing error680*/681private static int readBERInternal(InputStream is,682ByteArrayOutputStream bout, int tag) throws IOException {683684if (tag == -1) { // Not read before the call, read now685tag = is.read();686if (tag == -1) {687throw new IOException("BER/DER tag info absent");688}689if ((tag & 0x1f) == 0x1f) {690throw new IOException("Multi octets tag not supported");691}692bout.write(tag);693}694695int n = is.read();696if (n == -1) {697throw new IOException("BER/DER length info absent");698}699bout.write(n);700701int length;702703if (n == 0x80) { // Indefinite-length encoding704if ((tag & 0x20) != 0x20) {705throw new IOException(706"Non constructed encoding must have definite length");707}708while (true) {709int subTag = readBERInternal(is, bout, -1);710if (subTag == 0) { // EOC, end of indefinite-length section711break;712}713}714} else {715if (n < 0x80) {716length = n;717} else if (n == 0x81) {718length = is.read();719if (length == -1) {720throw new IOException("Incomplete BER/DER length info");721}722bout.write(length);723} else if (n == 0x82) {724int highByte = is.read();725int lowByte = is.read();726if (lowByte == -1) {727throw new IOException("Incomplete BER/DER length info");728}729bout.write(highByte);730bout.write(lowByte);731length = (highByte << 8) | lowByte;732} else if (n == 0x83) {733int highByte = is.read();734int midByte = is.read();735int lowByte = is.read();736if (lowByte == -1) {737throw new IOException("Incomplete BER/DER length info");738}739bout.write(highByte);740bout.write(midByte);741bout.write(lowByte);742length = (highByte << 16) | (midByte << 8) | lowByte;743} else if (n == 0x84) {744int highByte = is.read();745int nextByte = is.read();746int midByte = is.read();747int lowByte = is.read();748if (lowByte == -1) {749throw new IOException("Incomplete BER/DER length info");750}751if (highByte > 127) {752throw new IOException("Invalid BER/DER data (a little huge?)");753}754bout.write(highByte);755bout.write(nextByte);756bout.write(midByte);757bout.write(lowByte);758length = (highByte << 24 ) | (nextByte << 16) |759(midByte << 8) | lowByte;760} else { // ignore longer length forms761throw new IOException("Invalid BER/DER data (too huge?)");762}763if (readFully(is, bout, length) != length) {764throw new IOException("Incomplete BER/DER data");765}766}767return tag;768}769770private void commitEvent(X509CertImpl info) {771X509CertificateEvent xce = new X509CertificateEvent();772if (xce.shouldCommit() || EventHelper.isLoggingSecurity()) {773PublicKey pKey = info.getPublicKey();774String algId = info.getSigAlgName();775String serNum = info.getSerialNumber().toString(16);776String subject = info.getSubjectDN().getName();777String issuer = info.getIssuerDN().getName();778String keyType = pKey.getAlgorithm();779int length = KeyUtil.getKeySize(pKey);780int hashCode = info.hashCode();781long beginDate = info.getNotBefore().getTime();782long endDate = info.getNotAfter().getTime();783if (xce.shouldCommit()) {784xce.algorithm = algId;785xce.serialNumber = serNum;786xce.subject = subject;787xce.issuer = issuer;788xce.keyType = keyType;789xce.keyLength = length;790xce.certificateId = hashCode;791xce.validFrom = beginDate;792xce.validUntil = endDate;793xce.commit();794}795if (EventHelper.isLoggingSecurity()) {796EventHelper.logX509CertificateEvent(algId,797serNum,798subject,799issuer,800keyType,801length,802hashCode,803beginDate,804endDate);805}806}807}808}809810811