Skip to content

Commit

Permalink
Merge pull request #1 from mibrito707/debug
Browse files Browse the repository at this point in the history
Support Android 29+
  • Loading branch information
mibrito707 authored Jul 9, 2019
2 parents cf7a39c + e324fed commit 7f39069
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 117 deletions.
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
# Project no longer mantained.

As Crypho does not use Cordova for a long time now, it has become clear that we cannot keep maintaining this project any longer, or give it the attention it deserves.
A big thanks to all the contributors.

# SecureStorage plugin for Apache Cordova

[![NPM](https://nodei.co/npm/cordova-plugin-secure-storage.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/cordova-plugin-secure-storage/)

## Introduction

This plugin is for use with [Apache Cordova](http://cordova.apache.org/) and allows your application to securely store secrets
Expand Down Expand Up @@ -182,6 +175,29 @@ The inverse process is followed on `get`.
Native AES is used.
Minimum android supported version is 5.0 Lollipop. If you need to support earlier Android versions use version 2.6.8.

##### Android Init Options.
- `packageName` - See [Sharing data between 2 apps on Android](#sharing-data-android)
- `userAuthenticationValidityDuration` - Sets the duration of time (seconds) for which the Private Encryption Key is authorized to be used after the user is successfully authenticated. [KeyGenParameterSpec.Builder#setUserAuthenticationValidityDurationSeconds(int)](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder.html#setUserAuthenticationValidityDurationSeconds(int))
- `unlockCredentialsTitle` - Custom title for Confirm Credentials screen. See [KeyguardManager#createConfirmDeviceCredentialIntent(title, description)](https://developer.android.com/reference/android/app/KeyguardManager.html#createConfirmDeviceCredentialIntent(java.lang.CharSequence,%20java.lang.CharSequence))
- `unlockCredentialsDescription` - Custom description for Confirm Credentials screen.

```js
var ss = new cordova.plugins.SecureStorage(
function() {
console.log("Success");
},
function(error) {
console.log("Error " + error);
},
"my_app",
{
android: {
packageName: "com.test.app1"
}
}
);
```

##### Users must have a secure screen-lock set.

The plugin will only work correctly if the user has sufficiently secure settings on the lock screen. If not, the plugin will fail to initialize and the failure callback will be called on `init()`. This is because in order to use the Android Credential Storage and create RSA keys the device needs to be somewhat secure.
Expand Down Expand Up @@ -219,7 +235,7 @@ var _init = function() {
_init();
```

##### Sharing data between 2 apps on Android.
##### <a name="sharing-data-android"></a> Sharing data between 2 apps on Android.

The plugin can be used to share data securely between 2 Android apps.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-secure-storage",
"version": "3.0.2",
"version": "4.0.0",
"description": "Secure storage plugin for iOS & Android",
"author": "Yiorgis Gozadinos <[email protected]>",
"contributors": [
Expand Down
113 changes: 55 additions & 58 deletions src/android/AES.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.crypho.plugins;

import android.util.Log;
import android.util.Base64;

import org.json.JSONException;
import org.json.JSONObject;

import java.security.Key;
Expand All @@ -16,67 +13,67 @@
import javax.crypto.KeyGenerator;

public class AES {
private static final String CIPHER_MODE = "CCM";
private static final int KEY_SIZE = 256;
private static final int VERSION = 1;
private static final Cipher CIPHER = getCipher();
private static final String CIPHER_MODE = "CCM";
private static final int KEY_SIZE = 256;
private static final int VERSION = 1;
private static final Cipher CIPHER = getCipher();

public static JSONObject encrypt(byte[] msg, byte[] adata) throws Exception {
byte[] iv, ct, secretKeySpec_enc;
synchronized (CIPHER) {
SecretKeySpec secretKeySpec = generateKeySpec();
secretKeySpec_enc = secretKeySpec.getEncoded();
initCipher(Cipher.ENCRYPT_MODE, secretKeySpec, null, adata);
iv = CIPHER.getIV();
ct = CIPHER.doFinal(msg);
}
public static JSONObject encrypt(byte[] msg, byte[] adata) throws Exception {
byte[] iv, ct, secretKeySpec_enc;
synchronized (CIPHER) {
SecretKeySpec secretKeySpec = generateKeySpec();
secretKeySpec_enc = secretKeySpec.getEncoded();
initCipher(Cipher.ENCRYPT_MODE, secretKeySpec, null, adata);
iv = CIPHER.getIV();
ct = CIPHER.doFinal(msg);
}

JSONObject value = new JSONObject();
value.put("iv", Base64.encodeToString(iv, Base64.DEFAULT));
value.put("v", Integer.toString(VERSION));
value.put("ks", Integer.toString(KEY_SIZE));
value.put("cipher", "AES");
value.put("mode", CIPHER_MODE);
value.put("adata", Base64.encodeToString(adata, Base64.DEFAULT));
value.put("ct", Base64.encodeToString(ct, Base64.DEFAULT));
JSONObject value = new JSONObject();
value.put("iv", Base64.encodeToString(iv, Base64.DEFAULT));
value.put("v", Integer.toString(VERSION));
value.put("ks", Integer.toString(KEY_SIZE));
value.put("cipher", "AES");
value.put("mode", CIPHER_MODE);
value.put("adata", Base64.encodeToString(adata, Base64.DEFAULT));
value.put("ct", Base64.encodeToString(ct, Base64.DEFAULT));

JSONObject result = new JSONObject();
result.put("key", Base64.encodeToString(secretKeySpec_enc, Base64.DEFAULT));
result.put("value", value);
result.put("native", true);
JSONObject result = new JSONObject();
result.put("key", Base64.encodeToString(secretKeySpec_enc, Base64.DEFAULT));
result.put("value", value);
result.put("native", true);

return result;
}
return result;
}

public static String decrypt(byte[] buf, byte[] key, byte[] iv, byte[] adata) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
synchronized (CIPHER) {
initCipher(Cipher.DECRYPT_MODE, secretKeySpec, iv, adata);
return new String(CIPHER.doFinal(buf));
}
}
public static String decrypt(byte[] buf, byte[] key, byte[] iv, byte[] adata) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
synchronized (CIPHER) {
initCipher(Cipher.DECRYPT_MODE, secretKeySpec, iv, adata);
return new String(CIPHER.doFinal(buf));
}
}

private static SecretKeySpec generateKeySpec() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(KEY_SIZE, new SecureRandom());
SecretKey sc = keyGenerator.generateKey();
return new SecretKeySpec(sc.getEncoded(), "AES");
}
private static SecretKeySpec generateKeySpec() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(KEY_SIZE, new SecureRandom());
SecretKey sc = keyGenerator.generateKey();
return new SecretKeySpec(sc.getEncoded(), "AES");
}

private static void initCipher(int cipherMode, Key key, byte[] iv, byte[] adata) throws Exception {
if (iv != null) {
CIPHER.init(cipherMode, key, new IvParameterSpec(iv));
} else {
CIPHER.init(cipherMode, key);
}
CIPHER.updateAAD(adata);
}
private static void initCipher(int cipherMode, Key key, byte[] iv, byte[] adata) throws Exception {
if (iv != null) {
CIPHER.init(cipherMode, key, new IvParameterSpec(iv));
} else {
CIPHER.init(cipherMode, key);
}
CIPHER.updateAAD(adata);
}

private static Cipher getCipher() {
try {
return Cipher.getInstance("AES/" + CIPHER_MODE + "/NoPadding");
} catch (Exception e) {
return null;
}
}
private static Cipher getCipher() {
try {
return Cipher.getInstance("AES/" + CIPHER_MODE + "/NoPadding");
} catch (Exception e) {
return null;
}
}
}
127 changes: 107 additions & 20 deletions src/android/RSA.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
package com.crypho.plugins;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.security.keystore.KeyInfo;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.KeyPairGeneratorSpec;


import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.Calendar;

import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;

import static org.apache.cordova.CordovaActivity.TAG;

public class RSA {
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final Cipher CIPHER = getCipher();
private static final Integer CERT_VALID_YEARS = 100;
private static final Boolean IS_API_23_AVAILABLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
private static final String TAG = "SecureStorage";

public static byte[] encrypt(byte[] buf, String alias) throws Exception {
return runCipher(Cipher.ENCRYPT_MODE, alias, buf);
Expand All @@ -26,30 +41,52 @@ public static byte[] decrypt(byte[] buf, String alias) throws Exception {
return runCipher(Cipher.DECRYPT_MODE, alias, buf);
}

public static void createKeyPair(Context ctx, String alias) throws Exception {
Calendar notBefore = Calendar.getInstance();
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, 100);
String principalString = String.format("CN=%s, OU=%s", alias, ctx.getPackageName());
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
.setAlias(alias)
.setSubject(new X500Principal(principalString))
.setSerialNumber(BigInteger.ONE)
.setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime())
.setEncryptionRequired()
.setKeySize(2048)
.setKeyType("RSA")
.build();
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", KEYSTORE_PROVIDER);
public static void createKeyPair(Context ctx, String alias, Integer userAuthenticationValidityDuration) throws Exception {
AlgorithmParameterSpec spec = IS_API_23_AVAILABLE ? getInitParams(alias, userAuthenticationValidityDuration) : getInitParamsLegacy(ctx, alias);

KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER);
kpGenerator.initialize(spec);
kpGenerator.generateKeyPair();
}

public static boolean isEntryAvailable(String alias) {
/**
* Check if Encryption Keys are available and secure.
*
* @param alias
* @return boolean
*/
public static boolean encryptionKeysAvailable(String alias) {
try {
return loadKey(Cipher.ENCRYPT_MODE, alias) != null;
Key privateKey = loadKey(Cipher.DECRYPT_MODE, alias);
if (privateKey == null) {
return false;
}
KeyInfo keyInfo;
KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), KEYSTORE_PROVIDER);
keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
return keyInfo.isInsideSecureHardware();
} catch (Exception e) {
Log.i(TAG, "Checking encryption keys failed.", e);
return false;
}
}

/**
* Check if we need to prompt for User's Credentials
*
* @param alias
* @return
*/
public static boolean userAuthenticationRequired(String alias) {
try {
// Do a quick encrypt/decrypt test
byte[] encrypted = encrypt(alias.getBytes(), alias);
decrypt(encrypted, alias);
return false;
} catch (UserNotAuthenticatedException noAuthEx) {
return true;
} catch (Exception e) {
// Other
return false;
}
}
Expand All @@ -65,6 +102,11 @@ private static byte[] runCipher(int cipherMode, String alias, byte[] buf) throws
private static Key loadKey(int cipherMode, String alias) throws Exception {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null, null);

if (!keyStore.containsAlias(alias)) {
throw new Exception("KeyStore doesn't contain alias: " + alias);
}

Key key;
switch (cipherMode) {
case Cipher.ENCRYPT_MODE:
Expand All @@ -73,13 +115,14 @@ private static Key loadKey(int cipherMode, String alias) throws Exception {
throw new Exception("Failed to load the public key for " + alias);
}
break;
case Cipher.DECRYPT_MODE:
case Cipher.DECRYPT_MODE:
key = keyStore.getKey(alias, null);
if (key == null) {
throw new Exception("Failed to load the private key for " + alias);
}
break;
default : throw new Exception("Invalid cipher mode parameter");
default:
throw new Exception("Invalid cipher mode parameter");
}
return key;
}
Expand All @@ -91,4 +134,48 @@ private static Cipher getCipher() {
return null;
}
}

/**
* Generate Encryption Keys Parameter Spec
*
* @param alias String
* @return AlgorithmParameterSpec
* @// TODO: 2019-07-08 Fix setUserAuthenticationValidityDurationSeconds workaround
*/
@TargetApi(Build.VERSION_CODES.M)
private static AlgorithmParameterSpec getInitParams(String alias, Integer userAuthenticationValidityDuration) {
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, CERT_VALID_YEARS);

return new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
.setCertificateNotBefore(Calendar.getInstance().getTime())
.setCertificateNotAfter(notAfter.getTime())
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(userAuthenticationValidityDuration)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setBlockModes(KeyProperties.BLOCK_MODE_ECB)
.build();
}

/**
* Generate Encryption Keys Parameter Spec
* Fallback to legacy (pre API 23) Spec Generator
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static AlgorithmParameterSpec getInitParamsLegacy(Context ctx, String alias) throws Exception {
Calendar notAfter = Calendar.getInstance();
notAfter.add(Calendar.YEAR, CERT_VALID_YEARS);

return new KeyPairGeneratorSpec.Builder(ctx)
.setAlias(alias)
.setSubject(new X500Principal(String.format("CN=%s, OU=%s", alias, ctx.getPackageName())))
.setSerialNumber(BigInteger.ONE)
.setStartDate(Calendar.getInstance().getTime())
.setEndDate(notAfter.getTime())
.setEncryptionRequired()
.setKeySize(2048)
.setKeyType(KeyProperties.KEY_ALGORITHM_RSA)
.build();
}
}
Loading

0 comments on commit 7f39069

Please sign in to comment.