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