1/*
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
18package java.security.cert;
19
20import java.io.ByteArrayInputStream;
21import java.io.NotSerializableException;
22import java.io.ObjectStreamException;
23import java.io.ObjectStreamField;
24import java.io.Serializable;
25import java.util.Iterator;
26import java.util.List;
27
28/**
29 * An immutable certificate path that can be validated. All certificates in the
30 * path are of the same type (i.e., X509).
31 * <p>
32 * A {@code CertPath} can be represented as a byte array in at least one
33 * supported encoding scheme (i.e. PkiPath or PKCS7) when serialized.
34 * <p>
35 * When a {@code List} of the certificates is obtained it must be immutable.
36 * <p>
37 * A {@code CertPath} must be thread-safe without requiring coordinated access.
38 *
39 * @see Certificate
40 */
41public abstract class CertPath implements Serializable {
42
43    private static final long serialVersionUID = 6068470306649138683L;
44    // Standard name of the type of certificates in this path
45    private final String type;
46
47    /**
48     * Creates a new {@code CertPath} instance for the specified certificate
49     * type.
50     *
51     * @param type
52     *            the certificate type.
53     */
54    protected CertPath(String type) {
55        this.type = type;
56    }
57
58    /**
59     * Returns the type of {@code Certificate} in this instance.
60     *
61     * @return the certificate type.
62     */
63    public String getType() {
64        return type;
65    }
66
67    /**
68     * Returns {@code true} if {@code Certificate}s in the list are the same
69     * type and the lists are equal (and by implication the certificates
70     * contained within are the same).
71     *
72     * @param other
73     *            {@code CertPath} to be compared for equality.
74     * @return {@code true} if the object are equal, {@code false} otherwise.
75     */
76    public boolean equals(Object other) {
77        if (this == other) {
78            return true;
79        }
80        if (other instanceof CertPath) {
81            CertPath o = (CertPath)other;
82            if (getType().equals(o.getType())) {
83                if (getCertificates().equals(o.getCertificates())) {
84                    return true;
85                }
86            }
87        }
88        return false;
89    }
90
91    /**
92     * Overrides {@code Object.hashCode()}. The function is defined as follows:
93     * <pre>
94     * {@code hashCode = 31 * path.getType().hashCode() +
95     * path.getCertificates().hashCode();}
96     * </pre>
97     *
98     * @return the hash code for this instance.
99     */
100    public int hashCode() {
101        int hash = getType().hashCode();
102        hash = hash*31 + getCertificates().hashCode();
103        return hash;
104    }
105
106    /**
107     * Returns a {@code String} representation of this {@code CertPath}
108     * instance. It is the result of calling {@code toString} on all {@code
109     * Certificate}s in the {@code List}.
110     *
111     * @return a string representation of this instance.
112     */
113    public String toString() {
114        StringBuilder sb = new StringBuilder(getType());
115        sb.append(" Cert Path, len=");
116        sb.append(getCertificates().size());
117        sb.append(": [\n");
118        int n=1;
119        for (Iterator<? extends Certificate> i=getCertificates().iterator(); i.hasNext(); n++) {
120            sb.append("---------------certificate ");
121            sb.append(n);
122            sb.append("---------------\n");
123            sb.append(((Certificate)i.next()).toString());
124        }
125        sb.append("\n]");
126        return sb.toString();
127    }
128
129    /**
130     * Returns an immutable List of the {@code Certificate}s contained
131     * in the {@code CertPath}.
132     *
133     * @return a list of {@code Certificate}s in the {@code CertPath}.
134     */
135    public abstract List<? extends Certificate> getCertificates();
136
137    /**
138     * Returns an encoding of the {@code CertPath} using the default encoding.
139     *
140     * @return default encoding of the {@code CertPath}.
141     * @throws CertificateEncodingException
142     *             if the encoding fails.
143     */
144    public abstract byte[] getEncoded()
145        throws CertificateEncodingException;
146
147    /**
148     * Returns an encoding of the {@code CertPath} using the specified encoding.
149     *
150     * @param encoding
151     *            encoding that should be generated.
152     * @return default encoding of the {@code CertPath}.
153     * @throws CertificateEncodingException
154     *             if the encoding fails.
155     */
156    public abstract byte[] getEncoded(String encoding)
157        throws CertificateEncodingException;
158
159    /**
160     * Returns an {@code Iterator} over the supported encodings for a
161     * representation of the certificate path.
162     *
163     * @return {@code Iterator} over supported encodings (as {@code String}s).
164     */
165    public abstract Iterator<String> getEncodings();
166
167    /**
168     * Returns an alternate object to be serialized.
169     *
170     * @return an alternate object to be serialized.
171     * @throws ObjectStreamException
172     *             if the creation of the alternate object fails.
173     */
174    protected Object writeReplace() throws ObjectStreamException {
175        try {
176            return new CertPathRep(getType(), getEncoded());
177        } catch (CertificateEncodingException e) {
178            throw new NotSerializableException("Could not create serialization object: " + e);
179        }
180    }
181
182    /**
183     * The alternate {@code Serializable} class to be used for serialization and
184     * deserialization on {@code CertPath} objects.
185     */
186    protected static class CertPathRep implements Serializable {
187
188        private static final long serialVersionUID = 3015633072427920915L;
189        // Standard name of the type of certificates in this path
190        private final String type;
191        // cert path data
192        private final byte[] data;
193
194        // Force default serialization to use writeUnshared/readUnshared
195        // for cert path data
196        private static final ObjectStreamField[] serialPersistentFields = {
197            new ObjectStreamField("type", String.class),
198            new ObjectStreamField("data", byte[].class, true),
199        };
200
201        /**
202         * Creates a new {@code CertPathRep} instance with the specified type
203         * and encoded data.
204         *
205         * @param type
206         *            the certificate type.
207         * @param data
208         *            the encoded data.
209         */
210        protected CertPathRep(String type, byte[] data) {
211            this.type = type;
212            this.data = data;
213        }
214
215        /**
216         * Deserializes a {@code CertPath} from a serialized {@code CertPathRep}
217         * object.
218         *
219         * @return the deserialized {@code CertPath}.
220         * @throws ObjectStreamException
221         *             if deserialization fails.
222         */
223        protected Object readResolve() throws ObjectStreamException {
224            try {
225                CertificateFactory cf = CertificateFactory.getInstance(type);
226                return cf.generateCertPath(new ByteArrayInputStream(data));
227            } catch (Throwable t) {
228                throw new NotSerializableException("Could not resolve cert path: " + t);
229            }
230        }
231    }
232}
233