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