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.Manifest; 20 import android.app.KeyguardManager; 21 import android.content.Context; 22 import android.content.pm.FeatureInfo; 23 import android.content.pm.PackageManager; 24 import android.hardware.biometrics.BiometricManager; 25 import android.hardware.biometrics.BiometricManager.Authenticators; 26 import android.hardware.biometrics.BiometricPrompt; 27 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; 28 import android.hardware.biometrics.BiometricPrompt.AuthenticationResult; 29 import android.hardware.biometrics.BiometricPrompt.CryptoObject; 30 import android.os.Bundle; 31 import android.os.CancellationSignal; 32 import android.security.identity.AccessControlProfile; 33 import android.security.identity.AccessControlProfileId; 34 import android.security.identity.CredentialDataRequest; 35 import android.security.identity.CredentialDataResult; 36 import android.security.identity.IdentityCredential; 37 import android.security.identity.IdentityCredentialStore; 38 import android.security.identity.PersonalizationData; 39 import android.security.identity.PresentationSession; 40 import android.security.identity.WritableIdentityCredential; 41 import android.util.Log; 42 import android.widget.Button; 43 import android.widget.Toast; 44 45 import com.android.cts.verifier.PassFailButtons; 46 import com.android.cts.verifier.R; 47 48 import java.security.cert.X509Certificate; 49 import java.util.Arrays; 50 import java.util.Collection; 51 import java.util.LinkedHashMap; 52 import java.util.LinkedList; 53 import java.util.Locale; 54 import java.util.Map; 55 56 /** 57 * @hide 58 */ 59 public class IdentityCredentialAuthenticationMultiDocument extends PassFailButtons.Activity { 60 private static final boolean DEBUG = false; 61 private static final String TAG = "IdentityCredentialAuthenticationMultiDocument"; 62 63 private static final int BIOMETRIC_REQUEST_PERMISSION_CODE = 0; 64 65 private static final int PRESENTATION_SESSION_FEATURE_VERSION_NEEDED = 202201; 66 67 private BiometricManager mBiometricManager; 68 private KeyguardManager mKeyguardManager; 69 getTitleRes()70 protected int getTitleRes() { 71 return R.string.sec_identity_credential_authentication_multi_document_test; 72 } 73 getDescriptionRes()74 private int getDescriptionRes() { 75 return R.string.sec_identity_credential_authentication_multi_document_test_info; 76 } 77 78 // Returns 0 if Identity Credential is not implemented. Otherwise returns the feature version. 79 // getFeatureVersion(Context appContext)80 private static int getFeatureVersion(Context appContext) { 81 PackageManager pm = appContext.getPackageManager(); 82 83 if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) { 84 FeatureInfo[] infos = pm.getSystemAvailableFeatures(); 85 for (int n = 0; n < infos.length; n++) { 86 FeatureInfo info = infos[n]; 87 if (info.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) { 88 return info.version; 89 } 90 } 91 } 92 93 // Use of the system feature is not required since Android 12. So for Android 11 94 // return 202009 which is the feature version shipped with Android 11. 95 IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext); 96 if (store != null) { 97 return 202009; 98 } 99 100 return 0; 101 } 102 103 @Override onCreate(Bundle savedInstanceState)104 protected void onCreate(Bundle savedInstanceState) { 105 super.onCreate(savedInstanceState); 106 setContentView(R.layout.sec_screen_lock_keys_main); 107 setPassFailButtonClickListeners(); 108 setInfoResources(getTitleRes(), getDescriptionRes(), -1); 109 getPassButton().setEnabled(false); 110 requestPermissions(new String[]{Manifest.permission.USE_BIOMETRIC}, 111 BIOMETRIC_REQUEST_PERMISSION_CODE); 112 } 113 114 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] state)115 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { 116 if (requestCode == BIOMETRIC_REQUEST_PERMISSION_CODE 117 && state[0] == PackageManager.PERMISSION_GRANTED) { 118 mBiometricManager = getSystemService(BiometricManager.class); 119 mKeyguardManager = getSystemService(KeyguardManager.class); 120 Button startTestButton = findViewById(R.id.sec_start_test_button); 121 122 if (!mKeyguardManager.isKeyguardSecure()) { 123 // Show a message that the user hasn't set up a lock screen. 124 showToast("Secure lock screen hasn't been set up.\n Go to " 125 + "'Settings -> Security -> Screen lock' to set up a lock screen"); 126 startTestButton.setEnabled(false); 127 return; 128 } 129 130 startTestButton.setOnClickListener(v -> startTest()); 131 } 132 } 133 showToast(String message)134 protected void showToast(String message) { 135 Toast.makeText(this, message, Toast.LENGTH_LONG).show(); 136 Log.i(TAG, "Showing Toast: " + message); 137 } 138 provisionCredential(IdentityCredentialStore store, String credentialName)139 private void provisionCredential(IdentityCredentialStore store, 140 String credentialName) throws Exception { 141 store.deleteCredentialByName(credentialName); 142 WritableIdentityCredential wc = store.createCredential( 143 credentialName, "org.iso.18013-5.2019.mdl"); 144 145 // 'Bar' encoded as CBOR tstr 146 byte[] barCbor = {0x63, 0x42, 0x61, 0x72}; 147 148 AccessControlProfile acp = new AccessControlProfile.Builder(new AccessControlProfileId(0)) 149 .setUserAuthenticationRequired(true) 150 .setUserAuthenticationTimeout(0) 151 .build(); 152 LinkedList<AccessControlProfileId> idsProfile0 = new LinkedList<AccessControlProfileId>(); 153 idsProfile0.add(new AccessControlProfileId(0)); 154 PersonalizationData pd = new PersonalizationData.Builder() 155 .addAccessControlProfile(acp) 156 .putEntry("org.iso.18013-5.2019", "Foo", idsProfile0, barCbor) 157 .build(); 158 byte[] proofOfProvisioningSignature = wc.personalize(pd); 159 160 // Create authentication keys. 161 IdentityCredential credential = store.getCredentialByName(credentialName, 162 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 163 credential.setAvailableAuthenticationKeys(1, 10); 164 Collection<X509Certificate> dynAuthKeyCerts = credential.getAuthKeysNeedingCertification(); 165 credential.storeStaticAuthenticationData(dynAuthKeyCerts.iterator().next(), new byte[0]); 166 } 167 getFooStatus(PresentationSession session, String credentialName)168 private int getFooStatus(PresentationSession session, String credentialName) throws Exception { 169 Map<String, Collection<String>> isEntriesToRequest = new LinkedHashMap<>(); 170 isEntriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("Foo")); 171 172 CredentialDataResult rd = session.getCredentialData( 173 credentialName, 174 new CredentialDataRequest.Builder() 175 .setIssuerSignedEntriesToRequest(isEntriesToRequest) 176 .build()); 177 return rd.getIssuerSignedEntries().getStatus("org.iso.18013-5.2019", "Foo"); 178 } 179 startTest()180 protected void startTest() { 181 IdentityCredentialStore store = IdentityCredentialStore.getInstance(this); 182 if (store == null) { 183 showToast("No Identity Credential support, test passed."); 184 getPassButton().setEnabled(true); 185 return; 186 } 187 int featureVersion = getFeatureVersion(this); 188 Log.i(TAG, "Identity Credential featureVersion: " + featureVersion); 189 if (featureVersion < PRESENTATION_SESSION_FEATURE_VERSION_NEEDED) { 190 showToast(String.format( 191 Locale.US, 192 "Identity Credential version %d or later is required but " 193 + "version %d was found. Test passed.", 194 PRESENTATION_SESSION_FEATURE_VERSION_NEEDED, 195 featureVersion)); 196 getPassButton().setEnabled(true); 197 return; 198 } 199 200 final int result = mBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG); 201 switch (result) { 202 case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED: 203 showToast("No strong biometrics (Class 3) enrolled.\n" 204 + "Go to 'Settings -> Security' to enroll"); 205 Button startTestButton = findViewById(R.id.sec_start_test_button); 206 startTestButton.setEnabled(false); 207 return; 208 case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE: 209 showToast("No strong biometrics (Class 3), test passed."); 210 showToast("No Identity Credential support, test passed."); 211 getPassButton().setEnabled(true); 212 return; 213 } 214 215 try { 216 provisionCredential(store, "credential1"); 217 provisionCredential(store, "credential2"); 218 219 PresentationSession session = store.createPresentationSession( 220 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 221 222 // First, check that Foo cannot be retrieved without authentication. 223 // 224 int status = getFooStatus(session, "credential1"); 225 if (status != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) { 226 showToast("Unexpected credential1 pre-auth status " 227 + status + " expected STATUS_USER_AUTHENTICATION_FAILED"); 228 return; 229 } 230 status = getFooStatus(session, "credential2"); 231 if (status != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) { 232 showToast("Unexpected credential2 pre-auth status " 233 + status + " expected STATUS_USER_AUTHENTICATION_FAILED"); 234 return; 235 } 236 237 // Try one more time, this time with a CryptoObject that we'll use with 238 // BiometricPrompt. This should work. 239 // 240 CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(session); 241 BiometricPrompt.Builder builder = new BiometricPrompt.Builder(this); 242 builder.setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG); 243 builder.setTitle("Identity Credential"); 244 builder.setDescription("Authenticate to unlock multiple credentials."); 245 builder.setNegativeButton("Cancel", 246 getMainExecutor(), 247 (dialogInterface, i) -> showToast("Canceled biometric prompt.")); 248 final BiometricPrompt prompt = builder.build(); 249 final AuthenticationCallback callback = new AuthenticationCallback() { 250 @Override 251 public void onAuthenticationSucceeded(AuthenticationResult authResult) { 252 try { 253 // Check that Foo can be retrieved because we used 254 // the CryptoObject to auth with. 255 int status = getFooStatus(session, "credential1"); 256 if (status != CredentialDataResult.Entries.STATUS_OK) { 257 showToast("Unexpected credential1 post-auth status " 258 + status + " expected STATUS_OK"); 259 return; 260 } 261 status = getFooStatus(session, "credential2"); 262 if (status != CredentialDataResult.Entries.STATUS_OK) { 263 showToast("Unexpected credential2 post-auth status " 264 + status + " expected STATUS_OK"); 265 return; 266 } 267 268 // Finally, check that Foo cannot be retrieved again from another session 269 PresentationSession anotherSession = store.createPresentationSession( 270 IdentityCredentialStore 271 .CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256); 272 status = getFooStatus(anotherSession, "credential1"); 273 if (status 274 != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) { 275 showToast("Unexpected credential1 other session status " 276 + status + " expected STATUS_USER_AUTHENTICATION_FAILED"); 277 return; 278 } 279 status = getFooStatus(anotherSession, "credential2"); 280 if (status 281 != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) { 282 showToast("Unexpected credential2 other session status " 283 + status + " expected STATUS_USER_AUTHENTICATION_FAILED"); 284 return; 285 } 286 287 showToast("Test passed."); 288 getPassButton().setEnabled(true); 289 290 } catch (Exception e) { 291 showToast("Unexpected exception " + e); 292 } 293 } 294 }; 295 296 prompt.authenticate(cryptoObject, new CancellationSignal(), getMainExecutor(), 297 callback); 298 } catch (Exception e) { 299 showToast("Unexpection exception " + e); 300 } 301 } 302 } 303