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 {
onKeyPairGenerated(AttestedKeyPair keyPair)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 
GenerateKeyTask(KeyGenerationListener listener)85         public GenerateKeyTask(KeyGenerationListener listener) {
86             mListener = listener;
87         }
88 
89         @Override
doInBackground(KeyGenParameterSpec... specs)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
onPostExecute(AttestedKeyPair kp)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
onKeyPairGenerated(AttestedKeyPair keyPair)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).
installCertificate(X509Certificate cert, boolean isUserVisible)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.
selectCertificate(KeyChainAliasCallback callback)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
onClick(View v)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
onClick(View v)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
alias(String alias)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
onClick(View v)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
alias(String alias)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
onCreate(Bundle savedInstanceState)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 
prepareSecondTest()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
finish()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 
logStatus(String status)313     private void logStatus(String status) {
314         runOnUiThread(
315                 () -> {
316                     mLogView.setText(status);
317                 });
318     }
319 }
320