1 /* 2 * Copyright (C) 2019 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 android.content.pm.PackageManager; 20 import android.icu.util.Calendar; 21 import android.os.Bundle; 22 import android.security.ConfirmationAlreadyPresentingException; 23 import android.security.ConfirmationCallback; 24 import android.security.ConfirmationNotAvailableException; 25 import android.security.ConfirmationPrompt; 26 import android.security.keystore.KeyGenParameterSpec; 27 import android.security.keystore.KeyProperties; 28 import android.view.View; 29 import android.view.View.OnClickListener; 30 import android.widget.Button; 31 import android.widget.Toast; 32 33 import com.android.cts.verifier.PassFailButtons; 34 import com.android.cts.verifier.R; 35 36 import java.io.IOException; 37 import java.security.InvalidAlgorithmParameterException; 38 import java.security.InvalidKeyException; 39 import java.security.KeyPairGenerator; 40 import java.security.KeyStore; 41 import java.security.KeyStoreException; 42 import java.security.NoSuchAlgorithmException; 43 import java.security.NoSuchProviderException; 44 import java.security.Signature; 45 import java.security.SignatureException; 46 import java.security.UnrecoverableEntryException; 47 import java.security.cert.CertificateException; 48 import java.util.Date; 49 50 public class ProtectedConfirmationTest extends PassFailButtons.Activity { 51 52 /** 53 * Alias for our key in the Android Key Store. 54 */ 55 private static final String KEY_NAME = "my_confirmation_key"; 56 private boolean teeTestSuccess = false; 57 private boolean strongboxTestSuccess = false; 58 59 @Override onCreate(Bundle savedInstanceState)60 protected void onCreate(Bundle savedInstanceState) { 61 super.onCreate(savedInstanceState); 62 setContentView(R.layout.sec_protected_confirmation_main); 63 setPassFailButtonClickListeners(); 64 65 boolean protectedConfirmationSupported = 66 ConfirmationPrompt.isSupported(getApplicationContext()); 67 if (protectedConfirmationSupported) { 68 setInfoResources(R.string.sec_protected_confirmation_test, 69 R.string.sec_protected_confirmation_test_info, -1); 70 getPassButton().setEnabled(false); 71 } else { 72 setInfoResources(R.string.sec_protected_confirmation_not_supported_title, 73 R.string.sec_protected_confirmation_not_supported_info, -1); 74 getPassButton().setEnabled(true); 75 return; 76 } 77 boolean hasStrongbox = this.getPackageManager() 78 .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); 79 80 findViewById(R.id.sec_protected_confirmation_tee_test_success) 81 .setVisibility(View.INVISIBLE); 82 findViewById(R.id.sec_protected_confirmation_strongbox_test_success) 83 .setVisibility(View.INVISIBLE); 84 Button startTestButton = (Button) findViewById(R.id.sec_start_test_button); 85 startTestButton.setOnClickListener(new OnClickListener() { 86 @Override 87 public void onClick(View v) { 88 showToast("Test running..."); 89 v.post(new Runnable() { 90 @Override 91 public void run() { 92 runTest(false /* useStrongbox */); 93 } 94 }); 95 } 96 97 }); 98 99 Button startStrongboxTestButton = 100 (Button) findViewById(R.id.sec_start_test_strongbox_button); 101 if (hasStrongbox) { 102 startStrongboxTestButton.setOnClickListener(new OnClickListener() { 103 @Override 104 public void onClick(View v) { 105 showToast("Test running..."); 106 v.post(new Runnable() { 107 @Override 108 public void run() { 109 runTest(true /* useStrongbox */); 110 } 111 }); 112 } 113 114 }); 115 } else { 116 startStrongboxTestButton.setVisibility(View.GONE); 117 // since strongbox is unavailable we mark the strongbox test as passed so that the tee 118 // test alone can make the test pass. 119 strongboxTestSuccess = true; 120 } 121 122 } 123 124 /** 125 * Creates an asymmetric signing key in AndroidKeyStore which can only be used for signing 126 * user confirmed messages. 127 */ createKey(boolean useStrongbox)128 private void createKey(boolean useStrongbox) { 129 Calendar calendar = Calendar.getInstance(); 130 Date validityStart = calendar.getTime(); 131 calendar.add(Calendar.YEAR, 1); 132 Date validityEnd = calendar.getTime(); 133 try { 134 KeyPairGenerator kpg = KeyPairGenerator.getInstance( 135 KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); 136 KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder( 137 KEY_NAME, 138 KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); 139 builder.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512); 140 builder.setAttestationChallenge("CtsVerifierTest".getBytes()); 141 builder.setUserConfirmationRequired(true); 142 builder.setIsStrongBoxBacked(useStrongbox); 143 builder.setKeyValidityStart(validityStart); 144 builder.setKeyValidityEnd(validityEnd); 145 kpg.initialize(builder.build()); 146 kpg.generateKeyPair(); 147 } catch (NoSuchAlgorithmException | NoSuchProviderException | 148 InvalidAlgorithmParameterException e) { 149 throw new RuntimeException("Failed to create confirmation key", e); 150 } 151 } 152 trySign(byte[] dataThatWasConfirmed)153 private boolean trySign(byte[] dataThatWasConfirmed) { 154 try { 155 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 156 keyStore.load(null); 157 KeyStore.Entry key = keyStore.getEntry(KEY_NAME, null); 158 Signature s = Signature.getInstance("SHA256withECDSA"); 159 s.initSign(((KeyStore.PrivateKeyEntry) key).getPrivateKey()); 160 s.update(dataThatWasConfirmed); 161 s.sign(); 162 } catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException | 163 UnrecoverableEntryException | InvalidKeyException e) { 164 throw new RuntimeException("Failed to load confirmation key", e); 165 } catch (SignatureException e) { 166 return false; 167 } 168 return true; 169 } 170 runTest(boolean useStrongbox)171 private void runTest(boolean useStrongbox) { 172 createKey(useStrongbox); 173 if (trySign(getString(R.string.sec_protected_confirmation_message) 174 .getBytes())) { 175 showToast("Test failed. Key could sign without confirmation."); 176 } else { 177 showConfirmationPrompt( 178 getString(R.string.sec_protected_confirmation_message), 179 useStrongbox); 180 } 181 } 182 showConfirmationPrompt(String confirmationMessage, boolean useStrongbox)183 private void showConfirmationPrompt(String confirmationMessage, boolean useStrongbox) { 184 ConfirmationPrompt.Builder builder = new ConfirmationPrompt.Builder(this); 185 builder.setPromptText(confirmationMessage); 186 builder.setExtraData(new byte[]{0x1, 0x02, 0x03}); 187 ConfirmationPrompt prompt = builder.build(); 188 try { 189 prompt.presentPrompt(getMainExecutor(), 190 new ConfirmationCallback() { 191 @Override 192 public void onConfirmed(byte[] dataThatWasConfirmed) { 193 super.onConfirmed(dataThatWasConfirmed); 194 if (trySign(dataThatWasConfirmed)) { 195 markTestSuccess(useStrongbox); 196 } else { 197 showToast("Failed to sign confirmed message"); 198 } 199 } 200 201 @Override 202 public void onDismissed() { 203 super.onDismissed(); 204 showToast("User dismissed the dialog."); 205 } 206 207 @Override 208 public void onCanceled() { 209 super.onCanceled(); 210 showToast("Confirmation dialog was canceled."); 211 } 212 213 @Override 214 public void onError(Throwable e) { 215 super.onError(e); 216 throw new RuntimeException("Confirmation Callback encountered an error", 217 e); 218 } 219 }); 220 } catch (ConfirmationAlreadyPresentingException | ConfirmationNotAvailableException e) { 221 throw new RuntimeException("Error trying to present the confirmation prompt", e); 222 } 223 } 224 showToast(String message)225 private void showToast(String message) { 226 Toast.makeText(this, message, Toast.LENGTH_LONG) 227 .show(); 228 } 229 markTestSuccess(boolean strongbox)230 private void markTestSuccess(boolean strongbox) { 231 if (strongbox) { 232 if (!strongboxTestSuccess) { 233 findViewById(R.id.sec_protected_confirmation_strongbox_test_success) 234 .setVisibility(View.VISIBLE); 235 } 236 strongboxTestSuccess = true; 237 } else { 238 if (!teeTestSuccess) { 239 findViewById(R.id.sec_protected_confirmation_tee_test_success) 240 .setVisibility(View.VISIBLE); 241 } 242 teeTestSuccess = true; 243 } 244 if (strongboxTestSuccess && teeTestSuccess) { 245 showToast("Test passed."); 246 getPassButton().setEnabled(true); 247 } 248 } 249 } 250 251