Home | History | Annotate | Download | only in certinstaller
      1 /*
      2  * Copyright (C) 2009 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 com.android.certinstaller;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.os.Bundle;
     22 import android.os.RemoteException;
     23 import android.security.Credentials;
     24 import android.security.KeyChain;
     25 import android.security.IKeyChainService;
     26 import android.text.Html;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 import com.android.org.bouncycastle.asn1.ASN1InputStream;
     30 import com.android.org.bouncycastle.asn1.ASN1Sequence;
     31 import com.android.org.bouncycastle.asn1.DEROctetString;
     32 import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
     33 import java.io.ByteArrayInputStream;
     34 import java.io.IOException;
     35 import java.security.KeyFactory;
     36 import java.security.KeyStore.PasswordProtection;
     37 import java.security.KeyStore.PrivateKeyEntry;
     38 import java.security.KeyStore;
     39 import java.security.NoSuchAlgorithmException;
     40 import java.security.PrivateKey;
     41 import java.security.cert.Certificate;
     42 import java.security.cert.CertificateEncodingException;
     43 import java.security.cert.CertificateException;
     44 import java.security.cert.CertificateFactory;
     45 import java.security.cert.X509Certificate;
     46 import java.security.spec.InvalidKeySpecException;
     47 import java.security.spec.PKCS8EncodedKeySpec;
     48 import java.util.ArrayList;
     49 import java.util.Enumeration;
     50 import java.util.HashMap;
     51 import java.util.List;
     52 
     53 /**
     54  * A helper class for accessing the raw data in the intent extra and handling
     55  * certificates.
     56  */
     57 class CredentialHelper {
     58     private static final String DATA_KEY = "data";
     59     private static final String CERTS_KEY = "crts";
     60 
     61     private static final String TAG = "CredentialHelper";
     62 
     63     // keep raw data from intent's extra
     64     private HashMap<String, byte[]> mBundle = new HashMap<String, byte[]>();
     65 
     66     private String mName = "";
     67     private PrivateKey mUserKey;
     68     private X509Certificate mUserCert;
     69     private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
     70 
     71     CredentialHelper() {
     72     }
     73 
     74     CredentialHelper(Intent intent) {
     75         Bundle bundle = intent.getExtras();
     76         if (bundle == null) {
     77             return;
     78         }
     79 
     80         String name = bundle.getString(KeyChain.EXTRA_NAME);
     81         bundle.remove(KeyChain.EXTRA_NAME);
     82         if (name != null) {
     83             mName = name;
     84         }
     85 
     86         Log.d(TAG, "# extras: " + bundle.size());
     87         for (String key : bundle.keySet()) {
     88             byte[] bytes = bundle.getByteArray(key);
     89             Log.d(TAG, "   " + key + ": " + ((bytes == null) ? -1 : bytes.length));
     90             mBundle.put(key, bytes);
     91         }
     92         parseCert(getData(KeyChain.EXTRA_CERTIFICATE));
     93     }
     94 
     95     synchronized void onSaveStates(Bundle outStates) {
     96         try {
     97             outStates.putSerializable(DATA_KEY, mBundle);
     98             outStates.putString(KeyChain.EXTRA_NAME, mName);
     99             if (mUserKey != null) {
    100                 outStates.putByteArray(Credentials.USER_PRIVATE_KEY,
    101                         mUserKey.getEncoded());
    102             }
    103             ArrayList<byte[]> certs = new ArrayList<byte[]>(mCaCerts.size() + 1);
    104             if (mUserCert != null) {
    105                 certs.add(mUserCert.getEncoded());
    106             }
    107             for (X509Certificate cert : mCaCerts) {
    108                 certs.add(cert.getEncoded());
    109             }
    110             outStates.putByteArray(CERTS_KEY, Util.toBytes(certs));
    111         } catch (CertificateEncodingException e) {
    112             throw new AssertionError(e);
    113         }
    114     }
    115 
    116     void onRestoreStates(Bundle savedStates) {
    117         mBundle = (HashMap) savedStates.getSerializable(DATA_KEY);
    118         mName = savedStates.getString(KeyChain.EXTRA_NAME);
    119         byte[] bytes = savedStates.getByteArray(Credentials.USER_PRIVATE_KEY);
    120         if (bytes != null) {
    121             setPrivateKey(bytes);
    122         }
    123 
    124         ArrayList<byte[]> certs = Util.fromBytes(savedStates.getByteArray(CERTS_KEY));
    125         for (byte[] cert : certs) {
    126             parseCert(cert);
    127         }
    128     }
    129 
    130     X509Certificate getUserCertificate() {
    131         return mUserCert;
    132     }
    133 
    134     private void parseCert(byte[] bytes) {
    135         if (bytes == null) {
    136             return;
    137         }
    138 
    139         try {
    140             CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    141             X509Certificate cert = (X509Certificate)
    142                     certFactory.generateCertificate(
    143                             new ByteArrayInputStream(bytes));
    144             if (isCa(cert)) {
    145                 Log.d(TAG, "got a CA cert");
    146                 mCaCerts.add(cert);
    147             } else {
    148                 Log.d(TAG, "got a user cert");
    149                 mUserCert = cert;
    150             }
    151         } catch (CertificateException e) {
    152             Log.w(TAG, "parseCert(): " + e);
    153         }
    154     }
    155 
    156     private boolean isCa(X509Certificate cert) {
    157         try {
    158             // TODO: add a test about this
    159             byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
    160             if (asn1EncodedBytes == null) {
    161                 return false;
    162             }
    163             DEROctetString derOctetString = (DEROctetString)
    164                     new ASN1InputStream(asn1EncodedBytes).readObject();
    165             byte[] octets = derOctetString.getOctets();
    166             ASN1Sequence sequence = (ASN1Sequence)
    167                     new ASN1InputStream(octets).readObject();
    168             return BasicConstraints.getInstance(sequence).isCA();
    169         } catch (IOException e) {
    170             return false;
    171         }
    172     }
    173 
    174     boolean hasPkcs12KeyStore() {
    175         return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
    176     }
    177 
    178     boolean hasKeyPair() {
    179         return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY)
    180                 && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
    181     }
    182 
    183     boolean hasUserCertificate() {
    184         return (mUserCert != null);
    185     }
    186 
    187     boolean hasCaCerts() {
    188         return !mCaCerts.isEmpty();
    189     }
    190 
    191     boolean hasAnyForSystemInstall() {
    192         return (mUserKey != null) || hasUserCertificate() || hasCaCerts();
    193     }
    194 
    195     void setPrivateKey(byte[] bytes) {
    196         try {
    197             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    198             mUserKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
    199         } catch (NoSuchAlgorithmException e) {
    200             throw new AssertionError(e);
    201         } catch (InvalidKeySpecException e) {
    202             throw new AssertionError(e);
    203         }
    204     }
    205 
    206     boolean containsAnyRawData() {
    207         return !mBundle.isEmpty();
    208     }
    209 
    210     byte[] getData(String key) {
    211         return mBundle.get(key);
    212     }
    213 
    214     void putPkcs12Data(byte[] data) {
    215         mBundle.put(KeyChain.EXTRA_PKCS12, data);
    216     }
    217 
    218     CharSequence getDescription(Context context) {
    219         // TODO: create more descriptive string
    220         StringBuilder sb = new StringBuilder();
    221         String newline = "<br>";
    222         if (mUserKey != null) {
    223             sb.append(context.getString(R.string.one_userkey)).append(newline);
    224         }
    225         if (mUserCert != null) {
    226             sb.append(context.getString(R.string.one_usercrt)).append(newline);
    227         }
    228         int n = mCaCerts.size();
    229         if (n > 0) {
    230             if (n == 1) {
    231                 sb.append(context.getString(R.string.one_cacrt));
    232             } else {
    233                 sb.append(context.getString(R.string.n_cacrts, n));
    234             }
    235         }
    236         return Html.fromHtml(sb.toString());
    237     }
    238 
    239     void setName(String name) {
    240         mName = name;
    241     }
    242 
    243     String getName() {
    244         return mName;
    245     }
    246 
    247     Intent createSystemInstallIntent() {
    248         Intent intent = new Intent("com.android.credentials.INSTALL");
    249         // To prevent the private key from being sniffed, we explicitly spell
    250         // out the intent receiver class.
    251         intent.setClassName("com.android.settings", "com.android.settings.CredentialStorage");
    252         try {
    253             if (mUserKey != null) {
    254                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME,
    255                         Credentials.USER_PRIVATE_KEY + mName);
    256                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA,
    257                         mUserKey.getEncoded());
    258             }
    259             if (mUserCert != null) {
    260                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME,
    261                         Credentials.USER_CERTIFICATE + mName);
    262                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA,
    263                         Credentials.convertToPem(mUserCert));
    264             }
    265             if (!mCaCerts.isEmpty()) {
    266                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME,
    267                         Credentials.CA_CERTIFICATE + mName);
    268                 X509Certificate[] caCerts
    269                         = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
    270                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA,
    271                         Credentials.convertToPem(caCerts));
    272             }
    273             return intent;
    274         } catch (IOException e) {
    275             throw new AssertionError(e);
    276         } catch (CertificateEncodingException e) {
    277             throw new AssertionError(e);
    278         }
    279     }
    280 
    281     boolean installCaCertsToKeyChain(IKeyChainService keyChainService) {
    282         for (X509Certificate caCert : mCaCerts) {
    283             byte[] bytes = null;
    284             try {
    285                 bytes = caCert.getEncoded();
    286             } catch (CertificateEncodingException e) {
    287                 throw new AssertionError(e);
    288             }
    289             if (bytes != null) {
    290                 try {
    291                     keyChainService.installCaCertificate(bytes);
    292                 } catch (RemoteException e) {
    293                     Log.w(TAG, "installCaCertsToKeyChain(): " + e);
    294                     return false;
    295                 }
    296             }
    297         }
    298         return true;
    299     }
    300 
    301     boolean extractPkcs12(String password) {
    302         try {
    303             return extractPkcs12Internal(password);
    304         } catch (Exception e) {
    305             Log.w(TAG, "extractPkcs12(): " + e, e);
    306             return false;
    307         }
    308     }
    309 
    310     private boolean extractPkcs12Internal(String password)
    311             throws Exception {
    312         // TODO: add test about this
    313         java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12");
    314         PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
    315         keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
    316                       passwordProtection.getPassword());
    317 
    318         Enumeration<String> aliases = keystore.aliases();
    319         if (!aliases.hasMoreElements()) {
    320             return false;
    321         }
    322 
    323         while (aliases.hasMoreElements()) {
    324             String alias = aliases.nextElement();
    325             KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
    326             Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
    327 
    328             if (entry instanceof PrivateKeyEntry) {
    329                 if (TextUtils.isEmpty(mName)) {
    330                     mName = alias;
    331                 }
    332                 return installFrom((PrivateKeyEntry) entry);
    333             }
    334         }
    335         return true;
    336     }
    337 
    338     private synchronized boolean installFrom(PrivateKeyEntry entry) {
    339         mUserKey = entry.getPrivateKey();
    340         mUserCert = (X509Certificate) entry.getCertificate();
    341 
    342         Certificate[] certs = entry.getCertificateChain();
    343         Log.d(TAG, "# certs extracted = " + certs.length);
    344         mCaCerts = new ArrayList<X509Certificate>(certs.length);
    345         for (Certificate c : certs) {
    346             X509Certificate cert = (X509Certificate) c;
    347             if (isCa(cert)) {
    348                 mCaCerts.add(cert);
    349             }
    350         }
    351         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
    352 
    353         return true;
    354     }
    355 }
    356