Support for master password and storing password as hashes (HMAC) instead of plaintext,
additional crypto utils with tests (partialy for in-progress pref export encryption), changed PasswordCheck to more common UI and to use lambdas (will need given password in lambda callback for prefs enc)
This commit is contained in:
parent
4716b04972
commit
6e9ccc593f
|
@ -50,8 +50,10 @@ import info.nightscout.androidaps.plugins.source.EversensePlugin
|
||||||
import info.nightscout.androidaps.plugins.source.GlimpPlugin
|
import info.nightscout.androidaps.plugins.source.GlimpPlugin
|
||||||
import info.nightscout.androidaps.plugins.source.PoctechPlugin
|
import info.nightscout.androidaps.plugins.source.PoctechPlugin
|
||||||
import info.nightscout.androidaps.plugins.source.TomatoPlugin
|
import info.nightscout.androidaps.plugins.source.TomatoPlugin
|
||||||
|
import info.nightscout.androidaps.utils.CryptoUtil
|
||||||
import info.nightscout.androidaps.utils.OKDialog.show
|
import info.nightscout.androidaps.utils.OKDialog.show
|
||||||
import info.nightscout.androidaps.utils.SafeParse
|
import info.nightscout.androidaps.utils.SafeParse
|
||||||
|
import info.nightscout.androidaps.utils.protection.PasswordCheck
|
||||||
import info.nightscout.androidaps.utils.protection.ProtectionCheck
|
import info.nightscout.androidaps.utils.protection.ProtectionCheck
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||||
|
@ -98,6 +100,8 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
||||||
@Inject lateinit var wearPlugin: WearPlugin
|
@Inject lateinit var wearPlugin: WearPlugin
|
||||||
@Inject lateinit var maintenancePlugin: MaintenancePlugin
|
@Inject lateinit var maintenancePlugin: MaintenancePlugin
|
||||||
|
|
||||||
|
@Inject lateinit var passwordCheck: PasswordCheck
|
||||||
|
|
||||||
@Inject lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
@Inject lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||||
|
|
||||||
override fun androidInjector(): AndroidInjector<Any> = androidInjector
|
override fun androidInjector(): AndroidInjector<Any> = androidInjector
|
||||||
|
@ -254,19 +258,19 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
||||||
// Preferences
|
// Preferences
|
||||||
if (pref.getKey() == resourceHelper.gs(R.string.key_settings_protection)) {
|
if (pref.getKey() == resourceHelper.gs(R.string.key_settings_protection)) {
|
||||||
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_settings_password))
|
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_settings_password))
|
||||||
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString()
|
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
|
||||||
}
|
}
|
||||||
// Application
|
// Application
|
||||||
// Application
|
// Application
|
||||||
if (pref.getKey() == resourceHelper.gs(R.string.key_application_protection)) {
|
if (pref.getKey() == resourceHelper.gs(R.string.key_application_protection)) {
|
||||||
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_application_password))
|
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_application_password))
|
||||||
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString()
|
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
|
||||||
}
|
}
|
||||||
// Bolus
|
// Bolus
|
||||||
// Bolus
|
// Bolus
|
||||||
if (pref.getKey() == resourceHelper.gs(R.string.key_bolus_protection)) {
|
if (pref.getKey() == resourceHelper.gs(R.string.key_bolus_protection)) {
|
||||||
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_bolus_password))
|
val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_bolus_password))
|
||||||
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString()
|
if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pref is EditTextPreference) {
|
if (pref is EditTextPreference) {
|
||||||
|
@ -281,6 +285,16 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pref is Preference) {
|
||||||
|
if ((pref.getKey() != null) && (pref.getKey().contains("_password"))) {
|
||||||
|
if (sp.getString(pref.getKey(), "").startsWith("hmac:")) {
|
||||||
|
pref.setSummary("******")
|
||||||
|
} else {
|
||||||
|
pref.setSummary(resourceHelper.gs(R.string.password_not_set))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pref?.let { adjustUnitDependentPrefs(it) }
|
pref?.let { adjustUnitDependentPrefs(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,4 +308,29 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang
|
||||||
updatePrefSummary(p)
|
updatePrefSummary(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We use Preference and custom editor instead of EditTextPreference
|
||||||
|
// to hash password while it is saved and never have to show it, even hashed
|
||||||
|
|
||||||
|
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
||||||
|
if (preference != null) {
|
||||||
|
if (preference.key == resourceHelper.gs(R.string.key_master_password)) {
|
||||||
|
passwordCheck.setPassword(this.context!!, R.string.master_password, R.string.key_master_password)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (preference.key == resourceHelper.gs(R.string.key_settings_password)) {
|
||||||
|
passwordCheck.setPassword(this.context!!, R.string.settings_password, R.string.key_settings_password)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (preference.key == resourceHelper.gs(R.string.key_bolus_password)) {
|
||||||
|
passwordCheck.setPassword(this.context!!, R.string.bolus_password, R.string.key_bolus_password)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (preference.key == resourceHelper.gs(R.string.key_application_password)) {
|
||||||
|
passwordCheck.setPassword(this.context!!, R.string.application_password, R.string.key_application_password)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onPreferenceTreeClick(preference)
|
||||||
|
}
|
||||||
}
|
}
|
142
app/src/main/java/info/nightscout/androidaps/utils/CryptoUtil.kt
Normal file
142
app/src/main/java/info/nightscout/androidaps/utils/CryptoUtil.kt
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package info.nightscout.androidaps.utils
|
||||||
|
|
||||||
|
import org.spongycastle.util.encoders.Base64
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.spec.KeySpec
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import javax.crypto.SecretKeyFactory
|
||||||
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
import javax.crypto.spec.PBEKeySpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
private val HEX_CHARS = "0123456789abcdef"
|
||||||
|
private val HEX_CHARS_ARRAY = "0123456789abcdef".toCharArray()
|
||||||
|
|
||||||
|
fun String.hexStringToByteArray() : ByteArray {
|
||||||
|
|
||||||
|
val upperCased = this.toLowerCase()
|
||||||
|
val result = ByteArray(length / 2)
|
||||||
|
for (i in 0 until length step 2) {
|
||||||
|
val firstIndex = HEX_CHARS.indexOf(upperCased[i]);
|
||||||
|
val secondIndex = HEX_CHARS.indexOf(upperCased[i + 1]);
|
||||||
|
|
||||||
|
val octet = firstIndex.shl(4).or(secondIndex)
|
||||||
|
result.set(i.shr(1), octet.toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toHex() : String{
|
||||||
|
val result = StringBuffer()
|
||||||
|
|
||||||
|
forEach {
|
||||||
|
val octet = it.toInt()
|
||||||
|
val firstIndex = (octet and 0xF0).ushr(4)
|
||||||
|
val secondIndex = octet and 0x0F
|
||||||
|
result.append(HEX_CHARS_ARRAY[firstIndex])
|
||||||
|
result.append(HEX_CHARS_ARRAY[secondIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
object CryptoUtil {
|
||||||
|
|
||||||
|
private const val IV_LENGTH_BYTE = 12
|
||||||
|
private const val TAG_LENGTH_BIT = 128
|
||||||
|
private const val AES_KEY_SIZE_BIT = 256
|
||||||
|
private const val PBKDF2_ITERATIONS = 50000 // check delays it cause on real device
|
||||||
|
private const val SALT_SIZE_BYTE = 32
|
||||||
|
|
||||||
|
private val secureRandom: SecureRandom = SecureRandom()
|
||||||
|
|
||||||
|
fun sha256(source: String): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
val hashRaw = digest.digest(source.toByteArray())
|
||||||
|
return hashRaw.toHex()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hmac256(str: String, secret: String): String? {
|
||||||
|
val sha256_HMAC = Mac.getInstance("HmacSHA256")
|
||||||
|
val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256")
|
||||||
|
sha256_HMAC.init(secretKey)
|
||||||
|
return sha256_HMAC.doFinal(str.toByteArray()).toHex()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepCipherKey(passPhrase: String, salt:ByteArray, iterationCount:Int = PBKDF2_ITERATIONS, keyStrength:Int = AES_KEY_SIZE_BIT): SecretKeySpec {
|
||||||
|
val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1")
|
||||||
|
val spec: KeySpec = PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, keyStrength)
|
||||||
|
val tmp: SecretKey = factory.generateSecret(spec)
|
||||||
|
return SecretKeySpec(tmp.getEncoded(), "AES")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mineSalt(len :Int = SALT_SIZE_BYTE): ByteArray {
|
||||||
|
val salt = ByteArray(len)
|
||||||
|
secureRandom.nextBytes(salt)
|
||||||
|
return salt
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encrypt(passPhrase: String, salt:ByteArray, rawData: String ): String? {
|
||||||
|
val iv: ByteArray?
|
||||||
|
val encrypted: ByteArray?
|
||||||
|
return try {
|
||||||
|
iv = ByteArray(IV_LENGTH_BYTE)
|
||||||
|
secureRandom.nextBytes(iv)
|
||||||
|
val cipherEnc: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||||
|
cipherEnc.init(Cipher.ENCRYPT_MODE, prepCipherKey(passPhrase, salt), GCMParameterSpec(TAG_LENGTH_BIT, iv))
|
||||||
|
encrypted = cipherEnc.doFinal(rawData.toByteArray())
|
||||||
|
val byteBuffer: ByteBuffer = ByteBuffer.allocate(1 + iv.size + encrypted.size)
|
||||||
|
byteBuffer.put(iv.size.toByte())
|
||||||
|
byteBuffer.put(iv)
|
||||||
|
byteBuffer.put(encrypted)
|
||||||
|
String(Base64.encode(byteBuffer.array()))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decrypt(passPhrase: String, salt:ByteArray, encryptedData: String): String? {
|
||||||
|
val iv: ByteArray?
|
||||||
|
val encrypted: ByteArray?
|
||||||
|
return try {
|
||||||
|
val byteBuffer = ByteBuffer.wrap(Base64.decode(encryptedData))
|
||||||
|
val ivLength = byteBuffer.get().toInt()
|
||||||
|
iv = ByteArray(ivLength)
|
||||||
|
byteBuffer[iv]
|
||||||
|
encrypted = ByteArray(byteBuffer.remaining())
|
||||||
|
byteBuffer[encrypted]
|
||||||
|
val cipherDec: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||||
|
cipherDec.init(Cipher.DECRYPT_MODE, prepCipherKey(passPhrase, salt), GCMParameterSpec(TAG_LENGTH_BIT, iv))
|
||||||
|
val dec = cipherDec.doFinal(encrypted)
|
||||||
|
String(dec)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkPassword(password: String, referenceHash: String): Boolean {
|
||||||
|
return if (referenceHash.startsWith("hmac:")) {
|
||||||
|
val hashSegments = referenceHash.split(":")
|
||||||
|
if (hashSegments.size != 3)
|
||||||
|
return false
|
||||||
|
return hmac256(password, hashSegments[1]) == hashSegments[2]
|
||||||
|
} else {
|
||||||
|
password == referenceHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hashPassword(password: String): String {
|
||||||
|
return if (!password.startsWith("hmac:")) {
|
||||||
|
val salt = mineSalt().toHex()
|
||||||
|
return "hmac:${salt}:${hmac256(password, salt)}"
|
||||||
|
} else {
|
||||||
|
password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,13 +2,17 @@ package info.nightscout.androidaps.utils.protection
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import info.nightscout.androidaps.R
|
import info.nightscout.androidaps.R
|
||||||
|
import info.nightscout.androidaps.utils.CryptoUtil
|
||||||
import info.nightscout.androidaps.utils.ToastUtils
|
import info.nightscout.androidaps.utils.ToastUtils
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -18,34 +22,74 @@ import javax.inject.Singleton
|
||||||
class PasswordCheck @Inject constructor(val sp: SP) {
|
class PasswordCheck @Inject constructor(val sp: SP) {
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: Runnable?, cancel: Runnable? = null, fail: Runnable? = null) {
|
fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) {
|
||||||
val password = sp.getString(preference, "")
|
val password = sp.getString(preference, "")
|
||||||
if (password == "") {
|
if (password == "") {
|
||||||
ok?.run()
|
ok?.invoke("")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null)
|
||||||
|
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = activity.getString(labelId)
|
||||||
|
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp)
|
||||||
|
|
||||||
val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null)
|
val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null)
|
||||||
|
|
||||||
val alertDialogBuilder = AlertDialog.Builder(activity)
|
val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme))
|
||||||
alertDialogBuilder.setView(promptsView)
|
alertDialogBuilder.setView(promptsView)
|
||||||
|
|
||||||
val label = promptsView.findViewById<View>(R.id.passwordprompt_text) as TextView
|
|
||||||
label.text = activity.getString(labelId)
|
|
||||||
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
|
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
|
||||||
|
|
||||||
alertDialogBuilder
|
alertDialogBuilder
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
|
.setCustomTitle(titleLayout)
|
||||||
.setPositiveButton(activity.getString(R.string.ok)) { _, _ ->
|
.setPositiveButton(activity.getString(R.string.ok)) { _, _ ->
|
||||||
val enteredPassword = userInput.text.toString()
|
val enteredPassword = userInput.text.toString()
|
||||||
if (password == enteredPassword) ok?.run()
|
if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword)
|
||||||
else {
|
else {
|
||||||
ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword))
|
ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword))
|
||||||
fail?.run()
|
fail?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(activity.getString(R.string.cancel)
|
.setNegativeButton(activity.getString(R.string.cancel)
|
||||||
) { dialog, _ ->
|
) { dialog, _ ->
|
||||||
cancel?.run()
|
cancel?.invoke()
|
||||||
|
dialog.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
alertDialogBuilder.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int) {
|
||||||
|
val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null)
|
||||||
|
|
||||||
|
val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null)
|
||||||
|
(titleLayout.findViewById<View>(R.id.alertdialog_title) as TextView).text = context.getText(labelId)
|
||||||
|
(titleLayout.findViewById<View>(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp)
|
||||||
|
|
||||||
|
val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme))
|
||||||
|
alertDialogBuilder.setView(promptsView)
|
||||||
|
|
||||||
|
val userInput = promptsView.findViewById<View>(R.id.passwordprompt_pass) as EditText
|
||||||
|
|
||||||
|
alertDialogBuilder
|
||||||
|
.setCancelable(false)
|
||||||
|
.setCustomTitle(titleLayout)
|
||||||
|
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
|
||||||
|
val enteredPassword = userInput.text.toString()
|
||||||
|
if (enteredPassword.isNotEmpty()) {
|
||||||
|
sp.putString(preference, CryptoUtil.hashPassword(enteredPassword))
|
||||||
|
} else {
|
||||||
|
if (sp.contains(preference)) {
|
||||||
|
sp.remove(preference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set))
|
||||||
|
}
|
||||||
|
.setNegativeButton(context.getString(R.string.cancel)
|
||||||
|
) { dialog, _ ->
|
||||||
|
ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed))
|
||||||
dialog.cancel()
|
dialog.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ class ProtectionCheck @Inject constructor(
|
||||||
enum class ProtectionType {
|
enum class ProtectionType {
|
||||||
NONE,
|
NONE,
|
||||||
BIOMETRIC,
|
BIOMETRIC,
|
||||||
PASSWORD
|
MASTER_PASSWORD,
|
||||||
|
CUSTOM_PASSWORD
|
||||||
}
|
}
|
||||||
|
|
||||||
private val passwordsResourceIDs = listOf(
|
private val passwordsResourceIDs = listOf(
|
||||||
|
@ -43,7 +44,8 @@ class ProtectionCheck @Inject constructor(
|
||||||
return when (ProtectionType.values()[sp.getInt(protectionTypeResourceIDs[protection.ordinal], ProtectionType.NONE.ordinal)]) {
|
return when (ProtectionType.values()[sp.getInt(protectionTypeResourceIDs[protection.ordinal], ProtectionType.NONE.ordinal)]) {
|
||||||
ProtectionType.NONE -> false
|
ProtectionType.NONE -> false
|
||||||
ProtectionType.BIOMETRIC -> true
|
ProtectionType.BIOMETRIC -> true
|
||||||
ProtectionType.PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != ""
|
ProtectionType.MASTER_PASSWORD -> sp.getString(R.string.key_master_password, "") != ""
|
||||||
|
ProtectionType.CUSTOM_PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,8 +57,10 @@ class ProtectionCheck @Inject constructor(
|
||||||
ok?.run()
|
ok?.run()
|
||||||
ProtectionType.BIOMETRIC ->
|
ProtectionType.BIOMETRIC ->
|
||||||
BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail)
|
BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail)
|
||||||
ProtectionType.PASSWORD ->
|
ProtectionType.MASTER_PASSWORD ->
|
||||||
passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], ok, cancel, fail)
|
passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() })
|
||||||
|
ProtectionType.CUSTOM_PASSWORD ->
|
||||||
|
passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], { ok?.run() }, { cancel?.run() }, { fail?.run() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
9
app/src/main/res/drawable/ic_key.xml
Normal file
9
app/src/main/res/drawable/ic_key.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M22 18V22H18V19H15V16H12L9.74 13.74C9.19 13.91 8.61 14 8 14A6 6 0 0 1 2 8A6 6 0 0 1 8 2A6 6 0 0 1 14 8C14 8.61 13.91 9.19 13.74 9.74L22 18M7 5A2 2 0 0 0 5 7A2 2 0 0 0 7 9A2 2 0 0 0 9 7A2 2 0 0 0 7 5Z"
|
||||||
|
android:fillColor="#ffffff" />
|
||||||
|
</vector>
|
17
app/src/main/res/drawable/ic_key_48dp.xml
Normal file
17
app/src/main/res/drawable/ic_key_48dp.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="48dp"
|
||||||
|
android:tint="#FFFFFF"
|
||||||
|
>
|
||||||
|
<group
|
||||||
|
android:scaleX="0.66"
|
||||||
|
android:scaleY="0.66"
|
||||||
|
android:pivotX="12"
|
||||||
|
android:pivotY="12">
|
||||||
|
<path
|
||||||
|
android:pathData="M22 18V22H18V19H15V16H12L9.74 13.74C9.19 13.91 8.61 14 8 14A6 6 0 0 1 2 8A6 6 0 0 1 8 2A6 6 0 0 1 14 8C14 8.61 13.91 9.19 13.74 9.74L22 18M7 5A2 2 0 0 0 5 7A2 2 0 0 0 7 9A2 2 0 0 0 9 7A2 2 0 0 0 7 5Z"
|
||||||
|
android:fillColor="#FF000000" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -5,17 +5,13 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="10dp" >
|
android:padding="10dp" >
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/passwordprompt_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/passwordprompt_pass"
|
android:id="@+id/passwordprompt_pass"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textPassword">
|
android:hint="@string/password_hint"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
>
|
||||||
|
|
||||||
<requestFocus />
|
<requestFocus />
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,22 @@
|
||||||
<string name="settings_protection">Settings protection</string>
|
<string name="settings_protection">Settings protection</string>
|
||||||
<string name="application_protection">Application protection</string>
|
<string name="application_protection">Application protection</string>
|
||||||
<string name="bolus_protection">Bolus protection</string>
|
<string name="bolus_protection">Bolus protection</string>
|
||||||
|
<string name="master_password">Master password</string>
|
||||||
<string name="settings_password">Settings password</string>
|
<string name="settings_password">Settings password</string>
|
||||||
<string name="application_password">Application password</string>
|
<string name="application_password">Application password</string>
|
||||||
<string name="bolus_password">Bolus password</string>
|
<string name="bolus_password">Bolus password</string>
|
||||||
<string name="unlock_settings">Unlock settings</string>
|
<string name="unlock_settings">Unlock settings</string>
|
||||||
<string name="biometric">Biometric</string>
|
<string name="biometric">Biometric</string>
|
||||||
<string name="password">Password</string>
|
<string name="custom_password">Custom password</string>
|
||||||
<string name="noprotection">No protection</string>
|
<string name="noprotection">No protection</string>
|
||||||
<string name="protection">Protection</string>
|
<string name="protection">Protection</string>
|
||||||
|
|
||||||
|
<string name="password_set">Password set!</string>
|
||||||
|
<string name="password_not_set">Password not set</string>
|
||||||
|
<string name="password_not_changed">Password not changed</string>
|
||||||
|
<string name="password_hint">Enter password here</string>
|
||||||
|
|
||||||
|
<string name="key_master_password">master_password</string>
|
||||||
<string name="key_settings_password" translatable="false">settings_password</string>
|
<string name="key_settings_password" translatable="false">settings_password</string>
|
||||||
<string name="key_application_password" translatable="false">application_password</string>
|
<string name="key_application_password" translatable="false">application_password</string>
|
||||||
<string name="key_bolus_password">translatable="false"bolus_password</string>
|
<string name="key_bolus_password">translatable="false"bolus_password</string>
|
||||||
|
@ -24,13 +31,15 @@
|
||||||
<string-array name="protectiontype">
|
<string-array name="protectiontype">
|
||||||
<item>@string/noprotection</item>
|
<item>@string/noprotection</item>
|
||||||
<item>@string/biometric</item>
|
<item>@string/biometric</item>
|
||||||
<item>@string/password</item>
|
<item>@string/master_password</item>
|
||||||
|
<item>@string/custom_password</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="protectiontypeValues">
|
<string-array name="protectiontypeValues">
|
||||||
<item>0</item>
|
<item>0</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<item>2</item>
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -22,6 +22,12 @@
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/protection">
|
<PreferenceCategory android:title="@string/protection">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:key="@string/key_master_password"
|
||||||
|
android:title="@string/master_password"
|
||||||
|
/>
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="1"
|
android:defaultValue="1"
|
||||||
android:entries="@array/protectiontype"
|
android:entries="@array/protectiontype"
|
||||||
|
@ -29,7 +35,7 @@
|
||||||
android:key="@string/key_settings_protection"
|
android:key="@string/key_settings_protection"
|
||||||
android:title="@string/settings_protection" />
|
android:title="@string/settings_protection" />
|
||||||
|
|
||||||
<EditTextPreference
|
<Preference
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:key="@string/key_settings_password"
|
android:key="@string/key_settings_password"
|
||||||
android:title="@string/settings_password" />
|
android:title="@string/settings_password" />
|
||||||
|
@ -41,7 +47,7 @@
|
||||||
android:key="@string/key_application_protection"
|
android:key="@string/key_application_protection"
|
||||||
android:title="@string/application_protection" />
|
android:title="@string/application_protection" />
|
||||||
|
|
||||||
<EditTextPreference
|
<Preference
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:key="@string/key_application_password"
|
android:key="@string/key_application_password"
|
||||||
android:title="@string/application_password" />
|
android:title="@string/application_password" />
|
||||||
|
@ -53,7 +59,7 @@
|
||||||
android:key="@string/key_bolus_protection"
|
android:key="@string/key_bolus_protection"
|
||||||
android:title="@string/bolus_protection" />
|
android:title="@string/bolus_protection" />
|
||||||
|
|
||||||
<EditTextPreference
|
<Preference
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:key="@string/key_bolus_password"
|
android:key="@string/key_bolus_password"
|
||||||
android:title="@string/bolus_password" />
|
android:title="@string/bolus_password" />
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package info.nightscout.androidaps.utils
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.TestBase
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.powermock.core.classloader.annotations.PowerMockIgnore
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
|
|
||||||
|
@PowerMockIgnore("javax.crypto.*")
|
||||||
|
@RunWith(PowerMockRunner::class)
|
||||||
|
class CryptoUtilTest: TestBase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFixedSaltCrypto() {
|
||||||
|
val salt = byteArrayOf(
|
||||||
|
-33, -29, 16, -19, 99, -111, -3, 2, 116, 106, 47, 38, -54, 11, -77, 28,
|
||||||
|
111, -15, -65, -110, 4, -32, -29, -70, -95, -88, -53, 19, 87, -103, 123, -15)
|
||||||
|
|
||||||
|
val password = "thisIsFixedPassword"
|
||||||
|
val payload = "FIXED-PAYLOAD"
|
||||||
|
val encrypted = CryptoUtil.encrypt(password, salt, payload)
|
||||||
|
|
||||||
|
Assert.assertNotNull(encrypted)
|
||||||
|
val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!)
|
||||||
|
Assert.assertEquals(decrypted, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStandardCrypto() {
|
||||||
|
val salt = CryptoUtil.mineSalt()
|
||||||
|
|
||||||
|
val password = "topSikret"
|
||||||
|
val payload = "{what:payloadYouWantToProtect}"
|
||||||
|
val encrypted = CryptoUtil.encrypt(password, salt, payload)
|
||||||
|
|
||||||
|
Assert.assertNotNull(encrypted)
|
||||||
|
val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!)
|
||||||
|
Assert.assertEquals(decrypted, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testHashVector() {
|
||||||
|
val payload = "{what:payloadYouWantToProtect}"
|
||||||
|
val hash = CryptoUtil.sha256(payload)
|
||||||
|
Assert.assertEquals(hash, "a1aafe3ed6cc127e6d102ddbc40a205147230e9cfd178daf108c83543bbdcd13")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testHmac() {
|
||||||
|
val payload = "{what:payloadYouWantToProtect}"
|
||||||
|
val password = "topSikret"
|
||||||
|
val expectedHmac = "ea2213953d0f2e55047cae2d23fb4f0de1b805d55e6271efa70d6b85fb692bea" // generated using other HMAC tool
|
||||||
|
val hash = CryptoUtil.hmac256(payload, password)
|
||||||
|
Assert.assertEquals(hash, expectedHmac)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPlainPasswordCheck() {
|
||||||
|
Assert.assertTrue(CryptoUtil.checkPassword("same", "same"))
|
||||||
|
Assert.assertFalse(CryptoUtil.checkPassword("same", "other"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testHashedPasswordCheck() {
|
||||||
|
Assert.assertTrue(CryptoUtil.checkPassword("givenSecret", CryptoUtil.hashPassword("givenSecret")))
|
||||||
|
Assert.assertFalse(CryptoUtil.checkPassword("givenSecret", CryptoUtil.hashPassword("otherSecret")))
|
||||||
|
|
||||||
|
Assert.assertTrue(CryptoUtil.checkPassword("givenHashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
|
||||||
|
Assert.assertFalse(CryptoUtil.checkPassword("givenMashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
|
||||||
|
Assert.assertFalse(CryptoUtil.checkPassword("givenHashToCheck", "hmac:0fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
|
||||||
|
Assert.assertFalse(CryptoUtil.checkPassword("givenHashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:b0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue