1package org.bouncycastle.jce.provider;
2
3import java.io.ByteArrayOutputStream;
4import java.io.IOException;
5import java.math.BigInteger;
6import java.security.InvalidKeyException;
7import java.security.NoSuchAlgorithmException;
8import java.security.NoSuchProviderException;
9import java.security.Principal;
10import java.security.PublicKey;
11import java.security.Signature;
12import java.security.SignatureException;
13import java.security.cert.CRLException;
14import java.security.cert.Certificate;
15import java.security.cert.X509CRL;
16import java.security.cert.X509CRLEntry;
17import java.security.cert.X509Certificate;
18import java.util.Collections;
19import java.util.Date;
20import java.util.Enumeration;
21import java.util.HashSet;
22import java.util.Iterator;
23import java.util.Set;
24
25import javax.security.auth.x500.X500Principal;
26
27import org.bouncycastle.asn1.ASN1Encodable;
28import org.bouncycastle.asn1.ASN1InputStream;
29import org.bouncycastle.asn1.ASN1OutputStream;
30import org.bouncycastle.asn1.ASN1Sequence;
31import org.bouncycastle.asn1.DERInteger;
32import org.bouncycastle.asn1.DERObjectIdentifier;
33import org.bouncycastle.asn1.util.ASN1Dump;
34import org.bouncycastle.asn1.x509.CRLDistPoint;
35import org.bouncycastle.asn1.x509.CRLNumber;
36import org.bouncycastle.asn1.x509.CertificateList;
37import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
38import org.bouncycastle.asn1.x509.TBSCertList;
39import org.bouncycastle.asn1.x509.X509Extension;
40import org.bouncycastle.asn1.x509.X509Extensions;
41import org.bouncycastle.jce.X509Principal;
42import org.bouncycastle.util.encoders.Hex;
43import org.bouncycastle.x509.extension.X509ExtensionUtil;
44
45/**
46 * The following extensions are listed in RFC 2459 as relevant to CRLs
47 *
48 * Authority Key Identifier
49 * Issuer Alternative Name
50 * CRL Number
51 * Delta CRL Indicator (critical)
52 * Issuing Distribution Point (critical)
53 */
54public class X509CRLObject
55    extends X509CRL
56{
57    private CertificateList c;
58    private String sigAlgName;
59    private byte[] sigAlgParams;
60    private boolean isIndirect;
61
62    public X509CRLObject(
63        CertificateList c)
64        throws CRLException
65    {
66        this.c = c;
67
68        try
69        {
70            this.sigAlgName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
71
72            if (c.getSignatureAlgorithm().getParameters() != null)
73            {
74                this.sigAlgParams = ((ASN1Encodable)c.getSignatureAlgorithm().getParameters()).getDEREncoded();
75            }
76            else
77            {
78                this.sigAlgParams = null;
79            }
80
81            this.isIndirect = isIndirectCRL();
82        }
83        catch (Exception e)
84        {
85            throw new CRLException("CRL contents invalid: " + e);
86        }
87    }
88
89    /**
90     * Will return true if any extensions are present and marked
91     * as critical as we currently dont handle any extensions!
92     */
93    public boolean hasUnsupportedCriticalExtension()
94    {
95        Set extns = getCriticalExtensionOIDs();
96
97        if (extns == null)
98        {
99            return false;
100        }
101
102        extns.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
103        extns.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
104
105        return !extns.isEmpty();
106    }
107
108    private Set getExtensionOIDs(boolean critical)
109    {
110        if (this.getVersion() == 2)
111        {
112            X509Extensions extensions = c.getTBSCertList().getExtensions();
113
114            if (extensions != null)
115            {
116                Set set = new HashSet();
117                Enumeration e = extensions.oids();
118
119                while (e.hasMoreElements())
120                {
121                    DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement();
122                    X509Extension ext = extensions.getExtension(oid);
123
124                    if (critical == ext.isCritical())
125                    {
126                        set.add(oid.getId());
127                    }
128                }
129
130                return set;
131            }
132        }
133
134        return null;
135    }
136
137    public Set getCriticalExtensionOIDs()
138    {
139        return getExtensionOIDs(true);
140    }
141
142    public Set getNonCriticalExtensionOIDs()
143    {
144        return getExtensionOIDs(false);
145    }
146
147    public byte[] getExtensionValue(String oid)
148    {
149        X509Extensions exts = c.getTBSCertList().getExtensions();
150
151        if (exts != null)
152        {
153            X509Extension   ext = exts.getExtension(new DERObjectIdentifier(oid));
154
155            if (ext != null)
156            {
157                try
158                {
159                    return ext.getValue().getEncoded();
160                }
161                catch (Exception e)
162                {
163                    throw new IllegalStateException("error parsing " + e.toString());
164                }
165            }
166        }
167
168        return null;
169    }
170
171    public byte[] getEncoded()
172        throws CRLException
173    {
174        try
175        {
176            return c.getEncoded(ASN1Encodable.DER);
177        }
178        catch (IOException e)
179        {
180            throw new CRLException(e.toString());
181        }
182    }
183
184    public void verify(PublicKey key)
185        throws CRLException,  NoSuchAlgorithmException,
186            InvalidKeyException, NoSuchProviderException, SignatureException
187    {
188        verify(key, BouncyCastleProvider.PROVIDER_NAME);
189    }
190
191    public void verify(PublicKey key, String sigProvider)
192        throws CRLException, NoSuchAlgorithmException,
193            InvalidKeyException, NoSuchProviderException, SignatureException
194    {
195        if (!c.getSignatureAlgorithm().equals(c.getTBSCertList().getSignature()))
196        {
197            throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
198        }
199
200        Signature sig = Signature.getInstance(getSigAlgName(), sigProvider);
201
202        sig.initVerify(key);
203        sig.update(this.getTBSCertList());
204        if (!sig.verify(this.getSignature()))
205        {
206            throw new SignatureException("CRL does not verify with supplied public key.");
207        }
208    }
209
210    public int getVersion()
211    {
212        return c.getVersion();
213    }
214
215    public Principal getIssuerDN()
216    {
217        return new X509Principal(c.getIssuer());
218    }
219
220    public X500Principal getIssuerX500Principal()
221    {
222        try
223        {
224            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
225            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
226
227            aOut.writeObject(c.getIssuer());
228
229            return new X500Principal(bOut.toByteArray());
230        }
231        catch (IOException e)
232        {
233            throw new IllegalStateException("can't encode issuer DN");
234        }
235    }
236
237    public Date getThisUpdate()
238    {
239        return c.getThisUpdate().getDate();
240    }
241
242    public Date getNextUpdate()
243    {
244        if (c.getNextUpdate() != null)
245        {
246            return c.getNextUpdate().getDate();
247        }
248
249        return null;
250    }
251
252    private Set loadCRLEntries()
253    {
254        Set entrySet = new HashSet();
255        Enumeration certs = c.getRevokedCertificateEnumeration();
256
257        X500Principal previousCertificateIssuer = getIssuerX500Principal();
258        while (certs.hasMoreElements())
259        {
260            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
261            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
262            entrySet.add(crlEntry);
263            previousCertificateIssuer = crlEntry.getCertificateIssuer();
264        }
265
266        return entrySet;
267    }
268
269    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber)
270    {
271        Enumeration certs = c.getRevokedCertificateEnumeration();
272
273        X500Principal previousCertificateIssuer = getIssuerX500Principal();
274        while (certs.hasMoreElements())
275        {
276            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)certs.nextElement();
277            X509CRLEntryObject crlEntry = new X509CRLEntryObject(entry, isIndirect, previousCertificateIssuer);
278
279            if (serialNumber.equals(entry.getUserCertificate().getValue()))
280            {
281                return crlEntry;
282            }
283
284            previousCertificateIssuer = crlEntry.getCertificateIssuer();
285        }
286
287        return null;
288    }
289
290    public Set getRevokedCertificates()
291    {
292        Set entrySet = loadCRLEntries();
293
294        if (!entrySet.isEmpty())
295        {
296            return Collections.unmodifiableSet(entrySet);
297        }
298
299        return null;
300    }
301
302    public byte[] getTBSCertList()
303        throws CRLException
304    {
305        try
306        {
307            return c.getTBSCertList().getEncoded("DER");
308        }
309        catch (IOException e)
310        {
311            throw new CRLException(e.toString());
312        }
313    }
314
315    public byte[] getSignature()
316    {
317        return c.getSignature().getBytes();
318    }
319
320    public String getSigAlgName()
321    {
322        return sigAlgName;
323    }
324
325    public String getSigAlgOID()
326    {
327        return c.getSignatureAlgorithm().getObjectId().getId();
328    }
329
330    public byte[] getSigAlgParams()
331    {
332        if (sigAlgParams != null)
333        {
334            byte[] tmp = new byte[sigAlgParams.length];
335
336            System.arraycopy(sigAlgParams, 0, tmp, 0, tmp.length);
337
338            return tmp;
339        }
340
341        return null;
342    }
343
344    /**
345     * Returns a string representation of this CRL.
346     *
347     * @return a string representation of this CRL.
348     */
349    public String toString()
350    {
351        StringBuffer buf = new StringBuffer();
352        String nl = System.getProperty("line.separator");
353
354        buf.append("              Version: ").append(this.getVersion()).append(
355            nl);
356        buf.append("             IssuerDN: ").append(this.getIssuerDN())
357            .append(nl);
358        buf.append("          This update: ").append(this.getThisUpdate())
359            .append(nl);
360        buf.append("          Next update: ").append(this.getNextUpdate())
361            .append(nl);
362        buf.append("  Signature Algorithm: ").append(this.getSigAlgName())
363            .append(nl);
364
365        byte[] sig = this.getSignature();
366
367        buf.append("            Signature: ").append(
368            new String(Hex.encode(sig, 0, 20))).append(nl);
369        for (int i = 20; i < sig.length; i += 20)
370        {
371            if (i < sig.length - 20)
372            {
373                buf.append("                       ").append(
374                    new String(Hex.encode(sig, i, 20))).append(nl);
375            }
376            else
377            {
378                buf.append("                       ").append(
379                    new String(Hex.encode(sig, i, sig.length - i))).append(nl);
380            }
381        }
382
383        X509Extensions extensions = c.getTBSCertList().getExtensions();
384
385        if (extensions != null)
386        {
387            Enumeration e = extensions.oids();
388
389            if (e.hasMoreElements())
390            {
391                buf.append("           Extensions: ").append(nl);
392            }
393
394            while (e.hasMoreElements())
395            {
396                DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
397                X509Extension ext = extensions.getExtension(oid);
398
399                if (ext.getValue() != null)
400                {
401                    byte[] octs = ext.getValue().getOctets();
402                    ASN1InputStream dIn = new ASN1InputStream(octs);
403                    buf.append("                       critical(").append(
404                        ext.isCritical()).append(") ");
405                    try
406                    {
407                        if (oid.equals(X509Extensions.CRLNumber))
408                        {
409                            buf.append(
410                                new CRLNumber(DERInteger.getInstance(
411                                    dIn.readObject()).getPositiveValue()))
412                                .append(nl);
413                        }
414                        else if (oid.equals(X509Extensions.DeltaCRLIndicator))
415                        {
416                            buf.append(
417                                "Base CRL: "
418                                    + new CRLNumber(DERInteger.getInstance(
419                                        dIn.readObject()).getPositiveValue()))
420                                .append(nl);
421                        }
422                        else if (oid
423                            .equals(X509Extensions.IssuingDistributionPoint))
424                        {
425                            buf.append(
426                                new IssuingDistributionPoint((ASN1Sequence) dIn
427                                    .readObject())).append(nl);
428                        }
429                        else if (oid
430                            .equals(X509Extensions.CRLDistributionPoints))
431                        {
432                            buf.append(
433                                new CRLDistPoint((ASN1Sequence) dIn
434                                    .readObject())).append(nl);
435                        }
436                        else if (oid.equals(X509Extensions.FreshestCRL))
437                        {
438                            buf.append(
439                                new CRLDistPoint((ASN1Sequence) dIn
440                                    .readObject())).append(nl);
441                        }
442                        else
443                        {
444                            buf.append(oid.getId());
445                            buf.append(" value = ").append(
446                                ASN1Dump.dumpAsString(dIn.readObject()))
447                                .append(nl);
448                        }
449                    }
450                    catch (Exception ex)
451                    {
452                        buf.append(oid.getId());
453                        buf.append(" value = ").append("*****").append(nl);
454                    }
455                }
456                else
457                {
458                    buf.append(nl);
459                }
460            }
461        }
462        Set set = getRevokedCertificates();
463        if (set != null)
464        {
465            Iterator it = set.iterator();
466            while (it.hasNext())
467            {
468                buf.append(it.next());
469                buf.append(nl);
470            }
471        }
472        return buf.toString();
473    }
474
475    /**
476     * Checks whether the given certificate is on this CRL.
477     *
478     * @param cert the certificate to check for.
479     * @return true if the given certificate is on this CRL,
480     * false otherwise.
481     */
482    public boolean isRevoked(Certificate cert)
483    {
484        if (!cert.getType().equals("X.509"))
485        {
486            throw new RuntimeException("X.509 CRL used with non X.509 Cert");
487        }
488
489        TBSCertList.CRLEntry[] certs = c.getRevokedCertificates();
490
491        if (certs != null)
492        {
493            BigInteger serial = ((X509Certificate)cert).getSerialNumber();
494
495            for (int i = 0; i < certs.length; i++)
496            {
497                if (certs[i].getUserCertificate().getValue().equals(serial))
498                {
499                    return true;
500                }
501            }
502        }
503
504        return false;
505    }
506
507    private boolean isIndirectCRL()
508        throws CRLException
509    {
510        byte[] idp = getExtensionValue(X509Extensions.IssuingDistributionPoint.getId());
511        boolean isIndirect = false;
512        try
513        {
514            if (idp != null)
515            {
516                isIndirect = IssuingDistributionPoint.getInstance(
517                        X509ExtensionUtil.fromExtensionValue(idp))
518                        .isIndirectCRL();
519            }
520        }
521        catch (Exception e)
522        {
523            throw new ExtCRLException(
524                    "Exception reading IssuingDistributionPoint", e);
525        }
526
527        return isIndirect;
528    }
529}
530
531