• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.security.provider;
27 
28 import java.io.*;
29 import java.util.*;
30 import java.security.cert.*;
31 import sun.security.x509.X509CertImpl;
32 import sun.security.x509.X509CRLImpl;
33 import sun.security.pkcs.PKCS7;
34 import sun.security.provider.certpath.X509CertPath;
35 import sun.security.provider.certpath.X509CertificatePair;
36 import sun.security.util.DerValue;
37 import sun.security.util.Cache;
38 import sun.misc.BASE64Decoder;
39 import sun.security.pkcs.ParsingException;
40 
41 /**
42  * This class defines a certificate factory for X.509 v3 certificates &
43  * certification paths, and X.509 v2 certificate revocation lists (CRLs).
44  *
45  * @author Jan Luehe
46  * @author Hemma Prafullchandra
47  * @author Sean Mullan
48  *
49  *
50  * @see java.security.cert.CertificateFactorySpi
51  * @see java.security.cert.Certificate
52  * @see java.security.cert.CertPath
53  * @see java.security.cert.CRL
54  * @see java.security.cert.X509Certificate
55  * @see java.security.cert.X509CRL
56  * @see sun.security.x509.X509CertImpl
57  * @see sun.security.x509.X509CRLImpl
58  */
59 
60 public class X509Factory extends CertificateFactorySpi {
61 
62     public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
63     public static final String END_CERT = "-----END CERTIFICATE-----";
64 
65     private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX
66 
67     private static final Cache certCache = Cache.newSoftMemoryCache(750);
68     private static final Cache crlCache = Cache.newSoftMemoryCache(750);
69 
70     /**
71      * Generates an X.509 certificate object and initializes it with
72      * the data read from the input stream <code>is</code>.
73      *
74      * @param is an input stream with the certificate data.
75      *
76      * @return an X.509 certificate object initialized with the data
77      * from the input stream.
78      *
79      * @exception CertificateException on parsing errors.
80      */
engineGenerateCertificate(InputStream is)81     public Certificate engineGenerateCertificate(InputStream is)
82         throws CertificateException
83     {
84         if (is == null) {
85             // clear the caches (for debugging)
86             certCache.clear();
87             X509CertificatePair.clearCache();
88             throw new CertificateException("Missing input stream");
89         }
90         try {
91             byte[] encoding = readOneBlock(is);
92             if (encoding != null) {
93                 X509CertImpl cert = (X509CertImpl)getFromCache(certCache, encoding);
94                 if (cert != null) {
95                     return cert;
96                 }
97                 cert = new X509CertImpl(encoding);
98                 addToCache(certCache, cert.getEncodedInternal(), cert);
99                 return cert;
100             } else {
101                 throw new IOException("Empty input");
102             }
103         } catch (IOException ioe) {
104             throw (CertificateException)new CertificateException
105             ("Could not parse certificate: " + ioe.toString()).initCause(ioe);
106         }
107     }
108 
109     /**
110      * Read from the stream until length bytes have been read or EOF has
111      * been reached. Return the number of bytes actually read.
112      */
readFully(InputStream in, ByteArrayOutputStream bout, int length)113     private static int readFully(InputStream in, ByteArrayOutputStream bout,
114             int length) throws IOException {
115         int read = 0;
116         byte[] buffer = new byte[2048];
117         while (length > 0) {
118             int n = in.read(buffer, 0, length<2048?length:2048);
119             if (n <= 0) {
120                 break;
121             }
122             bout.write(buffer, 0, n);
123             read += n;
124             length -= n;
125         }
126         return read;
127     }
128 
129     /**
130      * Return an interned X509CertImpl for the given certificate.
131      * If the given X509Certificate or X509CertImpl is already present
132      * in the cert cache, the cached object is returned. Otherwise,
133      * if it is a X509Certificate, it is first converted to a X509CertImpl.
134      * Then the X509CertImpl is added to the cache and returned.
135      *
136      * Note that all certificates created via generateCertificate(InputStream)
137      * are already interned and this method does not need to be called.
138      * It is useful for certificates that cannot be created via
139      * generateCertificate() and for converting other X509Certificate
140      * implementations to an X509CertImpl.
141      */
intern(X509Certificate c)142     public static synchronized X509CertImpl intern(X509Certificate c)
143             throws CertificateException {
144         if (c == null) {
145             return null;
146         }
147         boolean isImpl = c instanceof X509CertImpl;
148         byte[] encoding;
149         if (isImpl) {
150             encoding = ((X509CertImpl)c).getEncodedInternal();
151         } else {
152             encoding = c.getEncoded();
153         }
154         X509CertImpl newC = (X509CertImpl)getFromCache(certCache, encoding);
155         if (newC != null) {
156             return newC;
157         }
158         if (isImpl) {
159             newC = (X509CertImpl)c;
160         } else {
161             newC = new X509CertImpl(encoding);
162             encoding = newC.getEncodedInternal();
163         }
164         addToCache(certCache, encoding, newC);
165         return newC;
166     }
167 
168     /**
169      * Return an interned X509CRLImpl for the given certificate.
170      * For more information, see intern(X509Certificate).
171      */
intern(X509CRL c)172     public static synchronized X509CRLImpl intern(X509CRL c)
173             throws CRLException {
174         if (c == null) {
175             return null;
176         }
177         boolean isImpl = c instanceof X509CRLImpl;
178         byte[] encoding;
179         if (isImpl) {
180             encoding = ((X509CRLImpl)c).getEncodedInternal();
181         } else {
182             encoding = c.getEncoded();
183         }
184         X509CRLImpl newC = (X509CRLImpl)getFromCache(crlCache, encoding);
185         if (newC != null) {
186             return newC;
187         }
188         if (isImpl) {
189             newC = (X509CRLImpl)c;
190         } else {
191             newC = new X509CRLImpl(encoding);
192             encoding = newC.getEncodedInternal();
193         }
194         addToCache(crlCache, encoding, newC);
195         return newC;
196     }
197 
198     /**
199      * Get the X509CertImpl or X509CRLImpl from the cache.
200      */
getFromCache(Cache cache, byte[] encoding)201     private static synchronized Object getFromCache(Cache cache,
202             byte[] encoding) {
203         Object key = new Cache.EqualByteArray(encoding);
204         Object value = cache.get(key);
205         return value;
206     }
207 
208     /**
209      * Add the X509CertImpl or X509CRLImpl to the cache.
210      */
addToCache(Cache cache, byte[] encoding, Object value)211     private static synchronized void addToCache(Cache cache, byte[] encoding,
212             Object value) {
213         if (encoding.length > ENC_MAX_LENGTH) {
214             return;
215         }
216         Object key = new Cache.EqualByteArray(encoding);
217         cache.put(key, value);
218     }
219 
220     /**
221      * Generates a <code>CertPath</code> object and initializes it with
222      * the data read from the <code>InputStream</code> inStream. The data
223      * is assumed to be in the default encoding.
224      *
225      * @param inStream an <code>InputStream</code> containing the data
226      * @return a <code>CertPath</code> initialized with the data from the
227      *   <code>InputStream</code>
228      * @exception CertificateException if an exception occurs while decoding
229      * @since 1.4
230      */
engineGenerateCertPath(InputStream inStream)231     public CertPath engineGenerateCertPath(InputStream inStream)
232         throws CertificateException
233     {
234         if (inStream == null) {
235             throw new CertificateException("Missing input stream");
236         }
237         try {
238             byte[] encoding = readOneBlock(inStream);
239             if (encoding != null) {
240                 return new X509CertPath(new ByteArrayInputStream(encoding));
241             } else {
242                 throw new IOException("Empty input");
243             }
244         } catch (IOException ioe) {
245             throw new CertificateException(ioe.getMessage());
246         }
247     }
248 
249     /**
250      * Generates a <code>CertPath</code> object and initializes it with
251      * the data read from the <code>InputStream</code> inStream. The data
252      * is assumed to be in the specified encoding.
253      *
254      * @param inStream an <code>InputStream</code> containing the data
255      * @param encoding the encoding used for the data
256      * @return a <code>CertPath</code> initialized with the data from the
257      *   <code>InputStream</code>
258      * @exception CertificateException if an exception occurs while decoding or
259      *   the encoding requested is not supported
260      * @since 1.4
261      */
engineGenerateCertPath(InputStream inStream, String encoding)262     public CertPath engineGenerateCertPath(InputStream inStream,
263         String encoding) throws CertificateException
264     {
265         if (inStream == null) {
266             throw new CertificateException("Missing input stream");
267         }
268         try {
269             byte[] data = readOneBlock(inStream);
270             if (data != null) {
271                 return new X509CertPath(new ByteArrayInputStream(data), encoding);
272             } else {
273                 throw new IOException("Empty input");
274             }
275         } catch (IOException ioe) {
276             throw new CertificateException(ioe.getMessage());
277         }
278     }
279 
280     /**
281      * Generates a <code>CertPath</code> object and initializes it with
282      * a <code>List</code> of <code>Certificate</code>s.
283      * <p>
284      * The certificates supplied must be of a type supported by the
285      * <code>CertificateFactory</code>. They will be copied out of the supplied
286      * <code>List</code> object.
287      *
288      * @param certificates a <code>List</code> of <code>Certificate</code>s
289      * @return a <code>CertPath</code> initialized with the supplied list of
290      *   certificates
291      * @exception CertificateException if an exception occurs
292      * @since 1.4
293      */
294     public CertPath
engineGenerateCertPath(List<? extends Certificate> certificates)295         engineGenerateCertPath(List<? extends Certificate> certificates)
296         throws CertificateException
297     {
298         return(new X509CertPath(certificates));
299     }
300 
301     /**
302      * Returns an iteration of the <code>CertPath</code> encodings supported
303      * by this certificate factory, with the default encoding first.
304      * <p>
305      * Attempts to modify the returned <code>Iterator</code> via its
306      * <code>remove</code> method result in an
307      * <code>UnsupportedOperationException</code>.
308      *
309      * @return an <code>Iterator</code> over the names of the supported
310      *         <code>CertPath</code> encodings (as <code>String</code>s)
311      * @since 1.4
312      */
engineGetCertPathEncodings()313     public Iterator<String> engineGetCertPathEncodings() {
314         return(X509CertPath.getEncodingsStatic());
315     }
316 
317     /**
318      * Returns a (possibly empty) collection view of X.509 certificates read
319      * from the given input stream <code>is</code>.
320      *
321      * @param is the input stream with the certificates.
322      *
323      * @return a (possibly empty) collection view of X.509 certificate objects
324      * initialized with the data from the input stream.
325      *
326      * @exception CertificateException on parsing errors.
327      */
328     public Collection<? extends java.security.cert.Certificate>
engineGenerateCertificates(InputStream is)329             engineGenerateCertificates(InputStream is)
330             throws CertificateException {
331         if (is == null) {
332             throw new CertificateException("Missing input stream");
333         }
334         try {
335             return parseX509orPKCS7Cert(is);
336         } catch (IOException ioe) {
337             throw new CertificateException(ioe);
338         }
339     }
340 
341     /**
342      * Generates an X.509 certificate revocation list (CRL) object and
343      * initializes it with the data read from the given input stream
344      * <code>is</code>.
345      *
346      * @param is an input stream with the CRL data.
347      *
348      * @return an X.509 CRL object initialized with the data
349      * from the input stream.
350      *
351      * @exception CRLException on parsing errors.
352      */
engineGenerateCRL(InputStream is)353     public CRL engineGenerateCRL(InputStream is)
354         throws CRLException
355     {
356         if (is == null) {
357             // clear the cache (for debugging)
358             crlCache.clear();
359             throw new CRLException("Missing input stream");
360         }
361         try {
362             byte[] encoding = readOneBlock(is);
363             if (encoding != null) {
364                 X509CRLImpl crl = (X509CRLImpl)getFromCache(crlCache, encoding);
365                 if (crl != null) {
366                     return crl;
367                 }
368                 crl = new X509CRLImpl(encoding);
369                 addToCache(crlCache, crl.getEncodedInternal(), crl);
370                 return crl;
371             } else {
372                 throw new IOException("Empty input");
373             }
374         } catch (IOException ioe) {
375             throw new CRLException(ioe.getMessage());
376         }
377     }
378 
379     /**
380      * Returns a (possibly empty) collection view of X.509 CRLs read
381      * from the given input stream <code>is</code>.
382      *
383      * @param is the input stream with the CRLs.
384      *
385      * @return a (possibly empty) collection view of X.509 CRL objects
386      * initialized with the data from the input stream.
387      *
388      * @exception CRLException on parsing errors.
389      */
engineGenerateCRLs( InputStream is)390     public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(
391             InputStream is) throws CRLException
392     {
393         if (is == null) {
394             throw new CRLException("Missing input stream");
395         }
396         try {
397             return parseX509orPKCS7CRL(is);
398         } catch (IOException ioe) {
399             throw new CRLException(ioe.getMessage());
400         }
401     }
402 
403     /*
404      * Parses the data in the given input stream as a sequence of DER
405      * encoded X.509 certificates (in binary or base 64 encoded format) OR
406      * as a single PKCS#7 encoded blob (in binary or base64 encoded format).
407      */
408     private Collection<? extends java.security.cert.Certificate>
parseX509orPKCS7Cert(InputStream is)409         parseX509orPKCS7Cert(InputStream is)
410         throws CertificateException, IOException
411     {
412         Collection<X509CertImpl> coll = new ArrayList<>();
413         byte[] data = readOneBlock(is);
414         if (data == null) {
415             return new ArrayList<>(0);
416         }
417         try {
418             PKCS7 pkcs7 = new PKCS7(data);
419             X509Certificate[] certs = pkcs7.getCertificates();
420             // certs are optional in PKCS #7
421             if (certs != null) {
422                 return Arrays.asList(certs);
423             } else {
424                 // no crls provided
425                 return new ArrayList<>(0);
426             }
427         } catch (ParsingException e) {
428             while (data != null) {
429                 coll.add(new X509CertImpl(data));
430                 data = readOneBlock(is);
431             }
432         }
433         return coll;
434     }
435 
436     /*
437      * Parses the data in the given input stream as a sequence of DER encoded
438      * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7
439      * encoded blob (in binary or base 64 encoded format).
440      */
441     private Collection<? extends java.security.cert.CRL>
parseX509orPKCS7CRL(InputStream is)442         parseX509orPKCS7CRL(InputStream is)
443         throws CRLException, IOException
444     {
445         Collection<X509CRLImpl> coll = new ArrayList<>();
446         byte[] data = readOneBlock(is);
447         if (data == null) {
448             return new ArrayList<>(0);
449         }
450         try {
451             PKCS7 pkcs7 = new PKCS7(data);
452             X509CRL[] crls = pkcs7.getCRLs();
453             // CRLs are optional in PKCS #7
454             if (crls != null) {
455                 return Arrays.asList(crls);
456             } else {
457                 // no crls provided
458                 return new ArrayList<>(0);
459             }
460         } catch (ParsingException e) {
461             while (data != null) {
462                 coll.add(new X509CRLImpl(data));
463                 data = readOneBlock(is);
464             }
465         }
466         return coll;
467     }
468 
469     /**
470      * Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded
471      * binary block or a PEM-style BASE64-encoded ASCII data. In the latter
472      * case, it's de-BASE64'ed before return.
473      *
474      * After the reading, the input stream pointer is after the BER block, or
475      * after the newline character after the -----END SOMETHING----- line.
476      *
477      * @param is the InputStream
478      * @returns byte block or null if end of stream
479      * @throws IOException If any parsing error
480      */
readOneBlock(InputStream is)481     private static byte[] readOneBlock(InputStream is) throws IOException {
482 
483         // The first character of a BLOCK.
484         int c = is.read();
485         if (c == -1) {
486             return null;
487         }
488         if (c == DerValue.tag_Sequence) {
489             ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
490             bout.write(c);
491             readBERInternal(is, bout, c);
492             return bout.toByteArray();
493         } else {
494             // Read BASE64 encoded data, might skip info at the beginning
495             char[] data = new char[2048];
496             int pos = 0;
497 
498             // Step 1: Read until header is found
499             int hyphen = (c=='-') ? 1: 0;   // count of consequent hyphens
500             int last = (c=='-') ? -1: c;    // the char before hyphen
501             while (true) {
502                 int next = is.read();
503                 if (next == -1) {
504                     // We accept useless data after the last block,
505                     // say, empty lines.
506                     return null;
507                 }
508                 if (next == '-') {
509                     hyphen++;
510                 } else {
511                     hyphen = 0;
512                     last = next;
513                 }
514                 if (hyphen == 5 && (last==-1 || last=='\r' || last=='\n')) {
515                     break;
516                 }
517             }
518 
519             // Step 2: Read the rest of header, determine the line end
520             int end;
521             StringBuffer header = new StringBuffer("-----");
522             while (true) {
523                 int next = is.read();
524                 if (next == -1) {
525                     throw new IOException("Incomplete data");
526                 }
527                 if (next == '\n') {
528                     end = '\n';
529                     break;
530                 }
531                 if (next == '\r') {
532                     next = is.read();
533                     if (next == -1) {
534                         throw new IOException("Incomplete data");
535                     }
536                     if (next == '\n') {
537                         end = '\n';
538                     } else {
539                         end = '\r';
540                         data[pos++] = (char)next;
541                     }
542                     break;
543                 }
544                 header.append((char)next);
545             }
546 
547             // Step 3: Read the data
548             while (true) {
549                 int next = is.read();
550                 if (next == -1) {
551                     throw new IOException("Incomplete data");
552                 }
553                 if (next != '-') {
554                     data[pos++] = (char)next;
555                     if (pos >= data.length) {
556                         data = Arrays.copyOf(data, data.length+1024);
557                     }
558                 } else {
559                     break;
560                 }
561             }
562 
563             // Step 4: Consume the footer
564             StringBuffer footer = new StringBuffer("-");
565             while (true) {
566                 int next = is.read();
567                 // Add next == '\n' for maximum safety, in case endline
568                 // is not consistent.
569                 if (next == -1 || next == end || next == '\n') {
570                     break;
571                 }
572                 if (next != '\r') footer.append((char)next);
573             }
574 
575             checkHeaderFooter(header.toString(), footer.toString());
576 
577             BASE64Decoder decoder = new BASE64Decoder();
578             return decoder.decodeBuffer(new String(data, 0, pos));
579         }
580     }
581 
checkHeaderFooter(String header, String footer)582     private static void checkHeaderFooter(String header,
583             String footer) throws IOException {
584         if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
585                 !header.endsWith("-----")) {
586             throw new IOException("Illegal header: " + header);
587         }
588         if (footer.length() < 14 || !footer.startsWith("-----END ") ||
589                 !footer.endsWith("-----")) {
590             throw new IOException("Illegal footer: " + footer);
591         }
592         String headerType = header.substring(11, header.length()-5);
593         String footerType = footer.substring(9, footer.length()-5);
594         if (!headerType.equals(footerType)) {
595             throw new IOException("Header and footer do not match: " +
596                     header + " " + footer);
597         }
598     }
599 
600     /**
601      * Read one BER data block. This method is aware of indefinite-length BER
602      * encoding and will read all of the sub-sections in a recursive way
603      *
604      * @param is    Read from this InputStream
605      * @param bout  Write into this OutputStream
606      * @param tag   Tag already read (-1 mean not read)
607      * @returns     The current tag, used to check EOC in indefinite-length BER
608      * @throws IOException Any parsing error
609      */
readBERInternal(InputStream is, ByteArrayOutputStream bout, int tag)610     private static int readBERInternal(InputStream is,
611             ByteArrayOutputStream bout, int tag) throws IOException {
612 
613         if (tag == -1) {        // Not read before the call, read now
614             tag = is.read();
615             if (tag == -1) {
616                 throw new IOException("BER/DER tag info absent");
617             }
618             if ((tag & 0x1f) == 0x1f) {
619                 throw new IOException("Multi octets tag not supported");
620             }
621             bout.write(tag);
622         }
623 
624         int n = is.read();
625         if (n == -1) {
626             throw new IOException("BER/DER length info ansent");
627         }
628         bout.write(n);
629 
630         int length;
631 
632         if (n == 0x80) {        // Indefinite-length encoding
633             if ((tag & 0x20) != 0x20) {
634                 throw new IOException(
635                         "Non constructed encoding must have definite length");
636             }
637             while (true) {
638                 int subTag = readBERInternal(is, bout, -1);
639                 if (subTag == 0) {   // EOC, end of indefinite-length section
640                     break;
641                 }
642             }
643         } else {
644             if (n < 0x80) {
645                 length = n;
646             } else if (n == 0x81) {
647                 length = is.read();
648                 if (length == -1) {
649                     throw new IOException("Incomplete BER/DER length info");
650                 }
651                 bout.write(length);
652             } else if (n == 0x82) {
653                 int highByte = is.read();
654                 int lowByte = is.read();
655                 if (lowByte == -1) {
656                     throw new IOException("Incomplete BER/DER length info");
657                 }
658                 bout.write(highByte);
659                 bout.write(lowByte);
660                 length = (highByte << 8) | lowByte;
661             } else if (n == 0x83) {
662                 int highByte = is.read();
663                 int midByte = is.read();
664                 int lowByte = is.read();
665                 if (lowByte == -1) {
666                     throw new IOException("Incomplete BER/DER length info");
667                 }
668                 bout.write(highByte);
669                 bout.write(midByte);
670                 bout.write(lowByte);
671                 length = (highByte << 16) | (midByte << 8) | lowByte;
672             } else if (n == 0x84) {
673                 int highByte = is.read();
674                 int nextByte = is.read();
675                 int midByte = is.read();
676                 int lowByte = is.read();
677                 if (lowByte == -1) {
678                     throw new IOException("Incomplete BER/DER length info");
679                 }
680                 if (highByte > 127) {
681                     throw new IOException("Invalid BER/DER data (a little huge?)");
682                 }
683                 bout.write(highByte);
684                 bout.write(nextByte);
685                 bout.write(midByte);
686                 bout.write(lowByte);
687                 length = (highByte << 24 ) | (nextByte << 16) |
688                         (midByte << 8) | lowByte;
689             } else { // ignore longer length forms
690                 throw new IOException("Invalid BER/DER data (too huge?)");
691             }
692             if (readFully(is, bout, length) != length) {
693                 throw new IOException("Incomplete BER/DER data");
694             }
695         }
696         return tag;
697     }
698 }
699