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