Home | History | Annotate | Download | only in x509
      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 }
    538