From 8def0f75e53a0b8dec344705ebda8a852d89bac9 Mon Sep 17 00:00:00 2001 From: Liliana Faustino <101108623+LilianaFaustinoBloco@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:16:01 +0100 Subject: [PATCH] fix: Require Android 6 or newer (#312) Related to https://github.com/relaycorp/relayverse/issues/45 --- README.md | 4 - lib/build.gradle | 2 +- .../awaladroid/AndroidPrivateKeyStoreTest.kt | 8 ++ .../awaladroid/test/FakeAndroidKeyStore.kt | 106 ++++++++++++++++++ lib/src/test/resources/robolectric.properties | 2 +- 5 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 lib/src/test/java/tech/relaycorp/awaladroid/test/FakeAndroidKeyStore.kt diff --git a/README.md b/README.md index 273f7ce1..1170f8ff 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,6 @@ android.jetifier.blacklist = bcprov-jdk15on-1.*.jar The items below summarize the security and privacy considerations specific to this app. For a more general overview of the security considerations in Awala, please refer to [RS-019](https://specs.awala.network/RS-019). -### No encryption at rest on Android 5 - -We use the [Android Keystore system](https://developer.android.com/training/articles/keystore) to protect sensitive cryptographic material, such as long-term and ephemeral keys. Unfortunately, [Android 5 doesn't actually encrypt anything at rest](https://github.com/relaycorp/relaynet-gateway-android/issues/247). - ### External communication This library exclusively communicates with the private gateway installed on the device. It does not communicate with other apps or any Internet host. diff --git a/lib/build.gradle b/lib/build.gradle index 67c56b29..b17252ab 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -14,7 +14,7 @@ android { buildToolsVersion "30.0.3" defaultConfig { - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 33 versionCode 1 versionName "1.0.0" diff --git a/lib/src/test/java/tech/relaycorp/awaladroid/AndroidPrivateKeyStoreTest.kt b/lib/src/test/java/tech/relaycorp/awaladroid/AndroidPrivateKeyStoreTest.kt index f1fee8e8..4d5552d3 100644 --- a/lib/src/test/java/tech/relaycorp/awaladroid/AndroidPrivateKeyStoreTest.kt +++ b/lib/src/test/java/tech/relaycorp/awaladroid/AndroidPrivateKeyStoreTest.kt @@ -3,16 +3,24 @@ package tech.relaycorp.awaladroid import java.io.File import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import tech.relaycorp.awala.keystores.file.FileKeystoreRoot +import tech.relaycorp.awaladroid.test.FakeAndroidKeyStore import tech.relaycorp.relaynet.testing.pki.KeyPairSet import tech.relaycorp.relaynet.testing.pki.PDACertPath @RunWith(RobolectricTestRunner::class) public class AndroidPrivateKeyStoreTest { + + @Before + public fun setUp() { + FakeAndroidKeyStore.setup + } + @Test public fun saveAndRetrieve(): Unit = runTest { val androidContext = RuntimeEnvironment.getApplication() diff --git a/lib/src/test/java/tech/relaycorp/awaladroid/test/FakeAndroidKeyStore.kt b/lib/src/test/java/tech/relaycorp/awaladroid/test/FakeAndroidKeyStore.kt new file mode 100644 index 00000000..0c1ed70d --- /dev/null +++ b/lib/src/test/java/tech/relaycorp/awaladroid/test/FakeAndroidKeyStore.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2023 Relaycorp, Inc. + * Copyright 2020 Appmattus Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tech.relaycorp.awaladroid.test + +import java.io.InputStream +import java.io.OutputStream +import java.security.Key +import java.security.KeyStore +import java.security.KeyStoreSpi +import java.security.Provider +import java.security.SecureRandom +import java.security.Security +import java.security.cert.Certificate +import java.security.spec.AlgorithmParameterSpec +import java.util.Date +import java.util.Enumeration +import javax.crypto.KeyGenerator +import javax.crypto.KeyGeneratorSpi +import javax.crypto.SecretKey + +// Source: https://proandroiddev.com/testing-jetpack-security-with-robolectric-9f9cf2aa4f61 +public object FakeAndroidKeyStore { + + public val setup: Int by lazy { + Security.addProvider(object : Provider("AndroidKeyStore", 1.0, "") { + init { + put("KeyStore.AndroidKeyStore", FakeKeyStore::class.java.name) + put("KeyGenerator.AES", FakeAesKeyGenerator::class.java.name) + } + }) + } + + @Suppress("unused") + public class FakeKeyStore : KeyStoreSpi() { + private val wrapped = KeyStore.getInstance(KeyStore.getDefaultType()) + + override fun engineIsKeyEntry(alias: String?): Boolean = wrapped.isKeyEntry(alias) + override fun engineIsCertificateEntry(alias: String?): Boolean = + wrapped.isCertificateEntry(alias) + + override fun engineGetCertificate(alias: String?): Certificate = + wrapped.getCertificate(alias) + + override fun engineGetCreationDate(alias: String?): Date = wrapped.getCreationDate(alias) + override fun engineDeleteEntry(alias: String?): Unit = wrapped.deleteEntry(alias) + override fun engineSetKeyEntry( + alias: String?, + key: Key?, + password: CharArray?, + chain: Array? + ): Unit = + wrapped.setKeyEntry(alias, key, password, chain) + + override fun engineSetKeyEntry( + alias: String?, + key: ByteArray?, + chain: Array? + ): Unit = wrapped.setKeyEntry(alias, key, chain) + + override fun engineStore(stream: OutputStream?, password: CharArray?): Unit = + wrapped.store(stream, password) + + override fun engineSize(): Int = wrapped.size() + override fun engineAliases(): Enumeration = wrapped.aliases() + override fun engineContainsAlias(alias: String?): Boolean = wrapped.containsAlias(alias) + override fun engineLoad(stream: InputStream?, password: CharArray?): Unit = + wrapped.load(stream, password) + + override fun engineGetCertificateChain(alias: String?): Array = + wrapped.getCertificateChain(alias) + + override fun engineSetCertificateEntry(alias: String?, cert: Certificate?): Unit = + wrapped.setCertificateEntry(alias, cert) + + override fun engineGetCertificateAlias(cert: Certificate?): String = + wrapped.getCertificateAlias(cert) + + override fun engineGetKey(alias: String?, password: CharArray?): Key? = + wrapped.getKey(alias, password) + } + + @Suppress("unused") + public class FakeAesKeyGenerator : KeyGeneratorSpi() { + private val wrapped = KeyGenerator.getInstance("AES") + + override fun engineInit(random: SecureRandom?): Unit = Unit + override fun engineInit(params: AlgorithmParameterSpec?, random: SecureRandom?): Unit = Unit + override fun engineInit(keysize: Int, random: SecureRandom?): Unit = Unit + override fun engineGenerateKey(): SecretKey = wrapped.generateKey() + } +} diff --git a/lib/src/test/resources/robolectric.properties b/lib/src/test/resources/robolectric.properties index 8bfbb3c5..a44b8420 100644 --- a/lib/src/test/resources/robolectric.properties +++ b/lib/src/test/resources/robolectric.properties @@ -1 +1 @@ -sdk=21 +sdk=23