Merge branch 'dagger3' of https://github.com/MilosKozak/AndroidAPS into dagger3

This commit is contained in:
Milos Kozak 2020-04-07 11:56:15 +02:00
commit a10a62fc81
7 changed files with 95 additions and 42 deletions

View file

@ -50,6 +50,7 @@ import info.nightscout.androidaps.queue.commands.*
import info.nightscout.androidaps.setupwizard.SWEventListener
import info.nightscout.androidaps.setupwizard.SWScreen
import info.nightscout.androidaps.setupwizard.elements.*
import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.resources.ResourceHelperImplementation
@ -261,6 +262,7 @@ open class AppModule {
@ContributesAndroidInjector fun graphDataInjector(): GraphData
@ContributesAndroidInjector fun cryptoUtilInjector(): CryptoUtil
@ContributesAndroidInjector fun importExportPrefsInjector(): ImportExportPrefs
@ContributesAndroidInjector fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
@ContributesAndroidInjector fun classicPrefsFormatInjector(): ClassicPrefsFormat

View file

@ -18,6 +18,7 @@ import javax.inject.Singleton
@Singleton
class EncryptedPrefsFormat @Inject constructor(
private var resourceHelper: ResourceHelper,
private var cryptoUtil: CryptoUtil,
private var storage: Storage
) : PrefsFormat {
@ -58,14 +59,14 @@ class EncryptedPrefsFormat @Inject constructor(
var encodedContent = ""
if (encrypted) {
val salt = CryptoUtil.mineSalt()
val salt = cryptoUtil.mineSalt()
val rawContent = content.toString()
val contentAttempt = CryptoUtil.encrypt(masterPassword!!, salt, rawContent)
val contentAttempt = cryptoUtil.encrypt(masterPassword!!, salt, rawContent)
if (contentAttempt != null) {
encodedContent = contentAttempt
security.put("algorithm", "v1")
security.put("salt", salt.toHex())
security.put("content_hash", CryptoUtil.sha256(rawContent))
security.put("content_hash", cryptoUtil.sha256(rawContent))
} else {
// fallback when encryption does not work
encrypted = false
@ -80,7 +81,7 @@ class EncryptedPrefsFormat @Inject constructor(
container.put("content", if (encrypted) encodedContent else content)
var fileContents = container.toString(2)
val fileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE)
val fileHash = cryptoUtil.hmac256(fileContents, KEY_CONSCIENCE)
fileContents = fileContents.replace(Regex("(\\\"file_hash\\\"\\s*\\:\\s*\\\")(--to-be-calculated--)(\\\")"), "$1" + fileHash + "$3")
@ -102,7 +103,7 @@ class EncryptedPrefsFormat @Inject constructor(
val jsonBody = storage.getFileContents(file)
val fileContents = jsonBody.replace(Regex("(?is)(\\\"file_hash\\\"\\s*\\:\\s*\\\")([^\"]*)(\\\")"), "$1--to-be-calculated--$3")
val calculatedFileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE)
val calculatedFileHash = cryptoUtil.hmac256(fileContents, KEY_CONSCIENCE)
val container = JSONObject(jsonBody)
if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content") && container.has("metadata")) {
@ -144,11 +145,11 @@ class EncryptedPrefsFormat @Inject constructor(
if (security.has("salt") && security.has("content_hash")) {
val salt = security.getString("salt").hexStringToByteArray()
val decrypted = CryptoUtil.decrypt(masterPassword!!, salt, container.getString("content"))
val decrypted = cryptoUtil.decrypt(masterPassword!!, salt, container.getString("content"))
if (decrypted != null) {
try {
val contentHash = CryptoUtil.sha256(decrypted)
val contentHash = cryptoUtil.sha256(decrypted)
if (contentHash == security.getString("content_hash")) {
contentJsonObj = JSONObject(decrypted)

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.utils
import info.nightscout.androidaps.logging.AAPSLogger
import org.spongycastle.util.encoders.Base64
import java.nio.ByteBuffer
import java.security.MessageDigest
@ -12,6 +13,8 @@ import javax.crypto.SecretKeyFactory
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject
import javax.inject.Singleton
private val HEX_CHARS = "0123456789abcdef"
private val HEX_CHARS_ARRAY = "0123456789abcdef".toCharArray()
@ -45,15 +48,21 @@ fun ByteArray.toHex() : String{
return result.toString()
}
object CryptoUtil {
@Singleton
class CryptoUtil @Inject constructor(
val aapsLogger: AAPSLogger
) {
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
companion object {
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()
var lastException: Exception? = null
fun sha256(source: String): String {
val digest = MessageDigest.getInstance("SHA-256")
@ -85,6 +94,7 @@ object CryptoUtil {
val iv: ByteArray?
val encrypted: ByteArray?
return try {
lastException = null
iv = ByteArray(IV_LENGTH_BYTE)
secureRandom.nextBytes(iv)
val cipherEnc: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
@ -96,7 +106,9 @@ object CryptoUtil {
byteBuffer.put(encrypted)
String(Base64.encode(byteBuffer.array()))
} catch (e: Exception) {
null
lastException = e
aapsLogger.error("Encryption failed due to technical exception: ${e}")
null
}
}
@ -104,6 +116,7 @@ object CryptoUtil {
val iv: ByteArray?
val encrypted: ByteArray?
return try {
lastException = null
val byteBuffer = ByteBuffer.wrap(Base64.decode(encryptedData))
val ivLength = byteBuffer.get().toInt()
iv = ByteArray(ivLength)
@ -115,6 +128,8 @@ object CryptoUtil {
val dec = cipherDec.doFinal(encrypted)
String(dec)
} catch (e: Exception) {
lastException = e
aapsLogger.error("Decryption failed due to technical exception: ${e}")
null
}
}

View file

@ -19,7 +19,10 @@ import javax.inject.Singleton
val AUTOFILL_HINT_NEW_PASSWORD = "newPassword"
@Singleton
class PasswordCheck @Inject constructor(val sp: SP) {
class PasswordCheck @Inject constructor(
val sp: SP,
val cryptoUtil: CryptoUtil
) {
@SuppressLint("InflateParams")
fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) {
@ -45,7 +48,7 @@ class PasswordCheck @Inject constructor(val sp: SP) {
.setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key))
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
val enteredPassword = userInput.text.toString()
if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword)
if (cryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword)
else {
ToastUtils.errorToast(context, context.getString(R.string.wrongpassword))
fail?.invoke()
@ -80,7 +83,7 @@ class PasswordCheck @Inject constructor(val sp: SP) {
.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
val enteredPassword = userInput.text.toString()
if (enteredPassword.isNotEmpty()) {
sp.putString(preference, CryptoUtil.hashPassword(enteredPassword))
sp.putString(preference, cryptoUtil.hashPassword(enteredPassword))
ToastUtils.okToast(context, context.getString(R.string.password_set))
ok?.invoke(enteredPassword)
} else {

View file

@ -5,10 +5,11 @@ import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.plugins.general.maintenance.formats.*
import info.nightscout.androidaps.testing.mockers.AAPSMocker
import info.nightscout.androidaps.testing.utils.SingleStringStorage
import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.assumeAES256isSupported
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import org.hamcrest.CoreMatchers
import org.json.JSONException
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@ -30,6 +31,8 @@ class EncryptedPrefsFormatTest : TestBase() {
@Mock lateinit var resourceHelper: ResourceHelper
@Mock lateinit var sp: SP
var cryptoUtil: CryptoUtil = CryptoUtil(aapsLogger)
@Before
fun mock() {
AAPSMocker.prepareMock()
@ -52,9 +55,11 @@ class EncryptedPrefsFormatTest : TestBase() {
"}"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
assumeAES256isSupported(cryptoUtil)
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(2))
Assert.assertThat(prefs.values["key1"], CoreMatchers.`is`("A"))
Assert.assertThat(prefs.values["keyB"], CoreMatchers.`is`("2"))
@ -67,7 +72,7 @@ class EncryptedPrefsFormatTest : TestBase() {
@Test
fun preferenceSavingTest() {
val storage = SingleStringStorage("")
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefs = Prefs(
mapOf(
"key1" to "A",
@ -84,7 +89,7 @@ class EncryptedPrefsFormatTest : TestBase() {
@Test
fun importExportStabilityTest() {
val storage = SingleStringStorage("")
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefsIn = Prefs(
mapOf(
"testpref1" to "--1--",
@ -97,6 +102,8 @@ class EncryptedPrefsFormatTest : TestBase() {
encryptedFormat.savePreferences(AAPSMocker.getMockedFile(), prefsIn, "tajemnica")
val prefsOut = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "tajemnica")
assumeAES256isSupported(cryptoUtil)
Assert.assertThat(prefsOut.values.size, CoreMatchers.`is`(2))
Assert.assertThat(prefsOut.values["testpref1"], CoreMatchers.`is`("--1--"))
Assert.assertThat(prefsOut.values["testpref2"], CoreMatchers.`is`("another"))
@ -121,7 +128,7 @@ class EncryptedPrefsFormatTest : TestBase() {
"}"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "it-is-NOT-right-secret")
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(0))
@ -148,9 +155,11 @@ class EncryptedPrefsFormatTest : TestBase() {
"}"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
assumeAES256isSupported(cryptoUtil)
// contents were not tampered and we can decrypt them
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(2))
@ -173,7 +182,7 @@ class EncryptedPrefsFormatTest : TestBase() {
"}"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(0))
@ -188,7 +197,7 @@ class EncryptedPrefsFormatTest : TestBase() {
"}"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
val prefs = encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
Assert.assertThat(prefs.values.size, CoreMatchers.`is`(0))
@ -200,7 +209,7 @@ class EncryptedPrefsFormatTest : TestBase() {
val frozenPrefs = "whatever man, i duno care"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
}
@ -219,7 +228,7 @@ class EncryptedPrefsFormatTest : TestBase() {
"}"
val storage = SingleStringStorage(frozenPrefs)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, storage)
val encryptedFormat = EncryptedPrefsFormat(resourceHelper, cryptoUtil, storage)
encryptedFormat.loadPreferences(AAPSMocker.getMockedFile(), "sikret")
}

View file

@ -13,6 +13,7 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.PumpDescription
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction
@ -156,6 +157,7 @@ class SmsCommunicatorPluginTest : TestBaseWithProfile() {
`when`(virtualPumpPlugin.shortStatus(ArgumentMatchers.anyBoolean())).thenReturn("Virtual Pump")
`when`(virtualPumpPlugin.isSuspended).thenReturn(false)
`when`(virtualPumpPlugin.pumpDescription).thenReturn(PumpDescription())
`when`(treatmentsPlugin.lastCalculationTreatments).thenReturn(IobTotal(0))
`when`(treatmentsPlugin.lastCalculationTempBasals).thenReturn(IobTotal(0))

View file

@ -1,16 +1,31 @@
package info.nightscout.androidaps.utils
import info.nightscout.androidaps.TestBase
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.not
import org.junit.Assert
import org.junit.Assume.assumeThat
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.modules.junit4.PowerMockRunner
// https://stackoverflow.com/questions/52344522/joseexception-couldnt-create-aes-gcm-nopadding-cipher-illegal-key-size
// https://stackoverflow.com/questions/47708951/can-aes-256-work-on-android-devices-with-api-level-26
// Java prior to Oracle Java 8u161 does not have policy for 256 bit AES - but Android support it
// when test is run in Vanilla JVM without policy - Invalid key size exception is thrown
fun assumeAES256isSupported(cryptoUtil: CryptoUtil) {
cryptoUtil.lastException?.message?.let { exceptionMessage ->
assumeThat("Upgrade your testing environment Java (OpenJDK or Java 8u161) and JAVA_HOME - AES 256 is supported by Android so this exception should not happen!", exceptionMessage, not(containsString("key size")))
}
}
@PowerMockIgnore("javax.crypto.*")
@RunWith(PowerMockRunner::class)
class CryptoUtilTest: TestBase() {
var cryptoUtil: CryptoUtil = CryptoUtil(aapsLogger)
@Test
fun testFixedSaltCrypto() {
val salt = byteArrayOf(
@ -19,30 +34,36 @@ class CryptoUtilTest: TestBase() {
val password = "thisIsFixedPassword"
val payload = "FIXED-PAYLOAD"
val encrypted = CryptoUtil.encrypt(password, salt, payload)
val encrypted = cryptoUtil.encrypt(password, salt, payload)
assumeAES256isSupported(cryptoUtil)
Assert.assertNotNull(encrypted)
val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!)
val decrypted = cryptoUtil.decrypt(password, salt, encrypted!!)
assumeAES256isSupported(cryptoUtil)
Assert.assertEquals(decrypted, payload)
}
@Test
fun testStandardCrypto() {
val salt = CryptoUtil.mineSalt()
val salt = cryptoUtil.mineSalt()
val password = "topSikret"
val payload = "{what:payloadYouWantToProtect}"
val encrypted = CryptoUtil.encrypt(password, salt, payload)
val encrypted = cryptoUtil.encrypt(password, salt, payload)
assumeAES256isSupported(cryptoUtil)
Assert.assertNotNull(encrypted)
val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!)
val decrypted = cryptoUtil.decrypt(password, salt, encrypted!!)
assumeAES256isSupported(cryptoUtil)
Assert.assertEquals(decrypted, payload)
}
@Test
fun testHashVector() {
val payload = "{what:payloadYouWantToProtect}"
val hash = CryptoUtil.sha256(payload)
val hash = cryptoUtil.sha256(payload)
Assert.assertEquals(hash, "a1aafe3ed6cc127e6d102ddbc40a205147230e9cfd178daf108c83543bbdcd13")
}
@ -51,25 +72,25 @@ class CryptoUtilTest: TestBase() {
val payload = "{what:payloadYouWantToProtect}"
val password = "topSikret"
val expectedHmac = "ea2213953d0f2e55047cae2d23fb4f0de1b805d55e6271efa70d6b85fb692bea" // generated using other HMAC tool
val hash = CryptoUtil.hmac256(payload, password)
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"))
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("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"))
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"))
}
}