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