1/*
2 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package sun.security.provider.certpath;
26
27import java.io.IOException;
28import java.security.GeneralSecurityException;
29import java.security.PublicKey;
30import java.security.cert.CertificateEncodingException;
31import java.security.cert.CertificateException;
32import java.security.cert.X509Certificate;
33import java.security.interfaces.DSAPublicKey;
34
35import javax.security.auth.x500.X500Principal;
36
37import sun.security.util.DerOutputStream;
38import sun.security.util.DerValue;
39import sun.security.util.Cache;
40import sun.security.x509.X509CertImpl;
41import sun.security.provider.X509Factory;
42
43/**
44 * This class represents an X.509 Certificate Pair object, which is primarily
45 * used to hold a pair of cross certificates issued between Certification
46 * Authorities. The ASN.1 structure is listed below. The forward certificate
47 * of the CertificatePair contains a certificate issued to this CA by another
48 * CA. The reverse certificate of the CertificatePair contains a certificate
49 * issued by this CA to another CA. When both the forward and the reverse
50 * certificates are present in the CertificatePair, the issuer name in one
51 * certificate shall match the subject name in the other and vice versa, and
52 * the subject public key in one certificate shall be capable of verifying the
53 * digital signature on the other certificate and vice versa.  If a subject
54 * public key in one certificate does not contain required key algorithm
55 * parameters, then the signature check involving that key is not done.<p>
56 *
57 * The ASN.1 syntax for this object is:
58 * <pre>
59 * CertificatePair      ::=     SEQUENCE {
60 *      forward [0]     Certificate OPTIONAL,
61 *      reverse [1]     Certificate OPTIONAL
62 *                      -- at least one of the pair shall be present -- }
63 * </pre><p>
64 *
65 * This structure uses EXPLICIT tagging. References: Annex A of
66 * X.509(2000), X.509(1997).
67 *
68 * @author      Sean Mullan
69 * @since       1.4
70 */
71
72public class X509CertificatePair {
73
74    /* ASN.1 explicit tags */
75    private static final byte TAG_FORWARD = 0;
76    private static final byte TAG_REVERSE = 1;
77
78    private X509Certificate forward;
79    private X509Certificate reverse;
80    private byte[] encoded;
81
82    private static final Cache<Object, X509CertificatePair> cache
83        = Cache.newSoftMemoryCache(750);
84
85    /**
86     * Creates an empty instance of X509CertificatePair.
87     */
88    public X509CertificatePair() {}
89
90    /**
91     * Creates an instance of X509CertificatePair. At least one of
92     * the pair must be non-null.
93     *
94     * @param forward The forward component of the certificate pair
95     *          which represents a certificate issued to this CA by other CAs.
96     * @param reverse The reverse component of the certificate pair
97     *          which represents a certificate issued by this CA to other CAs.
98     * @throws CertificateException If an exception occurs.
99     */
100    public X509CertificatePair(X509Certificate forward, X509Certificate reverse)
101                throws CertificateException {
102        if (forward == null && reverse == null) {
103            throw new CertificateException("at least one of certificate pair "
104                + "must be non-null");
105        }
106
107        this.forward = forward;
108        this.reverse = reverse;
109
110        checkPair();
111    }
112
113    /**
114     * Create a new X509CertificatePair from its encoding.
115     *
116     * For internal use only, external code should use generateCertificatePair.
117     */
118    private X509CertificatePair(byte[] encoded) throws CertificateException {
119        try {
120            parse(new DerValue(encoded));
121            this.encoded = encoded;
122        } catch (IOException ex) {
123            throw new CertificateException(ex.toString());
124        }
125        checkPair();
126    }
127
128    /**
129     * Clear the cache for debugging.
130     */
131    public static synchronized void clearCache() {
132        cache.clear();
133    }
134
135    /**
136     * Create a X509CertificatePair from its encoding. Uses cache lookup
137     * if possible.
138     */
139    public static synchronized X509CertificatePair generateCertificatePair
140            (byte[] encoded) throws CertificateException {
141        Object key = new Cache.EqualByteArray(encoded);
142        X509CertificatePair pair = cache.get(key);
143        if (pair != null) {
144            return pair;
145        }
146        pair = new X509CertificatePair(encoded);
147        key = new Cache.EqualByteArray(pair.encoded);
148        cache.put(key, pair);
149        return pair;
150    }
151
152    /**
153     * Sets the forward component of the certificate pair.
154     */
155    public void setForward(X509Certificate cert) throws CertificateException {
156        checkPair();
157        forward = cert;
158    }
159
160    /**
161     * Sets the reverse component of the certificate pair.
162     */
163    public void setReverse(X509Certificate cert) throws CertificateException {
164        checkPair();
165        reverse = cert;
166    }
167
168    /**
169     * Returns the forward component of the certificate pair.
170     *
171     * @return The forward certificate, or null if not set.
172     */
173    public X509Certificate getForward() {
174        return forward;
175    }
176
177    /**
178     * Returns the reverse component of the certificate pair.
179     *
180     * @return The reverse certificate, or null if not set.
181     */
182    public X509Certificate getReverse() {
183        return reverse;
184    }
185
186    /**
187     * Return the DER encoded form of the certificate pair.
188     *
189     * @return The encoded form of the certificate pair.
190     * @throws CerticateEncodingException If an encoding exception occurs.
191     */
192    public byte[] getEncoded() throws CertificateEncodingException {
193        try {
194            if (encoded == null) {
195                DerOutputStream tmp = new DerOutputStream();
196                emit(tmp);
197                encoded = tmp.toByteArray();
198            }
199        } catch (IOException ex) {
200            throw new CertificateEncodingException(ex.toString());
201        }
202        return encoded;
203    }
204
205    /**
206     * Return a printable representation of the certificate pair.
207     *
208     * @return A String describing the contents of the pair.
209     */
210    @Override
211    public String toString() {
212        StringBuilder sb = new StringBuilder();
213        sb.append("X.509 Certificate Pair: [\n");
214        if (forward != null)
215            sb.append("  Forward: ").append(forward).append("\n");
216        if (reverse != null)
217            sb.append("  Reverse: ").append(reverse).append("\n");
218        sb.append("]");
219        return sb.toString();
220    }
221
222    /* Parse the encoded bytes */
223    private void parse(DerValue val)
224        throws IOException, CertificateException
225    {
226        if (val.tag != DerValue.tag_Sequence) {
227            throw new IOException
228                ("Sequence tag missing for X509CertificatePair");
229        }
230
231        while (val.data != null && val.data.available() != 0) {
232            DerValue opt = val.data.getDerValue();
233            short tag = (byte) (opt.tag & 0x01f);
234            switch (tag) {
235                case TAG_FORWARD:
236                    if (opt.isContextSpecific() && opt.isConstructed()) {
237                        if (forward != null) {
238                            throw new IOException("Duplicate forward "
239                                + "certificate in X509CertificatePair");
240                        }
241                        opt = opt.data.getDerValue();
242                        forward = X509Factory.intern
243                                        (new X509CertImpl(opt.toByteArray()));
244                    }
245                    break;
246                case TAG_REVERSE:
247                    if (opt.isContextSpecific() && opt.isConstructed()) {
248                        if (reverse != null) {
249                            throw new IOException("Duplicate reverse "
250                                + "certificate in X509CertificatePair");
251                        }
252                        opt = opt.data.getDerValue();
253                        reverse = X509Factory.intern
254                                        (new X509CertImpl(opt.toByteArray()));
255                    }
256                    break;
257                default:
258                    throw new IOException("Invalid encoding of "
259                        + "X509CertificatePair");
260            }
261        }
262        if (forward == null && reverse == null) {
263            throw new CertificateException("at least one of certificate pair "
264                + "must be non-null");
265        }
266    }
267
268    /* Translate to encoded bytes */
269    private void emit(DerOutputStream out)
270        throws IOException, CertificateEncodingException
271    {
272        DerOutputStream tagged = new DerOutputStream();
273
274        if (forward != null) {
275            DerOutputStream tmp = new DerOutputStream();
276            tmp.putDerValue(new DerValue(forward.getEncoded()));
277            tagged.write(DerValue.createTag(DerValue.TAG_CONTEXT,
278                         true, TAG_FORWARD), tmp);
279        }
280
281        if (reverse != null) {
282            DerOutputStream tmp = new DerOutputStream();
283            tmp.putDerValue(new DerValue(reverse.getEncoded()));
284            tagged.write(DerValue.createTag(DerValue.TAG_CONTEXT,
285                         true, TAG_REVERSE), tmp);
286        }
287
288        out.write(DerValue.tag_Sequence, tagged);
289    }
290
291    /*
292     * Check for a valid certificate pair
293     */
294    private void checkPair() throws CertificateException {
295
296        /* if either of pair is missing, return w/o error */
297        if (forward == null || reverse == null) {
298            return;
299        }
300        /*
301         * If both elements of the pair are present, check that they
302         * are a valid pair.
303         */
304        X500Principal fwSubject = forward.getSubjectX500Principal();
305        X500Principal fwIssuer = forward.getIssuerX500Principal();
306        X500Principal rvSubject = reverse.getSubjectX500Principal();
307        X500Principal rvIssuer = reverse.getIssuerX500Principal();
308        if (!fwIssuer.equals(rvSubject) || !rvIssuer.equals(fwSubject)) {
309            throw new CertificateException("subject and issuer names in "
310                + "forward and reverse certificates do not match");
311        }
312
313        /* check signatures unless key parameters are missing */
314        try {
315            PublicKey pk = reverse.getPublicKey();
316            if (!(pk instanceof DSAPublicKey) ||
317                        ((DSAPublicKey)pk).getParams() != null) {
318                forward.verify(pk);
319            }
320            pk = forward.getPublicKey();
321            if (!(pk instanceof DSAPublicKey) ||
322                        ((DSAPublicKey)pk).getParams() != null) {
323                reverse.verify(pk);
324            }
325        } catch (GeneralSecurityException e) {
326            throw new CertificateException("invalid signature: "
327                + e.getMessage());
328        }
329    }
330}
331