/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.example.android.confirmcredential; import android.app.Activity; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import android.security.keystore.UserNotAuthenticatedException; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; /** * Main entry point for the sample, showing a backpack and "Purchase" button. */ public class MainActivity extends Activity { /** Alias for our key in the Android Key Store. */ private static final String KEY_NAME = "my_key"; private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6}; private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1; /** * If the user has unlocked the device Within the last this number of seconds, * it can be considered as an authenticator. */ private static final int AUTHENTICATION_DURATION_SECONDS = 30; private KeyguardManager mKeyguardManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); Button purchaseButton = (Button) findViewById(R.id.purchase_button); if (!mKeyguardManager.isKeyguardSecure()) { // Show a message that the user hasn't set up a lock screen. Toast.makeText(this, "Secure lock screen hasn't set up.\n" + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen", Toast.LENGTH_LONG).show(); purchaseButton.setEnabled(false); return; } createKey(); findViewById(R.id.purchase_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Test to encrypt something. It might fail if the timeout expired (30s). tryEncrypt(); } }); } /** * Tries to encrypt some data with the generated key in {@link #createKey} which is * only works if the user has just authenticated via device credentials. */ private boolean tryEncrypt() { try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); // Try encrypting something, it will only work if the user authenticated within // the last AUTHENTICATION_DURATION_SECONDS seconds. cipher.init(Cipher.ENCRYPT_MODE, secretKey); cipher.doFinal(SECRET_BYTE_ARRAY); // If the user has recently authenticated, you will reach here. showAlreadyAuthenticated(); return true; } catch (UserNotAuthenticatedException e) { // User is not authenticated, let's authenticate with device credentials. showAuthenticationScreen(); return false; } catch (KeyPermanentlyInvalidatedException e) { // This happens if the lock screen has been disabled or reset after the key was // generated after the key was generated. Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" + e.getMessage(), Toast.LENGTH_LONG).show(); return false; } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException(e); } } /** * Creates a symmetric key in the Android Key Store which can only be used after the user has * authenticated with device credentials within the last X seconds. */ private void createKey() { // Generate a key to decrypt payment credentials, tokens, etc. // This will most likely be a registration step for the user when they are setting up your app. try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); KeyGenerator keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); // Set the alias of the entry in Android KeyStore where the key will appear // and the constrains (purposes) in the constructor of the Builder keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) // Require that the user has unlocked in the last 30 seconds .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .build()); keyGenerator.generateKey(); } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException | KeyStoreException | CertificateException | IOException e) { throw new RuntimeException("Failed to create a symmetric key", e); } } private void showAuthenticationScreen() { // Create the Confirm Credentials screen. You can customize the title and description. Or // we will provide a generic one for you if you leave it null Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); if (intent != null) { startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { // Challenge completed, proceed with using cipher if (resultCode == RESULT_OK) { if (tryEncrypt()) { showPurchaseConfirmation(); } } else { // The user canceled or didn’t complete the lock screen // operation. Go to error/cancellation flow. } } } private void showPurchaseConfirmation() { findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE); findViewById(R.id.purchase_button).setEnabled(false); } private void showAlreadyAuthenticated() { TextView textView = (TextView) findViewById( R.id.already_has_valid_device_credential_message); textView.setVisibility(View.VISIBLE); textView.setText(getString( R.string.already_confirmed_device_credentials_within_last_x_seconds, AUTHENTICATION_DURATION_SECONDS)); findViewById(R.id.purchase_button).setEnabled(false); } }