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