Initial commit

This initial commit includes HUSH specific changes starting at this commit:
d14637012c
This commit is contained in:
fekt
2022-11-29 20:49:44 -05:00
commit 4adbc901a0
355 changed files with 31799 additions and 0 deletions

1
lockbox/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

49
lockbox/build.gradle Normal file
View 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
}

View File

21
lockbox/proguard-rules.pro vendored Normal file
View 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

View File

@@ -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")!!))
}
}

View File

@@ -0,0 +1,2 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android" />

View 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())
}
}