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