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