1 /* 2 * Copyright (c) 1997, 2006, 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 sun.security.x509; 27 28 import java.io.InputStream; 29 import java.io.IOException; 30 import java.security.cert.CRLException; 31 import java.security.cert.CRLReason; 32 import java.security.cert.CertificateException; 33 import java.security.cert.X509CRLEntry; 34 import java.math.BigInteger; 35 import java.util.*; 36 37 import javax.security.auth.x500.X500Principal; 38 39 import sun.security.util.*; 40 import sun.misc.HexDumpEncoder; 41 42 /** 43 * <p>Abstract class for a revoked certificate in a CRL. 44 * This class is for each entry in the <code>revokedCertificates</code>, 45 * so it deals with the inner <em>SEQUENCE</em>. 46 * The ASN.1 definition for this is: 47 * <pre> 48 * revokedCertificates SEQUENCE OF SEQUENCE { 49 * userCertificate CertificateSerialNumber, 50 * revocationDate ChoiceOfTime, 51 * crlEntryExtensions Extensions OPTIONAL 52 * -- if present, must be v2 53 * } OPTIONAL 54 * 55 * CertificateSerialNumber ::= INTEGER 56 * 57 * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension 58 * 59 * Extension ::= SEQUENCE { 60 * extnId OBJECT IDENTIFIER, 61 * critical BOOLEAN DEFAULT FALSE, 62 * extnValue OCTET STRING 63 * -- contains a DER encoding of a value 64 * -- of the type registered for use with 65 * -- the extnId object identifier value 66 * } 67 * </pre> 68 * 69 * @author Hemma Prafullchandra 70 */ 71 72 public class X509CRLEntryImpl extends X509CRLEntry 73 implements Comparable<X509CRLEntryImpl> { 74 75 private SerialNumber serialNumber = null; 76 private Date revocationDate = null; 77 private CRLExtensions extensions = null; 78 private byte[] revokedCert = null; 79 private X500Principal certIssuer; 80 81 private final static boolean isExplicit = false; 82 private static final long YR_2050 = 2524636800000L; 83 84 /** 85 * Constructs a revoked certificate entry using the given 86 * serial number and revocation date. 87 * 88 * @param num the serial number of the revoked certificate. 89 * @param date the Date on which revocation took place. 90 */ 91 public X509CRLEntryImpl(BigInteger num, Date date) { 92 this.serialNumber = new SerialNumber(num); 93 this.revocationDate = date; 94 } 95 96 /** 97 * Constructs a revoked certificate entry using the given 98 * serial number, revocation date and the entry 99 * extensions. 100 * 101 * @param num the serial number of the revoked certificate. 102 * @param date the Date on which revocation took place. 103 * @param crlEntryExts the extensions for this entry. 104 */ 105 public X509CRLEntryImpl(BigInteger num, Date date, 106 CRLExtensions crlEntryExts) { 107 this.serialNumber = new SerialNumber(num); 108 this.revocationDate = date; 109 this.extensions = crlEntryExts; 110 } 111 112 /** 113 * Unmarshals a revoked certificate from its encoded form. 114 * 115 * @param revokedCert the encoded bytes. 116 * @exception CRLException on parsing errors. 117 */ 118 public X509CRLEntryImpl(byte[] revokedCert) throws CRLException { 119 try { 120 parse(new DerValue(revokedCert)); 121 } catch (IOException e) { 122 this.revokedCert = null; 123 throw new CRLException("Parsing error: " + e.toString()); 124 } 125 } 126 127 /** 128 * Unmarshals a revoked certificate from its encoded form. 129 * 130 * @param derVal the DER value containing the revoked certificate. 131 * @exception CRLException on parsing errors. 132 */ 133 public X509CRLEntryImpl(DerValue derValue) throws CRLException { 134 try { 135 parse(derValue); 136 } catch (IOException e) { 137 revokedCert = null; 138 throw new CRLException("Parsing error: " + e.toString()); 139 } 140 } 141 142 /** 143 * Returns true if this revoked certificate entry has 144 * extensions, otherwise false. 145 * 146 * @return true if this CRL entry has extensions, otherwise 147 * false. 148 */ 149 public boolean hasExtensions() { 150 return (extensions != null); 151 } 152 153 /** 154 * Encodes the revoked certificate to an output stream. 155 * 156 * @param outStrm an output stream to which the encoded revoked 157 * certificate is written. 158 * @exception CRLException on encoding errors. 159 */ 160 public void encode(DerOutputStream outStrm) throws CRLException { 161 try { 162 if (revokedCert == null) { 163 DerOutputStream tmp = new DerOutputStream(); 164 // sequence { serialNumber, revocationDate, extensions } 165 serialNumber.encode(tmp); 166 167 if (revocationDate.getTime() < YR_2050) { 168 tmp.putUTCTime(revocationDate); 169 } else { 170 tmp.putGeneralizedTime(revocationDate); 171 } 172 173 if (extensions != null) 174 extensions.encode(tmp, isExplicit); 175 176 DerOutputStream seq = new DerOutputStream(); 177 seq.write(DerValue.tag_Sequence, tmp); 178 179 revokedCert = seq.toByteArray(); 180 } 181 outStrm.write(revokedCert); 182 } catch (IOException e) { 183 throw new CRLException("Encoding error: " + e.toString()); 184 } 185 } 186 187 /** 188 * Returns the ASN.1 DER-encoded form of this CRL Entry, 189 * which corresponds to the inner SEQUENCE. 190 * 191 * @exception CRLException if an encoding error occurs. 192 */ 193 public byte[] getEncoded() throws CRLException { 194 return getEncoded0().clone(); 195 } 196 197 // Called internally to avoid clone 198 private byte[] getEncoded0() throws CRLException { 199 if (revokedCert == null) 200 this.encode(new DerOutputStream()); 201 return revokedCert; 202 } 203 204 @Override 205 public X500Principal getCertificateIssuer() { 206 return certIssuer; 207 } 208 209 void setCertificateIssuer(X500Principal crlIssuer, X500Principal certIssuer) { 210 if (crlIssuer.equals(certIssuer)) { 211 this.certIssuer = null; 212 } else { 213 this.certIssuer = certIssuer; 214 } 215 } 216 217 /** 218 * Gets the serial number from this X509CRLEntry, 219 * i.e. the <em>userCertificate</em>. 220 * 221 * @return the serial number. 222 */ 223 public BigInteger getSerialNumber() { 224 return serialNumber.getNumber(); 225 } 226 227 /** 228 * Gets the revocation date from this X509CRLEntry, 229 * the <em>revocationDate</em>. 230 * 231 * @return the revocation date. 232 */ 233 public Date getRevocationDate() { 234 return new Date(revocationDate.getTime()); 235 } 236 237 /** 238 * This method is the overridden implementation of the getRevocationReason 239 * method in X509CRLEntry. It is better performance-wise since it returns 240 * cached values. 241 */ 242 @Override 243 public CRLReason getRevocationReason() { 244 Extension ext = getExtension(PKIXExtensions.ReasonCode_Id); 245 if (ext == null) { 246 return null; 247 } 248 CRLReasonCodeExtension rcExt = (CRLReasonCodeExtension) ext; 249 return rcExt.getReasonCode(); 250 } 251 252 /** 253 * This static method is the default implementation of the 254 * getRevocationReason method in X509CRLEntry. 255 */ 256 public static CRLReason getRevocationReason(X509CRLEntry crlEntry) { 257 try { 258 byte[] ext = crlEntry.getExtensionValue("2.5.29.21"); 259 if (ext == null) { 260 return null; 261 } 262 DerValue val = new DerValue(ext); 263 byte[] data = val.getOctetString(); 264 265 CRLReasonCodeExtension rcExt = 266 new CRLReasonCodeExtension(Boolean.FALSE, data); 267 return rcExt.getReasonCode(); 268 } catch (IOException ioe) { 269 return null; 270 } 271 } 272 273 /** 274 * get Reason Code from CRL entry. 275 * 276 * @returns Integer or null, if no such extension 277 * @throws IOException on error 278 */ 279 public Integer getReasonCode() throws IOException { 280 Object obj = getExtension(PKIXExtensions.ReasonCode_Id); 281 if (obj == null) 282 return null; 283 CRLReasonCodeExtension reasonCode = (CRLReasonCodeExtension)obj; 284 return (Integer)(reasonCode.get(reasonCode.REASON)); 285 } 286 287 /** 288 * Returns a printable string of this revoked certificate. 289 * 290 * @return value of this revoked certificate in a printable form. 291 */ 292 @Override 293 public String toString() { 294 StringBuilder sb = new StringBuilder(); 295 296 sb.append(serialNumber.toString()); 297 sb.append(" On: " + revocationDate.toString()); 298 if (certIssuer != null) { 299 sb.append("\n Certificate issuer: " + certIssuer); 300 } 301 if (extensions != null) { 302 Collection allEntryExts = extensions.getAllExtensions(); 303 Object[] objs = allEntryExts.toArray(); 304 305 sb.append("\n CRL Entry Extensions: " + objs.length); 306 for (int i = 0; i < objs.length; i++) { 307 sb.append("\n [" + (i+1) + "]: "); 308 Extension ext = (Extension)objs[i]; 309 try { 310 if (OIDMap.getClass(ext.getExtensionId()) == null) { 311 sb.append(ext.toString()); 312 byte[] extValue = ext.getExtensionValue(); 313 if (extValue != null) { 314 DerOutputStream out = new DerOutputStream(); 315 out.putOctetString(extValue); 316 extValue = out.toByteArray(); 317 HexDumpEncoder enc = new HexDumpEncoder(); 318 sb.append("Extension unknown: " 319 + "DER encoded OCTET string =\n" 320 + enc.encodeBuffer(extValue) + "\n"); 321 } 322 } else 323 sb.append(ext.toString()); //sub-class exists 324 } catch (Exception e) { 325 sb.append(", Error parsing this extension"); 326 } 327 } 328 } 329 sb.append("\n"); 330 return sb.toString(); 331 } 332 333 /** 334 * Return true if a critical extension is found that is 335 * not supported, otherwise return false. 336 */ 337 public boolean hasUnsupportedCriticalExtension() { 338 if (extensions == null) 339 return false; 340 return extensions.hasUnsupportedCriticalExtension(); 341 } 342 343 /** 344 * Gets a Set of the extension(s) marked CRITICAL in this 345 * X509CRLEntry. In the returned set, each extension is 346 * represented by its OID string. 347 * 348 * @return a set of the extension oid strings in the 349 * Object that are marked critical. 350 */ 351 public Set<String> getCriticalExtensionOIDs() { 352 if (extensions == null) { 353 return null; 354 } 355 Set<String> extSet = new TreeSet<>(); 356 for (Extension ex : extensions.getAllExtensions()) { 357 if (ex.isCritical()) { 358 extSet.add(ex.getExtensionId().toString()); 359 } 360 } 361 return extSet; 362 } 363 364 /** 365 * Gets a Set of the extension(s) marked NON-CRITICAL in this 366 * X509CRLEntry. In the returned set, each extension is 367 * represented by its OID string. 368 * 369 * @return a set of the extension oid strings in the 370 * Object that are marked critical. 371 */ 372 public Set<String> getNonCriticalExtensionOIDs() { 373 if (extensions == null) { 374 return null; 375 } 376 Set<String> extSet = new TreeSet<>(); 377 for (Extension ex : extensions.getAllExtensions()) { 378 if (!ex.isCritical()) { 379 extSet.add(ex.getExtensionId().toString()); 380 } 381 } 382 return extSet; 383 } 384 385 /** 386 * Gets the DER encoded OCTET string for the extension value 387 * (<em>extnValue</em>) identified by the passed in oid String. 388 * The <code>oid</code> string is 389 * represented by a set of positive whole number separated 390 * by ".", that means,<br> 391 * <positive whole number>.<positive whole number>.<positive 392 * whole number>.<...> 393 * 394 * @param oid the Object Identifier value for the extension. 395 * @return the DER encoded octet string of the extension value. 396 */ 397 public byte[] getExtensionValue(String oid) { 398 if (extensions == null) 399 return null; 400 try { 401 String extAlias = OIDMap.getName(new ObjectIdentifier(oid)); 402 Extension crlExt = null; 403 404 if (extAlias == null) { // may be unknown 405 ObjectIdentifier findOID = new ObjectIdentifier(oid); 406 Extension ex = null; 407 ObjectIdentifier inCertOID; 408 for (Enumeration<Extension> e = extensions.getElements(); 409 e.hasMoreElements();) { 410 ex = e.nextElement(); 411 inCertOID = ex.getExtensionId(); 412 if (inCertOID.equals(findOID)) { 413 crlExt = ex; 414 break; 415 } 416 } 417 } else 418 crlExt = extensions.get(extAlias); 419 if (crlExt == null) 420 return null; 421 byte[] extData = crlExt.getExtensionValue(); 422 if (extData == null) 423 return null; 424 425 DerOutputStream out = new DerOutputStream(); 426 out.putOctetString(extData); 427 return out.toByteArray(); 428 } catch (Exception e) { 429 return null; 430 } 431 } 432 433 /** 434 * get an extension 435 * 436 * @param oid ObjectIdentifier of extension desired 437 * @returns Extension of type <extension> or null, if not found 438 */ 439 public Extension getExtension(ObjectIdentifier oid) { 440 if (extensions == null) 441 return null; 442 443 // following returns null if no such OID in map 444 //XXX consider cloning this 445 return extensions.get(OIDMap.getName(oid)); 446 } 447 448 private void parse(DerValue derVal) 449 throws CRLException, IOException { 450 451 if (derVal.tag != DerValue.tag_Sequence) { 452 throw new CRLException("Invalid encoded RevokedCertificate, " + 453 "starting sequence tag missing."); 454 } 455 if (derVal.data.available() == 0) 456 throw new CRLException("No data encoded for RevokedCertificates"); 457 458 revokedCert = derVal.toByteArray(); 459 // serial number 460 DerInputStream in = derVal.toDerInputStream(); 461 DerValue val = in.getDerValue(); 462 this.serialNumber = new SerialNumber(val); 463 464 // revocationDate 465 int nextByte = derVal.data.peekByte(); 466 if ((byte)nextByte == DerValue.tag_UtcTime) { 467 this.revocationDate = derVal.data.getUTCTime(); 468 } else if ((byte)nextByte == DerValue.tag_GeneralizedTime) { 469 this.revocationDate = derVal.data.getGeneralizedTime(); 470 } else 471 throw new CRLException("Invalid encoding for revocation date"); 472 473 if (derVal.data.available() == 0) 474 return; // no extensions 475 476 // crlEntryExtensions 477 this.extensions = new CRLExtensions(derVal.toDerInputStream()); 478 } 479 480 /** 481 * Utility method to convert an arbitrary instance of X509CRLEntry 482 * to a X509CRLEntryImpl. Does a cast if possible, otherwise reparses 483 * the encoding. 484 */ 485 public static X509CRLEntryImpl toImpl(X509CRLEntry entry) 486 throws CRLException { 487 if (entry instanceof X509CRLEntryImpl) { 488 return (X509CRLEntryImpl)entry; 489 } else { 490 return new X509CRLEntryImpl(entry.getEncoded()); 491 } 492 } 493 494 /** 495 * Returns the CertificateIssuerExtension 496 * 497 * @return the CertificateIssuerExtension, or null if it does not exist 498 */ 499 CertificateIssuerExtension getCertificateIssuerExtension() { 500 return (CertificateIssuerExtension) 501 getExtension(PKIXExtensions.CertificateIssuer_Id); 502 } 503 504 /** 505 * Returns all extensions for this entry in a map 506 * @return the extension map, can be empty, but not null 507 */ 508 public Map<String, java.security.cert.Extension> getExtensions() { 509 if (extensions == null) { 510 return Collections.emptyMap(); 511 } 512 Collection<Extension> exts = extensions.getAllExtensions(); 513 Map<String, java.security.cert.Extension> map = new TreeMap<>(); 514 for (Extension ext : exts) { 515 map.put(ext.getId(), ext); 516 } 517 return map; 518 } 519 520 @Override 521 public int compareTo(X509CRLEntryImpl that) { 522 int compSerial = getSerialNumber().compareTo(that.getSerialNumber()); 523 if (compSerial != 0) { 524 return compSerial; 525 } 526 try { 527 byte[] thisEncoded = this.getEncoded0(); 528 byte[] thatEncoded = that.getEncoded0(); 529 for (int i=0; i<thisEncoded.length && i<thatEncoded.length; i++) { 530 int a = thisEncoded[i] & 0xff; 531 int b = thatEncoded[i] & 0xff; 532 if (a != b) return a-b; 533 } 534 return thisEncoded.length -thatEncoded.length; 535 } catch (CRLException ce) { 536 return -1; 537 } 538 } 539 } 540