1package org.bouncycastle.cert;
2
3import java.io.ByteArrayInputStream;
4import java.io.IOException;
5import java.io.InputStream;
6import java.io.OutputStream;
7import java.math.BigInteger;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Enumeration;
11import java.util.List;
12import java.util.Set;
13
14import org.bouncycastle.asn1.ASN1InputStream;
15import org.bouncycastle.asn1.ASN1ObjectIdentifier;
16import org.bouncycastle.asn1.DEROutputStream;
17import org.bouncycastle.asn1.x500.X500Name;
18import org.bouncycastle.asn1.x509.CertificateList;
19import org.bouncycastle.asn1.x509.Extension;
20import org.bouncycastle.asn1.x509.Extensions;
21import org.bouncycastle.asn1.x509.GeneralName;
22import org.bouncycastle.asn1.x509.GeneralNames;
23import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
24import org.bouncycastle.asn1.x509.TBSCertList;
25import org.bouncycastle.operator.ContentVerifier;
26import org.bouncycastle.operator.ContentVerifierProvider;
27
28/**
29 * Holding class for an X.509 CRL structure.
30 */
31public class X509CRLHolder
32{
33    private CertificateList x509CRL;
34    private boolean isIndirect;
35    private Extensions extensions;
36    private GeneralNames issuerName;
37
38    private static CertificateList parseStream(InputStream stream)
39        throws IOException
40    {
41        try
42        {
43            return CertificateList.getInstance(new ASN1InputStream(stream, true).readObject());
44        }
45        catch (ClassCastException e)
46        {
47            throw new CertIOException("malformed data: " + e.getMessage(), e);
48        }
49        catch (IllegalArgumentException e)
50        {
51            throw new CertIOException("malformed data: " + e.getMessage(), e);
52        }
53    }
54
55    private static boolean isIndirectCRL(Extensions extensions)
56    {
57        if (extensions == null)
58        {
59            return false;
60        }
61
62        Extension ext = extensions.getExtension(Extension.issuingDistributionPoint);
63
64        return ext != null && IssuingDistributionPoint.getInstance(ext.getParsedValue()).isIndirectCRL();
65    }
66
67    /**
68     * Create a X509CRLHolder from the passed in bytes.
69     *
70     * @param crlEncoding BER/DER encoding of the CRL
71     * @throws IOException in the event of corrupted data, or an incorrect structure.
72     */
73    public X509CRLHolder(byte[] crlEncoding)
74        throws IOException
75    {
76        this(parseStream(new ByteArrayInputStream(crlEncoding)));
77    }
78
79    /**
80     * Create a X509CRLHolder from the passed in InputStream.
81     *
82     * @param crlStream BER/DER encoded InputStream of the CRL
83     * @throws IOException in the event of corrupted data, or an incorrect structure.
84     */
85    public X509CRLHolder(InputStream crlStream)
86        throws IOException
87    {
88        this(parseStream(crlStream));
89    }
90
91    /**
92     * Create a X509CRLHolder from the passed in ASN.1 structure.
93     *
94     * @param x509CRL an ASN.1 CertificateList structure.
95     */
96    public X509CRLHolder(CertificateList x509CRL)
97    {
98        this.x509CRL = x509CRL;
99        this.extensions = x509CRL.getTBSCertList().getExtensions();
100        this.isIndirect = isIndirectCRL(extensions);
101        this.issuerName = new GeneralNames(new GeneralName(x509CRL.getIssuer()));
102    }
103
104    /**
105     * Return the ASN.1 encoding of this holder's CRL.
106     *
107     * @return a DER encoded byte array.
108     * @throws IOException if an encoding cannot be generated.
109     */
110    public byte[] getEncoded()
111        throws IOException
112    {
113        return x509CRL.getEncoded();
114    }
115
116    /**
117     * Return the issuer of this holder's CRL.
118     *
119     * @return the CRL issuer.
120     */
121    public X500Name getIssuer()
122    {
123        return X500Name.getInstance(x509CRL.getIssuer());
124    }
125
126    public X509CRLEntryHolder getRevokedCertificate(BigInteger serialNumber)
127    {
128        GeneralNames currentCA = issuerName;
129        for (Enumeration en = x509CRL.getRevokedCertificateEnumeration(); en.hasMoreElements();)
130        {
131            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)en.nextElement();
132
133            if (entry.getUserCertificate().getValue().equals(serialNumber))
134            {
135                return new X509CRLEntryHolder(entry, isIndirect, currentCA);
136            }
137
138            if (isIndirect && entry.hasExtensions())
139            {
140                Extension currentCaName = entry.getExtensions().getExtension(Extension.certificateIssuer);
141
142                if (currentCaName != null)
143                {
144                    currentCA = GeneralNames.getInstance(currentCaName.getParsedValue());
145                }
146            }
147        }
148
149        return null;
150    }
151
152    /**
153     * Return a collection of X509CRLEntryHolder objects, giving the details of the
154     * revoked certificates that appear on this CRL.
155     *
156     * @return the revoked certificates as a collection of X509CRLEntryHolder objects.
157     */
158    public Collection getRevokedCertificates()
159    {
160        TBSCertList.CRLEntry[] entries = x509CRL.getRevokedCertificates();
161        List l = new ArrayList(entries.length);
162        GeneralNames currentCA = issuerName;
163
164        for (Enumeration en = x509CRL.getRevokedCertificateEnumeration(); en.hasMoreElements();)
165        {
166            TBSCertList.CRLEntry entry = (TBSCertList.CRLEntry)en.nextElement();
167            X509CRLEntryHolder crlEntry = new X509CRLEntryHolder(entry, isIndirect, currentCA);
168
169            l.add(crlEntry);
170
171            currentCA = crlEntry.getCertificateIssuer();
172        }
173
174        return l;
175    }
176
177    /**
178     * Return whether or not the holder's CRL contains extensions.
179     *
180     * @return true if extension are present, false otherwise.
181     */
182    public boolean hasExtensions()
183    {
184        return extensions != null;
185    }
186
187    /**
188     * Look up the extension associated with the passed in OID.
189     *
190     * @param oid the OID of the extension of interest.
191     *
192     * @return the extension if present, null otherwise.
193     */
194    public Extension getExtension(ASN1ObjectIdentifier oid)
195    {
196        if (extensions != null)
197        {
198            return extensions.getExtension(oid);
199        }
200
201        return null;
202    }
203
204    /**
205     * Returns a list of ASN1ObjectIdentifier objects representing the OIDs of the
206     * extensions contained in this holder's CRL.
207     *
208     * @return a list of extension OIDs.
209     */
210    public List getExtensionOIDs()
211    {
212        return CertUtils.getExtensionOIDs(extensions);
213    }
214
215    /**
216     * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the
217     * critical extensions contained in this holder's CRL.
218     *
219     * @return a set of critical extension OIDs.
220     */
221    public Set getCriticalExtensionOIDs()
222    {
223        return CertUtils.getCriticalExtensionOIDs(extensions);
224    }
225
226    /**
227     * Returns a set of ASN1ObjectIdentifier objects representing the OIDs of the
228     * non-critical extensions contained in this holder's CRL.
229     *
230     * @return a set of non-critical extension OIDs.
231     */
232    public Set getNonCriticalExtensionOIDs()
233    {
234        return CertUtils.getNonCriticalExtensionOIDs(extensions);
235    }
236
237    /**
238     * Return the underlying ASN.1 structure for the CRL in this holder.
239     *
240     * @return a CertificateList object.
241     */
242    public CertificateList toASN1Structure()
243    {
244        return x509CRL;
245    }
246
247    /**
248     * Validate the signature on the CRL.
249     *
250     * @param verifierProvider a ContentVerifierProvider that can generate a verifier for the signature.
251     * @return true if the signature is valid, false otherwise.
252     * @throws CertException if the signature cannot be processed or is inappropriate.
253     */
254    public boolean isSignatureValid(ContentVerifierProvider verifierProvider)
255        throws CertException
256    {
257        TBSCertList tbsCRL = x509CRL.getTBSCertList();
258
259        if (!tbsCRL.getSignature().equals(x509CRL.getSignatureAlgorithm()))
260        {
261            throw new CertException("signature invalid - algorithm identifier mismatch");
262        }
263
264        ContentVerifier verifier;
265
266        try
267        {
268            verifier = verifierProvider.get((tbsCRL.getSignature()));
269
270            OutputStream sOut = verifier.getOutputStream();
271            DEROutputStream dOut = new DEROutputStream(sOut);
272
273            dOut.writeObject(tbsCRL);
274
275            sOut.close();
276        }
277        catch (Exception e)
278        {
279            throw new CertException("unable to process signature: " + e.getMessage(), e);
280        }
281
282        return verifier.verify(x509CRL.getSignature().getBytes());
283    }
284
285    public boolean equals(
286        Object o)
287    {
288        if (o == this)
289        {
290            return true;
291        }
292
293        if (!(o instanceof X509CRLHolder))
294        {
295            return false;
296        }
297
298        X509CRLHolder other = (X509CRLHolder)o;
299
300        return this.x509CRL.equals(other.x509CRL);
301    }
302
303    public int hashCode()
304    {
305        return this.x509CRL.hashCode();
306    }
307}
308