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