Implement Rust-owned Android Keystore secure storage (#93)
Dependency security audit / rustsec (push) Failing after 23s
Dependency security audit / rustsec (push) Failing after 23s
This commit is contained in:
1 parent
874c8139a4
commit
1abd75282e
36 files changed
+4380
-37
No files matched your search
+119
@@ -0,0 +1,119 @@
|
||||
package de.rfc1437.ironstorage.bridge
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidAuthorization
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidCiphertext
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidDeviceState
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeySpecification
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeystore
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeystoreException
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateDirectories
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateStorageErrorKind
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateStorageFfiException
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileSecretException
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileSecretLocator
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileSecretProtection
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class KeystoreContractTest {
|
||||
@Test fun rustOwnsRecordsPoliciesAndForeignSecretLifetime() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
NativeRuntime(context).privateStorage(
|
||||
MobilePrivateDirectories(
|
||||
context.filesDir.canonicalPath,
|
||||
context.noBackupFilesDir.canonicalPath,
|
||||
context.cacheDir.canonicalPath
|
||||
)
|
||||
).use { runtime ->
|
||||
val os = FixtureKeystore()
|
||||
val backend = runtime.androidSecretBackend(os)
|
||||
val locator = MobileSecretLocator.HttpsGitCredential("issue93-server", "fixture")
|
||||
val protection = MobileSecretProtection.DEVICE_UNLOCKED
|
||||
backend.unlock()
|
||||
try {
|
||||
backend.create(locator, protection, "public-token".toByteArray())
|
||||
assertThrows(MobileSecretException.AlreadyExists::class.java) {
|
||||
backend.create(locator, protection, "duplicate".toByteArray())
|
||||
}
|
||||
backend.replace(locator, protection, "replacement".toByteArray())
|
||||
val clear = backend.retrieve(locator, protection)
|
||||
try {
|
||||
assertArrayEquals("replacement".toByteArray(), clear)
|
||||
assertTrue(os.returned.all { it == 0.toByte() })
|
||||
} finally {
|
||||
clear.fill(0)
|
||||
}
|
||||
os.lockout = true
|
||||
assertThrows(MobileSecretException.Lockout::class.java) {
|
||||
backend.retrieve(locator, protection)
|
||||
}
|
||||
os.lockout = false
|
||||
backend.lock()
|
||||
assertThrows(MobileSecretException.Locked::class.java) {
|
||||
backend.retrieve(locator, protection)
|
||||
}
|
||||
backend.unlock()
|
||||
assertThrows(MobileSecretException.UnsupportedProtection::class.java) {
|
||||
backend.create(
|
||||
MobileSecretLocator.OpenPgpPassphrase("A".repeat(40)),
|
||||
protection,
|
||||
"public-passphrase".toByteArray()
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
backend.delete(locator, protection)
|
||||
}
|
||||
assertTrue(os.keys.isEmpty())
|
||||
os.corruptAliases = true
|
||||
val error = assertThrows(MobilePrivateStorageFfiException.Failed::class.java) {
|
||||
runtime.androidSecretBackend(os)
|
||||
}
|
||||
assertEquals(MobilePrivateStorageErrorKind.INVALID_SECURE_BINDING, error.kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Public fixture cipher only: production encryption is tested in the platform test APK.
|
||||
private class FixtureKeystore : MobileAndroidKeystore {
|
||||
val keys = mutableSetOf<String>()
|
||||
var returned = byteArrayOf()
|
||||
var lockout = false
|
||||
var corruptAliases = false
|
||||
override fun deviceState() = MobileAndroidDeviceState(37u, false, true, true, false)
|
||||
override fun createKey(specification: MobileAndroidKeySpecification) {
|
||||
assertEquals(MobileAndroidAuthorization.NONE, specification.authorization)
|
||||
keys.add(specification.alias)
|
||||
}
|
||||
override fun encrypt(
|
||||
alias: String,
|
||||
authorization: MobileAndroidAuthorization,
|
||||
aad: ByteArray,
|
||||
value: ByteArray
|
||||
): MobileAndroidCiphertext = try {
|
||||
MobileAndroidCiphertext(ByteArray(12), value.copyOf(value.size + 16))
|
||||
} finally {
|
||||
value.fill(0)
|
||||
}
|
||||
override fun decrypt(
|
||||
alias: String,
|
||||
authorization: MobileAndroidAuthorization,
|
||||
aad: ByteArray,
|
||||
value: MobileAndroidCiphertext
|
||||
): ByteArray {
|
||||
if (lockout) throw MobileAndroidKeystoreException.Lockout()
|
||||
returned = value.bytes.copyOf(value.bytes.size - 16)
|
||||
return returned
|
||||
}
|
||||
override fun deleteKey(alias: String) {
|
||||
keys.remove(alias)
|
||||
}
|
||||
override fun aliases(): List<String> {
|
||||
if (corruptAliases) throw MobileAndroidKeystoreException.Corrupted()
|
||||
return keys.toList()
|
||||
}
|
||||
override fun cancelPending() = Unit
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user