Added biometric cert password encryption to settings

Does nothing useful right now and just makes your client cert unusable while enabled. Obviously I plan to change that in the near-ish future.
This commit is contained in:
Corewala 2022-05-16 20:37:15 -04:00
parent 88583d1f49
commit 64f3308e1b
6 changed files with 194 additions and 7 deletions

View File

@ -56,6 +56,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.3.0-rc01'
implementation "androidx.biometric:biometric:1.1.0"
//ROOM DB
def room_version = "2.2.6"

View File

@ -12,6 +12,7 @@ class Buran: Application() {
const val PREF_KEY_CLIENT_CERT_HUMAN_READABLE = "client_cert_uri_human_readable"
const val PREF_KEY_CLIENT_CERT_ACTIVE = "client_cert_active"
const val PREF_KEY_CLIENT_CERT_PASSWORD = "client_cert_password"
const val CLIENT_CERT_PASSWORD_SECRET_KEY_NAME = "client_cert_secret_key_name"
const val PREF_KEY_USE_CUSTOM_TAB = "use_custom_tabs"
}
}

View File

@ -0,0 +1,106 @@
package corewala.buran.io.keymanager
import android.content.Context
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import corewala.buran.Buran
import corewala.buran.R
import java.nio.charset.Charset
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
data class EncryptedData(val ciphertext: ByteArray, val initializationVector: ByteArray)
@RequiresApi(Build.VERSION_CODES.P)
class BuranBiometricManager {
private lateinit var biometricPrompt: BiometricPrompt
private lateinit var promptInfo: BiometricPrompt.PromptInfo
private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
private val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
fun createBiometricPrompt(context: Context, fragment: Fragment, callback: BiometricPrompt.AuthenticationCallback){
val executor = ContextCompat.getMainExecutor(context)
biometricPrompt = BiometricPrompt(fragment, executor, callback)
promptInfo = BiometricPrompt.PromptInfo.Builder()
.setConfirmationRequired(false)
.setTitle(context.getString(R.string.confirm_your_identity))
.setSubtitle(context.getString(R.string.use_biometric_unlock))
.setNegativeButtonText(context.getString(R.string.cancel).toUpperCase())
.build()
}
fun authenticateToEncryptData() {
val cipher = getCipher()
val secretKey = getSecretKey(Buran.CLIENT_CERT_PASSWORD_SECRET_KEY_NAME)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
fun authenticateToDecryptData(initializationVector: ByteArray) {
val cipher = getCipher()
val secretKey = getSecretKey(Buran.CLIENT_CERT_PASSWORD_SECRET_KEY_NAME)
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, initializationVector))
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
// Allows ByteArrays to be stored in prefs as strings. Possibly the most horrifying function I've ever written.
fun decodeByteArray(encodedByteArray: String): ByteArray{
val byteList = encodedByteArray.substring(1, encodedByteArray.length - 1).split(", ")
var decodedByteArray = byteArrayOf()
for(byte in byteList){
decodedByteArray += byte.toInt().toByte()
}
println(decodedByteArray.contentToString())
return decodedByteArray
}
fun encryptData(plaintext: String, cipher: Cipher): EncryptedData {
val ciphertext = cipher.doFinal(plaintext.toByteArray(Charset.forName("UTF-8")))
return EncryptedData(ciphertext,cipher.iv)
}
fun decryptData(ciphertext: ByteArray, cipher: Cipher): String {
val plaintext = cipher.doFinal(ciphertext)
return String(plaintext, Charset.forName("UTF-8"))
}
private fun getCipher(): Cipher {
val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"
return Cipher.getInstance(transformation)
}
private fun getSecretKey(keyName: String): SecretKey {
val androidKeystore = "AndroidKeyStore"
val keyStore = KeyStore.getInstance(androidKeystore)
keyStore.load(null)
keyStore.getKey(keyName, null)?.let { return it as SecretKey }
val keyGenParams = KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(ENCRYPTION_BLOCK_MODE)
setEncryptionPaddings(ENCRYPTION_PADDING)
setKeySize(256)
setUserAuthenticationRequired(true)
}.build()
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
androidKeystore
)
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}
}

View File

