1 /*
2  * Copyright (c) 2000, 2012, 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.certpath;
27 
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.security.cert.CertificateEncodingException;
33 import java.security.cert.Certificate;
34 import java.security.cert.CertificateException;
35 import java.security.cert.CertificateFactory;
36 import java.security.cert.CertPath;
37 import java.security.cert.X509Certificate;
38 import java.util.*;
39 
40 import sun.security.pkcs.ContentInfo;
41 import sun.security.pkcs.PKCS7;
42 import sun.security.pkcs.SignerInfo;
43 import sun.security.x509.AlgorithmId;
44 import sun.security.util.DerValue;
45 import sun.security.util.DerOutputStream;
46 import sun.security.util.DerInputStream;
47 
48 /**
49  * A {@link java.security.cert.CertPath CertPath} (certification path)
50  * consisting exclusively of
51  * {@link java.security.cert.X509Certificate X509Certificate}s.
52  * <p>
53  * By convention, X.509 <code>CertPath</code>s are stored from target
54  * to trust anchor.
55  * That is, the issuer of one certificate is the subject of the following
56  * one. However, unvalidated X.509 <code>CertPath</code>s may not follow
57  * this convention. PKIX <code>CertPathValidator</code>s will detect any
58  * departure from this convention and throw a
59  * <code>CertPathValidatorException</code>.
60  *
61  * @author      Yassir Elley
62  * @since       1.4
63  */
64 public class X509CertPath extends CertPath {
65 
66     private static final long serialVersionUID = 4989800333263052980L;
67 
68     /**
69      * List of certificates in this chain
70      */
71     private List<X509Certificate> certs;
72 
73     /**
74      * The names of our encodings.  PkiPath is the default.
75      */
76     private static final String COUNT_ENCODING = "count";
77     private static final String PKCS7_ENCODING = "PKCS7";
78     private static final String PKIPATH_ENCODING = "PkiPath";
79 
80     /**
81      * List of supported encodings
82      */
83     private static final Collection<String> encodingList;
84 
85     static {
86         List<String> list = new ArrayList<>(2);
87         list.add(PKIPATH_ENCODING);
88         list.add(PKCS7_ENCODING);
89         encodingList = Collections.unmodifiableCollection(list);
90     }
91 
92     /**
93      * Creates an <code>X509CertPath</code> from a <code>List</code> of
94      * <code>X509Certificate</code>s.
95      * <p>
96      * The certificates are copied out of the supplied <code>List</code>
97      * object.
98      *
99      * @param certs a <code>List</code> of <code>X509Certificate</code>s
100      * @exception CertificateException if <code>certs</code> contains an element
101      *                      that is not an <code>X509Certificate</code>
102      */
103     @SuppressWarnings("unchecked")
X509CertPath(List<? extends Certificate> certs)104     public X509CertPath(List<? extends Certificate> certs) throws CertificateException {
105         super("X.509");
106 
107         // Ensure that the List contains only X509Certificates
108         //
109         // Note; The certs parameter is not necessarily to be of Certificate
110         // for some old code. For compatibility, to make sure the exception
111         // is CertificateException, rather than ClassCastException, please
112         // don't use
113         //     for (Certificate obj : certs)
114         for (Object obj : certs) {
115             if (obj instanceof X509Certificate == false) {
116                 throw new CertificateException
117                     ("List is not all X509Certificates: "
118                     + obj.getClass().getName());
119             }
120         }
121 
122         // Assumes that the resulting List is thread-safe. This is true
123         // because we ensure that it cannot be modified after construction
124         // and the methods in the Sun JDK 1.4 implementation of ArrayList that
125         // allow read-only access are thread-safe.
126         this.certs = Collections.unmodifiableList(
127                 new ArrayList<X509Certificate>((List<X509Certificate>)certs));
128     }
129 
130     /**
131      * Creates an <code>X509CertPath</code>, reading the encoded form
132      * from an <code>InputStream</code>. The data is assumed to be in
133      * the default encoding.
134      *
135      * @param is the <code>InputStream</code> to read the data from
136      * @exception CertificateException if an exception occurs while decoding
137      */
X509CertPath(InputStream is)138     public X509CertPath(InputStream is) throws CertificateException {
139         this(is, PKIPATH_ENCODING);
140     }
141 
142     /**
143      * Creates an <code>X509CertPath</code>, reading the encoded form
144      * from an InputStream. The data is assumed to be in the specified
145      * encoding.
146      *
147      * @param is the <code>InputStream</code> to read the data from
148      * @param encoding the encoding used
149      * @exception CertificateException if an exception occurs while decoding or
150      *   the encoding requested is not supported
151      */
X509CertPath(InputStream is, String encoding)152     public X509CertPath(InputStream is, String encoding)
153             throws CertificateException {
154         super("X.509");
155 
156         switch (encoding) {
157             case PKIPATH_ENCODING:
158                 certs = parsePKIPATH(is);
159                 break;
160             case PKCS7_ENCODING:
161                 certs = parsePKCS7(is);
162                 break;
163             default:
164                 throw new CertificateException("unsupported encoding");
165         }
166     }
167 
168     /**
169      * Parse a PKIPATH format CertPath from an InputStream. Return an
170      * unmodifiable List of the certificates.
171      *
172      * @param is the <code>InputStream</code> to read the data from
173      * @return an unmodifiable List of the certificates
174      * @exception CertificateException if an exception occurs
175      */
parsePKIPATH(InputStream is)176     private static List<X509Certificate> parsePKIPATH(InputStream is)
177             throws CertificateException {
178         List<X509Certificate> certList = null;
179         CertificateFactory certFac = null;
180 
181         if (is == null) {
182             throw new CertificateException("input stream is null");
183         }
184 
185         try {
186             DerInputStream dis = new DerInputStream(readAllBytes(is));
187             DerValue[] seq = dis.getSequence(3);
188             if (seq.length == 0) {
189                 return Collections.<X509Certificate>emptyList();
190             }
191 
192             certFac = CertificateFactory.getInstance("X.509");
193             certList = new ArrayList<X509Certificate>(seq.length);
194 
195             // append certs in reverse order (target to trust anchor)
196             for (int i = seq.length-1; i >= 0; i--) {
197                 certList.add((X509Certificate)certFac.generateCertificate
198                     (new ByteArrayInputStream(seq[i].toByteArray())));
199             }
200 
201             return Collections.unmodifiableList(certList);
202 
203         } catch (IOException ioe) {
204             throw new CertificateException("IOException parsing PkiPath data: "
205                     + ioe, ioe);
206         }
207     }
208 
209     /**
210      * Parse a PKCS#7 format CertPath from an InputStream. Return an
211      * unmodifiable List of the certificates.
212      *
213      * @param is the <code>InputStream</code> to read the data from
214      * @return an unmodifiable List of the certificates
215      * @exception CertificateException if an exception occurs
216      */
parsePKCS7(InputStream is)217     private static List<X509Certificate> parsePKCS7(InputStream is)
218             throws CertificateException {
219         List<X509Certificate> certList;
220 
221         if (is == null) {
222             throw new CertificateException("input stream is null");
223         }
224 
225         try {
226             if (is.markSupported() == false) {
227                 // Copy the entire input stream into an InputStream that does
228                 // support mark
229                 is = new ByteArrayInputStream(readAllBytes(is));
230             }
231             PKCS7 pkcs7 = new PKCS7(is);
232 
233             X509Certificate[] certArray = pkcs7.getCertificates();
234             // certs are optional in PKCS #7
235             if (certArray != null) {
236                 certList = Arrays.asList(certArray);
237             } else {
238                 // no certs provided
239                 certList = new ArrayList<X509Certificate>(0);
240             }
241         } catch (IOException ioe) {
242             throw new CertificateException("IOException parsing PKCS7 data: " +
243                                         ioe);
244         }
245         // Assumes that the resulting List is thread-safe. This is true
246         // because we ensure that it cannot be modified after construction
247         // and the methods in the Sun JDK 1.4 implementation of ArrayList that
248         // allow read-only access are thread-safe.
249         return Collections.unmodifiableList(certList);
250     }
251 
252     /*
253      * Reads the entire contents of an InputStream into a byte array.
254      *
255      * @param is the InputStream to read from
256      * @return the bytes read from the InputStream
257      */
readAllBytes(InputStream is)258     private static byte[] readAllBytes(InputStream is) throws IOException {
259         byte[] buffer = new byte[8192];
260         ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
261         int n;
262         while ((n = is.read(buffer)) != -1) {
263             baos.write(buffer, 0, n);
264         }
265         return baos.toByteArray();
266     }
267 
268     /**
269      * Returns the encoded form of this certification path, using the
270      * default encoding.
271      *
272      * @return the encoded bytes
273      * @exception CertificateEncodingException if an encoding error occurs
274      */
275     @Override
getEncoded()276     public byte[] getEncoded() throws CertificateEncodingException {
277         // @@@ Should cache the encoded form
278         return encodePKIPATH();
279     }
280 
281     /**
282      * Encode the CertPath using PKIPATH format.
283      *
284      * @return a byte array containing the binary encoding of the PkiPath object
285      * @exception CertificateEncodingException if an exception occurs
286      */
encodePKIPATH()287     private byte[] encodePKIPATH() throws CertificateEncodingException {
288 
289         ListIterator<X509Certificate> li = certs.listIterator(certs.size());
290         try {
291             DerOutputStream bytes = new DerOutputStream();
292             // encode certs in reverse order (trust anchor to target)
293             // according to PkiPath format
294             while (li.hasPrevious()) {
295                 X509Certificate cert = li.previous();
296                 // check for duplicate cert
297                 if (certs.lastIndexOf(cert) != certs.indexOf(cert)) {
298                     throw new CertificateEncodingException
299                         ("Duplicate Certificate");
300                 }
301                 // get encoded certificates
302                 byte[] encoded = cert.getEncoded();
303                 bytes.write(encoded);
304             }
305 
306             // Wrap the data in a SEQUENCE
307             DerOutputStream derout = new DerOutputStream();
308             derout.write(DerValue.tag_SequenceOf, bytes);
309             return derout.toByteArray();
310 
311         } catch (IOException ioe) {
312            throw new CertificateEncodingException("IOException encoding " +
313                    "PkiPath data: " + ioe, ioe);
314         }
315     }
316 
317     /**
318      * Encode the CertPath using PKCS#7 format.
319      *
320      * @return a byte array containing the binary encoding of the PKCS#7 object
321      * @exception CertificateEncodingException if an exception occurs
322      */
encodePKCS7()323     private byte[] encodePKCS7() throws CertificateEncodingException {
324         PKCS7 p7 = new PKCS7(new AlgorithmId[0],
325                              new ContentInfo(ContentInfo.DATA_OID, null),
326                              certs.toArray(new X509Certificate[certs.size()]),
327                              new SignerInfo[0]);
328         DerOutputStream derout = new DerOutputStream();
329         try {
330             p7.encodeSignedData(derout);
331         } catch (IOException ioe) {
332             throw new CertificateEncodingException(ioe.getMessage());
333         }
334         return derout.toByteArray();
335     }
336 
337     /**
338      * Returns the encoded form of this certification path, using the
339      * specified encoding.
340      *
341      * @param encoding the name of the encoding to use
342      * @return the encoded bytes
343      * @exception CertificateEncodingException if an encoding error occurs or
344      *   the encoding requested is not supported
345      */
346     @Override
getEncoded(String encoding)347     public byte[] getEncoded(String encoding)
348             throws CertificateEncodingException {
349         switch (encoding) {
350             case PKIPATH_ENCODING:
351                 return encodePKIPATH();
352             case PKCS7_ENCODING:
353                 return encodePKCS7();
354             default:
355                 throw new CertificateEncodingException("unsupported encoding");
356         }
357     }
358 
359     /**
360      * Returns the encodings supported by this certification path, with the
361      * default encoding first.
362      *
363      * @return an <code>Iterator</code> over the names of the supported
364      *         encodings (as Strings)
365      */
getEncodingsStatic()366     public static Iterator<String> getEncodingsStatic() {
367         return encodingList.iterator();
368     }
369 
370     /**
371      * Returns an iteration of the encodings supported by this certification
372      * path, with the default encoding first.
373      * <p>
374      * Attempts to modify the returned <code>Iterator</code> via its
375      * <code>remove</code> method result in an
376      * <code>UnsupportedOperationException</code>.
377      *
378      * @return an <code>Iterator</code> over the names of the supported
379      *         encodings (as Strings)
380      */
381     @Override
getEncodings()382     public Iterator<String> getEncodings() {
383         return getEncodingsStatic();
384     }
385 
386     /**
387      * Returns the list of certificates in this certification path.
388      * The <code>List</code> returned must be immutable and thread-safe.
389      *
390      * @return an immutable <code>List</code> of <code>X509Certificate</code>s
391      *         (may be empty, but not null)
392      */
393     @Override
getCertificates()394     public List<X509Certificate> getCertificates() {
395         return certs;
396     }
397 }
398