Home | History | Annotate | Download | only in certpath
      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      */
     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