Daemon RPC authentication with encrypted api key storage #10

Merged
akinwale merged 1 commit from rpc_auth into master 2017-08-25 06:40:41 +02:00
2 changed files with 206 additions and 1 deletions

View file

@ -1,9 +1,49 @@
package io.lbry.lbrynet; package io.lbry.lbrynet;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.security.KeyPairGeneratorSpec;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Calendar;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
public final class Utils { public final class Utils {
private static final String TAG = Utils.class.getName();
private static final String AES_MODE = "AES/ECB/PKCS7Padding";
private static final String KEY_ALIAS = "LBRYKey";
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
private static final String RSA_MODE = "RSA/ECB/PKCS1Padding";
private static final String SP_NAME = "app";
private static final String SP_ENCRYPTION_KEY = "key";
private static final String SP_API_SECRET_KEY = "api_secret";
public static String getAndroidRelease() { public static String getAndroidRelease() {
return android.os.Build.VERSION.RELEASE; return android.os.Build.VERSION.RELEASE;
} }
@ -55,4 +95,131 @@ public final class Utils {
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
public static void saveApiSecret(String secret, Context context, KeyStore keyStore) {
try {
SharedPreferences pref = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString(SP_API_SECRET_KEY, encrypt(secret.getBytes(), context, keyStore));
editor.commit();
} catch (Exception ex) {
Log.e(TAG, "lbrynetservice - Could not save the API secret", ex);
}
}
public static String loadApiSecret(Context context, KeyStore keyStore) {
SharedPreferences pref = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
String encryptedSecret = pref.getString(SP_API_SECRET_KEY, null);
if (encryptedSecret != null && encryptedSecret.trim().length() > 0) {
try {
byte[] decoded = Base64.decode(encryptedSecret, Base64.DEFAULT);
return new String(decrypt(decoded, context, keyStore), Charset.forName("UTF8"));
} catch (Exception ex) {
Log.e(TAG, "lbrynetservice - Could not load the API secret", ex);
}
}
return null;
}
public static String encrypt(byte[] input, Context context, KeyStore keyStore) throws Exception {
Cipher c = Cipher.getInstance(AES_MODE, "BC");
c.init(Cipher.ENCRYPT_MODE, getSecretKey(context, keyStore));
return Base64.encodeToString(c.doFinal(input), Base64.DEFAULT);
}
public static byte[] decrypt(byte[] encrypted, Context context, KeyStore keyStore) throws Exception {
Cipher c = Cipher.getInstance(AES_MODE, "BC");
c.init(Cipher.DECRYPT_MODE, getSecretKey(context, keyStore));
return c.doFinal(encrypted);
}
public static final KeyStore initKeyStore(Context context) throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER);
ks.load(null);
if (!ks.containsAlias(KEY_ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 100);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(KEY_ALIAS)
.setSubject(new X500Principal(String.format("CN=%s", KEY_ALIAS)))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.build();
try {
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA", KEYSTORE_PROVIDER);
keygen.initialize(spec);
keygen.generateKeyPair();
} catch (NoSuchProviderException ex) {
throw ex;
} catch (InvalidAlgorithmParameterException ex) {
throw ex;
}
}
return ks;
}
private static byte[] rsaEncrypt(byte[] secret, KeyStore keyStore) throws Exception {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
// Encrypt the text
Cipher inputCipher = Cipher.getInstance(RSA_MODE);
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher);
cipherOutputStream.write(secret);
cipherOutputStream.close();
return outputStream.toByteArray();
}
private static byte[] rsaDecrypt(byte[] encrypted, KeyStore keyStore) throws Exception {
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
Cipher output = Cipher.getInstance(RSA_MODE);
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(encrypted), output);
ArrayList<Byte> values = new ArrayList<Byte>();
int nextByte;
while ((nextByte = cipherInputStream.read()) != -1) {
values.add((byte) nextByte);
}
byte[] bytes = new byte[values.size()];
for(int i = 0; i < bytes.length; i++) {
bytes[i] = values.get(i).byteValue();
}
return bytes;
}
private static String generateSecretKey(Context context, KeyStore keyStore) throws Exception {
byte[] key = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(key);
byte[] encryptedKey = rsaEncrypt(key, keyStore);
String base64Encrypted = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
SharedPreferences pref = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString(SP_ENCRYPTION_KEY, base64Encrypted);
editor.commit();
return base64Encrypted;
}
private static Key getSecretKey(Context context, KeyStore keyStore) throws Exception{
SharedPreferences pref = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
String base64Key = pref.getString(SP_ENCRYPTION_KEY, null);
if (base64Key == null || base64Key.trim().length() == 0) {
base64Key = generateSecretKey(context, keyStore);
}
return new SecretKeySpec(rsaDecrypt(Base64.decode(base64Key, Base64.DEFAULT), keyStore), "AES");
}
} }

View file

@ -15,6 +15,44 @@ lbrynet.androidhelpers.paths.android_external_storage_dir = lambda: lbrynet_util
lbrynet.androidhelpers.paths.android_app_internal_storage_dir = lambda: lbrynet_utils.getAppInternalStorageDir(service.getApplicationContext()) lbrynet.androidhelpers.paths.android_app_internal_storage_dir = lambda: lbrynet_utils.getAppInternalStorageDir(service.getApplicationContext())
lbrynet.androidhelpers.paths.android_app_external_storage_dir = lambda: lbrynet_utils.getAppExternalStorageDir(service.getApplicationContext()) lbrynet.androidhelpers.paths.android_app_external_storage_dir = lambda: lbrynet_utils.getAppExternalStorageDir(service.getApplicationContext())
# RPC authentication secret
# Retrieve the Anroid keystore
ks = lbrynet_utils.initKeyStore(service.getApplicationContext());
import lbrynet.daemon.auth
from lbrynet.daemon.auth.util import APIKey, API_KEY_NAME
def load_api_keys(path):
key_name = API_KEY_NAME
context = service.getApplicationContext();
secret = lbrynet_utils.loadApiSecret(context, ks)
# TODO: For testing. Normally, this should not be displayed.
log.info('Loaded API Secret: %s', secret);
return { key_name: APIKey(secret, key_name, None) }
def save_api_keys(keys, path):
key_name = API_KEY_NAME
if key_name in keys:
secret = keys[key_name].secret
# TODO: For testing. Normally, this should not be displayed.
log.info('Saving API Secret: %s', secret);
context = service.getApplicationContext();
lbrynet_utils.saveApiSecret(secret, context, ks)
def initialize_api_key_file(key_path):
context = service.getApplicationContext();
secret = lbrynet_utils.loadApiSecret(context, ks)
if secret is None:
keys = {}
new_api_key = APIKey.new(name=API_KEY_NAME)
keys.update({new_api_key.name: new_api_key})
save_api_keys(keys, key_path)
lbrynet.daemon.auth.util.load_api_keys = load_api_keys
lbrynet.daemon.auth.util.save_api_keys = save_api_keys
lbrynet.daemon.auth.util.initialize_api_key_file = initialize_api_key_file
import logging.handlers import logging.handlers
from lbrynet.core import log_support from lbrynet.core import log_support
@ -72,7 +110,7 @@ def start():
if test_internet_connection(): if test_internet_connection():
analytics_manager = analytics.Manager.new_instance() analytics_manager = analytics.Manager.new_instance()
start_server_and_listen(False, analytics_manager) start_server_and_listen(True, analytics_manager)
reactor.run() reactor.run()
else: else:
log.info("Not connected to internet, unable to start") log.info("Not connected to internet, unable to start")