Commit 394b4bf7 authored by Thomas Guillem's avatar Thomas Guillem

keystore/file: encrypt/decrypt password via AndroidKeyStore

This works only on Android M, the used cipher is AES/CBC/PKCS7Padding, the IV
(Initialisation Vector) is generated by Android, and the key is stored by
Android.
parent 4df30d8a
......@@ -7,6 +7,9 @@ keystore_LTLIBRARIES = libmemory_keystore_plugin.la
libfile_keystore_plugin_la_SOURCES = keystore/file.c \
keystore/file_crypt.h \
keystore/list_util.c keystore/list_util.h
if HAVE_ANDROID
libfile_keystore_plugin_la_SOURCES += keystore/file_crypt_android.c
endif
keystore_LTLIBRARIES += libfile_keystore_plugin.la
libsecret_plugin_la_SOURCES = keystore/secret.c
......
......@@ -18,13 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#if 0
#if defined(__ANDROID__) || defined(_WIN32)
#if defined(__ANDROID__) /* TODO || defined(_WIN32) */
# define CRYPTFILE
#endif
#endif
#ifdef CRYPTFILE
struct crypt
{
......
/*****************************************************************************
* file_crypt_android.c: Crypt using AndroidKeyStore
*****************************************************************************
* Copyright © 2016 VLC authors, VideoLAN and VideoLabs
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdarg.h>
#include <stdio.h>
#include <vlc_common.h>
#include <vlc_memory.h>
#include <vlc_keystore.h>
#include "file_crypt.h"
#include <jni.h>
JNIEnv * android_getEnv(vlc_object_t *, const char *);
#define GET_ENV() android_getEnv(VLC_OBJECT(p_keystore), "android keystore")
static struct
{
struct
{
jmethodID toString;
} Object;
struct
{
jclass clazz;
jmethodID getInstance;
jmethodID load;
jmethodID getEntry;
struct
{
jmethodID getSecretKey;
} SecretKeyEntry;
} KeyStore;
struct
{
jint PURPOSE_ENCRYPT;
jint PURPOSE_DECRYPT;
jstring BLOCK_MODE_CBC;
jstring ENCRYPTION_PADDING_PKCS7;
jstring KEY_ALGORITHM_AES;
} KeyProperties;
struct
{
jclass clazz;
jmethodID getInstance;
jmethodID init;
jmethodID generateKey;
} KeyGenerator;
struct
{
struct
{
jclass clazz;
jmethodID ctor;
jmethodID setKeySize;
jmethodID setBlockModes;
jmethodID setEncryptionPaddings;
jmethodID build;
} Builder;
} KeyGenParameterSpec;
struct
{
jclass clazz;
jmethodID ctor;
} IvParameterSpec;
struct
{
jclass clazz;
jmethodID getInstance;
jmethodID init;
jmethodID doFinal;
jmethodID getIV;
jint ENCRYPT_MODE;
jint DECRYPT_MODE;
} Cipher;
jstring VLC_CIPHER;
} fields;
static jobject s_jkey = NULL;
#define CALL(caller, obj, method, ...) \
(*p_env)->caller(p_env, obj, fields.method, ##__VA_ARGS__)
#define CALL_VOID(obj, method, ...) \
CALL(CallVoidMethod, obj, method, ##__VA_ARGS__)
#define CALL_OBJ(obj, method, ...) \
CALL(CallObjectMethod, obj, method, ##__VA_ARGS__)
#define CALL_STATICOBJ(obj, method, ...) \
CALL(CallStaticObjectMethod, fields.obj.clazz, method, ##__VA_ARGS__)
#define CALL_INT(obj, method, ...) \
CALL(CallIntMethod, obj, method, ##__VA_ARGS__)
#define CALL_BOOL(obj, method, ...) \
CALL(CallBooleanMethod, obj, method, ##__VA_ARGS__)
#define NEW_OBJECT(arg, ...) \
(*p_env)->NewObject(p_env, fields.arg.clazz, fields.arg.ctor, ##__VA_ARGS__)
#define NEW_GREF(obj) \
(*p_env)->NewGlobalRef(p_env, obj)
#define DEL_GREF(obj) \
(*p_env)->DeleteGlobalRef(p_env, obj)
#define DEL_LREF(obj) \
(*p_env)->DeleteLocalRef(p_env, obj)
#define NEW_STR(str) \
(*p_env)->NewStringUTF(p_env, str)
static bool
check_expection(vlc_keystore *p_keystore, JNIEnv *p_env)
{
jthrowable jex = (*p_env)->ExceptionOccurred(p_env);
if (jex != NULL)
{
(*p_env)->ExceptionClear(p_env);
if (fields.Object.toString != NULL)
{
const char *psz_str = NULL;
jstring jstr = NULL;
jstr = (jstring) CALL_OBJ(jex, Object.toString);
if (jstr != NULL)
psz_str = (*p_env)->GetStringUTFChars(p_env, jstr, NULL);
if (psz_str != NULL)
{
msg_Err(p_keystore, "%s", psz_str);
(*p_env)->ReleaseStringUTFChars(p_env, jstr, psz_str);
}
DEL_LREF(jstr);
}
else
msg_Err(p_keystore, "unknown exception");
DEL_LREF(jex);
return true;
}
return false;
}
#define CHECK_EXCEPTION() check_expection(p_keystore, p_env)
#define GET_CLASS(str) do { \
if (clazz != NULL) \
DEL_LREF(clazz); \
clazz = (*p_env)->FindClass(p_env, (str)); \
if (CHECK_EXCEPTION()) \
return VLC_EGENERIC; \
} while (0)
#define GET_GLOBAL_CLASS(id) do { \
fields.id.clazz = (jclass) NEW_GREF(clazz); \
} while (0)
#define GET_ID(get, id, str, args) do { \
fields.id = (*p_env)->get(p_env, clazz, (str), (args)); \
if (CHECK_EXCEPTION()) \
return VLC_EGENERIC; \
} while (0)
#define GET_CONST_INT(id, str) do { \
jfieldID field = (*p_env)->GetStaticFieldID(p_env, clazz, (str), "I"); \
if (!CHECK_EXCEPTION()) \
{ \
fields.id = (*p_env)->GetStaticIntField(p_env, clazz, field); \
if (CHECK_EXCEPTION()) \
return VLC_EGENERIC; \
} else { \
return VLC_EGENERIC; \
} \
} while(0)
#define GET_CONST_OBJ(id, str, type) do { \
jfieldID field = (*p_env)->GetStaticFieldID(p_env, clazz, (str), type); \
if (!CHECK_EXCEPTION()) \
{ \
jobject jobj = (*p_env)->GetStaticObjectField(p_env, clazz, field); \
if (CHECK_EXCEPTION()) \
return VLC_EGENERIC; \
fields.id = NEW_GREF(jobj); \
DEL_LREF(jobj); \
} else { \
return VLC_EGENERIC; \
} \
} while(0)
/*
* Init JNI fields that will be used to fetch the key and crypt/encrypt
*/
static int
InitJni(vlc_keystore *p_keystore, JNIEnv *p_env)
{
jclass clazz = NULL;
GET_CLASS("java/lang/Object");
GET_ID(GetMethodID, Object.toString, "toString", "()Ljava/lang/String;");
GET_CLASS("java/security/KeyStore");
GET_GLOBAL_CLASS(KeyStore);
GET_ID(GetStaticMethodID, KeyStore.getInstance, "getInstance",
"(Ljava/lang/String;)Ljava/security/KeyStore;");
GET_ID(GetMethodID, KeyStore.load, "load",
"(Ljava/security/KeyStore$LoadStoreParameter;)V");
GET_ID(GetMethodID, KeyStore.getEntry, "getEntry",
"(Ljava/lang/String;Ljava/security/KeyStore$ProtectionParameter;)"
"Ljava/security/KeyStore$Entry;");
GET_CLASS("java/security/KeyStore$SecretKeyEntry");
GET_ID(GetMethodID, KeyStore.SecretKeyEntry.getSecretKey, "getSecretKey",
"()Ljavax/crypto/SecretKey;");
GET_CLASS("javax/crypto/spec/IvParameterSpec");
GET_GLOBAL_CLASS(IvParameterSpec);
GET_ID(GetMethodID, IvParameterSpec.ctor, "<init>", "([B)V");
GET_CLASS("javax/crypto/Cipher");
GET_GLOBAL_CLASS(Cipher);
GET_ID(GetStaticMethodID, Cipher.getInstance, "getInstance",
"(Ljava/lang/String;)Ljavax/crypto/Cipher;");
GET_ID(GetMethodID, Cipher.init, "init",
"(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V");
GET_ID(GetMethodID, Cipher.doFinal, "doFinal", "([B)[B");
GET_ID(GetMethodID, Cipher.getIV, "getIV", "()[B");
GET_CONST_INT(Cipher.ENCRYPT_MODE, "ENCRYPT_MODE");
GET_CONST_INT(Cipher.DECRYPT_MODE, "DECRYPT_MODE");
DEL_LREF(clazz);
jstring VLC_CIPHER = NEW_STR("AES/CBC/PKCS7Padding");
if (CHECK_EXCEPTION())
return VLC_EGENERIC;
fields.VLC_CIPHER = NEW_GREF(VLC_CIPHER);
DEL_LREF(VLC_CIPHER);
return VLC_SUCCESS;
}
/*
* Init JNI fields that will be used by generateKey()
*/
static int
InitJniGenKey(vlc_keystore *p_keystore, JNIEnv *p_env)
{
jclass clazz = NULL;
GET_CLASS("android/security/keystore/KeyProperties");
GET_CONST_INT(KeyProperties.PURPOSE_ENCRYPT, "PURPOSE_ENCRYPT");
GET_CONST_INT(KeyProperties.PURPOSE_DECRYPT, "PURPOSE_DECRYPT");
GET_CONST_OBJ(KeyProperties.BLOCK_MODE_CBC,
"BLOCK_MODE_CBC", "Ljava/lang/String;");
GET_CONST_OBJ(KeyProperties.ENCRYPTION_PADDING_PKCS7,
"ENCRYPTION_PADDING_PKCS7", "Ljava/lang/String;");
GET_CONST_OBJ(KeyProperties.KEY_ALGORITHM_AES,
"KEY_ALGORITHM_AES", "Ljava/lang/String;");
GET_CLASS("android/security/keystore/KeyGenParameterSpec$Builder");
GET_GLOBAL_CLASS(KeyGenParameterSpec.Builder);
GET_ID(GetMethodID, KeyGenParameterSpec.Builder.ctor, "<init>",
"(Ljava/lang/String;I)V");
GET_ID(GetMethodID, KeyGenParameterSpec.Builder.setKeySize, "setKeySize",
"(I)Landroid/security/keystore/KeyGenParameterSpec$Builder;");
GET_ID(GetMethodID, KeyGenParameterSpec.Builder.setBlockModes, "setBlockModes",
"([Ljava/lang/String;)"
"Landroid/security/keystore/KeyGenParameterSpec$Builder;");
GET_ID(GetMethodID, KeyGenParameterSpec.Builder.setEncryptionPaddings,
"setEncryptionPaddings", "([Ljava/lang/String;)"
"Landroid/security/keystore/KeyGenParameterSpec$Builder;");
GET_ID(GetMethodID, KeyGenParameterSpec.Builder.build, "build",
"()Landroid/security/keystore/KeyGenParameterSpec;");
GET_CLASS("javax/crypto/KeyGenerator");
GET_GLOBAL_CLASS(KeyGenerator);
GET_ID(GetStaticMethodID, KeyGenerator.getInstance, "getInstance",
"(Ljava/lang/String;Ljava/lang/String;)Ljavax/crypto/KeyGenerator;");
GET_ID(GetMethodID, KeyGenerator.init, "init",
"(Ljava/security/spec/AlgorithmParameterSpec;)V");
GET_ID(GetMethodID, KeyGenerator.generateKey, "generateKey",
"()Ljavax/crypto/SecretKey;");
DEL_LREF(clazz);
return VLC_SUCCESS;
}
/*
* Encrypt or Decrypt
*/
static size_t
Process(vlc_keystore *p_keystore, JNIEnv *p_env, jobject jcipher,
const uint8_t *p_src, size_t i_src_len,
const uint8_t *p_iv, uint32_t i_iv_len, uint8_t **pp_dst)
{
size_t i_dst_size = 0;
uint8_t *p_dst;
jbyteArray jsrcArray = NULL, jdstArray = NULL;
jsrcArray = (*p_env)->NewByteArray(p_env, i_src_len);
if (CHECK_EXCEPTION())
goto end;
(*p_env)->SetByteArrayRegion(p_env, jsrcArray, 0, i_src_len, (jbyte *)p_src);
jdstArray = (jbyteArray) CALL_OBJ(jcipher, Cipher.doFinal, jsrcArray);
if (CHECK_EXCEPTION())
goto end;
if (jdstArray == NULL)
goto end;
jsize dstSize = (*p_env)->GetArrayLength(p_env, jdstArray);
if (dstSize == 0)
goto end;
jbyte *p_bytes = (*p_env)->GetByteArrayElements(p_env, jdstArray, 0);
p_dst = i_iv_len > 0 ? malloc(dstSize + i_iv_len + sizeof(uint32_t))
: malloc(dstSize);
if (p_dst == NULL)
{
(*p_env)->ReleaseByteArrayElements(p_env, jdstArray, p_bytes, 0);
free(p_dst);
goto end;
}
if (i_iv_len > 0)
{
/* Store the IV just before the encrypted password */
memcpy(p_dst, &i_iv_len, sizeof(uint32_t));
memcpy(p_dst + sizeof(uint32_t), p_iv, i_iv_len);
memcpy(p_dst + sizeof(uint32_t) + i_iv_len, p_bytes, dstSize);
i_dst_size = dstSize + i_iv_len + sizeof(uint32_t);
}
else
{
memcpy(p_dst, p_bytes, dstSize);
i_dst_size = dstSize;
}
(*p_env)->ReleaseByteArrayElements(p_env, jdstArray, p_bytes, 0);
*pp_dst = p_dst;
end:
if (jsrcArray != NULL)
DEL_LREF(jsrcArray);
if (jdstArray != NULL)
DEL_LREF(jdstArray);
return i_dst_size;
}
static size_t
AndroidEncrypt(vlc_keystore *p_keystore, void *p_ctx, const uint8_t *p_src,
size_t i_src_len, uint8_t **pp_dst)
{
(void) p_ctx;
JNIEnv *p_env = GET_ENV();
if (p_env == NULL)
return 0;
jobject jcipher = NULL;
jcipher = CALL_STATICOBJ(Cipher, Cipher.getInstance, fields.VLC_CIPHER);
if (CHECK_EXCEPTION())
return 0;
size_t i_dst_len = 0;
CALL_VOID(jcipher, Cipher.init, fields.Cipher.ENCRYPT_MODE, s_jkey, NULL);
if (CHECK_EXCEPTION())
goto end;
/* Get the IV (Initialization Vector) initialized by Android that will be
* used to decrypt this secret. This IV will be stored with the encrypted
* secret */
jarray jivArray = (jarray) CALL_OBJ(jcipher, Cipher.getIV);
if (jivArray == NULL)
goto end;
jsize i_iv_len = (*p_env)->GetArrayLength(p_env, jivArray);
if (i_iv_len == 0)
goto end;
jbyte *p_iv_bytes = (*p_env)->GetByteArrayElements(p_env, jivArray, 0);
i_dst_len = Process(p_keystore, p_env, jcipher, p_src, i_src_len,
(const uint8_t *)p_iv_bytes, i_iv_len, pp_dst);
(*p_env)->ReleaseByteArrayElements(p_env, jivArray, p_iv_bytes, 0);
DEL_LREF(jivArray);
end:
DEL_LREF(jcipher);
return i_dst_len;
}
static size_t
AndroidDecrypt(vlc_keystore *p_keystore, void *p_ctx, const uint8_t *p_src,
size_t i_src_len, uint8_t **pp_dst)
{
(void) p_ctx;
JNIEnv *p_env = GET_ENV();
if (p_env == NULL)
return 0;
jobject jivArray = NULL, jiv = NULL, jcipher = NULL;
jcipher = CALL_STATICOBJ(Cipher, Cipher.getInstance, fields.VLC_CIPHER);
if (CHECK_EXCEPTION())
return 0;
/* Get the IV located at the beginning of the secret */
size_t i_dst_len = 0;
uint32_t i_iv_len;
if (i_src_len < sizeof(uint32_t))
goto end;
memcpy(&i_iv_len, p_src, sizeof(uint32_t));
if (i_iv_len == 0 || i_src_len < (sizeof(uint32_t) + i_iv_len))
goto end;
jivArray = (*p_env)->NewByteArray(p_env, i_iv_len);
if (CHECK_EXCEPTION())
goto end;
(*p_env)->SetByteArrayRegion(p_env, jivArray, 0, i_iv_len,
(jbyte *)(p_src + sizeof(uint32_t)) );
jiv = NEW_OBJECT(IvParameterSpec, jivArray);
if (CHECK_EXCEPTION())
goto end;
/* Use the IV to initialize the decrypt Cipher */
CALL_VOID(jcipher, Cipher.init, fields.Cipher.DECRYPT_MODE, s_jkey, jiv);
if (CHECK_EXCEPTION())
goto end;
i_dst_len = Process(p_keystore, p_env, jcipher,
p_src + sizeof(uint32_t) + i_iv_len,
i_src_len - sizeof(uint32_t) - i_iv_len,
NULL, 0, pp_dst);
end:
DEL_LREF(jcipher);
if (jivArray != NULL)
DEL_LREF(jivArray);
if (jiv != NULL)
DEL_LREF(jiv);
return i_dst_len;
}
/*
* Generate a AES/CBC/PKCS7Padding key that will be stored by the Android
* Keystore
*/
static jobject
GenerateKey(vlc_keystore *p_keystore, JNIEnv *p_env, jstring jstringAlias,
jstring jstringProvider)
{
if (InitJniGenKey(p_keystore, p_env) != VLC_SUCCESS)
return NULL;
jobject jkey = NULL, jbuilder = NULL, jspec = NULL,
jkeyGen = NULL;
jclass jstringClass = NULL;
jobjectArray jarray = NULL;
jbuilder = NEW_OBJECT(KeyGenParameterSpec.Builder, jstringAlias,
fields.KeyProperties.PURPOSE_ENCRYPT |
fields.KeyProperties.PURPOSE_DECRYPT);
CALL_OBJ(jbuilder, KeyGenParameterSpec.Builder.setKeySize, 256);
jstringClass = (*p_env)->FindClass(p_env, "java/lang/String");
if (CHECK_EXCEPTION())
goto end;
jarray = (*p_env)->NewObjectArray(p_env, 1, jstringClass, NULL);
if (CHECK_EXCEPTION())
goto end;
(*p_env)->SetObjectArrayElement(p_env, jarray, 0,
fields.KeyProperties.BLOCK_MODE_CBC);
CALL_OBJ(jbuilder, KeyGenParameterSpec.Builder.setBlockModes, jarray);
(*p_env)->SetObjectArrayElement(p_env, jarray, 0,
fields.KeyProperties.ENCRYPTION_PADDING_PKCS7);
CALL_OBJ(jbuilder, KeyGenParameterSpec.Builder.setEncryptionPaddings, jarray);
jspec = CALL_OBJ(jbuilder, KeyGenParameterSpec.Builder.build);
if (CHECK_EXCEPTION())
goto end;
jkeyGen = CALL_STATICOBJ(KeyGenerator, KeyGenerator.getInstance,
fields.KeyProperties.KEY_ALGORITHM_AES,
jstringProvider);
if (CHECK_EXCEPTION())
goto end;
CALL_VOID(jkeyGen, KeyGenerator.init, jspec);
if (CHECK_EXCEPTION())
goto end;
jkey = CALL_OBJ(jkeyGen, KeyGenerator.generateKey);
CHECK_EXCEPTION();
end:
if (jbuilder != NULL)
DEL_LREF(jbuilder);
if (jstringClass != NULL)
DEL_LREF(jstringClass);
if (jarray != NULL)
DEL_LREF(jarray);
if (jspec != NULL)
DEL_LREF(jspec);
if (jkeyGen != NULL)
DEL_LREF(jkeyGen);
return jkey;
}
/*
* Init JNI fields, fetch the key stored by Android or generate a new one
*/
static void
AndroidInit(vlc_keystore *p_keystore)
{
JNIEnv *p_env = GET_ENV();
if (p_env == NULL)
return;
if (InitJni(p_keystore, p_env) != VLC_SUCCESS)
return;
jobject jkeystore = NULL, jentry = NULL, jkey = NULL;
jstring jstringAlias = NULL, jstringProvider = NULL;
jstringAlias = NEW_STR("LibVLCAndroid");
if (CHECK_EXCEPTION())
goto end;
jstringProvider = NEW_STR("AndroidKeyStore");
if (CHECK_EXCEPTION())
goto end;
jkeystore = CALL_STATICOBJ(KeyStore, KeyStore.getInstance, jstringProvider);
if (CHECK_EXCEPTION())
goto end;
CALL_VOID(jkeystore, KeyStore.load, NULL);
if (CHECK_EXCEPTION())
goto end;
jentry = CALL_OBJ(jkeystore, KeyStore.getEntry, jstringAlias, NULL);
if (CHECK_EXCEPTION())
goto end;
if (jentry != NULL)
{
jkey = CALL_OBJ(jentry, KeyStore.SecretKeyEntry.getSecretKey);
if (CHECK_EXCEPTION())
goto end;
}
else
{
jkey = GenerateKey(p_keystore, p_env, jstringAlias, jstringProvider);
if (jkey == NULL)
goto end;
}
s_jkey = NEW_GREF(jkey);
end:
if (jstringAlias != NULL)
DEL_LREF(jstringAlias);
if (jstringProvider != NULL)
DEL_LREF(jstringProvider);
if (jkeystore != NULL)
DEL_LREF(jkeystore);
if (jentry != NULL)
DEL_LREF(jentry);
if (jkey != NULL)
DEL_LREF(jkey);
}
int
CryptInit(vlc_keystore *p_keystore, struct crypt *p_crypt)
{
static vlc_mutex_t s_lock = VLC_STATIC_MUTEX;
static bool s_init = false;
vlc_mutex_lock(&s_lock);
if (!s_init)
{
AndroidInit(p_keystore);
s_init = true;
}
if (s_jkey == NULL)
{
vlc_mutex_unlock(&s_lock);
return VLC_EGENERIC;
}
vlc_mutex_unlock(&s_lock);
p_crypt->pf_encrypt = AndroidEncrypt;
p_crypt->pf_decrypt = AndroidDecrypt;
return VLC_SUCCESS;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment