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