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

Fix of issue 318 #399

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1,647 changes: 893 additions & 754 deletions android/src/main/java/com/oblador/keychain/KeychainModule.java

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,14 @@
@SuppressWarnings("unused")
public class KeychainPackage implements ReactPackage {

private final KeychainModuleBuilder builder;

public KeychainPackage() {
this(new KeychainModuleBuilder());
}

public KeychainPackage(KeychainModuleBuilder builder) {
this.builder = builder;
}

@Override
@NonNull
public List<NativeModule> createNativeModules(@NonNull final ReactApplicationContext reactContext) {
return Collections.singletonList(builder.withReactContext(reactContext).build());
return Collections.singletonList(KeychainModule.withWarming(reactContext));
}

@NonNull
Expand Down
24 changes: 21 additions & 3 deletions android/src/main/java/com/oblador/keychain/PrefsStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand All @@ -19,10 +20,12 @@ public class PrefsStorage {
static public class ResultSet extends CipherStorage.CipherResult<byte[]> {
@KnownCiphers
public final String cipherStorageName;
public final byte[] vector;

public ResultSet(@KnownCiphers final String cipherStorageName, final byte[] usernameBytes, final byte[] passwordBytes) {
public ResultSet(@KnownCiphers final String cipherStorageName, final byte[] usernameBytes, final byte[] passwordBytes, final byte[] vectorBytes) {
super(usernameBytes, passwordBytes);

this.vector = vectorBytes;
this.cipherStorageName = cipherStorageName;
}
}
Expand All @@ -38,6 +41,7 @@ public PrefsStorage(@NonNull final ReactApplicationContext reactContext) {
public ResultSet getEncryptedEntry(@NonNull final String service) {
byte[] bytesForUsername = getBytesForUsername(service);
byte[] bytesForPassword = getBytesForPassword(service);
byte[] bytesForVector = getBytesForVector(service);
String cipherStorageName = getCipherStorageName(service);

// in case of wrong password or username
Expand All @@ -51,7 +55,7 @@ public ResultSet getEncryptedEntry(@NonNull final String service) {
cipherStorageName = KnownCiphers.FB;
}

return new ResultSet(cipherStorageName, bytesForUsername, bytesForPassword);
return new ResultSet(cipherStorageName, bytesForUsername, bytesForPassword, bytesForVector);

}

Expand All @@ -70,11 +74,14 @@ public void removeEntry(@NonNull final String service) {
public void storeEncryptedEntry(@NonNull final String service, @NonNull final EncryptionResult encryptionResult) {
final String keyForUsername = getKeyForUsername(service);
final String keyForPassword = getKeyForPassword(service);
final String keyForVector = getKeyForVector(service);
final String keyForCipherStorage = getKeyForCipherStorage(service);
final byte[] usernameBytes = encryptionResult.username == null ? new byte[0] : encryptionResult.username;

prefs.edit()
.putString(keyForUsername, Base64.encodeToString(encryptionResult.username, Base64.DEFAULT))
.putString(keyForUsername, Base64.encodeToString(usernameBytes, Base64.DEFAULT))
.putString(keyForPassword, Base64.encodeToString(encryptionResult.password, Base64.DEFAULT))
.putString(keyForVector, Base64.encodeToString(encryptionResult.vector, Base64.DEFAULT))
.putString(keyForCipherStorage, encryptionResult.cipherName)
.apply();
}
Expand All @@ -92,6 +99,12 @@ private byte[] getBytesForPassword(@NonNull final String service) {
return getBytes(key);
}

@Nullable
private byte[] getBytesForVector(@NonNull final String service) {
String key = getKeyForVector(service);
return getBytes(key);
}

@Nullable
private String getCipherStorageName(@NonNull final String service) {
String key = getKeyForCipherStorage(service);
Expand All @@ -109,6 +122,11 @@ public static String getKeyForPassword(@NonNull final String service) {
return service + ":" + "p";
}

@NonNull
public static String getKeyForVector(@NonNull final String service) {
return service + ":" + "v";
}

@NonNull
public static String getKeyForCipherStorage(@NonNull final String service) {
return service + ":" + "c";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import java.security.Key;

import javax.crypto.Cipher;

@SuppressWarnings({"unused", "WeakerAccess"})
public interface CipherStorage {
//region Helper classes
Expand All @@ -28,16 +30,18 @@ public CipherResult(final T username, final T password) {
class EncryptionResult extends CipherResult<byte[]> {
/** Name of used for encryption cipher storage. */
public final String cipherName;
public final byte[] vector;

/** Main constructor. */
public EncryptionResult(final byte[] username, final byte[] password, final String cipherName) {
public EncryptionResult(final byte[] username, final byte[] password, final byte[] vector, final String cipherName) {
super(username, password);
this.cipherName = cipherName;
this.vector = vector;
}

/** Helper constructor. Simplifies cipher name extraction. */
public EncryptionResult(final byte[] username, final byte[] password, @NonNull final CipherStorage cipherStorage) {
this(username, password, cipherStorage.getCipherStorageName());
public EncryptionResult(final byte[] username, final byte[] password, final byte[] vector, @NonNull final CipherStorage cipherStorage) {
this(username, password, vector, cipherStorage.getCipherStorageName());
}
}

Expand Down Expand Up @@ -74,12 +78,29 @@ public DecryptionContext(@NonNull final String keyAlias,
}
}

/** Ask access permission for decrypting credentials in provided context. */
class EncryptContext extends CipherResult<String> {
public final Key key;
public final String keyAlias;

public EncryptContext(@NonNull final String keyAlias,
@NonNull final Key key,
@NonNull final String password,
@NonNull final String username) {
super(username, password);
this.keyAlias = keyAlias;
this.key = key;
}
}

/** Get access to the results of decryption via properties. */
interface WithResults {
/** Get reference on results. */
@Nullable
DecryptionResult getResult();

@Nullable
EncryptionResult getEncryptionResult();
/** Get reference on capture error. */
@Nullable
Throwable getError();
Expand All @@ -91,12 +112,14 @@ interface WithResults {
/** Handler that allows to inject some actions during decrypt operations. */
interface DecryptionResultHandler extends WithResults {
/** Ask user for interaction, often its unlock of keystore by biometric data providing. */
void askAccessPermissions(@NonNull final DecryptionContext context);
void askAccessPermissions(@NonNull final DecryptionContext context, Cipher cipher);

/**
*
*/
void onDecrypt(@Nullable final DecryptionResult decryptionResult, @Nullable final Throwable error);
void onEncrypt(@Nullable final EncryptionResult encryptionResult);
void askAccessPermissionsEncryption(@NonNull final CipherStorage.EncryptContext context, Cipher cipher);
}
//endregion

Expand All @@ -107,7 +130,8 @@ interface DecryptionResultHandler extends WithResults {
EncryptionResult encrypt(@NonNull final String alias,
@NonNull final String username,
@NonNull final String password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level,
@NonNull final DecryptionResultHandler handler)
throws CryptoFailedException;

/**
Expand All @@ -119,15 +143,16 @@ EncryptionResult encrypt(@NonNull final String alias,
DecryptionResult decrypt(@NonNull final String alias,
@NonNull final byte[] username,
@NonNull final byte[] password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level,
byte[] vector)
throws CryptoFailedException;

/** Decrypt the credentials but redirect results of operation to handler. */
void decrypt(@NonNull final DecryptionResultHandler handler,
@NonNull final String alias,
@NonNull final byte[] username,
@NonNull final byte[] password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level, byte[] vector)
throws CryptoFailedException;

/** Remove key (by alias) from storage. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ public static final class IV {
public static IvParameterSpec readIv(@NonNull final byte[] bytes) throws IOException {
final byte[] iv = new byte[IV_LENGTH];

if (IV_LENGTH >= bytes.length)
if (IV_LENGTH <= bytes.length)
throw new IOException("Insufficient length of input data for IV extracting.");

System.arraycopy(bytes, 0, iv, 0, IV_LENGTH);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ public boolean isBiometrySupported() {
public EncryptionResult encrypt(@NonNull final String alias,
@NonNull final String username,
@NonNull final String password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level,
@NonNull DecryptionResultHandler handler)
throws CryptoFailedException {

throwIfInsufficientLevel(level);
throwIfNoCryptoAvailable();

Expand All @@ -87,6 +87,7 @@ public EncryptionResult encrypt(@NonNull final String alias,
return new EncryptionResult(
encryptedUsername,
encryptedPassword,
new byte[0],
this);
} catch (Throwable fail) {
throw new CryptoFailedException("Encryption failed for alias: " + alias, fail);
Expand All @@ -98,7 +99,8 @@ public EncryptionResult encrypt(@NonNull final String alias,
public DecryptionResult decrypt(@NonNull final String alias,
@NonNull final byte[] username,
@NonNull final byte[] password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level,
@NonNull final byte[] vector)
throws CryptoFailedException {

throwIfInsufficientLevel(level);
Expand Down Expand Up @@ -126,10 +128,10 @@ public void decrypt(@NonNull DecryptionResultHandler handler,
@NonNull String service,
@NonNull byte[] username,
@NonNull byte[] password,
@NonNull final SecurityLevel level) {
@NonNull final SecurityLevel level, byte[] vector) {

try {
final DecryptionResult results = decrypt(service, username, password, level);
final DecryptionResult results = decrypt(service, username, password, level, vector);

handler.onDecrypt(results, null);
} catch (Throwable fail) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.oblador.keychain.KeychainModule.KnownCiphers;
import com.oblador.keychain.SecurityLevel;
Expand All @@ -21,11 +20,9 @@
import java.security.spec.KeySpec;
import java.util.concurrent.atomic.AtomicInteger;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;

/**
* @see <a href="https://proandroiddev.com/secure-data-in-android-initialization-vector-6ca1c659762c">Secure Data in Android</a>
Expand Down Expand Up @@ -102,7 +99,8 @@ public String getDefaultAliasServiceName() {
public EncryptionResult encrypt(@NonNull final String alias,
@NonNull final String username,
@NonNull final String password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level,
@NonNull DecryptionResultHandler handler)
throws CryptoFailedException {

throwIfInsufficientLevel(level);
Expand All @@ -116,6 +114,7 @@ public EncryptionResult encrypt(@NonNull final String alias,
return new EncryptionResult(
encryptString(key, username),
encryptString(key, password),
new byte[0],
this);
} catch (GeneralSecurityException e) {
throw new CryptoFailedException("Could not encrypt data with alias: " + alias, e);
Expand All @@ -130,7 +129,8 @@ public EncryptionResult encrypt(@NonNull final String alias,
public DecryptionResult decrypt(@NonNull final String alias,
@NonNull final byte[] username,
@NonNull final byte[] password,
@NonNull final SecurityLevel level)
@NonNull final SecurityLevel level,
@NonNull final byte[] vector)
throws CryptoFailedException {

throwIfInsufficientLevel(level);
Expand Down Expand Up @@ -159,9 +159,9 @@ public void decrypt(@NonNull final DecryptionResultHandler handler,
@NonNull final String service,
@NonNull final byte[] username,
@NonNull final byte[] password,
@NonNull final SecurityLevel level) {
@NonNull final SecurityLevel level, byte[] vector) {
try {
final DecryptionResult results = decrypt(service, username, password, level);
final DecryptionResult results = decrypt(service, username, password, level, vector);

handler.onDecrypt(results, null);
} catch (Throwable fail) {
Expand Down Expand Up @@ -219,31 +219,6 @@ protected Key generateKey(@NonNull final KeyGenParameterSpec spec) throws Genera

return generator.generateKey();
}

/** Decrypt provided bytes to a string. */
@NonNull
@Override
protected String decryptBytes(@NonNull final Key key, @NonNull final byte[] bytes,
@Nullable final DecryptBytesHandler handler)
throws GeneralSecurityException, IOException {
final Cipher cipher = getCachedInstance();

try {
// read the initialization vector from bytes array
final IvParameterSpec iv = IV.readIv(bytes);
cipher.init(Cipher.DECRYPT_MODE, key, iv);

// decrypt the bytes using cipher.doFinal(). Using a CipherInputStream for decryption has historically led to issues
// on the Pixel family of devices.
// see https://github.com/oblador/react-native-keychain/issues/383
byte[] decryptedBytes = cipher.doFinal(bytes, IV.IV_LENGTH, bytes.length - IV.IV_LENGTH);
return new String(decryptedBytes, UTF8);
} catch (Throwable fail) {
Log.w(LOG_TAG, fail.getMessage(), fail);

throw fail;
}
}
//endregion

//region Initialization Vector encrypt/decrypt support
Expand Down