1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2015 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker package com.example.android.confirmcredential; 18*90c8c64dSAndroid Build Coastguard Worker 19*90c8c64dSAndroid Build Coastguard Worker import android.app.Activity; 20*90c8c64dSAndroid Build Coastguard Worker import android.app.KeyguardManager; 21*90c8c64dSAndroid Build Coastguard Worker import android.content.Context; 22*90c8c64dSAndroid Build Coastguard Worker import android.content.Intent; 23*90c8c64dSAndroid Build Coastguard Worker import android.os.Bundle; 24*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.KeyGenParameterSpec; 25*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.KeyPermanentlyInvalidatedException; 26*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.KeyProperties; 27*90c8c64dSAndroid Build Coastguard Worker import android.security.keystore.UserNotAuthenticatedException; 28*90c8c64dSAndroid Build Coastguard Worker import android.view.View; 29*90c8c64dSAndroid Build Coastguard Worker import android.widget.Button; 30*90c8c64dSAndroid Build Coastguard Worker import android.widget.TextView; 31*90c8c64dSAndroid Build Coastguard Worker import android.widget.Toast; 32*90c8c64dSAndroid Build Coastguard Worker 33*90c8c64dSAndroid Build Coastguard Worker import java.io.IOException; 34*90c8c64dSAndroid Build Coastguard Worker import java.security.InvalidAlgorithmParameterException; 35*90c8c64dSAndroid Build Coastguard Worker import java.security.InvalidKeyException; 36*90c8c64dSAndroid Build Coastguard Worker import java.security.KeyStore; 37*90c8c64dSAndroid Build Coastguard Worker import java.security.KeyStoreException; 38*90c8c64dSAndroid Build Coastguard Worker import java.security.NoSuchAlgorithmException; 39*90c8c64dSAndroid Build Coastguard Worker import java.security.NoSuchProviderException; 40*90c8c64dSAndroid Build Coastguard Worker import java.security.UnrecoverableKeyException; 41*90c8c64dSAndroid Build Coastguard Worker import java.security.cert.CertificateException; 42*90c8c64dSAndroid Build Coastguard Worker 43*90c8c64dSAndroid Build Coastguard Worker import javax.crypto.BadPaddingException; 44*90c8c64dSAndroid Build Coastguard Worker import javax.crypto.Cipher; 45*90c8c64dSAndroid Build Coastguard Worker import javax.crypto.IllegalBlockSizeException; 46*90c8c64dSAndroid Build Coastguard Worker import javax.crypto.KeyGenerator; 47*90c8c64dSAndroid Build Coastguard Worker import javax.crypto.NoSuchPaddingException; 48*90c8c64dSAndroid Build Coastguard Worker import javax.crypto.SecretKey; 49*90c8c64dSAndroid Build Coastguard Worker 50*90c8c64dSAndroid Build Coastguard Worker /** 51*90c8c64dSAndroid Build Coastguard Worker * Main entry point for the sample, showing a backpack and "Purchase" button. 52*90c8c64dSAndroid Build Coastguard Worker */ 53*90c8c64dSAndroid Build Coastguard Worker public class MainActivity extends Activity { 54*90c8c64dSAndroid Build Coastguard Worker 55*90c8c64dSAndroid Build Coastguard Worker /** Alias for our key in the Android Key Store. */ 56*90c8c64dSAndroid Build Coastguard Worker private static final String KEY_NAME = "my_key"; 57*90c8c64dSAndroid Build Coastguard Worker private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6}; 58*90c8c64dSAndroid Build Coastguard Worker 59*90c8c64dSAndroid Build Coastguard Worker private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 1; 60*90c8c64dSAndroid Build Coastguard Worker 61*90c8c64dSAndroid Build Coastguard Worker /** 62*90c8c64dSAndroid Build Coastguard Worker * If the user has unlocked the device Within the last this number of seconds, 63*90c8c64dSAndroid Build Coastguard Worker * it can be considered as an authenticator. 64*90c8c64dSAndroid Build Coastguard Worker */ 65*90c8c64dSAndroid Build Coastguard Worker private static final int AUTHENTICATION_DURATION_SECONDS = 30; 66*90c8c64dSAndroid Build Coastguard Worker 67*90c8c64dSAndroid Build Coastguard Worker private KeyguardManager mKeyguardManager; 68*90c8c64dSAndroid Build Coastguard Worker 69*90c8c64dSAndroid Build Coastguard Worker @Override onCreate(Bundle savedInstanceState)70*90c8c64dSAndroid Build Coastguard Worker protected void onCreate(Bundle savedInstanceState) { 71*90c8c64dSAndroid Build Coastguard Worker super.onCreate(savedInstanceState); 72*90c8c64dSAndroid Build Coastguard Worker setContentView(R.layout.activity_main); 73*90c8c64dSAndroid Build Coastguard Worker mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); 74*90c8c64dSAndroid Build Coastguard Worker Button purchaseButton = (Button) findViewById(R.id.purchase_button); 75*90c8c64dSAndroid Build Coastguard Worker if (!mKeyguardManager.isKeyguardSecure()) { 76*90c8c64dSAndroid Build Coastguard Worker // Show a message that the user hasn't set up a lock screen. 77*90c8c64dSAndroid Build Coastguard Worker Toast.makeText(this, 78*90c8c64dSAndroid Build Coastguard Worker "Secure lock screen hasn't set up.\n" 79*90c8c64dSAndroid Build Coastguard Worker + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen", 80*90c8c64dSAndroid Build Coastguard Worker Toast.LENGTH_LONG).show(); 81*90c8c64dSAndroid Build Coastguard Worker purchaseButton.setEnabled(false); 82*90c8c64dSAndroid Build Coastguard Worker return; 83*90c8c64dSAndroid Build Coastguard Worker } 84*90c8c64dSAndroid Build Coastguard Worker createKey(); 85*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.purchase_button).setOnClickListener(new View.OnClickListener() { 86*90c8c64dSAndroid Build Coastguard Worker @Override 87*90c8c64dSAndroid Build Coastguard Worker public void onClick(View v) { 88*90c8c64dSAndroid Build Coastguard Worker // Test to encrypt something. It might fail if the timeout expired (30s). 89*90c8c64dSAndroid Build Coastguard Worker tryEncrypt(); 90*90c8c64dSAndroid Build Coastguard Worker } 91*90c8c64dSAndroid Build Coastguard Worker }); 92*90c8c64dSAndroid Build Coastguard Worker } 93*90c8c64dSAndroid Build Coastguard Worker 94*90c8c64dSAndroid Build Coastguard Worker /** 95*90c8c64dSAndroid Build Coastguard Worker * Tries to encrypt some data with the generated key in {@link #createKey} which is 96*90c8c64dSAndroid Build Coastguard Worker * only works if the user has just authenticated via device credentials. 97*90c8c64dSAndroid Build Coastguard Worker */ tryEncrypt()98*90c8c64dSAndroid Build Coastguard Worker private boolean tryEncrypt() { 99*90c8c64dSAndroid Build Coastguard Worker try { 100*90c8c64dSAndroid Build Coastguard Worker KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 101*90c8c64dSAndroid Build Coastguard Worker keyStore.load(null); 102*90c8c64dSAndroid Build Coastguard Worker SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); 103*90c8c64dSAndroid Build Coastguard Worker Cipher cipher = Cipher.getInstance( 104*90c8c64dSAndroid Build Coastguard Worker KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" 105*90c8c64dSAndroid Build Coastguard Worker + KeyProperties.ENCRYPTION_PADDING_PKCS7); 106*90c8c64dSAndroid Build Coastguard Worker 107*90c8c64dSAndroid Build Coastguard Worker // Try encrypting something, it will only work if the user authenticated within 108*90c8c64dSAndroid Build Coastguard Worker // the last AUTHENTICATION_DURATION_SECONDS seconds. 109*90c8c64dSAndroid Build Coastguard Worker cipher.init(Cipher.ENCRYPT_MODE, secretKey); 110*90c8c64dSAndroid Build Coastguard Worker cipher.doFinal(SECRET_BYTE_ARRAY); 111*90c8c64dSAndroid Build Coastguard Worker 112*90c8c64dSAndroid Build Coastguard Worker // If the user has recently authenticated, you will reach here. 113*90c8c64dSAndroid Build Coastguard Worker showAlreadyAuthenticated(); 114*90c8c64dSAndroid Build Coastguard Worker return true; 115*90c8c64dSAndroid Build Coastguard Worker } catch (UserNotAuthenticatedException e) { 116*90c8c64dSAndroid Build Coastguard Worker // User is not authenticated, let's authenticate with device credentials. 117*90c8c64dSAndroid Build Coastguard Worker showAuthenticationScreen(); 118*90c8c64dSAndroid Build Coastguard Worker return false; 119*90c8c64dSAndroid Build Coastguard Worker } catch (KeyPermanentlyInvalidatedException e) { 120*90c8c64dSAndroid Build Coastguard Worker // This happens if the lock screen has been disabled or reset after the key was 121*90c8c64dSAndroid Build Coastguard Worker // generated after the key was generated. 122*90c8c64dSAndroid Build Coastguard Worker Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n" 123*90c8c64dSAndroid Build Coastguard Worker + e.getMessage(), 124*90c8c64dSAndroid Build Coastguard Worker Toast.LENGTH_LONG).show(); 125*90c8c64dSAndroid Build Coastguard Worker return false; 126*90c8c64dSAndroid Build Coastguard Worker } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException | 127*90c8c64dSAndroid Build Coastguard Worker CertificateException | UnrecoverableKeyException | IOException 128*90c8c64dSAndroid Build Coastguard Worker | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { 129*90c8c64dSAndroid Build Coastguard Worker throw new RuntimeException(e); 130*90c8c64dSAndroid Build Coastguard Worker } 131*90c8c64dSAndroid Build Coastguard Worker } 132*90c8c64dSAndroid Build Coastguard Worker 133*90c8c64dSAndroid Build Coastguard Worker /** 134*90c8c64dSAndroid Build Coastguard Worker * Creates a symmetric key in the Android Key Store which can only be used after the user has 135*90c8c64dSAndroid Build Coastguard Worker * authenticated with device credentials within the last X seconds. 136*90c8c64dSAndroid Build Coastguard Worker */ createKey()137*90c8c64dSAndroid Build Coastguard Worker private void createKey() { 138*90c8c64dSAndroid Build Coastguard Worker // Generate a key to decrypt payment credentials, tokens, etc. 139*90c8c64dSAndroid Build Coastguard Worker // This will most likely be a registration step for the user when they are setting up your app. 140*90c8c64dSAndroid Build Coastguard Worker try { 141*90c8c64dSAndroid Build Coastguard Worker KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 142*90c8c64dSAndroid Build Coastguard Worker keyStore.load(null); 143*90c8c64dSAndroid Build Coastguard Worker KeyGenerator keyGenerator = KeyGenerator.getInstance( 144*90c8c64dSAndroid Build Coastguard Worker KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); 145*90c8c64dSAndroid Build Coastguard Worker 146*90c8c64dSAndroid Build Coastguard Worker // Set the alias of the entry in Android KeyStore where the key will appear 147*90c8c64dSAndroid Build Coastguard Worker // and the constrains (purposes) in the constructor of the Builder 148*90c8c64dSAndroid Build Coastguard Worker keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, 149*90c8c64dSAndroid Build Coastguard Worker KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 150*90c8c64dSAndroid Build Coastguard Worker .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 151*90c8c64dSAndroid Build Coastguard Worker .setUserAuthenticationRequired(true) 152*90c8c64dSAndroid Build Coastguard Worker // Require that the user has unlocked in the last 30 seconds 153*90c8c64dSAndroid Build Coastguard Worker .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS) 154*90c8c64dSAndroid Build Coastguard Worker .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 155*90c8c64dSAndroid Build Coastguard Worker .build()); 156*90c8c64dSAndroid Build Coastguard Worker keyGenerator.generateKey(); 157*90c8c64dSAndroid Build Coastguard Worker } catch (NoSuchAlgorithmException | NoSuchProviderException 158*90c8c64dSAndroid Build Coastguard Worker | InvalidAlgorithmParameterException | KeyStoreException 159*90c8c64dSAndroid Build Coastguard Worker | CertificateException | IOException e) { 160*90c8c64dSAndroid Build Coastguard Worker throw new RuntimeException("Failed to create a symmetric key", e); 161*90c8c64dSAndroid Build Coastguard Worker } 162*90c8c64dSAndroid Build Coastguard Worker } 163*90c8c64dSAndroid Build Coastguard Worker showAuthenticationScreen()164*90c8c64dSAndroid Build Coastguard Worker private void showAuthenticationScreen() { 165*90c8c64dSAndroid Build Coastguard Worker // Create the Confirm Credentials screen. You can customize the title and description. Or 166*90c8c64dSAndroid Build Coastguard Worker // we will provide a generic one for you if you leave it null 167*90c8c64dSAndroid Build Coastguard Worker Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null); 168*90c8c64dSAndroid Build Coastguard Worker if (intent != null) { 169*90c8c64dSAndroid Build Coastguard Worker startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS); 170*90c8c64dSAndroid Build Coastguard Worker } 171*90c8c64dSAndroid Build Coastguard Worker } 172*90c8c64dSAndroid Build Coastguard Worker 173*90c8c64dSAndroid Build Coastguard Worker @Override onActivityResult(int requestCode, int resultCode, Intent data)174*90c8c64dSAndroid Build Coastguard Worker protected void onActivityResult(int requestCode, int resultCode, Intent data) { 175*90c8c64dSAndroid Build Coastguard Worker if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) { 176*90c8c64dSAndroid Build Coastguard Worker // Challenge completed, proceed with using cipher 177*90c8c64dSAndroid Build Coastguard Worker if (resultCode == RESULT_OK) { 178*90c8c64dSAndroid Build Coastguard Worker if (tryEncrypt()) { 179*90c8c64dSAndroid Build Coastguard Worker showPurchaseConfirmation(); 180*90c8c64dSAndroid Build Coastguard Worker } 181*90c8c64dSAndroid Build Coastguard Worker } else { 182*90c8c64dSAndroid Build Coastguard Worker // The user canceled or didn’t complete the lock screen 183*90c8c64dSAndroid Build Coastguard Worker // operation. Go to error/cancellation flow. 184*90c8c64dSAndroid Build Coastguard Worker } 185*90c8c64dSAndroid Build Coastguard Worker } 186*90c8c64dSAndroid Build Coastguard Worker } 187*90c8c64dSAndroid Build Coastguard Worker showPurchaseConfirmation()188*90c8c64dSAndroid Build Coastguard Worker private void showPurchaseConfirmation() { 189*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.confirmation_message).setVisibility(View.VISIBLE); 190*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.purchase_button).setEnabled(false); 191*90c8c64dSAndroid Build Coastguard Worker } 192*90c8c64dSAndroid Build Coastguard Worker showAlreadyAuthenticated()193*90c8c64dSAndroid Build Coastguard Worker private void showAlreadyAuthenticated() { 194*90c8c64dSAndroid Build Coastguard Worker TextView textView = (TextView) findViewById( 195*90c8c64dSAndroid Build Coastguard Worker R.id.already_has_valid_device_credential_message); 196*90c8c64dSAndroid Build Coastguard Worker textView.setVisibility(View.VISIBLE); 197*90c8c64dSAndroid Build Coastguard Worker textView.setText(getString( 198*90c8c64dSAndroid Build Coastguard Worker R.string.already_confirmed_device_credentials_within_last_x_seconds, 199*90c8c64dSAndroid Build Coastguard Worker AUTHENTICATION_DURATION_SECONDS)); 200*90c8c64dSAndroid Build Coastguard Worker findViewById(R.id.purchase_button).setEnabled(false); 201*90c8c64dSAndroid Build Coastguard Worker } 202*90c8c64dSAndroid Build Coastguard Worker 203*90c8c64dSAndroid Build Coastguard Worker } 204