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