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