      1 /*
      2  * Copyright (c) 2007, 2020, 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
     26 package java.security.cert;
     27
     28 import java.io.ObjectInputStream;
     29 import java.io.ObjectOutputStream;
     30 import java.io.IOException;
     31 import java.util.Collections;
     32 import java.util.Date;
     33 import java.util.HashMap;
     34 import java.util.Map;
     35 import javax.security.auth.x500.X500Principal;
     36
     37 import sun.security.util.IOUtils;
     38 import sun.security.util.KnownOIDs;
     39 import sun.security.util.ObjectIdentifier;
     40 import sun.security.x509.InvalidityDateExtension;
     41
     42 /**
     43  * An exception that indicates an X.509 certificate is revoked. A
     44  * {@code CertificateRevokedException} contains additional information
     45  * about the revoked certificate, such as the date on which the
     46  * certificate was revoked and the reason it was revoked.
     47  *
     48  * @author Sean Mullan
     49  * @since 1.7
     50  * @see CertPathValidatorException
     51  */
     52 public class CertificateRevokedException extends CertificateException {
     53
     54     @java.io.Serial
     55     private static final long serialVersionUID = 7839996631571608627L;
     56
     57     /**
     58      * @serial the date on which the certificate was revoked
     59      */
     60     private Date revocationDate;
     61     /**
     62      * @serial the revocation reason
     63      */
     64     private final CRLReason reason;
     65     /**
     66      * @serial the {@code X500Principal} that represents the name of the
     67      * authority that signed the certificate's revocation status information
     68      */
     69     private final X500Principal authority;
     70
     71     private transient Map<String, Extension> extensions;
     72
     73     /**
     74      * Constructs a {@code CertificateRevokedException} with
     75      * the specified revocation date, reason code, authority name, and map
     76      * of extensions.
     77      *
     78      * @param revocationDate the date on which the certificate was revoked. The
     79      *    date is copied to protect against subsequent modification.
     80      * @param reason the revocation reason
     81      * @param extensions a map of X.509 Extensions. Each key is an OID String
     82      *    that maps to the corresponding Extension. The map is copied to
     83      *    prevent subsequent modification.
     84      * @param authority the {@code X500Principal} that represents the name
     85      *    of the authority that signed the certificate's revocation status
     86      *    information
     87      * @throws NullPointerException if {@code revocationDate},
     88      *    {@code reason}, {@code authority}, or
     89      *    {@code extensions} is {@code null}
     90      * @throws ClassCastException if {@code extensions} contains an incorrectly
     91      *    typed key or value
     92      */
     93     public CertificateRevokedException(Date revocationDate, CRLReason reason,
     94         X500Principal authority, Map<String, Extension> extensions) {
     95         if (revocationDate == null || reason == null || authority == null ||
     96             extensions == null) {
     97             throw new NullPointerException();
     98         }
     99         this.revocationDate = new Date(revocationDate.getTime());
    100         this.reason = reason;
    101         this.authority = authority;
    102         // make sure Map only contains correct types
    103         this.extensions = Collections.checkedMap(new HashMap<>(),
    104                                                  String.class, Extension.class);
    105         this.extensions.putAll(extensions);
    106     }
    107
    108     /**
    109      * Returns the date on which the certificate was revoked. A new copy is
    110      * returned each time the method is invoked to protect against subsequent
    111      * modification.
    112      *
    113      * @return the revocation date
    114      */
    115     public Date getRevocationDate() {
    116         return (Date) revocationDate.clone();
    117     }
    118
    119     /**
    120      * Returns the reason the certificate was revoked.
    121      *
    122      * @return the revocation reason
    123      */
    124     public CRLReason getRevocationReason() {
    125         return reason;
    126     }
    127
    128     /**
    129      * Returns the name of the authority that signed the certificate's
    130      * revocation status information.
    131      *
    132      * @return the {@code X500Principal} that represents the name of the
    133      *     authority that signed the certificate's revocation status information
    134      */
    135     public X500Principal getAuthorityName() {
    136         return authority;
    137     }
    138
    139     /**
    140      * Returns the invalidity date, as specified in the Invalidity Date
    141      * extension of this {@code CertificateRevokedException}. The
    142      * invalidity date is the date on which it is known or suspected that the
    143      * private key was compromised or that the certificate otherwise became
    144      * invalid. This implementation calls {@code getExtensions()} and
    145      * checks the returned map for an entry for the Invalidity Date extension
    146      * OID ("2.5.29.24"). If found, it returns the invalidity date in the
    147      * extension; otherwise null. A new Date object is returned each time the
    148      * method is invoked to protect against subsequent modification.
    149      *
    150      * @return the invalidity date, or {@code null} if not specified
    151      */
    152     public Date getInvalidityDate() {
    153         Extension ext = getExtensions().get(KnownOIDs.InvalidityDate.value());
    154         if (ext == null) {
    155             return null;
    156         } else {
    157             try {
    158                 Date invalidity = InvalidityDateExtension.toImpl(ext).get("DATE");
    159                 return new Date(invalidity.getTime());
    160             } catch (IOException ioe) {
    161                 return null;
    162             }
    163         }
    164     }
    165
    166     /**
    167      * Returns a map of X.509 extensions containing additional information
    168      * about the revoked certificate, such as the Invalidity Date
    169      * Extension. Each key is an OID String that maps to the corresponding
    170      * Extension.
    171      *
    172      * @return an unmodifiable map of X.509 extensions, or an empty map
    173      *    if there are no extensions
    174      */
    175     public Map<String, Extension> getExtensions() {
    176         return Collections.unmodifiableMap(extensions);
    177     }
    178
    179     @Override
    180     public String getMessage() {
    181         return "Certificate has been revoked, reason: "
    182                + reason + ", revocation date: " + revocationDate
    183                + ", authority: " + authority + ", extension OIDs: "
    184                + extensions.keySet();
    185     }
    186
    187     /**
    188      * Serialize this {@code CertificateRevokedException} instance.
    189      *
    190      * @serialData the size of the extensions map (int), followed by all of
    191      * the extensions in the map, in no particular order. For each extension,
    192      * the following data is emitted: the OID String (Object), the criticality
    193      * flag (boolean), the length of the encoded extension value byte array
    194      * (int), and the encoded extension value bytes.
    195      */
    196     @java.io.Serial
    197     private void writeObject(ObjectOutputStream oos) throws IOException {
    198         // Write out the non-transient fields
    199         // (revocationDate, reason, authority)
    200         oos.defaultWriteObject();
    201
    202         // Write out the size (number of mappings) of the extensions map
    203         oos.writeInt(extensions.size());
    204
    205         // For each extension in the map, the following are emitted (in order):
    206         // the OID String (Object), the criticality flag (boolean), the length
    207         // of the encoded extension value byte array (int), and the encoded
    208         // extension value byte array. The extensions themselves are emitted
    209         // in no particular order.
    210         for (Map.Entry<String, Extension> entry : extensions.entrySet()) {
    211             Extension ext = entry.getValue();
    212             oos.writeObject(ext.getId());
    213             oos.writeBoolean(ext.isCritical());
    214             byte[] extVal = ext.getValue();
    215             oos.writeInt(extVal.length);
    216             oos.write(extVal);
    217         }
    218     }
    219
    220     /**
    221      * Deserialize the {@code CertificateRevokedException} instance.
    222      */
    223     @java.io.Serial
    224     private void readObject(ObjectInputStream ois)
    225         throws IOException, ClassNotFoundException {
    226         // Read in the non-transient fields
    227         // (revocationDate, reason, authority)
    228         ois.defaultReadObject();
    229
    230         // Defensively copy the revocation date
    231         revocationDate = new Date(revocationDate.getTime());
    232
    233         // Read in the size (number of mappings) of the extensions map
    234         // and create the extensions map
    235         int size = ois.readInt();
    236         if (size == 0) {
    237             extensions = Collections.emptyMap();
    238         } else if (size < 0) {
    239             throw new IOException("size cannot be negative");
    240         } else {
    241             extensions = new HashMap<>(size > 20 ? 20 : size);
    242         }
    243
    244         // Read in the extensions and put the mappings in the extensions map
    245         for (int i = 0; i < size; i++) {
    246             String oid = (String) ois.readObject();
    247             boolean critical = ois.readBoolean();
    248             byte[] extVal = IOUtils.readExactlyNBytes(ois, ois.readInt());
    249             Extension ext = sun.security.x509.Extension.newExtension
    250                 (ObjectIdentifier.of(oid), critical, extVal);
    251             extensions.put(oid, ext);
    252         }
    253     }
    254 }