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