diff --git a/src/main/java/io/lbry/lbrynet/Utils.java b/src/main/java/io/lbry/lbrynet/Utils.java index da4a6a7c..5201da35 100644 --- a/src/main/java/io/lbry/lbrynet/Utils.java +++ b/src/main/java/io/lbry/lbrynet/Utils.java @@ -1,9 +1,49 @@ package io.lbry.lbrynet; 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.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 { + + 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() { return android.os.Build.VERSION.RELEASE; } @@ -55,4 +95,131 @@ public final class Utils { 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 values = new ArrayList(); + 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"); + } } diff --git a/src/main/python/lbrynetservice.py b/src/main/python/lbrynetservice.py index bf6f7ea6..1e51c37e 100644 --- a/src/main/python/lbrynetservice.py +++ b/src/main/python/lbrynetservice.py @@ -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_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 from lbrynet.core import log_support @@ -72,7 +110,7 @@ def start(): if test_internet_connection(): analytics_manager = analytics.Manager.new_instance() - start_server_and_listen(False, analytics_manager) + start_server_and_listen(True, analytics_manager) reactor.run() else: log.info("Not connected to internet, unable to start")