View Javadoc
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 }