001: /*
002: * @(#)X509CertPath.java 1.12 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package sun.security.provider.certpath;
028:
029: import java.io.BufferedInputStream;
030: import java.io.ByteArrayInputStream;
031: import java.io.ByteArrayOutputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.security.cert.CertificateEncodingException;
035: import java.security.cert.Certificate;
036: import java.security.cert.CertificateException;
037: import java.security.cert.CertificateFactory;
038: import java.security.cert.X509Certificate;
039: import java.util.*;
040: import java.security.cert.CertPath;
041: import sun.security.pkcs.ContentInfo;
042: import sun.security.pkcs.PKCS7;
043: import sun.security.pkcs.SignerInfo;
044: import sun.security.x509.AlgorithmId;
045: import sun.security.util.DerValue;
046: import sun.security.util.DerOutputStream;
047: import sun.security.util.DerInputStream;
048:
049: /**
050: * A {@link java.security.cert.CertPath CertPath} (certification path)
051: * consisting exclusively of
052: * {@link java.security.cert.X509Certificate X509Certificate}s.
053: * <p>
054: * By convention, X.509 <code>CertPath</code>s are stored from target
055: * to trust anchor.
056: * That is, the issuer of one certificate is the subject of the following
057: * one. However, unvalidated X.509 <code>CertPath<code>s may not follow
058: * this convention. PKIX <code>CertPathValidator</code>s will detect any
059: * departure from this convention and throw a
060: * <code>CertPathValidatorException</code>.
061: *
062: * @version 1.12, 10/10/06
063: * @author Yassir Elley
064: * @since 1.4
065: */
066: public class X509CertPath extends CertPath {
067:
068: /**
069: * List of certificates in this chain
070: */
071: private List certs;
072:
073: /**
074: * The names of our encodings. PkiPath is the default.
075: */
076: private static final String COUNT_ENCODING = "count";
077: private static final String PKCS7_ENCODING = "PKCS7";
078: private static final String PKIPATH_ENCODING = "PkiPath";
079:
080: /**
081: * List of supported encodings
082: */
083: private static final Collection encodingList;
084:
085: static {
086: List list = new ArrayList(2);
087: list.add(PKIPATH_ENCODING);
088: list.add(PKCS7_ENCODING);
089: encodingList = Collections.unmodifiableCollection(list);
090: }
091:
092: /**
093: * Creates an <code>X509CertPath</code> from a <code>List</code> of
094: * <code>X509Certificate</code>s.
095: * <p>
096: * The certificates are copied out of the supplied <code>List</code>
097: * object.
098: *
099: * @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: public X509CertPath(List certs) throws CertificateException {
104: super ("X.509");
105: // Assumes that the resulting List is thread-safe. This is true
106: // because we ensure that it cannot be modified after construction
107: // and the methods in the Sun JDK 1.4 implementation of ArrayList that
108: // allow read-only access are thread-safe.
109: this .certs = Collections.unmodifiableList(new ArrayList(certs));
110:
111: // Ensure that the List contains only X509Certificates
112: for (Iterator t = this .certs.iterator(); t.hasNext();) {
113: Object obj = t.next();
114: if (obj instanceof X509Certificate == false) {
115: throw new CertificateException(
116: "List is not all X509Certificates: "
117: + obj.getClass().getName());
118: }
119: }
120: }
121:
122: /**
123: * Creates an <code>X509CertPath</code>, reading the encoded form
124: * from an <code>InputStream</code>. The data is assumed to be in
125: * the default encoding.
126: *
127: * @param is the <code>InputStream</code> to read the data from
128: * @exception CertificateException if an exception occurs while decoding
129: */
130: public X509CertPath(InputStream is) throws CertificateException {
131: this (is, PKIPATH_ENCODING);
132: }
133:
134: /**
135: * Creates an <code>X509CertPath</code>, reading the encoded form
136: * from an InputStream. The data is assumed to be in the specified
137: * encoding.
138: *
139: * @param is the <code>InputStream</code> to read the data from
140: * @param encoding the encoding used
141: * @exception CertificateException if an exception occurs while decoding or
142: * the encoding requested is not supported
143: */
144: public X509CertPath(InputStream is, String encoding)
145: throws CertificateException {
146: super ("X.509");
147:
148: if (PKIPATH_ENCODING.equals(encoding)) {
149: certs = parsePKIPATH(is);
150: } else if (PKCS7_ENCODING.equals(encoding)) {
151: certs = parsePKCS7(is);
152: } else {
153: throw new CertificateException("unsupported encoding");
154: }
155: }
156:
157: /**
158: * Parse a PKIPATH format CertPath from an InputStream. Return an
159: * unmodifiable List of the certificates.
160: *
161: * @param is the <code>InputStream</code> to read the data from
162: * @return an unmodifiable List of the certificates
163: * @exception CertificateException if an exception occurs
164: */
165: private static List parsePKIPATH(InputStream is)
166: throws CertificateException {
167: List certList = null;
168: CertificateFactory certFac = null;
169:
170: if (is == null) {
171: throw new CertificateException("input stream is null");
172: }
173:
174: try {
175: DerInputStream dis = new DerInputStream(readAllBytes(is));
176: DerValue[] seq = dis.getSequence(3);
177: if (seq.length == 0) {
178: return Collections.EMPTY_LIST;
179: }
180:
181: certFac = CertificateFactory.getInstance("X.509");
182: certList = new ArrayList(seq.length);
183:
184: // append certs in reverse order (target to trust anchor)
185: for (int i = seq.length - 1; i >= 0; i--) {
186: certList.add(certFac
187: .generateCertificate(new ByteArrayInputStream(
188: seq[i].toByteArray())));
189: }
190:
191: return Collections.unmodifiableList(certList);
192:
193: } catch (IOException ioe) {
194: CertificateException ce = new CertificateException(
195: "IOException" + " parsing PkiPath data: " + ioe);
196: ce.initCause(ioe);
197: throw ce;
198: }
199: }
200:
201: /**
202: * Parse a PKCS#7 format CertPath from an InputStream. Return an
203: * unmodifiable List of the certificates.
204: *
205: * @param is the <code>InputStream</code> to read the data from
206: * @return an unmodifiable List of the certificates
207: * @exception CertificateException if an exception occurs
208: */
209: private static List parsePKCS7(InputStream is)
210: throws CertificateException {
211: List certList;
212:
213: if (is == null) {
214: throw new CertificateException("input stream is null");
215: }
216:
217: try {
218: if (is.markSupported() == false) {
219: // Copy the entire input stream into an InputStream that does
220: // support mark
221: is = new ByteArrayInputStream(readAllBytes(is));
222: }
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(0);
233: }
234: } catch (IOException ioe) {
235: throw new CertificateException(
236: "IOException parsing PKCS7 data: " + 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)
252: throws IOException {
253: byte[] buffer = new byte[8192];
254: ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
255: int n;
256: while ((n = is.read(buffer)) != -1) {
257: baos.write(buffer, 0, n);
258: }
259: return baos.toByteArray();
260: }
261:
262: /**
263: * Returns the encoded form of this certification path, using the
264: * default encoding.
265: *
266: * @return the encoded bytes
267: * @exception CertificateEncodingException if an encoding error occurs
268: */
269: public byte[] getEncoded() throws CertificateEncodingException {
270: // @@@ Should cache the encoded form
271: return encodePKIPATH();
272: }
273:
274: /**
275: * Encode the CertPath using PKIPATH format.
276: *
277: * @return a byte array containing the binary encoding of the PkiPath object
278: * @exception CertificateEncodingException if an exception occurs
279: */
280: private byte[] encodePKIPATH() throws CertificateEncodingException {
281:
282: ListIterator li = certs.listIterator(certs.size());
283: try {
284: DerOutputStream bytes = new DerOutputStream();
285: // encode certs in reverse order (trust anchor to target)
286: // according to PkiPath format
287: while (li.hasPrevious()) {
288: X509Certificate cert = (X509Certificate) li.previous();
289: // check for duplicate cert
290: if (certs.lastIndexOf(cert) != certs.indexOf(cert)) {
291: throw new CertificateEncodingException(
292: "Duplicate Certificate");
293: }
294: // get encoded certificates
295: byte[] encoded = cert.getEncoded();
296: bytes.write(encoded);
297: }
298:
299: // Wrap the data in a SEQUENCE
300: DerOutputStream derout = new DerOutputStream();
301: derout.write(DerValue.tag_SequenceOf, bytes);
302: return derout.toByteArray();
303:
304: } catch (IOException ioe) {
305: CertificateEncodingException ce = new CertificateEncodingException(
306: "IOException encoding PkiPath data: " + ioe);
307: ce.initCause(ioe);
308: throw ce;
309: }
310: }
311:
312: /**
313: * Encode the CertPath using PKCS#7 format.
314: *
315: * @return a byte array containing the binary encoding of the PKCS#7 object
316: * @exception CertificateEncodingException if an exception occurs
317: */
318: private byte[] encodePKCS7() throws CertificateEncodingException {
319: PKCS7 p7 = new PKCS7(new AlgorithmId[0], new ContentInfo(
320: ContentInfo.DATA_OID, null), (X509Certificate[]) certs
321: .toArray(new X509Certificate[certs.size()]),
322: new SignerInfo[0]);
323: DerOutputStream derout = new DerOutputStream();
324: try {
325: p7.encodeSignedData(derout);
326: } catch (IOException ioe) {
327: throw new CertificateEncodingException(ioe.getMessage());
328: }
329: return derout.toByteArray();
330: }
331:
332: /**
333: * Returns the encoded form of this certification path, using the
334: * specified encoding.
335: *
336: * @param encoding the name of the encoding to use
337: * @return the encoded bytes
338: * @exception CertificateEncodingException if an encoding error occurs or
339: * the encoding requested is not supported
340: */
341: public byte[] getEncoded(String encoding)
342: throws CertificateEncodingException {
343: if (PKIPATH_ENCODING.equals(encoding)) {
344: return encodePKIPATH();
345: } else if (PKCS7_ENCODING.equals(encoding)) {
346: return encodePKCS7();
347: } else {
348: throw new CertificateEncodingException(
349: "unsupported encoding");
350: }
351: }
352:
353: /**
354: * Returns the encodings supported by this certification path, with the
355: * default encoding first.
356: *
357: * @return an <code>Iterator</code> over the names of the supported
358: * encodings (as Strings)
359: */
360: public static Iterator getEncodingsStatic() {
361: return encodingList.iterator();
362: }
363:
364: /**
365: * Returns an iteration of the encodings supported by this certification
366: * path, with the default encoding first.
367: * <p>
368: * Attempts to modify the returned <code>Iterator</code> via its
369: * <code>remove</code> method result in an
370: * <code>UnsupportedOperationException</code>.
371: *
372: * @return an <code>Iterator</code> over the names of the supported
373: * encodings (as Strings)
374: */
375: public Iterator getEncodings() {
376: return getEncodingsStatic();
377: }
378:
379: /**
380: * Returns the list of certificates in this certification path.
381: * The <code>List</code> returned must be immutable and thread-safe.
382: *
383: * @return an immutable <code>List</code> of <code>X509Certificate</code>s
384: * (may be empty, but not null)
385: */
386: public List getCertificates() {
387: return certs;
388: }
389: }
|