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