1/*
2 * Copyright (C) 2012 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 java.io.ByteArrayOutputStream;
20import java.io.InputStream;
21import java.math.BigInteger;
22import java.security.InvalidKeyException;
23import java.security.KeyFactory;
24import java.security.NoSuchAlgorithmException;
25import java.security.NoSuchProviderException;
26import java.security.Principal;
27import java.security.PublicKey;
28import java.security.Signature;
29import java.security.SignatureException;
30import java.security.cert.Certificate;
31import java.security.cert.CertificateEncodingException;
32import java.security.cert.CertificateException;
33import java.security.cert.CertificateExpiredException;
34import java.security.cert.CertificateNotYetValidException;
35import java.security.cert.CertificateParsingException;
36import java.security.cert.X509Certificate;
37import java.security.spec.InvalidKeySpecException;
38import java.security.spec.X509EncodedKeySpec;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Calendar;
42import java.util.Collection;
43import java.util.Collections;
44import java.util.Date;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Set;
48import java.util.TimeZone;
49import javax.crypto.BadPaddingException;
50import javax.security.auth.x500.X500Principal;
51import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
52
53public class OpenSSLX509Certificate extends X509Certificate {
54    private transient final long mContext;
55    private transient Integer mHashCode;
56
57    OpenSSLX509Certificate(long ctx) {
58        mContext = ctx;
59    }
60
61    public static OpenSSLX509Certificate fromX509DerInputStream(InputStream is)
62            throws ParsingException {
63        @SuppressWarnings("resource")
64        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
65
66        try {
67            final long certCtx = NativeCrypto.d2i_X509_bio(bis.getBioContext());
68            if (certCtx == 0) {
69                return null;
70            }
71            return new OpenSSLX509Certificate(certCtx);
72        } catch (Exception e) {
73            throw new ParsingException(e);
74        } finally {
75            bis.release();
76        }
77    }
78
79    public static OpenSSLX509Certificate fromX509Der(byte[] encoded) {
80        final long certCtx = NativeCrypto.d2i_X509(encoded);
81        if (certCtx == 0) {
82            return null;
83        }
84        return new OpenSSLX509Certificate(certCtx);
85    }
86
87    public static List<OpenSSLX509Certificate> fromPkcs7DerInputStream(InputStream is)
88            throws ParsingException {
89        @SuppressWarnings("resource")
90        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
91
92        final long[] certRefs;
93        try {
94            certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CERTS);
95        } catch (Exception e) {
96            throw new ParsingException(e);
97        } finally {
98            bis.release();
99        }
100
101        if (certRefs == null) {
102            return Collections.emptyList();
103        }
104
105        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
106                certRefs.length);
107        for (int i = 0; i < certRefs.length; i++) {
108            if (certRefs[i] == 0) {
109                continue;
110            }
111            certs.add(new OpenSSLX509Certificate(certRefs[i]));
112        }
113        return certs;
114    }
115
116    public static OpenSSLX509Certificate fromX509PemInputStream(InputStream is)
117            throws ParsingException {
118        @SuppressWarnings("resource")
119        final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
120
121        try {
122            final long certCtx = NativeCrypto.PEM_read_bio_X509(bis.getBioContext());
123            if (certCtx == 0L) {
124                return null;
125            }
126            return new OpenSSLX509Certificate(certCtx);
127        } catch (Exception e) {
128            throw new ParsingException(e);
129        } finally {
130            bis.release();
131        }
132    }
133
134    public static List<OpenSSLX509Certificate> fromPkcs7PemInputStream(InputStream is)
135            throws ParsingException {
136        @SuppressWarnings("resource")
137        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true);
138
139        final long[] certRefs;
140        try {
141            certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(),
142                    NativeCrypto.PKCS7_CERTS);
143        } catch (Exception e) {
144            throw new ParsingException(e);
145        } finally {
146            bis.release();
147        }
148
149        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
150                certRefs.length);
151        for (int i = 0; i < certRefs.length; i++) {
152            if (certRefs[i] == 0) {
153                continue;
154            }
155            certs.add(new OpenSSLX509Certificate(certRefs[i]));
156        }
157        return certs;
158    }
159
160    public static OpenSSLX509Certificate fromCertificate(Certificate cert)
161            throws CertificateEncodingException {
162        if (cert instanceof OpenSSLX509Certificate) {
163            return (OpenSSLX509Certificate) cert;
164        } else if (cert instanceof X509Certificate) {
165            return fromX509Der(cert.getEncoded());
166        } else {
167            throw new CertificateEncodingException("Only X.509 certificates are supported");
168        }
169    }
170
171    @Override
172    public Set<String> getCriticalExtensionOIDs() {
173        String[] critOids =
174                NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL);
175
176        /*
177         * This API has a special case that if there are no extensions, we
178         * should return null. So if we have no critical extensions, we'll check
179         * non-critical extensions.
180         */
181        if ((critOids.length == 0)
182                && (NativeCrypto.get_X509_ext_oids(mContext,
183                        NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) {
184            return null;
185        }
186
187        return new HashSet<String>(Arrays.asList(critOids));
188    }
189
190    @Override
191    public byte[] getExtensionValue(String oid) {
192        return NativeCrypto.X509_get_ext_oid(mContext, oid);
193    }
194
195    @Override
196    public Set<String> getNonCriticalExtensionOIDs() {
197        String[] nonCritOids =
198                NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_NON_CRITICAL);
199
200        /*
201         * This API has a special case that if there are no extensions, we
202         * should return null. So if we have no non-critical extensions, we'll
203         * check critical extensions.
204         */
205        if ((nonCritOids.length == 0)
206                && (NativeCrypto.get_X509_ext_oids(mContext,
207                        NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) {
208            return null;
209        }
210
211        return new HashSet<String>(Arrays.asList(nonCritOids));
212    }
213
214    @Override
215    public boolean hasUnsupportedCriticalExtension() {
216        return (NativeCrypto.get_X509_ex_flags(mContext) & NativeConstants.EXFLAG_CRITICAL) != 0;
217    }
218
219    @Override
220    public void checkValidity() throws CertificateExpiredException,
221            CertificateNotYetValidException {
222        checkValidity(new Date());
223    }
224
225    @Override
226    public void checkValidity(Date date) throws CertificateExpiredException,
227            CertificateNotYetValidException {
228        if (getNotBefore().compareTo(date) > 0) {
229            throw new CertificateNotYetValidException("Certificate not valid until "
230                    + getNotBefore().toString() + " (compared to " + date.toString() + ")");
231        }
232
233        if (getNotAfter().compareTo(date) < 0) {
234            throw new CertificateExpiredException("Certificate expired at "
235                    + getNotAfter().toString() + " (compared to " + date.toString() + ")");
236        }
237    }
238
239    @Override
240    public int getVersion() {
241        return (int) NativeCrypto.X509_get_version(mContext) + 1;
242    }
243
244    @Override
245    public BigInteger getSerialNumber() {
246        return new BigInteger(NativeCrypto.X509_get_serialNumber(mContext));
247    }
248
249    @Override
250    public Principal getIssuerDN() {
251        return getIssuerX500Principal();
252    }
253
254    @Override
255    public Principal getSubjectDN() {
256        return getSubjectX500Principal();
257    }
258
259    @Override
260    public Date getNotBefore() {
261        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
262        calendar.set(Calendar.MILLISECOND, 0);
263        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notBefore(mContext), calendar);
264        return calendar.getTime();
265    }
266
267    @Override
268    public Date getNotAfter() {
269        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
270        calendar.set(Calendar.MILLISECOND, 0);
271        NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notAfter(mContext), calendar);
272        return calendar.getTime();
273    }
274
275    @Override
276    public byte[] getTBSCertificate() throws CertificateEncodingException {
277        return NativeCrypto.get_X509_cert_info_enc(mContext);
278    }
279
280    @Override
281    public byte[] getSignature() {
282        return NativeCrypto.get_X509_signature(mContext);
283    }
284
285    @Override
286    public String getSigAlgName() {
287        String oid = getSigAlgOID();
288        String algName = Platform.oidToAlgorithmName(oid);
289        if (algName != null) {
290            return algName;
291        }
292        return oid;
293    }
294
295    @Override
296    public String getSigAlgOID() {
297        return NativeCrypto.get_X509_sig_alg_oid(mContext);
298    }
299
300    @Override
301    public byte[] getSigAlgParams() {
302        return NativeCrypto.get_X509_sig_alg_parameter(mContext);
303    }
304
305    @Override
306    public boolean[] getIssuerUniqueID() {
307        return NativeCrypto.get_X509_issuerUID(mContext);
308    }
309
310    @Override
311    public boolean[] getSubjectUniqueID() {
312        return NativeCrypto.get_X509_subjectUID(mContext);
313    }
314
315    @Override
316    public boolean[] getKeyUsage() {
317        final boolean[] kusage = NativeCrypto.get_X509_ex_kusage(mContext);
318        if (kusage == null) {
319            return null;
320        }
321
322        if (kusage.length >= 9) {
323            return kusage;
324        }
325
326        final boolean resized[] = new boolean[9];
327        System.arraycopy(kusage, 0, resized, 0, kusage.length);
328        return resized;
329    }
330
331    @Override
332    public int getBasicConstraints() {
333        if ((NativeCrypto.get_X509_ex_flags(mContext) & NativeConstants.EXFLAG_CA) == 0) {
334            return -1;
335        }
336
337        final int pathLen = NativeCrypto.get_X509_ex_pathlen(mContext);
338        if (pathLen == -1) {
339            return Integer.MAX_VALUE;
340        }
341
342        return pathLen;
343    }
344
345    @Override
346    public byte[] getEncoded() throws CertificateEncodingException {
347        return NativeCrypto.i2d_X509(mContext);
348    }
349
350    private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException,
351            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
352            SignatureException {
353        try {
354            NativeCrypto.X509_verify(mContext, pkey.getNativeRef());
355        } catch (RuntimeException e) {
356            throw new CertificateException(e);
357        } catch (BadPaddingException e) {
358            throw new SignatureException();
359        }
360    }
361
362    private void verifyInternal(PublicKey key, String sigProvider) throws CertificateException,
363            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
364            SignatureException {
365        final Signature sig;
366        if (sigProvider == null) {
367            sig = Signature.getInstance(getSigAlgName());
368        } else {
369            sig = Signature.getInstance(getSigAlgName(), sigProvider);
370        }
371
372        sig.initVerify(key);
373        sig.update(getTBSCertificate());
374        if (!sig.verify(getSignature())) {
375            throw new SignatureException("signature did not verify");
376        }
377    }
378
379    @Override
380    public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
381            InvalidKeyException, NoSuchProviderException, SignatureException {
382        if (key instanceof OpenSSLKeyHolder) {
383            OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey();
384            verifyOpenSSL(pkey);
385            return;
386        }
387
388        verifyInternal(key, null);
389    }
390
391    @Override
392    public void verify(PublicKey key, String sigProvider) throws CertificateException,
393            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
394            SignatureException {
395        verifyInternal(key, sigProvider);
396    }
397
398    @Override
399    public String toString() {
400        ByteArrayOutputStream os = new ByteArrayOutputStream();
401        long bioCtx = NativeCrypto.create_BIO_OutputStream(os);
402        try {
403            NativeCrypto.X509_print_ex(bioCtx, mContext, 0, 0);
404            return os.toString();
405        } finally {
406            NativeCrypto.BIO_free_all(bioCtx);
407        }
408    }
409
410    @Override
411    public PublicKey getPublicKey() {
412        /* First try to generate the key from supported OpenSSL key types. */
413        try {
414            OpenSSLKey pkey = new OpenSSLKey(NativeCrypto.X509_get_pubkey(mContext));
415            return pkey.getPublicKey();
416        } catch (NoSuchAlgorithmException ignored) {
417        }
418
419        /* Try generating the key using other Java providers. */
420        String oid = NativeCrypto.get_X509_pubkey_oid(mContext);
421        byte[] encoded = NativeCrypto.i2d_X509_PUBKEY(mContext);
422        try {
423            KeyFactory kf = KeyFactory.getInstance(oid);
424            return kf.generatePublic(new X509EncodedKeySpec(encoded));
425        } catch (NoSuchAlgorithmException ignored) {
426        } catch (InvalidKeySpecException ignored) {
427        }
428
429        /*
430         * We couldn't find anything else, so just return a nearly-unusable
431         * X.509-encoded key.
432         */
433        return new X509PublicKey(oid, encoded);
434    }
435
436    @Override
437    public X500Principal getIssuerX500Principal() {
438        final byte[] issuer = NativeCrypto.X509_get_issuer_name(mContext);
439        return new X500Principal(issuer);
440    }
441
442    @Override
443    public X500Principal getSubjectX500Principal() {
444        final byte[] subject = NativeCrypto.X509_get_subject_name(mContext);
445        return new X500Principal(subject);
446    }
447
448    @Override
449    public List<String> getExtendedKeyUsage() throws CertificateParsingException {
450        String[] extUsage = NativeCrypto.get_X509_ex_xkusage(mContext);
451        if (extUsage == null) {
452            return null;
453        }
454
455        return Arrays.asList(extUsage);
456    }
457
458    private static Collection<List<?>> alternativeNameArrayToList(Object[][] altNameArray) {
459        if (altNameArray == null) {
460            return null;
461        }
462
463        Collection<List<?>> coll = new ArrayList<List<?>>(altNameArray.length);
464        for (int i = 0; i < altNameArray.length; i++) {
465            coll.add(Collections.unmodifiableList(Arrays.asList(altNameArray[i])));
466        }
467
468        return Collections.unmodifiableCollection(coll);
469    }
470
471    @Override
472    public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
473        return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext,
474                NativeCrypto.GN_STACK_SUBJECT_ALT_NAME));
475    }
476
477    @Override
478    public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException {
479        return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext,
480                NativeCrypto.GN_STACK_ISSUER_ALT_NAME));
481    }
482
483    @Override
484    public boolean equals(Object other) {
485        if (other instanceof OpenSSLX509Certificate) {
486            OpenSSLX509Certificate o = (OpenSSLX509Certificate) other;
487
488            return NativeCrypto.X509_cmp(mContext, o.mContext) == 0;
489        }
490
491        return super.equals(other);
492    }
493
494    @Override
495    public int hashCode() {
496        if (mHashCode != null) {
497            return mHashCode;
498        }
499        mHashCode = super.hashCode();
500        return mHashCode;
501    }
502
503    /**
504     * Returns the raw pointer to the X509 context for use in JNI calls. The
505     * life cycle of this native pointer is managed by the
506     * {@code OpenSSLX509Certificate} instance and must not be destroyed or
507     * freed by users of this API.
508     */
509    public long getContext() {
510        return mContext;
511    }
512
513    /**
514     * Delete an extension.
515     *
516     * A modified copy of the certificate is returned. The original object
517     * is unchanged.
518     * If the extension is not present, an unmodified copy is returned.
519     */
520    public OpenSSLX509Certificate withDeletedExtension(String oid) {
521        OpenSSLX509Certificate copy = new OpenSSLX509Certificate(NativeCrypto.X509_dup(mContext));
522        NativeCrypto.X509_delete_ext(copy.getContext(), oid);
523        return copy;
524    }
525
526    @Override
527    protected void finalize() throws Throwable {
528        try {
529            if (mContext != 0) {
530                NativeCrypto.X509_free(mContext);
531            }
532        } finally {
533            super.finalize();
534        }
535    }
536}
537