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.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 
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 derVal 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      * @returns 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.toString());
294         sb.append("  On: " + revocationDate.toString());
295         if (certIssuer != null) {
296             sb.append("\n    Certificate issuer: " + certIssuer);
297         }
298         if (extensions != null) {
299             Collection<Extension> allEntryExts = extensions.getAllExtensions();
300             Extension[] exts = allEntryExts.toArray(new Extension[0]);
301 
302             sb.append("\n    CRL Entry Extensions: " + exts.length);
303             for (int i = 0; i < exts.length; i++) {
304                 sb.append("\n    [" + (i+1) + "]: ");
305                 Extension ext = exts[i];
306                 try {
307                     if (OIDMap.getClass(ext.getExtensionId()) == null) {
308                         sb.append(ext.toString());
309                         byte[] extValue = ext.getExtensionValue();
310                         if (extValue != null) {
311                             DerOutputStream out = new DerOutputStream();
312                             out.putOctetString(extValue);
313                             extValue = out.toByteArray();
314                             HexDumpEncoder enc = new HexDumpEncoder();
315                             sb.append("Extension unknown: "
316                                       + "DER encoded OCTET string =\n"
317                                       + enc.encodeBuffer(extValue) + "\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      */
hasUnsupportedCriticalExtension()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      */
getCriticalExtensionOIDs()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      */
getNonCriticalExtensionOIDs()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      */
getExtensionValue(String oid)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      */
getExtension(ObjectIdentifier oid)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 
parse(DerValue derVal)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      */
toImpl(X509CRLEntry entry)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      */
getCertificateIssuerExtension()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      */
getExtensions()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
compareTo(X509CRLEntryImpl that)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 }
537