| /* |
| * 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 void 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(); |
| } catch (UserNotAuthenticatedException e) { |
| // User is not authenticated, let's authenticate with device credentials. |
| showAuthenticationScreen(); |
| } 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(); |
| } 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) { |
| 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); |
| } |
| |
| } |