/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Alexander Y. Kleymenov */ package org.apache.harmony.security.provider.cert; import java.io.IOException; import java.io.InputStream; import java.security.cert.CertPath; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.harmony.security.asn1.ASN1Any; import org.apache.harmony.security.asn1.ASN1Explicit; import org.apache.harmony.security.asn1.ASN1Implicit; import org.apache.harmony.security.asn1.ASN1Oid; import org.apache.harmony.security.asn1.ASN1Sequence; import org.apache.harmony.security.asn1.ASN1SequenceOf; import org.apache.harmony.security.asn1.ASN1Type; import org.apache.harmony.security.asn1.BerInputStream; import org.apache.harmony.security.internal.nls.Messages; import org.apache.harmony.security.pkcs7.ContentInfo; import org.apache.harmony.security.pkcs7.SignedData; import org.apache.harmony.security.x509.Certificate; /** * This class is an implementation of X.509 CertPath. This implementation * provides ability to create the instance of X.509 Certification Path by * several means:<br> * *   1. It can be created over the list of X.509 certificates * (implementations of X509Certificate class) provided in constructor.<br> * *   2. It can be created by means of <code>getInstance</code> methods on * the base of the following ASN.1 DER encoded forms:<br> * *    - PkiPath as defined in ITU-T Recommendation X.509(2000) * Corrigendum 1(2001) (can be seen at * ftp://ftp.bull.com/pub/OSIdirectory/DefectResolution * /TechnicalCorrigenda/ApprovedTechnicalCorrigendaToX * .509/8%7CX.509-TC1(4th).pdf) <br> *    - PKCS #7 SignedData object provided in the form of ContentInfo * structure. CertPath object is generated on the base of certificates presented * in <code>certificates</code> field of the SignedData object which in its turn * is retrieved from ContentInfo structure. (see * http://www.ietf.org/rfc/rfc2315.txt for more info on PKCS #7) <br> *   */ public class X509CertPathImpl extends CertPath { /** * @serial */ private static final long serialVersionUID = 7989755106209515436L; // supported encoding types: public static final int PKI_PATH = 0; public static final int PKCS7 = 1; // supported encoding names private static final String[] encodingsArr = new String[] { "PkiPath", "PKCS7" }; //$NON-NLS-1$ //$NON-NLS-2$ static final List<String> encodings = Collections.unmodifiableList(Arrays .asList(encodingsArr)); /** * Generates certification path object on the base of PkiPath encoded form * provided via array of bytes. * * @throws CertificateException * if some problems occurred during the decoding. */ public static X509CertPathImpl getInstance(byte[] in) throws CertificateException { try { return (X509CertPathImpl) ASN1.decode(in); } catch (final IOException e) { throw new CertificateException(Messages.getString("security.15E", //$NON-NLS-1$ e.getMessage())); } } /** * Generates certification path object on the base of encoding provided via * array of bytes. The format of provided encoded form is specified by * parameter <code>encoding</code>. * * @throws CertificateException * if specified encoding form is not supported, or some problems * occurred during the decoding. */ public static X509CertPathImpl getInstance(byte[] in, String encoding) throws CertificateException { if (!encodings.contains(encoding)) { throw new CertificateException(Messages.getString( "security.15F", encoding)); //$NON-NLS-1$ } try { if (encodingsArr[0].equals(encoding)) { // generate the object from PkiPath encoded form return (X509CertPathImpl) ASN1.decode(in); } else { // generate the object from PKCS #7 encoded form final ContentInfo ci = (ContentInfo) ContentInfo.ASN1 .decode(in); final SignedData sd = ci.getSignedData(); if (sd == null) { throw new CertificateException( Messages.getString("security.160")); //$NON-NLS-1$ } List<Certificate> certs = sd.getCertificates(); if (certs == null) { certs = new ArrayList<Certificate>(); } final List<X509Certificate> result = new ArrayList<X509Certificate>(); for (int i = 0; i < certs.size(); i++) { result.add(new X509CertImpl(certs.get(i))); } return new X509CertPathImpl(result, PKCS7, ci.getEncoded()); } } catch (final IOException e) { throw new CertificateException(Messages.getString("security.15E", //$NON-NLS-1$ e.getMessage())); } } /** * Generates certification path object on the base of PkiPath encoded form * provided via input stream. * * @throws CertificateException * if some problems occurred during the decoding. */ public static X509CertPathImpl getInstance(InputStream in) throws CertificateException { try { return (X509CertPathImpl) ASN1.decode(in); } catch (final IOException e) { throw new CertificateException(Messages.getString("security.15E", //$NON-NLS-1$ e.getMessage())); } } /** * Generates certification path object on the base of encoding provided via * input stream. The format of provided encoded form is specified by * parameter <code>encoding</code>. * * @throws CertificateException * if specified encoding form is not supported, or some problems * occurred during the decoding. */ public static X509CertPathImpl getInstance(InputStream in, String encoding) throws CertificateException { if (!encodings.contains(encoding)) { throw new CertificateException(Messages.getString( "security.15F", encoding)); //$NON-NLS-1$ } try { if (encodingsArr[0].equals(encoding)) { // generate the object from PkiPath encoded form return (X509CertPathImpl) ASN1.decode(in); } else { // generate the object from PKCS #7 encoded form final ContentInfo ci = (ContentInfo) ContentInfo.ASN1 .decode(in); final SignedData sd = ci.getSignedData(); if (sd == null) { throw new CertificateException( Messages.getString("security.160")); //$NON-NLS-1$ } List<?> certs = sd.getCertificates(); if (certs == null) { // empty chain of certificates certs = new ArrayList<Object>(); } final List<X509Certificate> result = new ArrayList<X509Certificate>(); for (int i = 0; i < certs.size(); i++) { result.add(new X509CertImpl((Certificate) certs.get(i))); } return new X509CertPathImpl(result, PKCS7, ci.getEncoded()); } } catch (final IOException e) { throw new CertificateException(Messages.getString("security.15E", //$NON-NLS-1$ e.getMessage())); } } // the list of certificates representing this certification path private final List<X509Certificate> certificates; // PkiPath encoding of the certification path private byte[] pkiPathEncoding; // PKCS7 encoding of the certification path private byte[] pkcs7Encoding; /** * ASN.1 DER Encoder/Decoder for PkiPath structure. */ public static final ASN1SequenceOf ASN1 = new ASN1SequenceOf( ASN1Any.getInstance()) { /** * Builds the instance of X509CertPathImpl on the base of the list of * ASN.1 encodings of X.509 certificates provided via PkiPath structure. * This method participates in decoding process. */ @Override public Object getDecodedObject(BerInputStream in) throws IOException { // retrieve the decoded content final List<?> encodings = (List<?>) in.content; final int size = encodings.size(); final List<X509Certificate> certificates = new ArrayList<X509Certificate>( size); for (int i = 0; i < size; i++) { // create the X.509 certificate on the base of its encoded form // and add it to the list. certificates.add(new X509CertImpl( (Certificate) Certificate.ASN1 .decode((byte[]) encodings.get(i)))); } // create and return the resulting object return new X509CertPathImpl(certificates, PKI_PATH, in.getEncoded()); } /** * Returns the Collection of the encoded form of certificates contained * in the X509CertPathImpl object to be encoded. This method * participates in encoding process. */ @Override public Collection<?> getValues(Object object) { // object to be encoded final X509CertPathImpl cp = (X509CertPathImpl) object; // if it has no certificates in it - create the sequence of size 0 if (cp.certificates == null) { return new ArrayList<Object>(); } final int size = cp.certificates.size(); final List<byte[]> encodings = new ArrayList<byte[]>(size); try { for (int i = 0; i < size; i++) { // get the encoded form of certificate and place it into the // list to be encoded in PkiPath format encodings.add(cp.certificates.get(i).getEncoded()); } } catch (final CertificateEncodingException e) { throw new IllegalArgumentException( Messages.getString("security.161")); //$NON-NLS-1$ } return encodings; } }; // // encoder for PKCS#7 SignedData // it is assumed that only certificate field is important // all other fields contain precalculated encodings: // // encodes X509CertPathImpl objects // private static final ASN1Sequence ASN1_SIGNED_DATA = new ASN1Sequence( new ASN1Type[] { // version ,digestAlgorithms, content info ASN1Any.getInstance(), // certificates new ASN1Implicit(0, ASN1), // set of crls is optional and is missed here ASN1Any.getInstance(),// signers info }) { // precalculated ASN.1 encodings for // version ,digestAlgorithms, content info field of SignedData private final byte[] PRECALCULATED_HEAD = new byte[] { 0x02, 0x01, 0x01,// version (v1) 0x31, 0x00,// empty set of DigestAlgorithms 0x30, 0x03, 0x06, 0x01, 0x00 // empty ContentInfo with oid=0 }; // precalculated empty set of SignerInfos private final byte[] SIGNERS_INFO = new byte[] { 0x31, 0x00 }; // stub to prevent using the instance as decoder @Override public Object decode(BerInputStream in) throws IOException { throw new RuntimeException( "Invalid use of encoder for PKCS#7 SignedData object"); } @Override protected void getValues(Object object, Object[] values) { values[0] = PRECALCULATED_HEAD; values[1] = object; // pass X509CertPathImpl object values[2] = SIGNERS_INFO; } }; // --------------------------------------------------------------------- // ---- java.security.cert.CertPath abstract method implementations ---- // --------------------------------------------------------------------- private static final ASN1Sequence PKCS7_SIGNED_DATA_OBJECT = new ASN1Sequence( new ASN1Type[] { ASN1Any.getInstance(), // contentType new ASN1Explicit(0, ASN1_SIGNED_DATA) // SignedData }) { // precalculated ASN.1 encoding for SignedData object oid private final byte[] SIGNED_DATA_OID = ASN1Oid.getInstance().encode( ContentInfo.SIGNED_DATA); // stub to prevent using the instance as decoder @Override public Object decode(BerInputStream in) throws IOException { throw new RuntimeException( "Invalid use of encoder for PKCS#7 SignedData object"); } @Override protected void getValues(Object object, Object[] values) { values[0] = SIGNED_DATA_OID; values[1] = object; // pass X509CertPathImpl object } }; /** * Creates an instance of X.509 Certification Path over the specified list * of certificates. * * @throws CertificateException * if some of the object in the list is not an instance of * subclass of X509Certificate. */ public X509CertPathImpl(List<?> certs) throws CertificateException { super("X.509"); //$NON-NLS-1$ final int size = certs.size(); certificates = new ArrayList<X509Certificate>(size); for (int i = 0; i < size; i++) { final Object cert = certs.get(i); if (!(cert instanceof X509Certificate)) { throw new CertificateException( Messages.getString("security.15D")); //$NON-NLS-1$ } certificates.add((X509Certificate) cert); } } /* * Internally used constructor. Creates an X.509 Certification Path over the * specified list of certificates and their encoded form of specified type. * * @param certs - the list of certificates * * @param type - the type of the encoded form on the base of which this list * of certificates had been built. * * @param encoding - encoded form of certification path. */ private X509CertPathImpl(List<X509Certificate> certs, int type, byte[] encoding) { super("X.509"); //$NON-NLS-1$ if (type == PKI_PATH) { pkiPathEncoding = encoding; } else { // PKCS7 pkcs7Encoding = encoding; } // We do not need the type check and list cloning here, // because it has been done during decoding. certificates = certs; } /** * @see java.security.cert.CertPath#getCertificates() method documentation * for more info */ @Override public List<X509Certificate> getCertificates() { return Collections.unmodifiableList(certificates); } /** * @see java.security.cert.CertPath#getEncoded() method documentation for * more info */ @Override public byte[] getEncoded() throws CertificateEncodingException { if (pkiPathEncoding == null) { pkiPathEncoding = ASN1.encode(this); } final byte[] result = new byte[pkiPathEncoding.length]; System.arraycopy(pkiPathEncoding, 0, result, 0, pkiPathEncoding.length); return result; } /** * @see java.security.cert.CertPath#getEncoded(String) method documentation * for more info */ @Override public byte[] getEncoded(String encoding) throws CertificateEncodingException { if (!encodings.contains(encoding)) { throw new CertificateEncodingException(Messages.getString( "security.15F", encoding)); //$NON-NLS-1$ } if (encodingsArr[0].equals(encoding)) { // PkiPath encoded form return getEncoded(); } else { // PKCS7 encoded form if (pkcs7Encoding == null) { pkcs7Encoding = PKCS7_SIGNED_DATA_OBJECT.encode(this); } final byte[] result = new byte[pkcs7Encoding.length]; System.arraycopy(pkcs7Encoding, 0, result, 0, pkcs7Encoding.length); return result; } } /** * @see java.security.cert.CertPath#getEncodings() method documentation for * more info */ @Override public Iterator<String> getEncodings() { return encodings.iterator(); } }