1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.conscrypt;
18
19import org.apache.harmony.security.utils.AlgNameMapper;
20import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
21import java.io.ByteArrayInputStream;
22import java.io.ByteArrayOutputStream;
23import java.io.InputStream;
24import java.math.BigInteger;
25import java.security.InvalidKeyException;
26import java.security.NoSuchAlgorithmException;
27import java.security.NoSuchProviderException;
28import java.security.Principal;
29import java.security.PublicKey;
30import java.security.Signature;
31import java.security.SignatureException;
32import java.security.cert.CRLException;
33import java.security.cert.Certificate;
34import java.security.cert.X509CRL;
35import java.security.cert.X509CRLEntry;
36import java.security.cert.X509Certificate;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.Calendar;
40import java.util.Date;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Set;
44import java.util.TimeZone;
45import javax.security.auth.x500.X500Principal;
46
47public class OpenSSLX509CRL extends X509CRL {
48    private final long mContext;
49
50    private OpenSSLX509CRL(long ctx) {
51        mContext = ctx;
52    }
53
54    public static OpenSSLX509CRL fromX509DerInputStream(InputStream is) throws ParsingException {
55        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
56
57        try {
58            final long crlCtx = NativeCrypto.d2i_X509_CRL_bio(bis.getBioContext());
59            if (crlCtx == 0) {
60                return null;
61            }
62            return new OpenSSLX509CRL(crlCtx);
63        } catch (Exception e) {
64            throw new ParsingException(e);
65        } finally {
66            bis.release();
67        }
68    }
69
70    public static List<OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is)
71            throws ParsingException {
72        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
73
74        final long[] certRefs;
75        try {
76            certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CRLS);
77        } catch (Exception e) {
78            throw new ParsingException(e);
79        } finally {
80            bis.release();
81        }
82
83        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
84        for (int i = 0; i < certRefs.length; i++) {
85            if (certRefs[i] == 0) {
86                continue;
87            }
88            certs.add(new OpenSSLX509CRL(certRefs[i]));
89        }
90        return certs;
91    }
92
93    public static OpenSSLX509CRL fromX509PemInputStream(InputStream is) throws ParsingException {
94        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
95
96        try {
97            final long crlCtx = NativeCrypto.PEM_read_bio_X509_CRL(bis.getBioContext());
98            if (crlCtx == 0) {
99                return null;
100            }
101            return new OpenSSLX509CRL(crlCtx);
102        } catch (Exception e) {
103            throw new ParsingException(e);
104        } finally {
105            bis.release();
106        }
107    }
108
109    public static List<OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is)
110            throws ParsingException {
111        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
112
113        final long[] certRefs;
114        try {
115            certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(),
116                    NativeCrypto.PKCS7_CRLS);
117        } catch (Exception e) {
118            throw new ParsingException(e);
119        } finally {
120            bis.release();
121        }
122
123        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
124        for (int i = 0; i < certRefs.length; i++) {
125            if (certRefs[i] == 0) {
126                continue;
127            }
128            certs.add(new OpenSSLX509CRL(certRefs[i]));
129        }
130        return certs;
131    }
132
133    @Override
134    public Set<String> getCriticalExtensionOIDs() {
135        String[] critOids =
136                NativeCrypto.get_X509_CRL_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
137
138        /*
139         * This API has a special case that if there are no extensions, we
140         * should return null. So if we have no critical extensions, we'll check
141         * non-critical extensions.
142         */
143        if ((critOids.length == 0)
144                && (NativeCrypto.get_X509_CRL_ext_oids(mContext,
145                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) {
146            return null;
147        }
148
149        return new HashSet<String>(Arrays.asList(critOids));
150    }
151
152    @Override
153    public byte[] getExtensionValue(String oid) {
154        return NativeCrypto.X509_CRL_get_ext_oid(mContext, oid);
155    }
156
157    @Override
158    public Set<String> getNonCriticalExtensionOIDs() {
159        String[] nonCritOids =
160                NativeCrypto.get_X509_CRL_ext_oids(mContext,
161                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL);
162
163        /*
164         * This API has a special case that if there are no extensions, we
165         * should return null. So if we have no non-critical extensions, we'll
166         * check critical extensions.
167         */
168        if ((nonCritOids.length == 0)
169                && (NativeCrypto.get_X509_CRL_ext_oids(mContext,
170                        NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) {
171            return null;
172        }
173
174        return new HashSet<String>(Arrays.asList(nonCritOids));
175    }
176
177    @Override
178    public boolean hasUnsupportedCriticalExtension() {
179        final String[] criticalOids =
180                NativeCrypto.get_X509_CRL_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
181        for (String oid : criticalOids) {
182            final long extensionRef = NativeCrypto.X509_CRL_get_ext(mContext, oid);
183            if (NativeCrypto.X509_supported_extension(extensionRef) != 1) {
184                return true;
185            }
186        }
187
188        return false;
189    }
190
191    @Override
192    public byte[] getEncoded() throws CRLException {
193        return NativeCrypto.i2d_X509_CRL(mContext);
194    }
195
196    private void verifyOpenSSL(OpenSSLKey pkey) throws CRLException, NoSuchAlgorithmException,
197            InvalidKeyException, NoSuchProviderException, SignatureException {
198        NativeCrypto.X509_CRL_verify(mContext, pkey.getNativeRef());
199    }
200
201    private void verifyInternal(PublicKey key, String sigProvider) throws CRLException,
202            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
203            SignatureException {
204        String sigAlg = getSigAlgName();
205        if (sigAlg == null) {
206            sigAlg = getSigAlgOID();
207        }
208
209        final Signature sig;
210        if (sigProvider == null) {
211            sig = Signature.getInstance(sigAlg);
212        } else {
213            sig = Signature.getInstance(sigAlg, sigProvider);
214        }
215
216        sig.initVerify(key);
217        sig.update(getTBSCertList());
218        if (!sig.verify(getSignature())) {
219            throw new SignatureException("signature did not verify");
220        }
221    }
222
223    @Override
224    public void verify(PublicKey key) throws CRLException, NoSuchAlgorithmException,
225            InvalidKeyException, NoSuchProviderException, SignatureException {
226        if (key instanceof OpenSSLKeyHolder) {
227            OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey();
228            verifyOpenSSL(pkey);
229            return;
230        }
231
232        verifyInternal(key, null);
233    }
234
235    @Override
236    public void verify(PublicKey key, String sigProvider) throws CRLException,
237            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
238            SignatureException {
239        verifyInternal(key, sigProvider);
240    }
241
242    @Override
243    public int getVersion() {
244        return (int) NativeCrypto.X509_CRL_get_version(mContext) + 1;
245    }
246
247    @Override
248    public Principal getIssuerDN() {
249        return getIssuerX500Principal();
250    }
251
252    @Override
253    public X500Principal getIssuerX500Principal() {
254        final byte[] issuer = NativeCrypto.X509_CRL_get_issuer_name(mContext);
255        return new X500Principal(issuer);
256    }
257
258    @Override
259    public Date getThisUpdate() {
260        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
261        calendar.set(Calendar.MILLISECOND, 0);
262        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_CRL_get_lastUpdate(mContext),
263                calendar);
264        return calendar.getTime();
265    }
266
267    @Override
268    public Date getNextUpdate() {
269        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
270        calendar.set(Calendar.MILLISECOND, 0);
271        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_CRL_get_nextUpdate(mContext),
272                calendar);
273        return calendar.getTime();
274    }
275
276    @Override
277    public X509CRLEntry getRevokedCertificate(BigInteger serialNumber) {
278        final long revokedRef = NativeCrypto.X509_CRL_get0_by_serial(mContext,
279                serialNumber.toByteArray());
280        if (revokedRef == 0) {
281            return null;
282        }
283
284        return new OpenSSLX509CRLEntry(NativeCrypto.X509_REVOKED_dup(revokedRef));
285    }
286
287    @Override
288    public X509CRLEntry getRevokedCertificate(X509Certificate certificate) {
289        if (certificate instanceof OpenSSLX509Certificate) {
290            OpenSSLX509Certificate osslCert = (OpenSSLX509Certificate) certificate;
291            final long x509RevokedRef = NativeCrypto.X509_CRL_get0_by_cert(mContext,
292                    osslCert.getContext());
293
294            if (x509RevokedRef == 0) {
295                return null;
296            }
297
298            return new OpenSSLX509CRLEntry(NativeCrypto.X509_REVOKED_dup(x509RevokedRef));
299        }
300
301        return getRevokedCertificate(certificate.getSerialNumber());
302    }
303
304    @Override
305    public Set<? extends X509CRLEntry> getRevokedCertificates() {
306        final long[] entryRefs = NativeCrypto.X509_CRL_get_REVOKED(mContext);
307        if (entryRefs == null || entryRefs.length == 0) {
308            return null;
309        }
310
311        final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<OpenSSLX509CRLEntry>();
312        for (long entryRef : entryRefs) {
313            crlSet.add(new OpenSSLX509CRLEntry(entryRef));
314        }
315
316        return crlSet;
317    }
318
319    @Override
320    public byte[] getTBSCertList() throws CRLException {
321        return NativeCrypto.get_X509_CRL_crl_enc(mContext);
322    }
323
324    @Override
325    public byte[] getSignature() {
326        return NativeCrypto.get_X509_CRL_signature(mContext);
327    }
328
329    @Override
330    public String getSigAlgName() {
331        return AlgNameMapper.map2AlgName(getSigAlgOID());
332    }
333
334    @Override
335    public String getSigAlgOID() {
336        return NativeCrypto.get_X509_CRL_sig_alg_oid(mContext);
337    }
338
339    @Override
340    public byte[] getSigAlgParams() {
341        return NativeCrypto.get_X509_CRL_sig_alg_parameter(mContext);
342    }
343
344    @Override
345    public boolean isRevoked(Certificate cert) {
346        if (!(cert instanceof X509Certificate)) {
347            return false;
348        }
349
350        final OpenSSLX509Certificate osslCert;
351        if (cert instanceof OpenSSLX509Certificate) {
352            osslCert = (OpenSSLX509Certificate) cert;
353        } else {
354            try {
355                osslCert = OpenSSLX509Certificate.fromX509DerInputStream(new ByteArrayInputStream(
356                        cert.getEncoded()));
357            } catch (Exception e) {
358                throw new RuntimeException("cannot convert certificate", e);
359            }
360        }
361
362        final long x509RevokedRef = NativeCrypto.X509_CRL_get0_by_cert(mContext,
363                osslCert.getContext());
364
365        return x509RevokedRef != 0;
366    }
367
368    @Override
369    public String toString() {
370        ByteArrayOutputStream os = new ByteArrayOutputStream();
371        final long bioCtx = NativeCrypto.create_BIO_OutputStream(os);
372        try {
373            NativeCrypto.X509_CRL_print(bioCtx, mContext);
374            return os.toString();
375        } finally {
376            NativeCrypto.BIO_free_all(bioCtx);
377        }
378    }
379
380    @Override
381    protected void finalize() throws Throwable {
382        try {
383            if (mContext != 0) {
384                NativeCrypto.X509_CRL_free(mContext);
385            }
386        } finally {
387            super.finalize();
388        }
389    }
390
391}
392