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  */
25 package sun.security.provider.certpath;
26 
27 import java.io.IOException;
28 import java.security.GeneralSecurityException;
29 import java.security.PublicKey;
30 import java.security.cert.CertificateEncodingException;
31 import java.security.cert.CertificateException;
32 import java.security.cert.X509Certificate;
33 import java.security.interfaces.DSAPublicKey;
34 
35 import javax.security.auth.x500.X500Principal;
36 
37 import sun.security.util.DerOutputStream;
38 import sun.security.util.DerValue;
39 import sun.security.util.Cache;
40 import sun.security.x509.X509CertImpl;
41 import 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 
72 public 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      */
X509CertificatePair()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      */
X509CertificatePair(X509Certificate forward, X509Certificate reverse)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      */
X509CertificatePair(byte[] encoded)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      */
clearCache()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      */
generateCertificatePair(byte[] encoded)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      */
setForward(X509Certificate cert)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      */
setReverse(X509Certificate cert)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      */
getForward()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      */
getReverse()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      */
getEncoded()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
toString()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 */
parse(DerValue val)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 */
emit(DerOutputStream out)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      */
checkPair()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