VFS key generation & store/retrieve to/from keystore & pass on to factory if vfs enabled

This commit is contained in:
Christophe Deschamps 2021-04-06 13:05:37 +02:00 committed by Sylvain Berfini
parent ce601634f2
commit 3f36e4cc74
3 changed files with 155 additions and 1 deletions

View file

@ -253,6 +253,8 @@ dependencies {
// Only enable leak canary prior to release // Only enable leak canary prior to release
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4' //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
implementation "androidx.security:security-crypto:1.1.0-alpha03"
} }
if (firebaseEnabled()) { if (firebaseEnabled()) {

View file

@ -21,19 +21,36 @@ package org.linphone.core
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.media.AudioManager import android.media.AudioManager
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Vibrator import android.os.Vibrator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.telephony.PhoneStateListener import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.Base64
import android.util.Pair
import android.view.* import android.view.*
import androidx.emoji.bundled.BundledEmojiCompatConfig import androidx.emoji.bundled.BundledEmojiCompatConfig
import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import androidx.security.crypto.MasterKey.Builder
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import java.io.File import java.io.File
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.security.KeyStore
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import kotlin.math.abs import kotlin.math.abs
import org.linphone.LinphoneApplication.Companion.corePreferences import org.linphone.LinphoneApplication.Companion.corePreferences
import org.linphone.R import org.linphone.R
@ -208,7 +225,9 @@ class CoreContext(val context: Context, coreConfig: Config) {
if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.videoEnabled()) { if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled && call.currentParams.videoEnabled()) {
// Do not turn speaker on when video is enabled if headset or bluetooth is used // Do not turn speaker on when video is enabled if headset or bluetooth is used
if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(call)) { if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(
call
)) {
Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker") Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker")
AudioRouteUtils.routeAudioToSpeaker(call) AudioRouteUtils.routeAudioToSpeaker(call)
} }
@ -272,6 +291,9 @@ class CoreContext(val context: Context, coreConfig: Config) {
Log.i("[Context] Crashlytics enabled, register logging service listener") Log.i("[Context] Crashlytics enabled, register logging service listener")
} }
if (corePreferences.vfsEnabled) {
setupVFS(context)
}
core = Factory.instance().createCoreWithConfig(coreConfig, context) core = Factory.instance().createCoreWithConfig(coreConfig, context)
stopped = false stopped = false
@ -619,4 +641,122 @@ class CoreContext(val context: Context, coreConfig: Config) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
context.startActivity(intent) context.startActivity(intent)
} }
/* VFS */
companion object {
private val TRANSFORMATION = "AES/GCM/NoPadding"
private val ANDROID_KEY_STORE = "AndroidKeyStore"
private val ALIAS = "vfs"
private val LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256 = 2
}
@Throws(java.lang.Exception::class)
private fun generateSecretKey() {
val keyGenerator: KeyGenerator
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
keyGenerator.init(
KeyGenParameterSpec.Builder(
ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
)
keyGenerator.generateKey()
}
@Throws(java.lang.Exception::class)
private fun getSecretKey(): SecretKey? {
val ks = KeyStore.getInstance(ANDROID_KEY_STORE)
ks.load(null)
val entry = ks.getEntry(ALIAS, null) as KeyStore.SecretKeyEntry
return entry.secretKey
}
@Throws(java.lang.Exception::class)
fun generateToken(): String? {
return sha512(UUID.randomUUID().toString())
}
@Throws(java.lang.Exception::class)
private fun encryptData(textToEncrypt: String): Pair<ByteArray, ByteArray> {
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
val iv = cipher.iv
return Pair<ByteArray, ByteArray>(
iv,
cipher.doFinal(textToEncrypt.toByteArray(StandardCharsets.UTF_8))
)
}
@Throws(java.lang.Exception::class)
private fun decryptData(encrypted: String?, encryptionIv: ByteArray): String {
val cipher = Cipher.getInstance(TRANSFORMATION)
val spec = GCMParameterSpec(128, encryptionIv)
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)
val encryptedData = Base64.decode(encrypted, Base64.DEFAULT)
return String(cipher.doFinal(encryptedData), StandardCharsets.UTF_8)
}
@Throws(java.lang.Exception::class)
fun encryptToken(string_to_encrypt: String): Pair<String?, String?>? {
val encryptedData = encryptData(string_to_encrypt)
return Pair<String?, String?>(
Base64.encodeToString(encryptedData.first, Base64.DEFAULT),
Base64.encodeToString(encryptedData.second, Base64.DEFAULT)
)
}
@Throws(java.lang.Exception::class)
fun sha512(input: String): String? {
val md = MessageDigest.getInstance("SHA-512")
val messageDigest = md.digest(input.toByteArray())
val no = BigInteger(1, messageDigest)
var hashtext = no.toString(16)
while (hashtext.length < 32) {
hashtext = "0$hashtext"
}
return hashtext
}
@Throws(java.lang.Exception::class)
fun getVfsKey(sharedPreferences: SharedPreferences): String? {
val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
keyStore.load(null)
return decryptData(
sharedPreferences.getString("vfskey", null),
Base64.decode(sharedPreferences.getString("vfsiv", null), Base64.DEFAULT)
)
}
private fun setupVFS(c: Context) {
try {
val masterKey: MasterKey = Builder(
c,
MasterKey.DEFAULT_MASTER_KEY_ALIAS
).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
c, "vfs.prefs", masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
if (sharedPreferences.getString("vfsiv", null) == null) {
generateSecretKey()
generateToken()?.let { encryptToken(it) }?.let { data ->
sharedPreferences.edit().putString("vfsiv", data.first)
.putString("vfskey", data.second).commit()
}
}
Factory.instance().setVfsEncryption(
LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256,
Arrays.copyOfRange(getVfsKey(sharedPreferences)?.toByteArray(), 0, 32),
32
)
} catch (e: Exception) {
e.printStackTrace()
throw RuntimeException("Unable to setup VFS encryption")
}
}
} }

View file

@ -35,6 +35,18 @@ class CorePreferences constructor(private val context: Context) {
_config = value _config = value
} }
/* VFS encryption */
var vfsEnabled: Boolean
get() = config.getBool("app", "vfs", false)
set(value) {
if (!value && config.getBool("app", "vfs", false)) {
Log.w("[VFS] It is not possible to deactivate VFS after it has been activated")
return
}
config.setBool("app", "vfs", value)
}
/* App settings */ /* App settings */
var debugLogs: Boolean var debugLogs: Boolean