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