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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 191     public byte[] getEncoded() throws CRLException {
 192         return getEncoded0().clone();
 193     }
 194 
 195     // Called internally to avoid clone
 196     private byte[] getEncoded0() throws CRLException {
 197         if (revokedCert == null)
 198             this.encode(new DerOutputStream());
 199         return revokedCert;
 200     }
 201 
 202     @Override
 203     public X500Principal getCertificateIssuer() {
 204         return certIssuer;
 205     }
 206 
 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      */
 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      */
 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
 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      */
 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      */
 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
 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      */
 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      */
 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      */
 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      * &lt;positive whole number&gt;.&lt;positive whole number&gt;.&lt;positive
 390      * whole number&gt;.&lt;...&gt;
 391      *
 392      * @param oid the Object Identifier value for the extension.
 393      * @return the DER encoded octet string of the extension value.
 394      */
 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      */
 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 
 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      */
 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      */
 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      */
 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
 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 }