Home | History | Annotate | Download | only in security
      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 android.security;
     18 
     19 import com.android.org.conscrypt.OpenSSLEngine;
     20 import com.android.org.conscrypt.OpenSSLKeyHolder;
     21 
     22 import android.util.Log;
     23 
     24 import java.io.ByteArrayInputStream;
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.io.OutputStream;
     28 import java.security.InvalidKeyException;
     29 import java.security.Key;
     30 import java.security.KeyStore.Entry;
     31 import java.security.KeyStore.PrivateKeyEntry;
     32 import java.security.KeyStore.ProtectionParameter;
     33 import java.security.KeyStore;
     34 import java.security.KeyStoreException;
     35 import java.security.KeyStoreSpi;
     36 import java.security.NoSuchAlgorithmException;
     37 import java.security.PrivateKey;
     38 import java.security.UnrecoverableKeyException;
     39 import java.security.cert.Certificate;
     40 import java.security.cert.CertificateEncodingException;
     41 import java.security.cert.CertificateException;
     42 import java.security.cert.CertificateFactory;
     43 import java.security.cert.X509Certificate;
     44 import java.util.ArrayList;
     45 import java.util.Collection;
     46 import java.util.Collections;
     47 import java.util.Date;
     48 import java.util.Enumeration;
     49 import java.util.HashSet;
     50 import java.util.Iterator;
     51 import java.util.Set;
     52 
     53 /**
     54  * A java.security.KeyStore interface for the Android KeyStore. An instance of
     55  * it can be created via the {@link java.security.KeyStore#getInstance(String)
     56  * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a
     57  * java.security.KeyStore backed by this "AndroidKeyStore" implementation.
     58  * <p>
     59  * This is built on top of Android's keystore daemon. The convention of alias
     60  * use is:
     61  * <p>
     62  * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key,
     63  * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one
     64  * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE
     65  * entry which will have the rest of the chain concatenated in BER format.
     66  * <p>
     67  * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry
     68  * with a single certificate.
     69  *
     70  * @hide
     71  */
     72 public class AndroidKeyStore extends KeyStoreSpi {
     73     public static final String NAME = "AndroidKeyStore";
     74 
     75     private android.security.KeyStore mKeyStore;
     76 
     77     @Override
     78     public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,
     79             UnrecoverableKeyException {
     80         if (!isKeyEntry(alias)) {
     81             return null;
     82         }
     83 
     84         final OpenSSLEngine engine = OpenSSLEngine.getInstance("keystore");
     85         try {
     86             return engine.getPrivateKeyById(Credentials.USER_PRIVATE_KEY + alias);
     87         } catch (InvalidKeyException e) {
     88             UnrecoverableKeyException t = new UnrecoverableKeyException("Can't get key");
     89             t.initCause(e);
     90             throw t;
     91         }
     92     }
     93 
     94     @Override
     95     public Certificate[] engineGetCertificateChain(String alias) {
     96         if (alias == null) {
     97             throw new NullPointerException("alias == null");
     98         }
     99 
    100         final X509Certificate leaf = (X509Certificate) engineGetCertificate(alias);
    101         if (leaf == null) {
    102             return null;
    103         }
    104 
    105         final Certificate[] caList;
    106 
    107         final byte[] caBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
    108         if (caBytes != null) {
    109             final Collection<X509Certificate> caChain = toCertificates(caBytes);
    110 
    111             caList = new Certificate[caChain.size() + 1];
    112 
    113             final Iterator<X509Certificate> it = caChain.iterator();
    114             int i = 1;
    115             while (it.hasNext()) {
    116                 caList[i++] = it.next();
    117             }
    118         } else {
    119             caList = new Certificate[1];
    120         }
    121 
    122         caList[0] = leaf;
    123 
    124         return caList;
    125     }
    126 
    127     @Override
    128     public Certificate engineGetCertificate(String alias) {
    129         if (alias == null) {
    130             throw new NullPointerException("alias == null");
    131         }
    132 
    133         byte[] certificate = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
    134         if (certificate != null) {
    135             return toCertificate(certificate);
    136         }
    137 
    138         certificate = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
    139         if (certificate != null) {
    140             return toCertificate(certificate);
    141         }
    142 
    143         return null;
    144     }
    145 
    146     private static X509Certificate toCertificate(byte[] bytes) {
    147         try {
    148             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    149             return (X509Certificate) certFactory
    150                     .generateCertificate(new ByteArrayInputStream(bytes));
    151         } catch (CertificateException e) {
    152             Log.w(NAME, "Couldn't parse certificate in keystore", e);
    153             return null;
    154         }
    155     }
    156 
    157     @SuppressWarnings("unchecked")
    158     private static Collection<X509Certificate> toCertificates(byte[] bytes) {
    159         try {
    160             final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    161             return (Collection<X509Certificate>) certFactory
    162                     .generateCertificates(new ByteArrayInputStream(bytes));
    163         } catch (CertificateException e) {
    164             Log.w(NAME, "Couldn't parse certificates in keystore", e);
    165             return new ArrayList<X509Certificate>();
    166         }
    167     }
    168 
    169     private Date getModificationDate(String alias) {
    170         final long epochMillis = mKeyStore.getmtime(alias);
    171         if (epochMillis == -1L) {
    172             return null;
    173         }
    174 
    175         return new Date(epochMillis);
    176     }
    177 
    178     @Override
    179     public Date engineGetCreationDate(String alias) {
    180         if (alias == null) {
    181             throw new NullPointerException("alias == null");
    182         }
    183 
    184         Date d = getModificationDate(Credentials.USER_PRIVATE_KEY + alias);
    185         if (d != null) {
    186             return d;
    187         }
    188 
    189         d = getModificationDate(Credentials.USER_CERTIFICATE + alias);
    190         if (d != null) {
    191             return d;
    192         }
    193 
    194         return getModificationDate(Credentials.CA_CERTIFICATE + alias);
    195     }
    196 
    197     @Override
    198     public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
    199             throws KeyStoreException {
    200         if ((password != null) && (password.length > 0)) {
    201             throw new KeyStoreException("entries cannot be protected with passwords");
    202         }
    203 
    204         if (key instanceof PrivateKey) {
    205             setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);
    206         } else {
    207             throw new KeyStoreException("Only PrivateKeys are supported");
    208         }
    209     }
    210 
    211     private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain,
    212             KeyStoreParameter params) throws KeyStoreException {
    213         byte[] keyBytes = null;
    214 
    215         final String pkeyAlias;
    216         if (key instanceof OpenSSLKeyHolder) {
    217             pkeyAlias = ((OpenSSLKeyHolder) key).getOpenSSLKey().getAlias();
    218         } else {
    219             pkeyAlias = null;
    220         }
    221 
    222         final boolean shouldReplacePrivateKey;
    223         if (pkeyAlias != null && pkeyAlias.startsWith(Credentials.USER_PRIVATE_KEY)) {
    224             final String keySubalias = pkeyAlias.substring(Credentials.USER_PRIVATE_KEY.length());
    225             if (!alias.equals(keySubalias)) {
    226                 throw new KeyStoreException("Can only replace keys with same alias: " + alias
    227                         + " != " + keySubalias);
    228             }
    229 
    230             shouldReplacePrivateKey = false;
    231         } else {
    232             // Make sure the PrivateKey format is the one we support.
    233             final String keyFormat = key.getFormat();
    234             if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) {
    235                 throw new KeyStoreException(
    236                         "Only PrivateKeys that can be encoded into PKCS#8 are supported");
    237             }
    238 
    239             // Make sure we can actually encode the key.
    240             keyBytes = key.getEncoded();
    241             if (keyBytes == null) {
    242                 throw new KeyStoreException("PrivateKey has no encoding");
    243             }
    244 
    245             shouldReplacePrivateKey = true;
    246         }
    247 
    248         // Make sure the chain exists since this is a PrivateKey
    249         if ((chain == null) || (chain.length == 0)) {
    250             throw new KeyStoreException("Must supply at least one Certificate with PrivateKey");
    251         }
    252 
    253         // Do chain type checking.
    254         X509Certificate[] x509chain = new X509Certificate[chain.length];
    255         for (int i = 0; i < chain.length; i++) {
    256             if (!"X.509".equals(chain[i].getType())) {
    257                 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
    258                         + i);
    259             }
    260 
    261             if (!(chain[i] instanceof X509Certificate)) {
    262                 throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #"
    263                         + i);
    264             }
    265 
    266             x509chain[i] = (X509Certificate) chain[i];
    267         }
    268 
    269         final byte[] userCertBytes;
    270         try {
    271             userCertBytes = x509chain[0].getEncoded();
    272         } catch (CertificateEncodingException e) {
    273             throw new KeyStoreException("Couldn't encode certificate #1", e);
    274         }
    275 
    276         /*
    277          * If we have a chain, store it in the CA certificate slot for this
    278          * alias as concatenated DER-encoded certificates. These can be
    279          * deserialized by {@link CertificateFactory#generateCertificates}.
    280          */
    281         final byte[] chainBytes;
    282         if (chain.length > 1) {
    283             /*
    284              * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...}
    285              * so we only need the certificates starting at index 1.
    286              */
    287             final byte[][] certsBytes = new byte[x509chain.length - 1][];
    288             int totalCertLength = 0;
    289             for (int i = 0; i < certsBytes.length; i++) {
    290                 try {
    291                     certsBytes[i] = x509chain[i + 1].getEncoded();
    292                     totalCertLength += certsBytes[i].length;
    293                 } catch (CertificateEncodingException e) {
    294                     throw new KeyStoreException("Can't encode Certificate #" + i, e);
    295                 }
    296             }
    297 
    298             /*
    299              * Serialize this into one byte array so we can later call
    300              * CertificateFactory#generateCertificates to recover them.
    301              */
    302             chainBytes = new byte[totalCertLength];
    303             int outputOffset = 0;
    304             for (int i = 0; i < certsBytes.length; i++) {
    305                 final int certLength = certsBytes[i].length;
    306                 System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength);
    307                 outputOffset += certLength;
    308                 certsBytes[i] = null;
    309             }
    310         } else {
    311             chainBytes = null;
    312         }
    313 
    314         /*
    315          * Make sure we clear out all the appropriate types before trying to
    316          * write.
    317          */
    318         if (shouldReplacePrivateKey) {
    319             Credentials.deleteAllTypesForAlias(mKeyStore, alias);
    320         } else {
    321             Credentials.deleteCertificateTypesForAlias(mKeyStore, alias);
    322         }
    323 
    324         final int flags = (params == null) ? 0 : params.getFlags();
    325 
    326         if (shouldReplacePrivateKey
    327                 && !mKeyStore.importKey(Credentials.USER_PRIVATE_KEY + alias, keyBytes,
    328                         android.security.KeyStore.UID_SELF, flags)) {
    329             Credentials.deleteAllTypesForAlias(mKeyStore, alias);
    330             throw new KeyStoreException("Couldn't put private key in keystore");
    331         } else if (!mKeyStore.put(Credentials.USER_CERTIFICATE + alias, userCertBytes,
    332                 android.security.KeyStore.UID_SELF, flags)) {
    333             Credentials.deleteAllTypesForAlias(mKeyStore, alias);
    334             throw new KeyStoreException("Couldn't put certificate #1 in keystore");
    335         } else if (chainBytes != null
    336                 && !mKeyStore.put(Credentials.CA_CERTIFICATE + alias, chainBytes,
    337                         android.security.KeyStore.UID_SELF, flags)) {
    338             Credentials.deleteAllTypesForAlias(mKeyStore, alias);
    339             throw new KeyStoreException("Couldn't put certificate chain in keystore");
    340         }
    341     }
    342 
    343     @Override
    344     public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain)
    345             throws KeyStoreException {
    346         throw new KeyStoreException("Operation not supported because key encoding is unknown");
    347     }
    348 
    349     @Override
    350     public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
    351         if (isKeyEntry(alias)) {
    352             throw new KeyStoreException("Entry exists and is not a trusted certificate");
    353         }
    354 
    355         // We can't set something to null.
    356         if (cert == null) {
    357             throw new NullPointerException("cert == null");
    358         }
    359 
    360         final byte[] encoded;
    361         try {
    362             encoded = cert.getEncoded();
    363         } catch (CertificateEncodingException e) {
    364             throw new KeyStoreException(e);
    365         }
    366 
    367         if (!mKeyStore.put(Credentials.CA_CERTIFICATE + alias, encoded,
    368                 android.security.KeyStore.UID_SELF, android.security.KeyStore.FLAG_NONE)) {
    369             throw new KeyStoreException("Couldn't insert certificate; is KeyStore initialized?");
    370         }
    371     }
    372 
    373     @Override
    374     public void engineDeleteEntry(String alias) throws KeyStoreException {
    375         if (!isKeyEntry(alias) && !isCertificateEntry(alias)) {
    376             return;
    377         }
    378 
    379         if (!Credentials.deleteAllTypesForAlias(mKeyStore, alias)) {
    380             throw new KeyStoreException("No such entry " + alias);
    381         }
    382     }
    383 
    384     private Set<String> getUniqueAliases() {
    385         final String[] rawAliases = mKeyStore.saw("");
    386         if (rawAliases == null) {
    387             return new HashSet<String>();
    388         }
    389 
    390         final Set<String> aliases = new HashSet<String>(rawAliases.length);
    391         for (String alias : rawAliases) {
    392             final int idx = alias.indexOf('_');
    393             if ((idx == -1) || (alias.length() <= idx)) {
    394                 Log.e(NAME, "invalid alias: " + alias);
    395                 continue;
    396             }
    397 
    398             aliases.add(new String(alias.substring(idx + 1)));
    399         }
    400 
    401         return aliases;
    402     }
    403 
    404     @Override
    405     public Enumeration<String> engineAliases() {
    406         return Collections.enumeration(getUniqueAliases());
    407     }
    408 
    409     @Override
    410     public boolean engineContainsAlias(String alias) {
    411         if (alias == null) {
    412             throw new NullPointerException("alias == null");
    413         }
    414 
    415         return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias)
    416                 || mKeyStore.contains(Credentials.USER_CERTIFICATE + alias)
    417                 || mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
    418     }
    419 
    420     @Override
    421     public int engineSize() {
    422         return getUniqueAliases().size();
    423     }
    424 
    425     @Override
    426     public boolean engineIsKeyEntry(String alias) {
    427         return isKeyEntry(alias);
    428     }
    429 
    430     private boolean isKeyEntry(String alias) {
    431         if (alias == null) {
    432             throw new NullPointerException("alias == null");
    433         }
    434 
    435         return mKeyStore.contains(Credentials.USER_PRIVATE_KEY + alias);
    436     }
    437 
    438     private boolean isCertificateEntry(String alias) {
    439         if (alias == null) {
    440             throw new NullPointerException("alias == null");
    441         }
    442 
    443         return mKeyStore.contains(Credentials.CA_CERTIFICATE + alias);
    444     }
    445 
    446     @Override
    447     public boolean engineIsCertificateEntry(String alias) {
    448         return !isKeyEntry(alias) && isCertificateEntry(alias);
    449     }
    450 
    451     @Override
    452     public String engineGetCertificateAlias(Certificate cert) {
    453         if (cert == null) {
    454             return null;
    455         }
    456 
    457         final Set<String> nonCaEntries = new HashSet<String>();
    458 
    459         /*
    460          * First scan the PrivateKeyEntry types. The KeyStoreSpi documentation
    461          * says to only compare the first certificate in the chain which is
    462          * equivalent to the USER_CERTIFICATE prefix for the Android keystore
    463          * convention.
    464          */
    465         final String[] certAliases = mKeyStore.saw(Credentials.USER_CERTIFICATE);
    466         if (certAliases != null) {
    467             for (String alias : certAliases) {
    468                 final byte[] certBytes = mKeyStore.get(Credentials.USER_CERTIFICATE + alias);
    469                 if (certBytes == null) {
    470                     continue;
    471                 }
    472 
    473                 final Certificate c = toCertificate(certBytes);
    474                 nonCaEntries.add(alias);
    475 
    476                 if (cert.equals(c)) {
    477                     return alias;
    478                 }
    479             }
    480         }
    481 
    482         /*
    483          * Look at all the TrustedCertificateEntry types. Skip all the
    484          * PrivateKeyEntry we looked at above.
    485          */
    486         final String[] caAliases = mKeyStore.saw(Credentials.CA_CERTIFICATE);
    487         if (certAliases != null) {
    488             for (String alias : caAliases) {
    489                 if (nonCaEntries.contains(alias)) {
    490                     continue;
    491                 }
    492 
    493                 final byte[] certBytes = mKeyStore.get(Credentials.CA_CERTIFICATE + alias);
    494                 if (certBytes == null) {
    495                     continue;
    496                 }
    497 
    498                 final Certificate c =
    499                         toCertificate(mKeyStore.get(Credentials.CA_CERTIFICATE + alias));
    500                 if (cert.equals(c)) {
    501                     return alias;
    502                 }
    503             }
    504         }
    505 
    506         return null;
    507     }
    508 
    509     @Override
    510     public void engineStore(OutputStream stream, char[] password) throws IOException,
    511             NoSuchAlgorithmException, CertificateException {
    512         throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream");
    513     }
    514 
    515     @Override
    516     public void engineLoad(InputStream stream, char[] password) throws IOException,
    517             NoSuchAlgorithmException, CertificateException {
    518         if (stream != null) {
    519             throw new IllegalArgumentException("InputStream not supported");
    520         }
    521 
    522         if (password != null) {
    523             throw new IllegalArgumentException("password not supported");
    524         }
    525 
    526         // Unfortunate name collision.
    527         mKeyStore = android.security.KeyStore.getInstance();
    528     }
    529 
    530     @Override
    531     public void engineSetEntry(String alias, Entry entry, ProtectionParameter param)
    532             throws KeyStoreException {
    533         if (entry == null) {
    534             throw new KeyStoreException("entry == null");
    535         }
    536 
    537         if (engineContainsAlias(alias)) {
    538             engineDeleteEntry(alias);
    539         }
    540 
    541         if (entry instanceof KeyStore.TrustedCertificateEntry) {
    542             KeyStore.TrustedCertificateEntry trE = (KeyStore.TrustedCertificateEntry) entry;
    543             engineSetCertificateEntry(alias, trE.getTrustedCertificate());
    544             return;
    545         }
    546 
    547         if (param != null && !(param instanceof KeyStoreParameter)) {
    548             throw new KeyStoreException(
    549                     "protParam should be android.security.KeyStoreParameter; was: "
    550                     + param.getClass().getName());
    551         }
    552 
    553         if (entry instanceof PrivateKeyEntry) {
    554             PrivateKeyEntry prE = (PrivateKeyEntry) entry;
    555             setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(),
    556                     (KeyStoreParameter) param);
    557             return;
    558         }
    559 
    560         throw new KeyStoreException(
    561                 "Entry must be a PrivateKeyEntry or TrustedCertificateEntry; was " + entry);
    562     }
    563 
    564 }
    565