@ -7,13 +7,16 @@ import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.OpenableColumns
import android.view.inputmethod.EditorInfo
import androidx.appcompat.app.AppCompatDelegate
import androidx.biometric.BiometricPrompt
import androidx.preference.*
import corewala.buran.Buran
import corewala.buran.R
import corewala.buran.io.keymanager.BuranBiometricManager
const val PREFS_SET_CLIENT_CERT_REQ = 20
@ -272,28 +275,98 @@ class SettingsFragment: PreferenceFragmentCompat(), Preference.OnPreferenceChang
clientCertPassword.key = Buran.PREF_KEY_CLIENT_CERT_PASSWORD
clientCertPassword.title = getString(R.string.client_certificate_password)
val certPasword = preferenceManager.sharedPreferences.getString(
val certPassword = preferenceManager.sharedPreferences.getString(
Buran.PREF_KEY_CLIENT_CERT_PASSWORD,
null
)
if (certPasword != null && certPasword.isNotEmpty()) {
clientCertPassword.summary = getDots(certPasword)
if (certPassword != null && certPassword.isNotEmpty()) {
clientCertPassword.summary = getDots(certPassword)
} else {
clientCertPassword.summary = getString(R.string.no_password)
}
clientCertPassword.dialogTitle = getString(R.string.client_certificate_password)
clientCertPassword.isVisible = !preferenceManager.sharedPreferences.getBoolean("use_biometrics", false)
certificateCategory.addPreference(clientCertPassword)
val useBiometrics = SwitchPreferenceCompat(context)
useBiometrics.setDefaultValue(false)
useBiometrics.key = "use_biometrics"
useBiometrics.title = getString(R.string.biometric_cert_verification)
useBiometrics.isVisible = false
certificateCategory.addPreference(useBiometrics)
val passwordCiphertext = EditTextPreference(context)
passwordCiphertext.key = "password_ciphertext"
passwordCiphertext.isVisible = false
certificateCategory.addPreference(passwordCiphertext)
val passwordInitVector = EditTextPreference(context)
passwordInitVector.key = "password_init_vector"
passwordInitVector.isVisible = false
certificateCategory.addPreference(passwordInitVector)
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
useBiometrics.isVisible = certPassword?.isNotEmpty() ?: false
useBiometrics.setOnPreferenceChangeListener { _, newValue ->
val biometricManager = BuranBiometricManager()
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
println("Authentication error: $errorCode: $errString")
useBiometrics.isChecked = !(newValue as Boolean)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
println("Authentication failed")
useBiometrics.isChecked = !(newValue as Boolean)
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
println("Authentication succeeded")
if(newValue as Boolean){
val encryptedData = biometricManager.encryptData(certPassword!!, result.cryptoObject?.cipher!!)
val ciphertext = encryptedData.ciphertext
val initializationVector = encryptedData.initializationVector
passwordInitVector.text = initializationVector.contentToString()
passwordCiphertext.text = ciphertext.contentToString()
clientCertPassword.text = "encrypted"
}else{
val ciphertext = biometricManager.decodeByteArray(passwordCiphertext.text)
clientCertPassword.text = biometricManager.decryptData(ciphertext, result.cryptoObject?.cipher!!)
clientCertPassword.summary = getDots(clientCertPassword.text)
}
clientCertPassword.isVisible = !(newValue as Boolean)
}
}
biometricManager.createBiometricPrompt(requireContext(), this, callback)
if(newValue as Boolean){
biometricManager.authenticateToEncryptData()
}else{
val initializationVector = biometricManager.decodeByteArray(passwordInitVector.text)
biometricManager.authenticateToDecryptData(initializationVector)
}
true
}
}
clientCertPassword.setOnPreferenceChangeListener { _, newValue ->
val passphrase = "$newValue"
if (passphrase.isEmpty()) {
clientCertPassword.summary = getString(R.string.no_password)
useBiometrics.isVisible = false
} else {
clientCertPassword.summary = getDots(passphrase)
useBiometrics.isVisible = true
}
true//update the value
}
certificateCategory.addPreference(clientCertPassword)
}
private fun getDots(value: String): String {

View File

@ -70,6 +70,9 @@
<string name="no_password">Pas de mot de passe</string>
<string name="use_client_certificate">Utiliser Certificat Client</string>
<string name="client_certificate_required">Certificat Client Requis</string>
<string name="confirm_your_identity">Confirmez votre identité</string>
<string name="use_biometric_unlock">Utilisez vos informations biométriques pour continuer</string>
<string name="biometric_cert_verification">Certificat Client biométrique</string>
<string name="set_home_capsule">Choisir comme capsule d\'accueil</string>
<string name="check_for_updates">Rechercher des nouvelles versions</string>
<string name="new_version_available">Nouvelle version disponible</string>

View File

@ -70,6 +70,9 @@
<string name="no_password">No Password</string>
<string name="use_client_certificate">Use Client Certificate</string>
<string name="client_certificate_required">Client Certificate Required</string>
<string name="confirm_your_identity">Confirm your identity</string>
<string name="use_biometric_unlock">Verify your biometric credentials to continue</string>
<string name="biometric_cert_verification">Client Certificate biometrics</string>
<string name="set_home_capsule">Set home capsule</string>
<string name="check_for_updates">Check for updates</string>
<string name="new_version_available">New version available</string>