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