Home | History | Annotate | Download | only in managedprovisioning
      1 /*
      2  * Copyright (C) 2018 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.cts.verifier.managedprovisioning;
     18 
     19 import static android.keystore.cts.CertificateUtils.createCertificate;
     20 
     21 import android.app.admin.DevicePolicyManager;
     22 import android.content.Context;
     23 import android.os.AsyncTask;
     24 import android.os.Bundle;
     25 import android.security.AttestedKeyPair;
     26 import android.security.KeyChain;
     27 import android.security.KeyChainAliasCallback;
     28 import android.security.KeyChainException;
     29 import android.security.keystore.KeyGenParameterSpec;
     30 import android.security.keystore.KeyProperties;
     31 import android.text.method.ScrollingMovementMethod;
     32 import android.util.Log;
     33 import android.view.View;
     34 import android.widget.Button;
     35 import android.widget.TextView;
     36 import com.android.cts.verifier.PassFailButtons;
     37 import com.android.cts.verifier.R;
     38 import java.security.GeneralSecurityException;
     39 import java.security.Principal;
     40 import java.security.PrivateKey;
     41 import java.security.Signature;
     42 import java.security.cert.X509Certificate;
     43 import java.util.Arrays;
     44 import javax.security.auth.x500.X500Principal;
     45 
     46 /**
     47  * Activity to test KeyChain key generation. The following flows are tested: * Generating a key. *
     48  * Installing a (self-signed) certificate associated with the key, visible to users. * Setting
     49  * visibility of the certificate to not be visible to user.
     50  *
     51  * <p>After the key generation and certificate installation, it should be possible for a user to
     52  * select the key from the certificate selection prompt when {@code KeyChain.choosePrivateKeyAlias}
     53  * is called. The test then tests that the key is indeed usable for signing.
     54  *
     55  * <p>After the visibility is set to not-user-visible, the prompt is shown again, this time the
     56  * testes is asked to verify no keys are selectable and cancel the dialog.
     57  */
     58 public class KeyChainTestActivity extends PassFailButtons.Activity {
     59     private static final String TAG = "ByodKeyChainActivity";
     60 
     61     public static final String ACTION_KEYCHAIN =
     62             "com.android.cts.verifier.managedprovisioning.KEYCHAIN";
     63 
     64     public static final String ALIAS = "cts-verifier-gen-rsa-1";
     65     public static final String KEY_ALGORITHM = "RSA";
     66 
     67     private DevicePolicyManager mDevicePolicyManager;
     68     private AttestedKeyPair mAttestedKeyPair;
     69     private X509Certificate mCert;
     70     private TextView mLogView;
     71     private TextView mInstructionsView;
     72     private Button mSetupButton;
     73     private Button mGoButton;
     74 
     75     // Callback interface for when a key is generated.
     76     static interface KeyGenerationListener {
     77         void onKeyPairGenerated(AttestedKeyPair keyPair);
     78     }
     79 
     80     // Task for generating a key pair using {@code DevicePolicyManager.generateKeyPair}.
     81     // The listener, if provided, will be invoked after the key has been generated successfully.
     82     class GenerateKeyTask extends AsyncTask<KeyGenParameterSpec, Integer, AttestedKeyPair> {
     83         KeyGenerationListener mListener;
     84 
     85         public GenerateKeyTask(KeyGenerationListener listener) {
     86             mListener = listener;
     87         }
     88 
     89         @Override
     90         protected AttestedKeyPair doInBackground(KeyGenParameterSpec... specs) {
     91             Log.i(TAG, "Generating key pair.");
     92             try {
     93                 AttestedKeyPair kp =
     94                         mDevicePolicyManager.generateKeyPair(
     95                                 DeviceAdminTestReceiver.getReceiverComponentName(),
     96                                 KEY_ALGORITHM,
     97                                 specs[0],
     98                                 0);
     99                 if (kp != null) {
    100                     mLogView.setText("Key generated successfully.");
    101                 } else {
    102                     mLogView.setText("Failed generating key.");
    103                 }
    104                 return kp;
    105             } catch (SecurityException e) {
    106                 mLogView.setText("Security exception while generating key.");
    107                 Log.w(TAG, "Security exception", e);
    108             }
    109 
    110             return null;
    111         }
    112 
    113         @Override
    114         protected void onPostExecute(AttestedKeyPair kp) {
    115             super.onPostExecute(kp);
    116             if (mListener != null && kp != null) {
    117                 mListener.onKeyPairGenerated(kp);
    118             }
    119         }
    120     }
    121 
    122     // Helper for generating and installing a self-signed certificate.
    123     class CertificateInstaller implements KeyGenerationListener {
    124         @Override
    125         public void onKeyPairGenerated(AttestedKeyPair keyPair) {
    126             mAttestedKeyPair = keyPair;
    127             X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
    128             X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
    129             try {
    130                 mCert = createCertificate(mAttestedKeyPair.getKeyPair(), subject, issuer);
    131                 boolean installResult = installCertificate(mCert, true);
    132                 // called from onPostExecute so safe to interact with the UI here.
    133                 if (installResult) {
    134                     mLogView.setText("Test ready");
    135                     mInstructionsView.setText(R.string.provisioning_byod_keychain_info_first_test);
    136                     mGoButton.setEnabled(true);
    137                 } else {
    138                     mLogView.setText("FAILED certificate installation.");
    139                 }
    140             } catch (Exception e) {
    141                 Log.w(TAG, "Failed installing certificate", e);
    142                 mLogView.setText("Error generating a certificate.");
    143             }
    144         }
    145     }
    146 
    147     // Helper for calling {@code DevicePolicyManager.setKeyPairCertificate} with the user-visibility
    148     // specified in the constructor. Returns true if the call was successful (and no exceptions
    149     // were thrown).
    150     protected boolean installCertificate(X509Certificate cert, boolean isUserVisible) {
    151         try {
    152             return mDevicePolicyManager.setKeyPairCertificate(
    153                     DeviceAdminTestReceiver.getReceiverComponentName(),
    154                     ALIAS,
    155                     Arrays.asList(new X509Certificate[] {cert}),
    156                     isUserVisible);
    157         } catch (SecurityException e) {
    158             logStatus("Security exception while installing cert.");
    159             Log.w(TAG, "Security exception", e);
    160         }
    161         return false;
    162     }
    163 
    164     // Invokes choosePrivateKeyAlias.
    165     void selectCertificate(KeyChainAliasCallback callback) {
    166         String[] keyTypes = new String[] {KEY_ALGORITHM};
    167         Principal[] issuers = new Principal[0];
    168         KeyChain.choosePrivateKeyAlias(
    169                 KeyChainTestActivity.this, callback, keyTypes, issuers, null, null);
    170     }
    171 
    172     class TestPreparator implements View.OnClickListener {
    173         @Override
    174         public void onClick(View v) {
    175             mLogView.setText("Starting key generation");
    176             KeyGenParameterSpec spec =
    177                     new KeyGenParameterSpec.Builder(
    178                                     ALIAS,
    179                                     KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
    180                             .setKeySize(2048)
    181                             .setDigests(KeyProperties.DIGEST_SHA256)
    182                             .setSignaturePaddings(
    183                                     KeyProperties.SIGNATURE_PADDING_RSA_PSS,
    184                                     KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
    185                             .build();
    186             new GenerateKeyTask(new CertificateInstaller()).execute(spec);
    187         }
    188     }
    189 
    190     class SelectCertificate implements View.OnClickListener, KeyChainAliasCallback {
    191         @Override
    192         public void onClick(View v) {
    193             Log.i(TAG, "Selecting certificate");
    194             mLogView.setText("Waiting for prompt");
    195             selectCertificate(this);
    196         }
    197 
    198         @Override
    199         public void alias(String alias) {
    200             Log.i(TAG, "Got alias: " + alias);
    201             if (alias == null) {
    202                 logStatus("FAILED (no alias)");
    203                 return;
    204             } else if (!alias.equals(ALIAS)) {
    205                 logStatus("FAILED (wrong alias)");
    206                 return;
    207             }
    208             logStatus("Got right alias.");
    209             try {
    210                 PrivateKey privateKey = KeyChain.getPrivateKey(KeyChainTestActivity.this, alias);
    211                 if (privateKey == null) {
    212                     logStatus("FAILED (key unavailable)");
    213                     return;
    214                 }
    215 
    216                 byte[] data = new String("hello").getBytes();
    217                 Signature sign = Signature.getInstance("SHA256withRSA");
    218                 sign.initSign(privateKey);
    219                 sign.update(data);
    220                 if (sign.sign() != null) {
    221                     prepareSecondTest();
    222                 } else {
    223                     logStatus("FAILED (cannot sign)");
    224                 }
    225             } catch (GeneralSecurityException | KeyChainException | InterruptedException e) {
    226                 Log.w(TAG, "Failed using the key", e);
    227                 logStatus("FAILED (key unusable)");
    228             }
    229         }
    230     }
    231 
    232     class SelectCertificateExpectingNone implements View.OnClickListener, KeyChainAliasCallback {
    233         @Override
    234         public void onClick(View v) {
    235             Log.i(TAG, "Selecting certificate");
    236             mLogView.setText("Prompt should not appear.");
    237             selectCertificate(this);
    238         }
    239 
    240         @Override
    241         public void alias(String alias) {
    242             Log.i(TAG, "Got alias: " + alias);
    243             if (alias != null) {
    244                 logStatus("FAILED: Should have no certificate.");
    245             } else {
    246                 logStatus("PASSED (2/2)");
    247                 runOnUiThread(
    248                         () -> {
    249                             getPassButton().setEnabled(true);
    250                         });
    251             }
    252         }
    253     }
    254 
    255     @Override
    256     protected void onCreate(Bundle savedInstanceState) {
    257         super.onCreate(savedInstanceState);
    258         setContentView(R.layout.keychain_test);
    259         setPassFailButtonClickListeners();
    260         mDevicePolicyManager =
    261                 (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
    262 
    263         mLogView = (TextView) findViewById(R.id.provisioning_byod_keychain_test_log);
    264         mLogView.setMovementMethod(new ScrollingMovementMethod());
    265 
    266         mInstructionsView = (TextView) findViewById(R.id.provisioning_byod_keychain_instructions);
    267 
    268         mSetupButton = (Button) findViewById(R.id.prepare_test_button);
    269         mSetupButton.setOnClickListener(new TestPreparator());
    270 
    271         mGoButton = (Button) findViewById(R.id.run_test_button);
    272         mGoButton.setOnClickListener(new SelectCertificate());
    273         mGoButton.setEnabled(false);
    274 
    275         // Disable the pass button here, only enable it when the 2nd test passes.
    276         getPassButton().setEnabled(false);
    277     }
    278 
    279     protected void prepareSecondTest() {
    280         Runnable uiChanges;
    281         if (installCertificate(mCert, false)) {
    282             uiChanges =
    283                     () -> {
    284                         mLogView.setText("Second test ready.");
    285                         mInstructionsView.setText(
    286                                 R.string.provisioning_byod_keychain_info_second_test);
    287                         mGoButton.setText("Run 2nd test");
    288                         mGoButton.setOnClickListener(new SelectCertificateExpectingNone());
    289                     };
    290         } else {
    291             uiChanges =
    292                     () -> {
    293                         mLogView.setText("FAILED second test setup.");
    294                         mGoButton.setEnabled(false);
    295                     };
    296         }
    297 
    298         runOnUiThread(uiChanges);
    299     }
    300 
    301     @Override
    302     public void finish() {
    303         super.finish();
    304         try {
    305             mDevicePolicyManager.removeKeyPair(
    306                     DeviceAdminTestReceiver.getReceiverComponentName(), ALIAS);
    307             Log.i(TAG, "Deleted alias " + ALIAS);
    308         } catch (SecurityException e) {
    309             Log.w(TAG, "Failed deleting alias", e);
    310         }
    311     }
    312 
    313     private void logStatus(String status) {
    314         runOnUiThread(
    315                 () -> {
    316                     mLogView.setText(status);
    317                 });
    318     }
    319 }
    320