/* | 
|
 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. | 
|
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | 
|
 * | 
|
 * This code is free software; you can redistribute it and/or modify it | 
|
 * under the terms of the GNU General Public License version 2 only, as | 
|
 * published by the Free Software Foundation.  Oracle designates this | 
|
 * particular file as subject to the "Classpath" exception as provided | 
|
 * by Oracle in the LICENSE file that accompanied this code. | 
|
 * | 
|
 * This code is distributed in the hope that it will be useful, but WITHOUT | 
|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
|
 * version 2 for more details (a copy is included in the LICENSE file that | 
|
 * accompanied this code). | 
|
 * | 
|
 * You should have received a copy of the GNU General Public License version | 
|
 * 2 along with this work; if not, write to the Free Software Foundation, | 
|
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | 
|
 * | 
|
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | 
|
 * or visit www.oracle.com if you need additional information or have any | 
|
 * questions. | 
|
*/  | 
|
package sun.security.provider.certpath;  | 
|
import java.io.ByteArrayInputStream;  | 
|
import java.io.ByteArrayOutputStream;  | 
|
import java.io.IOException;  | 
|
import java.io.InputStream;  | 
|
import java.security.cert.CertificateEncodingException;  | 
|
import java.security.cert.Certificate;  | 
|
import java.security.cert.CertificateException;  | 
|
import java.security.cert.CertificateFactory;  | 
|
import java.security.cert.CertPath;  | 
|
import java.security.cert.X509Certificate;  | 
|
import java.util.*;  | 
|
import sun.security.pkcs.ContentInfo;  | 
|
import sun.security.pkcs.PKCS7;  | 
|
import sun.security.pkcs.SignerInfo;  | 
|
import sun.security.x509.AlgorithmId;  | 
|
import sun.security.util.DerValue;  | 
|
import sun.security.util.DerOutputStream;  | 
|
import sun.security.util.DerInputStream;  | 
|
/** | 
|
 * A {@link java.security.cert.CertPath CertPath} (certification path) | 
|
 * consisting exclusively of | 
|
 * {@link java.security.cert.X509Certificate X509Certificate}s. | 
|
 * <p> | 
|
 * By convention, X.509 <code>CertPath</code>s are stored from target | 
|
 * to trust anchor. | 
|
 * That is, the issuer of one certificate is the subject of the following | 
|
 * one. However, unvalidated X.509 <code>CertPath</code>s may not follow | 
|
 * this convention. PKIX <code>CertPathValidator</code>s will detect any | 
|
 * departure from this convention and throw a | 
|
 * <code>CertPathValidatorException</code>. | 
|
 * | 
|
 * @author      Yassir Elley | 
|
 * @since       1.4 | 
|
*/  | 
|
public class X509CertPath extends CertPath {  | 
|
private static final long serialVersionUID = 4989800333263052980L;  | 
|
    /** | 
|
     * List of certificates in this chain | 
|
*/  | 
|
private List<X509Certificate> certs;  | 
|
    /** | 
|
     * The names of our encodings.  PkiPath is the default. | 
|
*/  | 
|
private static final String COUNT_ENCODING = "count";  | 
|
private static final String PKCS7_ENCODING = "PKCS7";  | 
|
private static final String PKIPATH_ENCODING = "PkiPath";  | 
|
    /** | 
|
     * List of supported encodings | 
|
*/  | 
|
private static final Collection<String> encodingList;  | 
|
    static { | 
|
List<String> list = new ArrayList<>(2);  | 
|
list.add(PKIPATH_ENCODING);  | 
|
list.add(PKCS7_ENCODING);  | 
|
encodingList = Collections.unmodifiableCollection(list);  | 
|
}  | 
|
    /** | 
|
     * Creates an <code>X509CertPath</code> from a <code>List</code> of | 
|
     * <code>X509Certificate</code>s. | 
|
     * <p> | 
|
     * The certificates are copied out of the supplied <code>List</code> | 
|
     * object. | 
|
     * | 
|
     * @param certs a <code>List</code> of <code>X509Certificate</code>s | 
|
     * @exception CertificateException if <code>certs</code> contains an element | 
|
     *                      that is not an <code>X509Certificate</code> | 
|
*/  | 
|
    @SuppressWarnings("unchecked") | 
|
public X509CertPath(List<? extends Certificate> certs) throws CertificateException {  | 
|
        super("X.509"); | 
|
// Ensure that the List contains only X509Certificates  | 
|
//  | 
|
// Note; The certs parameter is not necessarily to be of Certificate  | 
|
// for some old code. For compatibility, to make sure the exception  | 
|
// is CertificateException, rather than ClassCastException, please  | 
|
// don't use  | 
|
        //     for (Certificate obj : certs) | 
|
for (Object obj : certs) {  | 
|
if (obj instanceof X509Certificate == false) {  | 
|
throw new CertificateException  | 
|
                    ("List is not all X509Certificates: " | 
|
+ obj.getClass().getName());  | 
|
}  | 
|
}  | 
|
// Assumes that the resulting List is thread-safe. This is true  | 
|
// because we ensure that it cannot be modified after construction  | 
|
// and the methods in the Sun JDK 1.4 implementation of ArrayList that  | 
|
        // allow read-only access are thread-safe. | 
|
this.certs = Collections.unmodifiableList(  | 
|
new ArrayList<X509Certificate>((List<X509Certificate>)certs));  | 
|
}  | 
|
    /** | 
|
     * Creates an <code>X509CertPath</code>, reading the encoded form | 
|
     * from an <code>InputStream</code>. The data is assumed to be in | 
|
     * the default encoding. | 
|
     * | 
|
     * @param is the <code>InputStream</code> to read the data from | 
|
     * @exception CertificateException if an exception occurs while decoding | 
|
*/  | 
|
public X509CertPath(InputStream is) throws CertificateException {  | 
|
this(is, PKIPATH_ENCODING);  | 
|
}  | 
|
    /** | 
|
     * Creates an <code>X509CertPath</code>, reading the encoded form | 
|
     * from an InputStream. The data is assumed to be in the specified | 
|
     * encoding. | 
|
     * | 
|
     * @param is the <code>InputStream</code> to read the data from | 
|
     * @param encoding the encoding used | 
|
     * @exception CertificateException if an exception occurs while decoding or | 
|
     *   the encoding requested is not supported | 
|
*/  | 
|
public X509CertPath(InputStream is, String encoding)  | 
|
throws CertificateException {  | 
|
        super("X.509"); | 
|
        switch (encoding) { | 
|
case PKIPATH_ENCODING:  | 
|
certs = parsePKIPATH(is);  | 
|
break;  | 
|
case PKCS7_ENCODING:  | 
|
certs = parsePKCS7(is);  | 
|
break;  | 
|
default:  | 
|
throw new CertificateException("unsupported encoding");  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Parse a PKIPATH format CertPath from an InputStream. Return an | 
|
     * unmodifiable List of the certificates. | 
|
     * | 
|
     * @param is the <code>InputStream</code> to read the data from | 
|
     * @return an unmodifiable List of the certificates | 
|
     * @exception CertificateException if an exception occurs | 
|
*/  | 
|
private static List<X509Certificate> parsePKIPATH(InputStream is)  | 
|
throws CertificateException {  | 
|
List<X509Certificate> certList = null;  | 
|
CertificateFactory certFac = null;  | 
|
if (is == null) {  | 
|
throw new CertificateException("input stream is null");  | 
|
}  | 
|
        try { | 
|
DerInputStream dis = new DerInputStream(readAllBytes(is));  | 
|
DerValue[] seq = dis.getSequence(3);  | 
|
if (seq.length == 0) {  | 
|
return Collections.<X509Certificate>emptyList();  | 
|
}  | 
|
certFac = CertificateFactory.getInstance("X.509");  | 
|
certList = new ArrayList<X509Certificate>(seq.length);  | 
|
            // append certs in reverse order (target to trust anchor) | 
|
for (int i = seq.length-1; i >= 0; i--) {  | 
|
certList.add((X509Certificate)certFac.generateCertificate  | 
|
(new ByteArrayInputStream(seq[i].toByteArray())));  | 
|
}  | 
|
return Collections.unmodifiableList(certList);  | 
|
} catch (IOException ioe) {  | 
|
throw new CertificateException("IOException parsing PkiPath data: "  | 
|
+ ioe, ioe);  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Parse a PKCS#7 format CertPath from an InputStream. Return an | 
|
     * unmodifiable List of the certificates. | 
|
     * | 
|
     * @param is the <code>InputStream</code> to read the data from | 
|
     * @return an unmodifiable List of the certificates | 
|
     * @exception CertificateException if an exception occurs | 
|
*/  | 
|
private static List<X509Certificate> parsePKCS7(InputStream is)  | 
|
throws CertificateException {  | 
|
List<X509Certificate> certList;  | 
|
if (is == null) {  | 
|
throw new CertificateException("input stream is null");  | 
|
}  | 
|
        try { | 
|
if (is.markSupported() == false) {  | 
|
// Copy the entire input stream into an InputStream that does  | 
|
                // support mark | 
|
is = new ByteArrayInputStream(readAllBytes(is));  | 
|
}  | 
|
PKCS7 pkcs7 = new PKCS7(is);  | 
|
X509Certificate[] certArray = pkcs7.getCertificates();  | 
|
            // certs are optional in PKCS #7 | 
|
if (certArray != null) {  | 
|
certList = Arrays.asList(certArray);  | 
|
            } else { | 
|
                // no certs provided | 
|
certList = new ArrayList<X509Certificate>(0);  | 
|
}  | 
|
} catch (IOException ioe) {  | 
|
throw new CertificateException("IOException parsing PKCS7 data: " +  | 
|
ioe);  | 
|
}  | 
|
// Assumes that the resulting List is thread-safe. This is true  | 
|
// because we ensure that it cannot be modified after construction  | 
|
// and the methods in the Sun JDK 1.4 implementation of ArrayList that  | 
|
        // allow read-only access are thread-safe. | 
|
return Collections.unmodifiableList(certList);  | 
|
}  | 
|
    /* | 
|
     * Reads the entire contents of an InputStream into a byte array. | 
|
     * | 
|
     * @param is the InputStream to read from | 
|
     * @return the bytes read from the InputStream | 
|
*/  | 
|
private static byte[] readAllBytes(InputStream is) throws IOException {  | 
|
byte[] buffer = new byte[8192];  | 
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);  | 
|
int n;  | 
|
while ((n = is.read(buffer)) != -1) {  | 
|
baos.write(buffer, 0, n);  | 
|
}  | 
|
return baos.toByteArray();  | 
|
}  | 
|
    /** | 
|
     * Returns the encoded form of this certification path, using the | 
|
     * default encoding. | 
|
     * | 
|
     * @return the encoded bytes | 
|
     * @exception CertificateEncodingException if an encoding error occurs | 
|
*/  | 
|
@Override  | 
|
public byte[] getEncoded() throws CertificateEncodingException {  | 
|
        // @@@ Should cache the encoded form | 
|
return encodePKIPATH();  | 
|
}  | 
|
    /** | 
|
     * Encode the CertPath using PKIPATH format. | 
|
     * | 
|
     * @return a byte array containing the binary encoding of the PkiPath object | 
|
     * @exception CertificateEncodingException if an exception occurs | 
|
*/  | 
|
private byte[] encodePKIPATH() throws CertificateEncodingException {  | 
|
ListIterator<X509Certificate> li = certs.listIterator(certs.size());  | 
|
        try { | 
|
DerOutputStream bytes = new DerOutputStream();  | 
|
// encode certs in reverse order (trust anchor to target)  | 
|
            // according to PkiPath format | 
|
while (li.hasPrevious()) {  | 
|
X509Certificate cert = li.previous();  | 
|
                // check for duplicate cert | 
|
if (certs.lastIndexOf(cert) != certs.indexOf(cert)) {  | 
|
throw new CertificateEncodingException  | 
|
                        ("Duplicate Certificate"); | 
|
}  | 
|
                // get encoded certificates | 
|
byte[] encoded = cert.getEncoded();  | 
|
bytes.write(encoded);  | 
|
}  | 
|
            // Wrap the data in a SEQUENCE | 
|
DerOutputStream derout = new DerOutputStream();  | 
|
derout.write(DerValue.tag_SequenceOf, bytes);  | 
|
return derout.toByteArray();  | 
|
} catch (IOException ioe) {  | 
|
throw new CertificateEncodingException("IOException encoding " +  | 
|
"PkiPath data: " + ioe, ioe);  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Encode the CertPath using PKCS#7 format. | 
|
     * | 
|
     * @return a byte array containing the binary encoding of the PKCS#7 object | 
|
     * @exception CertificateEncodingException if an exception occurs | 
|
*/  | 
|
private byte[] encodePKCS7() throws CertificateEncodingException {  | 
|
PKCS7 p7 = new PKCS7(new AlgorithmId[0],  | 
|
new ContentInfo(ContentInfo.DATA_OID, null),  | 
|
certs.toArray(new X509Certificate[certs.size()]),  | 
|
new SignerInfo[0]);  | 
|
DerOutputStream derout = new DerOutputStream();  | 
|
        try { | 
|
p7.encodeSignedData(derout);  | 
|
} catch (IOException ioe) {  | 
|
throw new CertificateEncodingException(ioe.getMessage());  | 
|
}  | 
|
return derout.toByteArray();  | 
|
}  | 
|
    /** | 
|
     * Returns the encoded form of this certification path, using the | 
|
     * specified encoding. | 
|
     * | 
|
     * @param encoding the name of the encoding to use | 
|
     * @return the encoded bytes | 
|
     * @exception CertificateEncodingException if an encoding error occurs or | 
|
     *   the encoding requested is not supported | 
|
*/  | 
|
@Override  | 
|
public byte[] getEncoded(String encoding)  | 
|
throws CertificateEncodingException {  | 
|
switch (encoding) {  | 
|
case PKIPATH_ENCODING:  | 
|
return encodePKIPATH();  | 
|
case PKCS7_ENCODING:  | 
|
return encodePKCS7();  | 
|
default:  | 
|
throw new CertificateEncodingException("unsupported encoding");  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Returns the encodings supported by this certification path, with the | 
|
     * default encoding first. | 
|
     * | 
|
     * @return an <code>Iterator</code> over the names of the supported | 
|
     *         encodings (as Strings) | 
|
*/  | 
|
public static Iterator<String> getEncodingsStatic() {  | 
|
return encodingList.iterator();  | 
|
}  | 
|
    /** | 
|
     * Returns an iteration of the encodings supported by this certification | 
|
     * path, with the default encoding first. | 
|
     * <p> | 
|
     * Attempts to modify the returned <code>Iterator</code> via its | 
|
     * <code>remove</code> method result in an | 
|
     * <code>UnsupportedOperationException</code>. | 
|
     * | 
|
     * @return an <code>Iterator</code> over the names of the supported | 
|
     *         encodings (as Strings) | 
|
*/  | 
|
@Override  | 
|
public Iterator<String> getEncodings() {  | 
|
return getEncodingsStatic();  | 
|
}  | 
|
    /** | 
|
     * Returns the list of certificates in this certification path. | 
|
     * The <code>List</code> returned must be immutable and thread-safe. | 
|
     * | 
|
     * @return an immutable <code>List</code> of <code>X509Certificate</code>s | 
|
     *         (may be empty, but not null) | 
|
*/  | 
|
@Override  | 
|
public List<X509Certificate> getCertificates() {  | 
|
return certs;  | 
|
}  | 
|
}  |