com.tozny.e3db.android.KeyStoreManager.java Source code

Java tutorial

Introduction

Here is the source code for com.tozny.e3db.android.KeyStoreManager.java

Source

/*
 * TOZNY NON-COMMERCIAL LICENSE
 *
 * Tozny dual licenses this product. For commercial use, please contact
 * info@tozny.com. For non-commercial use, the contents of this file are
 * subject to the TOZNY NON-COMMERCIAL LICENSE (the "License") which
 * permits use of the software only by government agencies, schools,
 * universities, non-profit organizations or individuals on projects that
 * do not receive external funding other than government research grants
 * and contracts.  Any other use requires a commercial license. You may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at https://tozny.com/legal/non-commercial-license.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License. Portions of the software are Copyright (c) TOZNY LLC, 2018.
 * All rights reserved.
 *
 */

package com.tozny.e3db.android;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.support.v4.content.PermissionChecker;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;

import com.tozny.e3db.E3DBCryptoException;

import javax.crypto.Cipher;

import java.io.IOException;
import java.security.*;

import static com.tozny.e3db.android.KeyAuthentication.KeyAuthenticationType.*;

class KeyStoreManager {

    @SuppressLint({ "MissingPermission", "NewApi" })
    private static void checkArgs(Context context, KeyAuthentication protection,
            KeyAuthenticator keyAuthenticator) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            if (protection.authenticationType() == FINGERPRINT || protection.authenticationType() == LOCK_SCREEN)
                throw new IllegalArgumentException(
                        protection.authenticationType().toString() + " not supported below API 23.");
        }

        if (protection.authenticationType() == PASSWORD || protection.authenticationType() == FINGERPRINT
                || protection.authenticationType() == LOCK_SCREEN) {
            if (keyAuthenticator == null) {
                throw new IllegalArgumentException("KeyAuthenticator can't be null for key protection type: "
                        + protection.authenticationType().toString());
            }
        }

        if (protection.authenticationType() == FINGERPRINT) {
            if (PermissionChecker.checkSelfPermission(context,
                    Manifest.permission.USE_FINGERPRINT) != PermissionChecker.PERMISSION_GRANTED)
                throw new IllegalArgumentException(
                        protection.authenticationType().toString() + " permission not granted.");

            // Some devices support fingerprint but the support library doesn't recognize it, so
            // we use the actual FingerprintManager here. (https://stackoverflow.com/a/45181416/169359)
            FingerprintManager mgr = context.getSystemService(FingerprintManager.class);
            if (mgr == null || !mgr.isHardwareDetected())
                throw new IllegalArgumentException(
                        protection.authenticationType().toString() + " hardware not available.");
        }

        if (protection.authenticationType() == LOCK_SCREEN) {
            if (protection.validUntilSecondsSinceUnlock() < 1)
                throw new IllegalArgumentException("secondsSinceUnlock must be greater than 0.");
        }
    }

    interface AuthenticatedCipherHandler {
        void onAuthenticated(Cipher cipher);

        void onCancel();

        void onError(Throwable e);
    }

    @SuppressLint("NewApi")
    static void getCipher(final Context context, final String identifier, final KeyAuthentication protection,
            final KeyAuthenticator keyAuthenticator, final CipherManager.GetCipher cipherGetter,
            final AuthenticatedCipherHandler authenticatedCipherHandler) {

        checkArgs(context, protection, keyAuthenticator);

        final Cipher cipher;

        switch (protection.authenticationType()) {
        case NONE:
            try {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
                    authenticatedCipherHandler.onAuthenticated(cipherGetter.getCipher(context, identifier,
                            FSKSWrapper.getSecretKey(context, identifier, protection, null)));
                else
                    authenticatedCipherHandler.onAuthenticated(cipherGetter.getCipher(context, identifier,
                            AKSWrapper.getSecretKey(identifier, protection)));
            } catch (InvalidKeyException | UnrecoverableKeyException | IOException | E3DBCryptoException e) {
                authenticatedCipherHandler.onError(e);
            }

            break;
        case FINGERPRINT:
            try {
                cipher = cipherGetter.getCipher(context, identifier,
                        AKSWrapper.getSecretKey(identifier, protection));

                keyAuthenticator.authenticateWithFingerprint(new FingerprintManagerCompat.CryptoObject(cipher),
                        new KeyAuthenticator.AuthenticateHandler() {
                            @Override
                            public void handleAuthenticated() {
                                try {
                                    authenticatedCipherHandler.onAuthenticated(cipher);
                                } catch (Throwable e) {
                                    authenticatedCipherHandler.onError(e);
                                }
                            }

                            @Override
                            public void handleCancel() {
                                authenticatedCipherHandler.onCancel();
                            }

                            @Override
                            public void handleError(Throwable e) {
                                authenticatedCipherHandler.onError(e);
                            }
                        });

            } catch (InvalidKeyException | UnrecoverableKeyException | IOException | E3DBCryptoException e) {
                authenticatedCipherHandler.onError(e);
            }

            break;
        case LOCK_SCREEN:
            try {
                cipher = cipherGetter.getCipher(context, identifier,
                        AKSWrapper.getSecretKey(identifier, protection));
                authenticatedCipherHandler.onAuthenticated(
                        cipher); /* If the user unlocked the screen within the timeout limit, then this is already authenticated. */

            } catch (InvalidKeyException e) {

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                        && e instanceof KeyPermanentlyInvalidatedException) {
                    authenticatedCipherHandler.onError(e);
                } else {
                    keyAuthenticator.authenticateWithLockScreen(new KeyAuthenticator.AuthenticateHandler() {
                        @Override
                        public void handleAuthenticated() {
                            try {
                                getCipher(context, identifier, protection, keyAuthenticator, cipherGetter,
                                        authenticatedCipherHandler);

                            } catch (Throwable e) {
                                authenticatedCipherHandler.onError(e);
                            }
                        }

                        @Override
                        public void handleCancel() {
                            authenticatedCipherHandler.onCancel();
                        }

                        @Override
                        public void handleError(Throwable e) {
                            authenticatedCipherHandler.onError(e);
                        }
                    });
                }
            } catch (UnrecoverableKeyException | IOException | E3DBCryptoException e) {
                authenticatedCipherHandler.onError(e);
            }

            break;
        case PASSWORD:
            keyAuthenticator.getPassword(new KeyAuthenticator.PasswordHandler() {

                @Override
                public void handlePassword(String password) throws UnrecoverableKeyException {
                    try {
                        authenticatedCipherHandler.onAuthenticated(cipherGetter.getCipher(context, identifier,
                                FSKSWrapper.getSecretKey(context, identifier, protection, password)));
                    } catch (InvalidKeyException | IOException | E3DBCryptoException e) {
                        authenticatedCipherHandler.onError(e);
                    }
                }

                @Override
                public void handleCancel() {
                    authenticatedCipherHandler.onCancel();
                }

                @Override
                public void handleError(Throwable e) {
                    authenticatedCipherHandler.onError(e);
                }
            });

            break;
        default:
            throw new IllegalStateException(
                    "Unhandled key protection: " + protection.authenticationType().toString());
        }

    }

    static void removeSecretKey(Context context, String identifier) throws E3DBCryptoException {
        FSKSWrapper.removeSecretKey(context, identifier);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            AKSWrapper.removeSecretKey(identifier);
    }
}