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