X509CRLEntryImpl.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 1997, 2015, 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
26package sun.security.x509;
27
28import java.io.IOException;
29import java.security.cert.CRLException;
30import java.security.cert.CRLReason;
31import java.security.cert.X509CRLEntry;
32import java.math.BigInteger;
33import java.util.*;
34
35import javax.security.auth.x500.X500Principal;
36
37import sun.security.util.*;
38import 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
70public 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    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 derValue 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     * @return 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)
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     */
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     */
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     */
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     */
402    public byte[] getExtensionValue(String oid) {
403        if (extensions == null)
404            return null;
405        try {
406            String extAlias = OIDMap.getName(new ObjectIdentifier(oid));
407            Extension crlExt = null;
408
409            if (extAlias == null) { // may be unknown
410                ObjectIdentifier findOID = new ObjectIdentifier(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     */
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
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     */
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     */
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     */
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
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