1 /* 2 * Copyright (C) 2011 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.security; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 22 import android.app.AlertDialog; 23 import android.app.KeyguardManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Bundle; 27 import android.security.keystore.KeyGenParameterSpec; 28 import android.security.keystore.KeyPermanentlyInvalidatedException; 29 import android.security.keystore.KeyProperties; 30 import android.security.keystore.UserNotAuthenticatedException; 31 import android.view.View; 32 import android.view.View.OnClickListener; 33 import android.widget.Button; 34 import android.widget.Toast; 35 36 import java.io.IOException; 37 import java.security.InvalidAlgorithmParameterException; 38 import java.security.InvalidKeyException; 39 import java.security.KeyStore; 40 import java.security.KeyStoreException; 41 import java.security.NoSuchAlgorithmException; 42 import java.security.NoSuchProviderException; 43 import java.security.UnrecoverableKeyException; 44 import java.security.cert.CertificateException; 45 46 import javax.crypto.BadPaddingException; 47 import javax.crypto.Cipher; 48 import javax.crypto.IllegalBlockSizeException; 49 import javax.crypto.KeyGenerator; 50 import javax.crypto.NoSuchPaddingException; 51 import javax.crypto.SecretKey; 52 53 public class LskfBoundKeysTest extends PassFailButtons.Activity { 54 55 /** Alias for our key in the Android Key Store. */ 56 private static final String KEY_NAME = "my_lock_key"; 57 private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6}; 58 private static final int AUTHENTICATION_DURATION_SECONDS = 5; 59 private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1; 60 61 private KeyguardManager mKeyguardManager; 62 63 @Override onCreate(Bundle savedInstanceState)64 protected void onCreate(Bundle savedInstanceState) { 65 super.onCreate(savedInstanceState); 66 setContentView(R.layout.sec_screen_lock_keys_main); 67 setPassFailButtonClickListeners(); 68 setInfoResources(R.string.sec_lskf_bound_key_test, R.string.sec_lskf_bound_key_test_info, -1); 69 70 mKeyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); 71 72 getPassButton().setEnabled(false); 73 74 Button startTestButton = (Button) findViewById(R.id.sec_start_test_button); 75 startTestButton.setOnClickListener(new OnClickListener() { 76 @Override 77 public void onClick(View v) { 78 showToast("Test running..."); 79 v.postDelayed(new Runnable() { 80 @Override 81 public void run() { 82 if (tryEncrypt()) { 83 showToast("Test failed. Key accessible without auth."); 84 } else { 85 showAuthenticationScreen(); 86 } 87 } 88 }, 89 AUTHENTICATION_DURATION_SECONDS * 1000); 90 } 91 92 }); 93 94 if (!mKeyguardManager.isKeyguardSecure()) { 95 // Show a message that the user hasn't set up a lock screen. 96 Toast.makeText(this, 97 "Secure lock screen hasn't set up.\n" 98 + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen", 99 Toast.LENGTH_LONG).show(); 100 startTestButton.setEnabled(false); 101 return; 102 } 103 104 createKey(); 105 } 106 107 /** 108 * Creates a symmetric key in the Android Key Store which can only be used after the user has 109 * authenticated with device credentials within the last X seconds. 110 */ createKey()111 private void createKey() { 112 // Generate a key to decrypt payment credentials, tokens, etc. 113 // This will most likely be a registration step for the user when they are setting up your app. 114 try { 115 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 116 keyStore.load(null); 117 KeyGenerator keyGenerator = KeyGenerator.getInstance( 118 KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); 119 120 // Set the alias of the entry in Android KeyStore where the key will appear 121 // and the constrains (purposes) in the constructor of the Builder 122 keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, 123 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 124 .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 125 .setUserAuthenticationRequired(true) 126 // Require that the user has unlocked in the last 30 seconds 127 .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) 128 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 129 .build()); 130 keyGenerator.generateKey(); 131 } catch (NoSuchAlgorithmException | NoSuchProviderException 132 | InvalidAlgorithmParameterException | KeyStoreException 133 | CertificateException | IOException e) { 134 throw new RuntimeException("Failed to create a symmetric key", e); 135 } 136 } 137 138 /** 139 * Tries to encrypt some data with the generated key in {@link #createKey} which is 140 * only works if the user has just authenticated via device credentials. 141 */ tryEncrypt()142 private boolean tryEncrypt() { 143 try { 144 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 145 keyStore.load(null); 146 SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); 147 Cipher cipher = Cipher.getInstance( 148 KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" 149 + KeyProperties.ENCRYPTION_PADDING_PKCS7); 150 151 // Try encrypting something, it will only work if the user authenticated within 152 // the last AUTHENTICATION_DURATION_SECONDS seconds. 153 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 154 cipher.doFinal(SECRET_BYTE_ARRAY); 155 return true; 156 } catch (UserNotAuthenticatedException e) { 157 // User is not authenticated, let's authenticate with device credentials. 158 return false; 159 } catch (KeyPermanentlyInvalidatedException e) { 160 // This happens if the lock screen has been disabled or reset after the key was 161 // generated after the key was generated. 162 createKey(); 163 Toast.makeText(this, "Set up lockscreen after test ran. Retry the test.\n" 164 + e.getMessage(), 165 Toast.LENGTH_LONG).show(); 166 return false; 167 } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException | 168 CertificateException | UnrecoverableKeyException | IOException 169 | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { 170 throw new RuntimeException(e); 171 } 172 } 173 showAuthenticationScreen()174 private void showAuthenticationScreen() { 175 // Create the Confirm Credentials screen. You can customize the title and description. Or 176 // we will provide a generic one for you if you leave it null 177 Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); 178 if (intent != null) { 179 startActivityForResult(intent, CONFIRM_CREDENTIALS_REQUEST_CODE); 180 } 181 } 182 showToast(String message)183 private void showToast(String message) { 184 Toast.makeText(this, message, Toast.LENGTH_LONG) 185 .show(); 186 } 187 188 @Override onActivityResult(int requestCode, int resultCode, Intent data)189 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 190 super.onActivityResult(requestCode, resultCode, data); 191 switch (requestCode) { 192 case CONFIRM_CREDENTIALS_REQUEST_CODE: 193 if (resultCode == RESULT_OK) { 194 /** 195 * Regarding to b/258823254, sometimes the test fail because the 196 * decryption of synthetic password has not been done when 197 * calling tryEncrypt(). So that add the retry for tryEncrypt() 198 */ 199 final long deadline = System.currentTimeMillis() + 1000L; 200 while (System.currentTimeMillis() < deadline) { 201 if (tryEncrypt()) { 202 showToast("Test passed."); 203 getPassButton().setEnabled(true); 204 return; 205 } 206 try { 207 Thread.sleep(100); 208 } catch(InterruptedException e) { 209 } 210 } 211 showToast("Test failed. Key not accessible after auth"); 212 } 213 } 214 } 215 } 216 217