001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Alexander Y. Kleymenov
020: * @version $Revision$
021: */package org.apache.harmony.security.provider.cert;
022:
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.security.cert.CertPath;
026: import java.security.cert.CertificateEncodingException;
027: import java.security.cert.CertificateException;
028: import java.security.cert.X509Certificate;
029: import java.util.ArrayList;
030: import java.util.Arrays;
031: import java.util.Collection;
032: import java.util.Collections;
033: import java.util.Iterator;
034: import java.util.List;
035:
036: import org.apache.harmony.security.asn1.ASN1Any;
037: import org.apache.harmony.security.asn1.ASN1Explicit;
038: import org.apache.harmony.security.asn1.ASN1Implicit;
039: import org.apache.harmony.security.asn1.ASN1Oid;
040: import org.apache.harmony.security.asn1.ASN1Sequence;
041: import org.apache.harmony.security.asn1.ASN1SequenceOf;
042: import org.apache.harmony.security.asn1.ASN1Type;
043: import org.apache.harmony.security.asn1.BerInputStream;
044: import org.apache.harmony.security.internal.nls.Messages;
045: import org.apache.harmony.security.pkcs7.ContentInfo;
046: import org.apache.harmony.security.pkcs7.SignedData;
047: import org.apache.harmony.security.x509.Certificate;
048:
049: /**
050: * This class is an implementation of X.509 CertPath. This implementation
051: * provides ability to create the instance of X.509 Certification Path
052: * by several means:<br>
053: *
054: * 1. It can be created over the list of X.509 certificates
055: * (implementations of X509Certificate class) provided in constructor.<br>
056: *
057: * 2. It can be created by means of <code>getInstance</code> methods
058: * on the base of the following ASN.1 DER encoded forms:<br>
059: *
060: * - PkiPath as defined in
061: * ITU-T Recommendation X.509(2000) Corrigendum 1(2001)
062: * (can be seen at
063: * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution/TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX.509/8%7CX.509-TC1(4th).pdf)
064: * <br>
065: * - PKCS #7 SignedData object provided in the form of
066: * ContentInfo structure. CertPath object is generated on the base of
067: * certificates presented in <code>certificates</code> field of the SignedData
068: * object which in its turn is retrieved from ContentInfo structure.
069: * (see http://www.ietf.org/rfc/rfc2315.txt
070: * for more info on PKCS #7)
071: * <br>
072: *
073: */
074: public class X509CertPathImpl extends CertPath {
075:
076: /**
077: * @serial
078: */
079: private static final long serialVersionUID = 7989755106209515436L;
080:
081: // supported encoding types:
082: public static final int PKI_PATH = 0;
083: public static final int PKCS7 = 1;
084:
085: // supported encoding names
086: private static final String[] encodingsArr = new String[] {
087: "PkiPath", "PKCS7" }; //$NON-NLS-1$ //$NON-NLS-2$
088: static final List encodings = Collections.unmodifiableList(Arrays
089: .asList(encodingsArr));
090: // the list of certificates representing this certification path
091: private final List certificates;
092: // PkiPath encoding of the certification path
093: private byte[] pkiPathEncoding;
094: // PKCS7 encoding of the certification path
095: private byte[] pkcs7Encoding;
096:
097: /**
098: * Creates an instance of X.509 Certification Path over the specified
099: * list of certificates.
100: * @throws CertificateException if some of the object in the list
101: * is not an instance of subclass of X509Certificate.
102: */
103: public X509CertPathImpl(List certs) throws CertificateException {
104: super ("X.509"); //$NON-NLS-1$
105: int size = certs.size();
106: certificates = new ArrayList(size);
107: for (int i = 0; i < size; i++) {
108: Object cert = certs.get(i);
109: if (!(cert instanceof X509Certificate)) {
110: throw new CertificateException(Messages
111: .getString("security.15D")); //$NON-NLS-1$
112: }
113: certificates.add(cert);
114: }
115: }
116:
117: /*
118: * Internally used constructor.
119: * Creates an X.509 Certification Path over the specified
120: * list of certificates and their encoded form of specified type.
121: * @param certs - the list of certificates
122: * @param type - the type of the encoded form on the base of which
123: * this list of certificates had been built.
124: * @param encoding - encoded form of certification path.
125: */
126: private X509CertPathImpl(List certs, int type, byte[] encoding) {
127: super ("X.509"); //$NON-NLS-1$
128: if (type == PKI_PATH) {
129: this .pkiPathEncoding = encoding;
130: } else { // PKCS7
131: this .pkcs7Encoding = encoding;
132: }
133: // We do not need the type check and list cloning here,
134: // because it has been done during decoding.
135: certificates = certs;
136: }
137:
138: /**
139: * Generates certification path object on the base of PkiPath
140: * encoded form provided via input stream.
141: * @throws CertificateException if some problems occurred during
142: * the decoding.
143: */
144: public static X509CertPathImpl getInstance(InputStream in)
145: throws CertificateException {
146: try {
147: return (X509CertPathImpl) ASN1.decode(in);
148: } catch (IOException e) {
149: throw new CertificateException(Messages.getString(
150: "security.15E", //$NON-NLS-1$
151: e.getMessage()));
152: }
153: }
154:
155: /**
156: * Generates certification path object on the base of encoding provided via
157: * input stream. The format of provided encoded form is specified by
158: * parameter <code>encoding</code>.
159: * @throws CertificateException if specified encoding form is not supported,
160: * or some problems occurred during the decoding.
161: */
162: public static X509CertPathImpl getInstance(InputStream in,
163: String encoding) throws CertificateException {
164: if (!encodings.contains(encoding)) {
165: throw new CertificateException(Messages.getString(
166: "security.15F", encoding)); //$NON-NLS-1$
167: }
168: try {
169: if (encodingsArr[0].equals(encoding)) {
170: // generate the object from PkiPath encoded form
171: return (X509CertPathImpl) ASN1.decode(in);
172: } else {
173: // generate the object from PKCS #7 encoded form
174: ContentInfo ci = (ContentInfo) ContentInfo.ASN1
175: .decode(in);
176: SignedData sd = ci.getSignedData();
177: if (sd == null) {
178: throw new CertificateException(Messages
179: .getString("security.160")); //$NON-NLS-1$
180: }
181: List certs = sd.getCertificates();
182: if (certs == null) {
183: // empty chain of certificates
184: certs = new ArrayList();
185: }
186: List result = new ArrayList();
187: for (int i = 0; i < certs.size(); i++) {
188: result.add(new X509CertImpl((Certificate) certs
189: .get(i)));
190: }
191: return new X509CertPathImpl(result, PKCS7, ci
192: .getEncoded());
193: }
194: } catch (IOException e) {
195: throw new CertificateException(Messages.getString(
196: "security.15E", //$NON-NLS-1$
197: e.getMessage()));
198: }
199: }
200:
201: /**
202: * Generates certification path object on the base of PkiPath
203: * encoded form provided via array of bytes.
204: * @throws CertificateException if some problems occurred during
205: * the decoding.
206: */
207: public static X509CertPathImpl getInstance(byte[] in)
208: throws CertificateException {
209: try {
210: return (X509CertPathImpl) ASN1.decode(in);
211: } catch (IOException e) {
212: throw new CertificateException(Messages.getString(
213: "security.15E", //$NON-NLS-1$
214: e.getMessage()));
215: }
216: }
217:
218: /**
219: * Generates certification path object on the base of encoding provided via
220: * array of bytes. The format of provided encoded form is specified by
221: * parameter <code>encoding</code>.
222: * @throws CertificateException if specified encoding form is not supported,
223: * or some problems occurred during the decoding.
224: */
225: public static X509CertPathImpl getInstance(byte[] in,
226: String encoding) throws CertificateException {
227: if (!encodings.contains(encoding)) {
228: throw new CertificateException(Messages.getString(
229: "security.15F", encoding)); //$NON-NLS-1$
230: }
231: try {
232: if (encodingsArr[0].equals(encoding)) {
233: // generate the object from PkiPath encoded form
234: return (X509CertPathImpl) ASN1.decode(in);
235: } else {
236: // generate the object from PKCS #7 encoded form
237: ContentInfo ci = (ContentInfo) ContentInfo.ASN1
238: .decode(in);
239: SignedData sd = ci.getSignedData();
240: if (sd == null) {
241: throw new CertificateException(Messages
242: .getString("security.160")); //$NON-NLS-1$
243: }
244: List certs = sd.getCertificates();
245: if (certs == null) {
246: certs = new ArrayList();
247: }
248: List result = new ArrayList();
249: for (int i = 0; i < certs.size(); i++) {
250: result.add(new X509CertImpl((Certificate) certs
251: .get(i)));
252: }
253: return new X509CertPathImpl(result, PKCS7, ci
254: .getEncoded());
255: }
256: } catch (IOException e) {
257: throw new CertificateException(Messages.getString(
258: "security.15E", //$NON-NLS-1$
259: e.getMessage()));
260: }
261: }
262:
263: // ---------------------------------------------------------------------
264: // ---- java.security.cert.CertPath abstract method implementations ----
265: // ---------------------------------------------------------------------
266:
267: /**
268: * @see java.security.cert.CertPath#getCertificates()
269: * method documentation for more info
270: */
271: public List getCertificates() {
272: return Collections.unmodifiableList(certificates);
273: }
274:
275: /**
276: * @see java.security.cert.CertPath#getEncoded()
277: * method documentation for more info
278: */
279: public byte[] getEncoded() throws CertificateEncodingException {
280: if (pkiPathEncoding == null) {
281: pkiPathEncoding = ASN1.encode(this );
282: }
283: byte[] result = new byte[pkiPathEncoding.length];
284: System.arraycopy(pkiPathEncoding, 0, result, 0,
285: pkiPathEncoding.length);
286: return result;
287: }
288:
289: /**
290: * @see java.security.cert.CertPath#getEncoded(String)
291: * method documentation for more info
292: */
293: public byte[] getEncoded(String encoding)
294: throws CertificateEncodingException {
295: if (!encodings.contains(encoding)) {
296: throw new CertificateEncodingException(Messages.getString(
297: "security.15F", encoding)); //$NON-NLS-1$
298: }
299: if (encodingsArr[0].equals(encoding)) {
300: // PkiPath encoded form
301: return getEncoded();
302: } else {
303: // PKCS7 encoded form
304: if (pkcs7Encoding == null) {
305: pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this );
306: }
307: byte[] result = new byte[pkcs7Encoding.length];
308: System.arraycopy(pkcs7Encoding, 0, result, 0,
309: pkcs7Encoding.length);
310: return result;
311: }
312: }
313:
314: /**
315: * @see java.security.cert.CertPath#getEncodings()
316: * method documentation for more info
317: */
318: public Iterator getEncodings() {
319: return encodings.iterator();
320: }
321:
322: /**
323: * ASN.1 DER Encoder/Decoder for PkiPath structure.
324: */
325: public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf(
326: ASN1Any.getInstance()) {
327:
328: /**
329: * Builds the instance of X509CertPathImpl on the base of the list
330: * of ASN.1 encodings of X.509 certificates provided via
331: * PkiPath structure.
332: * This method participates in decoding process.
333: */
334: public Object getDecodedObject(BerInputStream in)
335: throws IOException {
336: // retrieve the decoded content
337: List encodings = (List) in.content;
338: int size = encodings.size();
339: List certificates = new ArrayList(size);
340: for (int i = 0; i < size; i++) {
341: // create the X.509 certificate on the base of its encoded form
342: // and add it to the list.
343: certificates.add(new X509CertImpl(
344: (Certificate) Certificate.ASN1
345: .decode((byte[]) encodings.get(i))));
346: }
347: // create and return the resulting object
348: return new X509CertPathImpl(certificates, PKI_PATH, in
349: .getEncoded());
350: }
351:
352: /**
353: * Returns the Collection of the encoded form of certificates contained
354: * in the X509CertPathImpl object to be encoded.
355: * This method participates in encoding process.
356: */
357: public Collection getValues(Object object) {
358: // object to be encoded
359: X509CertPathImpl cp = (X509CertPathImpl) object;
360: // if it has no certificates in it - create the sequence of size 0
361: if (cp.certificates == null) {
362: return new ArrayList();
363: }
364: int size = cp.certificates.size();
365: List encodings = new ArrayList(size);
366: try {
367: for (int i = 0; i < size; i++) {
368: // get the encoded form of certificate and place it into the
369: // list to be encoded in PkiPath format
370: encodings.add(((X509Certificate) cp.certificates
371: .get(i)).getEncoded());
372: }
373: } catch (CertificateEncodingException e) {
374: throw new IllegalArgumentException(Messages
375: .getString("security.161")); //$NON-NLS-1$
376: }
377: return encodings;
378: }
379: };
380:
381: //
382: // encoder for PKCS#7 SignedData
383: // it is assumed that only certificate field is important
384: // all other fields contain precalculated encodings:
385: //
386: // encodes X509CertPathImpl objects
387: //
388: private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence(
389: new ASN1Type[] {
390: // version ,digestAlgorithms, content info
391: ASN1Any.getInstance(),
392: // certificates
393: new ASN1Implicit(0, ASN1),
394: // set of crls is optional and is missed here
395: ASN1Any.getInstance(),// signers info
396: }) {
397:
398: // precalculated ASN.1 encodings for
399: // version ,digestAlgorithms, content info field of SignedData
400: private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02,
401: 0x01, 0x01,// version (v1)
402: 0x31, 0x00,// empty set of DigestAlgorithms
403: 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0
404: };
405:
406: // precalculated empty set of SignerInfos
407: private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 };
408:
409: protected void getValues(Object object, Object[] values) {
410: values[0] = PRECALCULATED_HEAD;
411: values[1] = object; // pass X509CertPathImpl object
412: values[2] = SIGNERS_INFO;
413: }
414:
415: // stub to prevent using the instance as decoder
416: public Object decode(BerInputStream in) throws IOException {
417: throw new RuntimeException(
418: "Invalid use of encoder for PKCS#7 SignedData object");
419: }
420: };
421:
422: private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence(
423: new ASN1Type[] { ASN1Any.getInstance(), // contentType
424: new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData
425: }) {
426:
427: // precalculated ASN.1 encoding for SignedData object oid
428: private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance()
429: .encode(ContentInfo.SIGNED_DATA);
430:
431: protected void getValues(Object object, Object[] values) {
432: values[0] = SIGNED_DATA_OID;
433: values[1] = object; // pass X509CertPathImpl object
434: }
435:
436: // stub to prevent using the instance as decoder
437: public Object decode(BerInputStream in) throws IOException {
438: throw new RuntimeException(
439: "Invalid use of encoder for PKCS#7 SignedData object");
440: }
441: };
442: }
|