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