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