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.util.Log;
     28 import com.android.org.bouncycastle.asn1.ASN1InputStream;
     29 import com.android.org.bouncycastle.asn1.ASN1Sequence;
     30 import com.android.org.bouncycastle.asn1.DEROctetString;
     31 import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
     32 import java.io.ByteArrayInputStream;
     33 import java.io.IOException;
     34 import java.security.KeyFactory;
     35 import java.security.KeyStore.PasswordProtection;
     36 import java.security.KeyStore.PrivateKeyEntry;
     37 import java.security.KeyStore;
     38 import java.security.NoSuchAlgorithmException;
     39 import java.security.PrivateKey;
     40 import java.security.cert.Certificate;
     41 import java.security.cert.CertificateEncodingException;
     42 import java.security.cert.CertificateException;
     43 import java.security.cert.CertificateFactory;
     44 import java.security.cert.X509Certificate;
     45 import java.security.spec.InvalidKeySpecException;
     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 
     52 /**
     53  * A helper class for accessing the raw data in the intent extra and handling
     54  * certificates.
     55  */
     56 class CredentialHelper {
     57     static final String CERT_NAME_KEY = "name";
     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(CERT_NAME_KEY);
     81         bundle.remove(CERT_NAME_KEY);
     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(CERT_NAME_KEY, 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(CERT_NAME_KEY);
    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 new BasicConstraints(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.USER_PRIVATE_KEY + mName,
    255                                 Credentials.convertToPem(mUserKey));
    256             }
    257             if (mUserCert != null) {
    258                 intent.putExtra(Credentials.USER_CERTIFICATE + mName,
    259                                 Credentials.convertToPem(mUserCert));
    260             }
    261             if (!mCaCerts.isEmpty()) {
    262                 Object[] caCerts = (Object[])
    263                         mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
    264                 intent.putExtra(Credentials.CA_CERTIFICATE + mName,
    265                                 Credentials.convertToPem(caCerts));
    266             }
    267             return intent;
    268         } catch (IOException e) {
    269             throw new AssertionError(e);
    270         }
    271     }
    272 
    273     boolean installCaCertsToKeyChain(IKeyChainService keyChainService) {
    274         for (X509Certificate caCert : mCaCerts) {
    275             byte[] bytes = null;
    276             try {
    277                 bytes = caCert.getEncoded();
    278             } catch (CertificateEncodingException e) {
    279                 throw new AssertionError(e);
    280             }
    281             if (bytes != null) {
    282                 try {
    283                     keyChainService.installCaCertificate(bytes);
    284                 } catch (RemoteException e) {
    285                     Log.w(TAG, "installCaCertsToKeyChain(): " + e);
    286                     return false;
    287                 }
    288             }
    289         }
    290         return true;
    291     }
    292 
    293     boolean extractPkcs12(String password) {
    294         try {
    295             return extractPkcs12Internal(password);
    296         } catch (Exception e) {
    297             Log.w(TAG, "extractPkcs12(): " + e, e);
    298             return false;
    299         }
    300     }
    301 
    302     private boolean extractPkcs12Internal(String password)
    303             throws Exception {
    304         // TODO: add test about this
    305         java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12");
    306         PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
    307         keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
    308                       passwordProtection.getPassword());
    309 
    310         Enumeration<String> aliases = keystore.aliases();
    311         if (!aliases.hasMoreElements()) {
    312             return false;
    313         }
    314 
    315         while (aliases.hasMoreElements()) {
    316             String alias = aliases.nextElement();
    317             KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
    318             Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
    319 
    320             if (entry instanceof PrivateKeyEntry) {
    321                 mName = alias;
    322                 return installFrom((PrivateKeyEntry) entry);
    323             }
    324         }
    325         return true;
    326     }
    327 
    328     private synchronized boolean installFrom(PrivateKeyEntry entry) {
    329         mUserKey = entry.getPrivateKey();
    330         mUserCert = (X509Certificate) entry.getCertificate();
    331 
    332         Certificate[] certs = entry.getCertificateChain();
    333         Log.d(TAG, "# certs extracted = " + certs.length);
    334         mCaCerts = new ArrayList<X509Certificate>(certs.length);
    335         for (Certificate c : certs) {
    336             X509Certificate cert = (X509Certificate) c;
    337             if (isCa(cert)) {
    338                 mCaCerts.add(cert);
    339             }
    340         }
    341         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
    342 
    343         return true;
    344     }
    345 }
    346