Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Android fingerprint #148

Closed
wants to merge 13 commits into from
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
logs
*.log

#Android
android/.gradle/
android/.idea/
android/android.iml
android/local.properties
android/react-native-keychain.iml

# Runtime data
pids
*.pid
Expand Down
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ repositories {
dependencies {
implementation 'com.facebook.react:react-native:+'
implementation 'com.facebook.conceal:conceal:1.1.3@aar'
implementation 'moe.feng.support.biometricprompt:library:1.0.1'
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.oblador.keychain;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.content.Context;
import android.app.KeyguardManager;
Expand All @@ -10,7 +12,9 @@ public static boolean isFingerprintAuthAvailable(Context context) {
if (android.os.Build.VERSION.SDK_INT >= 23) {
FingerprintManager fingerprintManager =
(FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
return fingerprintManager != null && fingerprintManager.isHardwareDetected() &&
return fingerprintManager != null &&
context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED &&
fingerprintManager.isHardwareDetected() &&
fingerprintManager.hasEnrolledFingerprints();
}
return false;
Expand Down
194 changes: 158 additions & 36 deletions android/src/main/java/com/oblador/keychain/KeychainModule.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.oblador.keychain.cipherStorage;

import android.app.Activity;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.support.annotation.NonNull;

import com.oblador.keychain.exceptions.CryptoFailedException;
Expand Down Expand Up @@ -31,13 +33,22 @@ public DecryptionResult(String username, String password) {
}
}

interface DecryptionResultHandler {
public void onDecrypt(DecryptionResult decryptionResult, String info, String error);
}

EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password) throws CryptoFailedException;

DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException;
void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException, KeyPermanentlyInvalidatedException;

void removeKey(@NonNull String service) throws KeyStoreAccessException;

String getCipherStorageName();

boolean getCipherBiometrySupported();

int getMinSupportedApiLevel();

boolean getRequiresCurrentActivity();
void setCurrentActivity(Activity activity);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.oblador.keychain.cipherStorage;

import android.app.Activity;
import android.os.Build;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.support.annotation.NonNull;

import com.facebook.android.crypto.keychain.AndroidConceal;
Expand All @@ -14,6 +16,8 @@

import java.nio.charset.Charset;

import static android.R.attr.password;

public class CipherStorageFacebookConceal implements CipherStorage {
public static final String CIPHER_STORAGE_NAME = "FacebookConceal";
public static final String KEYCHAIN_DATA = "RN_KEYCHAIN";
Expand All @@ -29,11 +33,17 @@ public String getCipherStorageName() {
return CIPHER_STORAGE_NAME;
}

@Override
public boolean getCipherBiometrySupported() {
return false;
}

@Override
public int getMinSupportedApiLevel() {
return Build.VERSION_CODES.JELLY_BEAN;
}


@Override
public EncryptionResult encrypt(@NonNull String service, @NonNull String username, @NonNull String password) throws CryptoFailedException {
if (!crypto.isAvailable()) {
Expand All @@ -53,7 +63,7 @@ public EncryptionResult encrypt(@NonNull String service, @NonNull String usernam
}

@Override
public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException {
public void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException, KeyPermanentlyInvalidatedException {
if (!crypto.isAvailable()) {
throw new CryptoFailedException("Crypto is missing");
}
Expand All @@ -64,9 +74,9 @@ public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] usernam
byte[] decryptedUsername = crypto.decrypt(username, usernameEntity);
byte[] decryptedPassword = crypto.decrypt(password, passwordEntity);

return new DecryptionResult(
decryptionResultHandler.onDecrypt(new DecryptionResult(
new String(decryptedUsername, Charset.forName("UTF-8")),
new String(decryptedPassword, Charset.forName("UTF-8")));
new String(decryptedPassword, Charset.forName("UTF-8"))), null, null);
} catch (Exception e) {
throw new CryptoFailedException("Decryption failed for service " + service, e);
}
Expand All @@ -90,4 +100,15 @@ private Entity createPasswordEntity(String service) {
private String getEntityPrefix(String service) {
return KEYCHAIN_DATA + ":" + service;
}

@Override
public boolean getRequiresCurrentActivity() {
// Facebook conceal does not need the current activity
return false;
}

@Override
public void setCurrentActivity(Activity activity) {
// Facebook conceal does not need the current activity
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.oblador.keychain.cipherStorage;

import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.annotation.NonNull;

Expand Down Expand Up @@ -48,6 +50,11 @@ public String getCipherStorageName() {
return CIPHER_STORAGE_NAME;
}

@Override
public boolean getCipherBiometrySupported() {
return false;
}

@Override
public int getMinSupportedApiLevel() {
return Build.VERSION_CODES.M;
Expand Down Expand Up @@ -97,7 +104,7 @@ private void generateKeyAndStoreUnderAlias(@NonNull String service) throws NoSuc
}

@Override
public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException {
public void decrypt(@NonNull DecryptionResultHandler decryptionResultHandler, @NonNull String service, @NonNull byte[] username, @NonNull byte[] password) throws CryptoFailedException, KeyPermanentlyInvalidatedException {
service = getDefaultServiceIfEmpty(service);

try {
Expand All @@ -108,7 +115,7 @@ public DecryptionResult decrypt(@NonNull String service, @NonNull byte[] usernam
String decryptedUsername = decryptBytes(key, username);
String decryptedPassword = decryptBytes(key, password);

return new DecryptionResult(decryptedUsername, decryptedPassword);
decryptionResultHandler.onDecrypt(new DecryptionResult(decryptedUsername, decryptedPassword), null, null);
} catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) {
throw new CryptoFailedException("Could not get key from Keystore", e);
} catch (KeyStoreAccessException e) {
Expand Down Expand Up @@ -198,4 +205,15 @@ private KeyStore getKeyStoreAndLoad() throws KeyStoreException, KeyStoreAccessEx
private String getDefaultServiceIfEmpty(@NonNull String service) {
return service.isEmpty() ? DEFAULT_SERVICE : service;
}

@Override
public boolean getRequiresCurrentActivity() {
// AESCBC does not need the current activity
return false;
}

@Override
public void setCurrentActivity(Activity activity) {
// AESCBC does not need the current activity
}
}
Loading