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: ").append(revocationDate);
 296         if (certIssuer != null) {
 297             sb.append("\n    Certificate issuer: ").append(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: ").append(exts.length);
 304             for (int i = 0; i < exts.length; i++) {
 305                 sb.append("\n    [").append(i + 1).append("]: ");
 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: DER encoded OCTET string =\n")
 317                                     .append(enc.encodeBuffer(extValue)).append('\n');
 318                         }
 319                     } else
 320                         sb.append(ext.toString()); //sub-class exists
 321                 } catch (Exception e) {
 322                     sb.append(", Error parsing this extension");
 323                 }
 324             }
 325         }
 326         sb.append("\n");
 327         return sb.toString();
 328     }
 329 
 330     /**
 331      * Return true if a critical extension is found that is
 332      * not supported, otherwise return false.
 333      */
 334     public boolean hasUnsupportedCriticalExtension() {
 335         if (extensions == null)
 336             return false;
 337         return extensions.hasUnsupportedCriticalExtension();
 338     }
 339 
 340     /**
 341      * Gets a Set of the extension(s) marked CRITICAL in this
 342      * X509CRLEntry.  In the returned set, each extension is
 343      * represented by its OID string.
 344      *
 345      * @return a set of the extension oid strings in the
 346      * Object that are marked critical.
 347      */
 348     public Set<String> getCriticalExtensionOIDs() {
 349         if (extensions == null) {
 350             return null;
 351         }
 352         Set<String> extSet = new TreeSet<>();
 353         for (Extension ex : extensions.getAllExtensions()) {
 354             if (ex.isCritical()) {
 355                 extSet.add(ex.getExtensionId().toString());
 356             }
 357         }
 358         return extSet;
 359     }
 360 
 361     /**
 362      * Gets a Set of the extension(s) marked NON-CRITICAL in this
 363      * X509CRLEntry. In the returned set, each extension is
 364      * represented by its OID string.
 365      *
 366      * @return a set of the extension oid strings in the
 367      * Object that are marked critical.
 368      */
 369     public Set<String> getNonCriticalExtensionOIDs() {
 370         if (extensions == null) {
 371             return null;
 372         }
 373         Set<String> extSet = new TreeSet<>();
 374         for (Extension ex : extensions.getAllExtensions()) {
 375             if (!ex.isCritical()) {
 376                 extSet.add(ex.getExtensionId().toString());
 377             }
 378         }
 379         return extSet;
 380     }
 381 
 382     /**
 383      * Gets the DER encoded OCTET string for the extension value
 384      * (<em>extnValue</em>) identified by the passed in oid String.
 385      * The <code>oid</code> string is
 386      * represented by a set of positive whole number separated
 387      * by ".", that means,<br>
 388      * &lt;positive whole number&gt;.&lt;positive whole number&gt;.&lt;positive
 389      * whole number&gt;.&lt;...&gt;
 390      *
 391      * @param oid the Object Identifier value for the extension.
 392      * @return the DER encoded octet string of the extension value.
 393      */
 394     public byte[] getExtensionValue(String oid) {
 395         if (extensions == null)
 396             return null;
 397         try {
 398             String extAlias = OIDMap.getName(new ObjectIdentifier(oid));
 399             Extension crlExt = null;
 400 
 401             if (extAlias == null) { // may be unknown
 402                 ObjectIdentifier findOID = new ObjectIdentifier(oid);
 403                 Extension ex = null;
 404                 ObjectIdentifier inCertOID;
 405                 for (Enumeration<Extension> e = extensions.getElements();
 406                                                  e.hasMoreElements();) {
 407                     ex = e.nextElement();
 408                     inCertOID = ex.getExtensionId();
 409                     if (inCertOID.equals((Object)findOID)) {
 410                         crlExt = ex;
 411                         break;
 412                     }
 413                 }
 414             } else
 415                 crlExt = extensions.get(extAlias);
 416             if (crlExt == null)
 417                 return null;
 418             byte[] extData = crlExt.getExtensionValue();
 419             if (extData == null)
 420                 return null;
 421 
 422             DerOutputStream out = new DerOutputStream();
 423             out.putOctetString(extData);
 424             return out.toByteArray();
 425         } catch (Exception e) {
 426             return null;
 427         }
 428     }
 429 
 430     /**
 431      * get an extension
 432      *
 433      * @param oid ObjectIdentifier of extension desired
 434      * @returns Extension of type <extension> or null, if not found
 435      */
 436     public Extension getExtension(ObjectIdentifier oid) {
 437         if (extensions == null)
 438             return null;
 439 
 440         // following returns null if no such OID in map
 441         //XXX consider cloning this
 442         return extensions.get(OIDMap.getName(oid));
 443     }
 444 
 445     private void parse(DerValue derVal)
 446     throws CRLException, IOException {
 447 
 448         if (derVal.tag != DerValue.tag_Sequence) {
 449             throw new CRLException("Invalid encoded RevokedCertificate, " +
 450                                   "starting sequence tag missing.");
 451         }
 452         if (derVal.data.available() == 0)
 453             throw new CRLException("No data encoded for RevokedCertificates");
 454 
 455         revokedCert = derVal.toByteArray();
 456         // serial number
 457         DerInputStream in = derVal.toDerInputStream();
 458         DerValue val = in.getDerValue();
 459         this.serialNumber = new SerialNumber(val);
 460 
 461         // revocationDate
 462         int nextByte = derVal.data.peekByte();
 463         if ((byte)nextByte == DerValue.tag_UtcTime) {
 464             this.revocationDate = derVal.data.getUTCTime();
 465         } else if ((byte)nextByte == DerValue.tag_GeneralizedTime) {
 466             this.revocationDate = derVal.data.getGeneralizedTime();
 467         } else
 468             throw new CRLException("Invalid encoding for revocation date");
 469 
 470         if (derVal.data.available() == 0)
 471             return;  // no extensions
 472 
 473         // crlEntryExtensions
 474         this.extensions = new CRLExtensions(derVal.toDerInputStream());
 475     }
 476 
 477     /**
 478      * Utility method to convert an arbitrary instance of X509CRLEntry
 479      * to a X509CRLEntryImpl. Does a cast if possible, otherwise reparses
 480      * the encoding.
 481      */
 482     public static X509CRLEntryImpl toImpl(X509CRLEntry entry)
 483             throws CRLException {
 484         if (entry instanceof X509CRLEntryImpl) {
 485             return (X509CRLEntryImpl)entry;
 486         } else {
 487             return new X509CRLEntryImpl(entry.getEncoded());
 488         }
 489     }
 490 
 491     /**
 492      * Returns the CertificateIssuerExtension
 493      *
 494      * @return the CertificateIssuerExtension, or null if it does not exist
 495      */
 496     CertificateIssuerExtension getCertificateIssuerExtension() {
 497         return (CertificateIssuerExtension)
 498             getExtension(PKIXExtensions.CertificateIssuer_Id);
 499     }
 500 
 501     /**
 502      * Returns all extensions for this entry in a map
 503      * @return the extension map, can be empty, but not null
 504      */
 505     public Map<String, java.security.cert.Extension> getExtensions() {
 506         if (extensions == null) {
 507             return Collections.emptyMap();
 508         }
 509         Collection<Extension> exts = extensions.getAllExtensions();
 510         Map<String, java.security.cert.Extension> map = new TreeMap<>();
 511         for (Extension ext : exts) {
 512             map.put(ext.getId(), ext);
 513         }
 514         return map;
 515     }
 516 
 517     @Override
 518     public int compareTo(X509CRLEntryImpl that) {
 519         int compSerial = getSerialNumber().compareTo(that.getSerialNumber());
 520         if (compSerial != 0) {
 521             return compSerial;
 522         }
 523         try {
 524             byte[] thisEncoded = this.getEncoded0();
 525             byte[] thatEncoded = that.getEncoded0();
 526             for (int i=0; i<thisEncoded.length && i<thatEncoded.length; i++) {
 527                 int a = thisEncoded[i] & 0xff;
 528                 int b = thatEncoded[i] & 0xff;
 529                 if (a != b) return a-b;
 530             }
 531             return thisEncoded.length -thatEncoded.length;
 532         } catch (CRLException ce) {
 533             return -1;
 534         }
 535     }
 536 }