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.app.admin.DevicePolicyManager;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.PackageManager;
     23 import android.os.Bundle;
     24 import android.os.RemoteException;
     25 import android.os.UserHandle;
     26 import android.security.Credentials;
     27 import android.security.KeyChain;
     28 import android.security.IKeyChainService;
     29 import android.text.Html;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 import com.android.org.bouncycastle.asn1.ASN1InputStream;
     33 import com.android.org.bouncycastle.asn1.ASN1Sequence;
     34 import com.android.org.bouncycastle.asn1.DEROctetString;
     35 import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
     36 import com.android.org.conscrypt.TrustedCertificateStore;
     37 
     38 import java.io.ByteArrayInputStream;
     39 import java.io.IOException;
     40 import java.security.KeyFactory;
     41 import java.security.KeyStore.PasswordProtection;
     42 import java.security.KeyStore.PrivateKeyEntry;
     43 import java.security.KeyStore;
     44 import java.security.NoSuchAlgorithmException;
     45 import java.security.PrivateKey;
     46 import java.security.cert.Certificate;
     47 import java.security.cert.CertificateEncodingException;
     48 import java.security.cert.CertificateException;
     49 import java.security.cert.CertificateFactory;
     50 import java.security.cert.X509Certificate;
     51 import java.security.spec.InvalidKeySpecException;
     52 import java.security.spec.PKCS8EncodedKeySpec;
     53 import java.util.ArrayList;
     54 import java.util.Enumeration;
     55 import java.util.HashMap;
     56 import java.util.List;
     57 
     58 /**
     59  * A helper class for accessing the raw data in the intent extra and handling
     60  * certificates.
     61  */
     62 class CredentialHelper {
     63     private static final String DATA_KEY = "data";
     64     private static final String CERTS_KEY = "crts";
     65 
     66     private static final String TAG = "CredentialHelper";
     67 
     68     // keep raw data from intent's extra
     69     private HashMap<String, byte[]> mBundle = new HashMap<String, byte[]>();
     70 
     71     private String mName = "";
     72     private int mUid = -1;
     73     private PrivateKey mUserKey;
     74     private X509Certificate mUserCert;
     75     private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
     76 
     77     CredentialHelper() {
     78     }
     79 
     80     CredentialHelper(Intent intent) {
     81         Bundle bundle = intent.getExtras();
     82         if (bundle == null) {
     83             return;
     84         }
     85 
     86         String name = bundle.getString(KeyChain.EXTRA_NAME);
     87         bundle.remove(KeyChain.EXTRA_NAME);
     88         if (name != null) {
     89             mName = name;
     90         }
     91 
     92         mUid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
     93         bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
     94 
     95         Log.d(TAG, "# extras: " + bundle.size());
     96         for (String key : bundle.keySet()) {
     97             byte[] bytes = bundle.getByteArray(key);
     98             Log.d(TAG, "   " + key + ": " + ((bytes == null) ? -1 : bytes.length));
     99             mBundle.put(key, bytes);
    100         }
    101         parseCert(getData(KeyChain.EXTRA_CERTIFICATE));
    102     }
    103 
    104     synchronized void onSaveStates(Bundle outStates) {
    105         try {
    106             outStates.putSerializable(DATA_KEY, mBundle);
    107             outStates.putString(KeyChain.EXTRA_NAME, mName);
    108             outStates.putInt(Credentials.EXTRA_INSTALL_AS_UID, mUid);
    109             if (mUserKey != null) {
    110                 outStates.putByteArray(Credentials.USER_PRIVATE_KEY,
    111                         mUserKey.getEncoded());
    112             }
    113             ArrayList<byte[]> certs = new ArrayList<byte[]>(mCaCerts.size() + 1);
    114             if (mUserCert != null) {
    115                 certs.add(mUserCert.getEncoded());
    116             }
    117             for (X509Certificate cert : mCaCerts) {
    118                 certs.add(cert.getEncoded());
    119             }
    120             outStates.putByteArray(CERTS_KEY, Util.toBytes(certs));
    121         } catch (CertificateEncodingException e) {
    122             throw new AssertionError(e);
    123         }
    124     }
    125 
    126     void onRestoreStates(Bundle savedStates) {
    127         mBundle = (HashMap) savedStates.getSerializable(DATA_KEY);
    128         mName = savedStates.getString(KeyChain.EXTRA_NAME);
    129         mUid = savedStates.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
    130         byte[] bytes = savedStates.getByteArray(Credentials.USER_PRIVATE_KEY);
    131         if (bytes != null) {
    132             setPrivateKey(bytes);
    133         }
    134 
    135         ArrayList<byte[]> certs = Util.fromBytes(savedStates.getByteArray(CERTS_KEY));
    136         for (byte[] cert : certs) {
    137             parseCert(cert);
    138         }
    139     }
    140 
    141     X509Certificate getUserCertificate() {
    142         return mUserCert;
    143     }
    144 
    145     private void parseCert(byte[] bytes) {
    146         if (bytes == null) {
    147             return;
    148         }
    149 
    150         try {
    151             CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    152             X509Certificate cert = (X509Certificate)
    153                     certFactory.generateCertificate(
    154                             new ByteArrayInputStream(bytes));
    155             if (isCa(cert)) {
    156                 Log.d(TAG, "got a CA cert");
    157                 mCaCerts.add(cert);
    158             } else {
    159                 Log.d(TAG, "got a user cert");
    160                 mUserCert = cert;
    161             }
    162         } catch (CertificateException e) {
    163             Log.w(TAG, "parseCert(): " + e);
    164         }
    165     }
    166 
    167     private boolean isCa(X509Certificate cert) {
    168         try {
    169             // TODO: add a test about this
    170             byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
    171             if (asn1EncodedBytes == null) {
    172                 return false;
    173             }
    174             DEROctetString derOctetString = (DEROctetString)
    175                     new ASN1InputStream(asn1EncodedBytes).readObject();
    176             byte[] octets = derOctetString.getOctets();
    177             ASN1Sequence sequence = (ASN1Sequence)
    178                     new ASN1InputStream(octets).readObject();
    179             return BasicConstraints.getInstance(sequence).isCA();
    180         } catch (IOException e) {
    181             return false;
    182         }
    183     }
    184 
    185     boolean hasPkcs12KeyStore() {
    186         return mBundle.containsKey(KeyChain.EXTRA_PKCS12);
    187     }
    188 
    189     boolean hasKeyPair() {
    190         return mBundle.containsKey(Credentials.EXTRA_PUBLIC_KEY)
    191                 && mBundle.containsKey(Credentials.EXTRA_PRIVATE_KEY);
    192     }
    193 
    194     boolean hasUserCertificate() {
    195         return (mUserCert != null);
    196     }
    197 
    198     boolean hasCaCerts() {
    199         return !mCaCerts.isEmpty();
    200     }
    201 
    202     boolean hasAnyForSystemInstall() {
    203         return (mUserKey != null) || hasUserCertificate() || hasCaCerts();
    204     }
    205 
    206     void setPrivateKey(byte[] bytes) {
    207         try {
    208             KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    209             mUserKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
    210         } catch (NoSuchAlgorithmException e) {
    211             throw new AssertionError(e);
    212         } catch (InvalidKeySpecException e) {
    213             throw new AssertionError(e);
    214         }
    215     }
    216 
    217     boolean containsAnyRawData() {
    218         return !mBundle.isEmpty();
    219     }
    220 
    221     byte[] getData(String key) {
    222         return mBundle.get(key);
    223     }
    224 
    225     void putPkcs12Data(byte[] data) {
    226         mBundle.put(KeyChain.EXTRA_PKCS12, data);
    227     }
    228 
    229     CharSequence getDescription(Context context) {
    230         // TODO: create more descriptive string
    231         StringBuilder sb = new StringBuilder();
    232         String newline = "<br>";
    233         if (mUserKey != null) {
    234             sb.append(context.getString(R.string.one_userkey)).append(newline);
    235         }
    236         if (mUserCert != null) {
    237             sb.append(context.getString(R.string.one_usercrt)).append(newline);
    238         }
    239         int n = mCaCerts.size();
    240         if (n > 0) {
    241             if (n == 1) {
    242                 sb.append(context.getString(R.string.one_cacrt));
    243             } else {
    244                 sb.append(context.getString(R.string.n_cacrts, n));
    245             }
    246         }
    247         return Html.fromHtml(sb.toString());
    248     }
    249 
    250     void setName(String name) {
    251         mName = name;
    252     }
    253 
    254     String getName() {
    255         return mName;
    256     }
    257 
    258     void setInstallAsUid(int uid) {
    259         mUid = uid;
    260     }
    261 
    262     boolean isInstallAsUidSet() {
    263         return mUid != -1;
    264     }
    265 
    266     int getInstallAsUid() {
    267         return mUid;
    268     }
    269 
    270     Intent createSystemInstallIntent(final Context context) {
    271         Intent intent = new Intent("com.android.credentials.INSTALL");
    272         // To prevent the private key from being sniffed, we explicitly spell
    273         // out the intent receiver class.
    274         if (!isWear(context)) {
    275             intent.setClassName(Util.SETTINGS_PACKAGE, "com.android.settings.CredentialStorage");
    276         } else {
    277             intent.setClassName("com.google.android.apps.wearable.settings",
    278                     "com.google.android.clockwork.settings.CredentialStorage");
    279         }
    280         intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid);
    281         try {
    282             if (mUserKey != null) {
    283                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME,
    284                         Credentials.USER_PRIVATE_KEY + mName);
    285                 intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA,
    286                         mUserKey.getEncoded());
    287             }
    288             if (mUserCert != null) {
    289                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME,
    290                         Credentials.USER_CERTIFICATE + mName);
    291                 intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA,
    292                         Credentials.convertToPem(mUserCert));
    293             }
    294             if (!mCaCerts.isEmpty()) {
    295                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME,
    296                         Credentials.CA_CERTIFICATE + mName);
    297                 X509Certificate[] caCerts
    298                         = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
    299                 intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA,
    300                         Credentials.convertToPem(caCerts));
    301             }
    302             return intent;
    303         } catch (IOException e) {
    304             throw new AssertionError(e);
    305         } catch (CertificateEncodingException e) {
    306             throw new AssertionError(e);
    307         }
    308     }
    309 
    310     boolean installVpnAndAppsTrustAnchors(Context context, IKeyChainService keyChainService) {
    311         final TrustedCertificateStore trustedCertificateStore = new TrustedCertificateStore();
    312         final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
    313         for (X509Certificate caCert : mCaCerts) {
    314             byte[] bytes = null;
    315             try {
    316                 bytes = caCert.getEncoded();
    317             } catch (CertificateEncodingException e) {
    318                 throw new AssertionError(e);
    319             }
    320             if (bytes != null) {
    321                 try {
    322                     keyChainService.installCaCertificate(bytes);
    323                 } catch (RemoteException e) {
    324                     Log.w(TAG, "installCaCertsToKeyChain(): " + e);
    325                     return false;
    326                 }
    327 
    328                 String alias = trustedCertificateStore.getCertificateAlias(caCert);
    329                 if (alias == null) {
    330                     Log.e(TAG, "alias is null");
    331                     return false;
    332                 }
    333 
    334                 // Some CTS verifier test asks testers to reset auto approved CA cert by removing
    335                 // lock sreen, but it's not possible if we don't have Android lock screen. (e.g.
    336                 // Android is running in the container).  In this case, disable auto cert approval.
    337                 if (context.getResources().getBoolean(R.bool.config_auto_cert_approval)) {
    338                     // Since the cert is installed by real user, the cert is approved by the user
    339                     dpm.approveCaCert(alias, UserHandle.myUserId(), true);
    340                 }
    341             }
    342         }
    343         return true;
    344     }
    345 
    346     boolean hasPassword() {
    347         if (!hasPkcs12KeyStore()) {
    348             return false;
    349         }
    350         try {
    351             return loadPkcs12Internal(new PasswordProtection(new char[] {})) == null;
    352         } catch (Exception e) {
    353             return true;
    354         }
    355     }
    356 
    357     boolean extractPkcs12(String password) {
    358         try {
    359             return extractPkcs12Internal(new PasswordProtection(password.toCharArray()));
    360         } catch (Exception e) {
    361             Log.w(TAG, "extractPkcs12(): " + e, e);
    362             return false;
    363         }
    364     }
    365 
    366     private boolean extractPkcs12Internal(PasswordProtection password)
    367             throws Exception {
    368         // TODO: add test about this
    369         java.security.KeyStore keystore = loadPkcs12Internal(password);
    370 
    371         Enumeration<String> aliases = keystore.aliases();
    372         if (!aliases.hasMoreElements()) {
    373             Log.e(TAG, "PKCS12 file has no elements");
    374             return false;
    375         }
    376 
    377         while (aliases.hasMoreElements()) {
    378             String alias = aliases.nextElement();
    379             if (keystore.isKeyEntry(alias)) {
    380                 KeyStore.Entry entry = keystore.getEntry(alias, password);
    381                 Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
    382 
    383                 if (entry instanceof PrivateKeyEntry) {
    384                     if (TextUtils.isEmpty(mName)) {
    385                         mName = alias;
    386                     }
    387                     return installFrom((PrivateKeyEntry) entry);
    388                 }
    389             } else {
    390                 // KeyStore.getEntry with non-null ProtectionParameter can only be invoked on
    391                 // PrivateKeyEntry or SecretKeyEntry.
    392                 // See https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html
    393                 Log.d(TAG, "Skip non-key entry, alias = " + alias);
    394             }
    395         }
    396         return true;
    397     }
    398 
    399     private java.security.KeyStore loadPkcs12Internal(PasswordProtection password)
    400             throws Exception {
    401         java.security.KeyStore keystore = java.security.KeyStore.getInstance("PKCS12");
    402         keystore.load(new ByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
    403                       password.getPassword());
    404         return keystore;
    405     }
    406 
    407     private synchronized boolean installFrom(PrivateKeyEntry entry) {
    408         mUserKey = entry.getPrivateKey();
    409         mUserCert = (X509Certificate) entry.getCertificate();
    410 
    411         Certificate[] certs = entry.getCertificateChain();
    412         Log.d(TAG, "# certs extracted = " + certs.length);
    413         mCaCerts = new ArrayList<X509Certificate>(certs.length);
    414         for (Certificate c : certs) {
    415             X509Certificate cert = (X509Certificate) c;
    416             if (isCa(cert)) {
    417                 mCaCerts.add(cert);
    418             }
    419         }
    420         Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
    421 
    422         return true;
    423     }
    424 
    425     private static boolean isWear(final Context context) {
    426         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
    427     }
    428 
    429     /**
    430      * Returns whether this credential contains CA certificates to be used as trust anchors
    431      * for VPN and apps.
    432      */
    433     public boolean includesVpnAndAppsTrustAnchors() {
    434         if (!hasCaCerts()) {
    435             return false;
    436         }
    437         if (getInstallAsUid() != android.security.KeyStore.UID_SELF) {
    438             // VPN and Apps trust anchors can only be installed under UID_SELF
    439             return false;
    440         }
    441 
    442         if (mUserKey != null) {
    443             // We are installing a key pair for client authentication, its CA
    444             // should have nothing to do with VPN and apps trust anchors.
    445             return false;
    446         } else {
    447             return true;
    448         }
    449     }
    450 }
    451