Initial commit
This initial commit includes HUSH specific changes starting at this commit:
d14637012c
This commit is contained in:
1
lockbox/.gitignore
vendored
Normal file
1
lockbox/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
49
lockbox/build.gradle
Normal file
49
lockbox/build.gradle
Normal file
@@ -0,0 +1,49 @@
|
||||
import cash.z.ecc.android.Deps
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion Deps.compileSdkVersion
|
||||
|
||||
useLibrary 'android.test.runner'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion Deps.minSdkVersion
|
||||
targetSdkVersion Deps.targetSdkVersion
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'cash.z.ecc.android.lockbox'
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation Deps.JavaX.INJECT
|
||||
implementation Deps.Kotlin.STDLIB
|
||||
implementation Deps.AndroidX.APPCOMPAT
|
||||
implementation Deps.AndroidX.CORE_KTX
|
||||
|
||||
// Zcash
|
||||
implementation Deps.Zcash.ANDROID_WALLET_PLUGINS
|
||||
|
||||
implementation Deps.Misc.Plugins.SECURE_STORAGE
|
||||
|
||||
androidTestImplementation Deps.Test.Android.JUNIT
|
||||
}
|
||||
0
lockbox/consumer-rules.pro
Normal file
0
lockbox/consumer-rules.pro
Normal file
21
lockbox/proguard-rules.pro
vendored
Normal file
21
lockbox/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,52 @@
|
||||
package cash.z.ecc.android.lockbox
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LockBoxText {
|
||||
|
||||
private lateinit var appContext: Context
|
||||
private lateinit var lockBox: LockBoxProvider
|
||||
|
||||
@Before
|
||||
fun start() {
|
||||
appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
lockBox = LockBox(appContext)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSeed_store() {
|
||||
val testMessage = "Some Bytes To Test"
|
||||
val testBytes = testMessage.toByteArray()
|
||||
lockBox.setBytes("seed", testBytes)
|
||||
assertEquals(testMessage, String(lockBox.getBytes("seed")!!))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSeed_storeNegatives() {
|
||||
val testBytes = byteArrayOf(0x00, 0x00, -0x0F, -0x0B)
|
||||
lockBox.setBytes("seed", testBytes)
|
||||
assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")!!))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSeed_storeLeadingZeros() {
|
||||
val testBytes = byteArrayOf(0x00, 0x00, 0x0F, 0x0B)
|
||||
lockBox.setBytes("seed", testBytes)
|
||||
assertTrue(testBytes.contentEquals(lockBox.getBytes("seed")!!))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPrivateKey_retrieve() {
|
||||
val testMessage = "Some Bytes To Test"
|
||||
lockBox.setCharsUtf8("spendingKey", testMessage.toCharArray())
|
||||
assertEquals(testMessage, String(lockBox.getCharsUtf8("spendingKey")!!))
|
||||
}
|
||||
}
|
||||
2
lockbox/src/main/AndroidManifest.xml
Normal file
2
lockbox/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
133
lockbox/src/main/java/cash/z/ecc/android/lockbox/LockBox.kt
Normal file
133
lockbox/src/main/java/cash/z/ecc/android/lockbox/LockBox.kt
Normal file
@@ -0,0 +1,133 @@
|
||||
package cash.z.ecc.android.lockbox
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.android.plugin.LockBoxPlugin
|
||||
import de.adorsys.android.securestoragelibrary.SecurePreferences
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.CharBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
|
||||
class LockBox(private val appContext: Context) : LockBoxPlugin {
|
||||
|
||||
private val maxLength: Int = 50
|
||||
|
||||
override fun setBoolean(key: String, value: Boolean) {
|
||||
setChunkedString(key, value.toString())
|
||||
}
|
||||
|
||||
override fun getBoolean(key: String): Boolean {
|
||||
return getChunkedString(key)?.toBoolean() ?: false
|
||||
}
|
||||
|
||||
override fun setBytes(key: String, value: ByteArray) {
|
||||
// using hex here because this library doesn't really work well for byte arrays
|
||||
// but hopefully we can code to arrays and then change the underlying library, later
|
||||
setChunkedString(key, value.toHex())
|
||||
}
|
||||
|
||||
override fun getBytes(key: String): ByteArray? {
|
||||
return getChunkedString(key)?.fromHex()
|
||||
}
|
||||
|
||||
override fun setCharsUtf8(key: String, value: CharArray) {
|
||||
// Using string here because this library doesn't work well for char arrays
|
||||
// but hopefully we can code to arrays and then change the underlying library, later
|
||||
setChunkedString(key, String(value))
|
||||
}
|
||||
|
||||
override fun getCharsUtf8(key: String): CharArray? {
|
||||
return getChunkedString(key)?.toCharArray()
|
||||
}
|
||||
|
||||
fun delete(key: String) {
|
||||
return SecurePreferences.removeValue(appContext, key)
|
||||
}
|
||||
fun clear() {
|
||||
SecurePreferences.clearAllValues(appContext)
|
||||
}
|
||||
|
||||
inline operator fun <reified T> set(key: String, value: T) {
|
||||
when (T::class) {
|
||||
Boolean::class -> setBoolean(key, value as Boolean)
|
||||
ByteArray::class -> setBytes(key, value as ByteArray)
|
||||
CharArray::class -> setCharsUtf8(key, value as CharArray)
|
||||
Double::class, Float::class, Integer::class, Long::class, String::class -> setChunkedString(key, value.toString())
|
||||
else -> throw UnsupportedOperationException("Lockbox does not yet support setting ${T::class.java.simpleName} objects but it can easily be added.")
|
||||
}
|
||||
}
|
||||
|
||||
inline operator fun <reified T> get(key: String): T? = when (T::class) {
|
||||
Boolean::class -> getBoolean(key)
|
||||
ByteArray::class -> getBytes(key)
|
||||
CharArray::class -> getCharsUtf8(key)
|
||||
Double::class -> getChunkedString(key)?.let { it.toDoubleOrNull() }
|
||||
Float::class -> getChunkedString(key)?.let { it.toFloatOrNull() }
|
||||
Integer::class -> getChunkedString(key)?.let { it.toIntOrNull() }
|
||||
Long::class -> getChunkedString(key)?.let { it.toLongOrNull() }
|
||||
String::class -> getChunkedString(key)
|
||||
else -> throw UnsupportedOperationException("Lockbox does not yet support getting ${T::class.simpleName} objects but it can easily be added")
|
||||
} as T
|
||||
|
||||
/**
|
||||
* Splits a string value into smaller pieces so as not to exceed the limit on the length of
|
||||
* String that can be stored.
|
||||
*/
|
||||
fun setChunkedString(key: String, value: String) {
|
||||
if (value.length > maxLength) {
|
||||
SecurePreferences.setValue(appContext, key, value.chunked(maxLength))
|
||||
} else {
|
||||
SecurePreferences.setValue(appContext, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string value from storage by first fetching the key, directly. If that is missing,
|
||||
* it checks for a chunked version of the key. If that exists, it will be merged and returned.
|
||||
* If not, then null will be returned.
|
||||
*
|
||||
* @return the key if found and null otherwise.
|
||||
*/
|
||||
fun getChunkedString(key: String): String? {
|
||||
return SecurePreferences.getStringValue(appContext, key, null)
|
||||
?: SecurePreferences.getStringListValue(appContext, key, listOf()).let { result ->
|
||||
if (result.size == 0) null else result.joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Extensions (TODO: find library that works better with arrays of bytes and chars)
|
||||
//
|
||||
|
||||
private fun ByteArray.toHex(): String {
|
||||
val sb = StringBuilder(size * 2)
|
||||
for (b in this)
|
||||
sb.append(String.format("%02x", b))
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun String.fromHex(): ByteArray {
|
||||
val len = length
|
||||
val data = ByteArray(len / 2)
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
data[i / 2] =
|
||||
((Character.digit(this[i], 16) shl 4) + Character.digit(this[i + 1], 16)).toByte()
|
||||
i += 2
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
private fun CharArray.toBytes(): ByteArray {
|
||||
val byteBuffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(this))
|
||||
return Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
|
||||
}
|
||||
|
||||
private fun ByteArray.fromBytes(): CharArray {
|
||||
val charBuffer = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(this))
|
||||
return Arrays.copyOf(charBuffer.array(), charBuffer.limit())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user