blob: 3699700f44a79b48b079fb303eee521bdfc3857f [file] [log] [blame]
The Android Open Source Projectadc854b2009-03-03 19:28:47 -08001/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18/**
19* @author Alexander Y. Kleymenov
20* @version $Revision$
21*/
22
23package org.apache.harmony.security.provider.cert;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.security.cert.CertPath;
28import java.security.cert.CertificateEncodingException;
29import java.security.cert.CertificateException;
30import java.security.cert.X509Certificate;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.Iterator;
36import java.util.List;
Kenny Root8c6714a2013-02-21 15:11:03 -080037
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080038import org.apache.harmony.security.asn1.ASN1Any;
39import org.apache.harmony.security.asn1.ASN1Explicit;
40import org.apache.harmony.security.asn1.ASN1Implicit;
41import org.apache.harmony.security.asn1.ASN1Oid;
42import org.apache.harmony.security.asn1.ASN1Sequence;
43import org.apache.harmony.security.asn1.ASN1SequenceOf;
44import org.apache.harmony.security.asn1.ASN1Type;
45import org.apache.harmony.security.asn1.BerInputStream;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080046import org.apache.harmony.security.pkcs7.ContentInfo;
47import org.apache.harmony.security.pkcs7.SignedData;
48import org.apache.harmony.security.x509.Certificate;
49
50/**
51 * This class is an implementation of X.509 CertPath. This implementation
52 * provides ability to create the instance of X.509 Certification Path
53 * by several means:<br>
54 *
55 * &nbsp; 1. It can be created over the list of X.509 certificates
56 * (implementations of X509Certificate class) provided in constructor.<br>
57 *
58 * &nbsp; 2. It can be created by means of <code>getInstance</code> methods
59 * on the base of the following ASN.1 DER encoded forms:<br>
60 *
61 * &nbsp;&nbsp; - PkiPath as defined in
62 * ITU-T Recommendation X.509(2000) Corrigendum 1(2001)
63 * (can be seen at
64 * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution/TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX.509/8%7CX.509-TC1(4th).pdf)
65 * <br>
66 * &nbsp;&nbsp; - PKCS #7 SignedData object provided in the form of
67 * ContentInfo structure. CertPath object is generated on the base of
68 * certificates presented in <code>certificates</code> field of the SignedData
69 * object which in its turn is retrieved from ContentInfo structure.
70 * (see http://www.ietf.org/rfc/rfc2315.txt
71 * for more info on PKCS #7)
72 * <br>
73 * &nbsp;
74 */
75public class X509CertPathImpl extends CertPath {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080076 /**
77 * @serial
78 */
79 private static final long serialVersionUID = 7989755106209515436L;
80
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080081 /**
Kenny Root8c6714a2013-02-21 15:11:03 -080082 * Supported encoding types for CerthPath. Used by the various APIs that
83 * encode this into bytes such as {@link #getEncoded()}.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -080084 */
Kenny Root8c6714a2013-02-21 15:11:03 -080085 private enum Encoding {
86 PKI_PATH("PkiPath"),
87 PKCS7("PKCS7");
88
89 private final String apiName;
90
91 Encoding(String apiName) {
92 this.apiName = apiName;
93 }
94
95 static Encoding findByApiName(String apiName) throws CertificateEncodingException {
96 for (Encoding element : values()) {
97 if (element.apiName.equals(apiName)) {
98 return element;
99 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800100 }
Kenny Root8c6714a2013-02-21 15:11:03 -0800101
102 return null;
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800103 }
104 }
105
Kenny Root8c6714a2013-02-21 15:11:03 -0800106 /** Unmodifiable list of encodings for the API. */
107 static final List<String> encodings = Collections.unmodifiableList(Arrays.asList(new String[] {
108 Encoding.PKI_PATH.apiName,
109 Encoding.PKCS7.apiName,
110 }));
111
112 /** The list of certificates in the order of target toward trust anchor. */
113 private final List<X509Certificate> certificates;
114
115 /** PkiPath encoding of the certification path. */
116 private byte[] pkiPathEncoding;
117
118 /** PKCS7 encoding of the certification path. */
119 private byte[] pkcs7Encoding;
120
121 /**
122 * Creates an instance of X.509 CertPath over the specified list of
123 * certificates.
124 *
125 * @throws CertificateException if some of the object in the list is not an
126 * instance of subclass of X509Certificate.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800127 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800128 public X509CertPathImpl(List<? extends java.security.cert.Certificate> certs)
129 throws CertificateException {
Elliott Hughesf33eae72010-05-13 12:36:25 -0700130 super("X.509");
Kenny Root8c6714a2013-02-21 15:11:03 -0800131
132 final int size = certs.size();
133 certificates = new ArrayList<X509Certificate>(size);
134
135 for (int i = 0; i < size; i++) {
136 final java.security.cert.Certificate cert = certs.get(i);
137 if (!(cert instanceof X509Certificate)) {
138 throw new CertificateException("Certificate " + i + " is not an X.509 certificate");
139 }
140
141 certificates.add((X509Certificate) cert);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800142 }
Kenny Root8c6714a2013-02-21 15:11:03 -0800143 }
144
145 /**
146 * Creates an X.509 CertPath over the specified {@code certs}. The
147 * {@code certs} should be sorted correctly when calling into the
148 * constructor. Additionally, the {@code encodedPath} should match the
149 * expected output for the {@code type} of encoding.
150 */
151 private X509CertPathImpl(List<X509Certificate> certs, Encoding type) {
152 super("X.509");
153
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800154 certificates = certs;
155 }
156
157 /**
Kenny Root8c6714a2013-02-21 15:11:03 -0800158 * Extract a CertPath from a PKCS#7 {@code contentInfo} object.
159 */
160 private static X509CertPathImpl getCertPathFromContentInfo(ContentInfo contentInfo)
161 throws CertificateException {
162 final SignedData sd = contentInfo.getSignedData();
163 if (sd == null) {
164 throw new CertificateException("Incorrect PKCS7 encoded form: missing signed data");
165 }
166
167 List<Certificate> certs = sd.getCertificates();
168 if (certs == null) {
169 certs = Collections.emptyList();
170 }
171
172 final List<X509Certificate> result = new ArrayList<X509Certificate>(certs.size());
173 for (Certificate cert : certs) {
174 result.add(new X509CertImpl(cert));
175 }
176
177 return new X509CertPathImpl(result, Encoding.PKCS7);
178 }
179
180 /**
181 * Generates certification path object on the base of PkiPath encoded form
182 * provided via input stream.
183 *
184 * @throws CertificateException if some problems occurred during the
185 * decoding.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800186 */
Brian Carlstrom9ff0e552010-10-20 11:54:07 -0700187 public static X509CertPathImpl getInstance(InputStream in) throws CertificateException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800188 try {
189 return (X509CertPathImpl) ASN1.decode(in);
190 } catch (IOException e) {
Kenny Root8c6714a2013-02-21 15:11:03 -0800191 throw new CertificateException("Failed to decode CertPath", e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800192 }
193 }
194
195 /**
Kenny Root8c6714a2013-02-21 15:11:03 -0800196 * Generates certification path object on the basis of encoding provided via
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800197 * input stream. The format of provided encoded form is specified by
198 * parameter <code>encoding</code>.
Kenny Root8c6714a2013-02-21 15:11:03 -0800199 *
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800200 * @throws CertificateException if specified encoding form is not supported,
Kenny Root8c6714a2013-02-21 15:11:03 -0800201 * or some problems occurred during the decoding.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800202 */
203 public static X509CertPathImpl getInstance(InputStream in, String encoding)
Brian Carlstrom9ff0e552010-10-20 11:54:07 -0700204 throws CertificateException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800205 try {
Kenny Root8c6714a2013-02-21 15:11:03 -0800206 final Encoding encType = Encoding.findByApiName(encoding);
207 if (encType == null) {
208 throw new CertificateException("Unsupported encoding: " + encoding);
209 }
210
211 switch (encType) {
212 case PKI_PATH:
213 return (X509CertPathImpl) ASN1.decode(in);
214 case PKCS7:
215 return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in));
216 default:
217 throw new CertificateException("Unsupported encoding: " + encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800218 }
219 } catch (IOException e) {
Kenny Root8c6714a2013-02-21 15:11:03 -0800220 throw new CertificateException("Failed to decode CertPath", e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800221 }
222 }
223
224 /**
225 * Generates certification path object on the base of PkiPath
226 * encoded form provided via array of bytes.
227 * @throws CertificateException if some problems occurred during
228 * the decoding.
229 */
Brian Carlstrom9ff0e552010-10-20 11:54:07 -0700230 public static X509CertPathImpl getInstance(byte[] in) throws CertificateException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800231 try {
232 return (X509CertPathImpl) ASN1.decode(in);
233 } catch (IOException e) {
Kenny Root8c6714a2013-02-21 15:11:03 -0800234 throw new CertificateException("Failed to decode CertPath", e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800235 }
236 }
237
238 /**
239 * Generates certification path object on the base of encoding provided via
240 * array of bytes. The format of provided encoded form is specified by
Kenny Root8c6714a2013-02-21 15:11:03 -0800241 * parameter {@code encoding}.
242 *
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800243 * @throws CertificateException if specified encoding form is not supported,
Kenny Root8c6714a2013-02-21 15:11:03 -0800244 * or some problems occurred during the decoding.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800245 */
246 public static X509CertPathImpl getInstance(byte[] in, String encoding)
Brian Carlstrom9ff0e552010-10-20 11:54:07 -0700247 throws CertificateException {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800248 try {
Kenny Root8c6714a2013-02-21 15:11:03 -0800249 final Encoding encType = Encoding.findByApiName(encoding);
250 if (encType == null) {
251 throw new CertificateException("Unsupported encoding: " + encoding);
252 }
253
254 switch (encType) {
255 case PKI_PATH:
256 return (X509CertPathImpl) ASN1.decode(in);
257 case PKCS7:
258 return getCertPathFromContentInfo((ContentInfo) ContentInfo.ASN1.decode(in));
259 default:
260 throw new CertificateException("Unsupported encoding: " + encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800261 }
262 } catch (IOException e) {
Kenny Root8c6714a2013-02-21 15:11:03 -0800263 throw new CertificateException("Failed to decode CertPath", e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800264 }
265 }
266
267 // ---------------------------------------------------------------------
268 // ---- java.security.cert.CertPath abstract method implementations ----
269 // ---------------------------------------------------------------------
270
271 /**
272 * @see java.security.cert.CertPath#getCertificates()
273 * method documentation for more info
274 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800275 @Override
276 public List<X509Certificate> getCertificates() {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800277 return Collections.unmodifiableList(certificates);
278 }
279
280 /**
Kenny Root8c6714a2013-02-21 15:11:03 -0800281 * Returns in PkiPath format which is our default encoding.
282 *
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800283 * @see java.security.cert.CertPath#getEncoded()
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800284 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800285 @Override
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800286 public byte[] getEncoded() throws CertificateEncodingException {
Kenny Root8c6714a2013-02-21 15:11:03 -0800287 return getEncoded(Encoding.PKI_PATH);
288 }
289
290 /**
291 * @see #getEncoded(String)
292 */
293 private byte[] getEncoded(Encoding encoding) throws CertificateEncodingException {
294 switch (encoding) {
295 case PKI_PATH:
296 if (pkiPathEncoding == null) {
297 pkiPathEncoding = ASN1.encode(this);
298 }
299
300 return pkiPathEncoding.clone();
301 case PKCS7:
302 if (pkcs7Encoding == null) {
303 pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this);
304 }
305
306 return pkcs7Encoding.clone();
307 default:
308 throw new CertificateEncodingException("Unsupported encoding: " + encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800309 }
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800310 }
311
312 /**
313 * @see java.security.cert.CertPath#getEncoded(String)
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800314 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800315 @Override
Elliott Hughes897538a2010-05-28 20:00:47 -0700316 public byte[] getEncoded(String encoding) throws CertificateEncodingException {
Kenny Root8c6714a2013-02-21 15:11:03 -0800317 final Encoding encType = Encoding.findByApiName(encoding);
318 if (encType == null) {
319 throw new CertificateEncodingException("Unsupported encoding: " + encoding);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800320 }
Kenny Root8c6714a2013-02-21 15:11:03 -0800321
322 return getEncoded(encType);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800323 }
324
325 /**
326 * @see java.security.cert.CertPath#getEncodings()
327 * method documentation for more info
328 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800329 @Override
330 public Iterator<String> getEncodings() {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800331 return encodings.iterator();
332 }
333
334 /**
335 * ASN.1 DER Encoder/Decoder for PkiPath structure.
336 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800337 public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf(ASN1Any.getInstance()) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800338 /**
Kenny Root8c6714a2013-02-21 15:11:03 -0800339 * Builds the instance of X509CertPathImpl on the base of the list of
340 * ASN.1 encodings of X.509 certificates provided via PkiPath structure.
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800341 * This method participates in decoding process.
342 */
343 public Object getDecodedObject(BerInputStream in) throws IOException {
344 // retrieve the decoded content
Kenny Root8c6714a2013-02-21 15:11:03 -0800345 final List<byte[]> encodedCerts = (List<byte[]>) in.content;
346
347 final int size = encodedCerts.size();
348 final List<X509Certificate> certificates = new ArrayList<X509Certificate>(size);
349
350 for (int i = size - 1; i >= 0; i--) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800351 // create the X.509 certificate on the base of its encoded form
352 // and add it to the list.
Kenny Root8c6714a2013-02-21 15:11:03 -0800353 certificates.add(new X509CertImpl((Certificate) Certificate.ASN1
354 .decode(encodedCerts.get(i))));
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800355 }
Kenny Root8c6714a2013-02-21 15:11:03 -0800356
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800357 // create and return the resulting object
Kenny Root8c6714a2013-02-21 15:11:03 -0800358 return new X509CertPathImpl(certificates, Encoding.PKI_PATH);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800359 }
360
361 /**
362 * Returns the Collection of the encoded form of certificates contained
363 * in the X509CertPathImpl object to be encoded.
364 * This method participates in encoding process.
365 */
Kenny Root8c6714a2013-02-21 15:11:03 -0800366 public Collection<byte[]> getValues(Object object) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800367 // object to be encoded
Kenny Root8c6714a2013-02-21 15:11:03 -0800368 final X509CertPathImpl cp = (X509CertPathImpl) object;
369
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800370 // if it has no certificates in it - create the sequence of size 0
371 if (cp.certificates == null) {
Kenny Root8c6714a2013-02-21 15:11:03 -0800372 return Collections.emptyList();
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800373 }
Kenny Root8c6714a2013-02-21 15:11:03 -0800374
375 final int size = cp.certificates.size();
376 final List<byte[]> encodings = new ArrayList<byte[]>(size);
377
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800378 try {
Kenny Root8c6714a2013-02-21 15:11:03 -0800379 for (int i = size - 1; i >= 0; i--) {
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800380 // get the encoded form of certificate and place it into the
381 // list to be encoded in PkiPath format
Kenny Root8c6714a2013-02-21 15:11:03 -0800382 encodings.add(cp.certificates.get(i).getEncoded());
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800383 }
384 } catch (CertificateEncodingException e) {
Kenny Root8c6714a2013-02-21 15:11:03 -0800385 throw new IllegalArgumentException("Encoding error occurred", e);
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800386 }
Kenny Root8c6714a2013-02-21 15:11:03 -0800387
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800388 return encodings;
389 }
390 };
Elliott Hughesf33eae72010-05-13 12:36:25 -0700391
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800392
Kenny Root8c6714a2013-02-21 15:11:03 -0800393 /**
394 * Encoder for PKCS#7 SignedData. It is assumed that only certificate field
395 * is important all other fields contain pre-calculated encodings.
396 */
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800397 private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence(
398 new ASN1Type[] {
399 // version ,digestAlgorithms, content info
400 ASN1Any.getInstance(),
401 // certificates
402 new ASN1Implicit(0, ASN1),
403 // set of crls is optional and is missed here
404 ASN1Any.getInstance(),// signers info
405 }) {
406
407 // precalculated ASN.1 encodings for
408 // version ,digestAlgorithms, content info field of SignedData
409 private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02, 0x01,
410 0x01,// version (v1)
411 0x31, 0x00,// empty set of DigestAlgorithms
412 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0
413 };
414
415 // precalculated empty set of SignerInfos
416 private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 };
417
418 protected void getValues(Object object, Object[] values) {
419 values[0] = PRECALCULATED_HEAD;
420 values[1] = object; // pass X509CertPathImpl object
421 values[2] = SIGNERS_INFO;
422 }
423
424 // stub to prevent using the instance as decoder
425 public Object decode(BerInputStream in) throws IOException {
426 throw new RuntimeException(
427 "Invalid use of encoder for PKCS#7 SignedData object");
428 }
429 };
Elliott Hughesf33eae72010-05-13 12:36:25 -0700430
The Android Open Source Projectadc854b2009-03-03 19:28:47 -0800431 private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence(
432 new ASN1Type[] { ASN1Any.getInstance(), // contentType
433 new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData
434 }) {
435
436 // precalculated ASN.1 encoding for SignedData object oid
437 private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance().encode(
438 ContentInfo.SIGNED_DATA);
439
440 protected void getValues(Object object, Object[] values) {
441 values[0] = SIGNED_DATA_OID;
442 values[1] = object; // pass X509CertPathImpl object
443 }
444
445 // stub to prevent using the instance as decoder
446 public Object decode(BerInputStream in) throws IOException {
447 throw new RuntimeException(
448 "Invalid use of encoder for PKCS#7 SignedData object");
449 }
450 };
451}