Skip to content

Commit

Permalink
Merge pull request #17 from bitmark-inc/Sang/fix/sdk
Browse files Browse the repository at this point in the history
fix: revert to 0.6.3
  • Loading branch information
phuocbitmark authored Sep 27, 2024
2 parents 245f3b1 + 323a730 commit 44f4088
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 662 deletions.
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ dependencies {
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
api "androidx.biometric:biometric:1.1.0"

}
3 changes: 1 addition & 2 deletions libauk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ android {

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
api "androidx.biometric:biometric:1.1.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'org.web3j:core:4.8.7-android'
implementation 'org.web3j:core:5.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'io.camlcase:kotlintezos:2.0.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@ class SecureFileStorageTest {
secureFileStorage.rxCompletable { gw ->
gw.writeOnFilesDir(
d.key,
d.value.toByteArray(),
true
d.value.toByteArray()
)
}
.test()
.assertComplete()
.assertNoErrors()

secureFileStorage.readOnFilesDir(d.key).map { byteArray -> String(byteArray) }
secureFileStorage.rxSingle { gw -> String(gw.readOnFilesDir(d.key)) }
.test()
.assertComplete()
.assertNoErrors()
Expand All @@ -65,15 +64,14 @@ class SecureFileStorageTest {
secureFileStorage.rxCompletable { gw ->
gw.writeOnFilesDir(
d.key,
d.value.toByteArray(),
true
d.value.toByteArray()
)
}
.test()
.assertComplete()
.assertNoErrors()

secureFileStorage.readOnFilesDir(d.key).map { byteArray -> String(byteArray) }
secureFileStorage.rxSingle { gw -> String(gw.readOnFilesDir(d.key)) }
.test()
.assertComplete()
.assertNoErrors()
Expand Down
84 changes: 9 additions & 75 deletions libauk/src/main/java/com/bitmark/libauk/model/Keys.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,26 @@ package com.bitmark.libauk.model
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import org.web3j.crypto.Bip32ECKeyPair
import java.math.BigInteger
import java.util.*

@JsonSerialize
data class KeyInfo(
data class KeyIdentity(
@Expose
@SerializedName("ethAddress")
val ethAddress: String,
@SerializedName("words")
val words: String,

@Expose
@SerializedName("creationDate")
val creationDate: Date
@SerializedName("passphrase")
val passphrase: String
)

@JsonSerialize
data class SeedPublicData(
data class KeyInfo(
@Expose
@SerializedName("ethAddress")
val ethAddress: String,

@Expose
@SerializedName("creationDate")
val creationDate: Date,

@Expose
@SerializedName("name")
val name: String?,

@Expose
@SerializedName("did")
val did: String,

@Expose
@SerializedName("preGenerateEthAddresses")
val preGenerateEthAddresses: Map<Int, String>,

@Expose
@SerializedName("preGenerateTezosAddresses")
val preGenerateTezosAddresses: Map<Int, String>,

@Expose
@SerializedName("preGenerateTezosPublicKeys")
val preGenerateTezosPublicKeys: Map<Int, String>,

@Expose
@SerializedName("encryptionPrivateKey")
val encryptionPrivateKey: ByteArray,

@Expose
@SerializedName("dIDPrivateKey")
private var dIDPrivateKey: BigInteger,

@Expose
@SerializedName("chainCode")
private val chainCode: ByteArray

) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as SeedPublicData

if (ethAddress != other.ethAddress) return false
if (creationDate != other.creationDate) return false
if (name != other.name) return false
if (did != other.did) return false
if (preGenerateEthAddresses != other.preGenerateEthAddresses) return false
if (preGenerateTezosAddresses != other.preGenerateTezosAddresses) return false
if (!encryptionPrivateKey.contentEquals(other.encryptionPrivateKey)) return false

return true
}

override fun hashCode(): Int {
var result = ethAddress.hashCode()
result = 31 * result + creationDate.hashCode()
result = 31 * result + (name?.hashCode() ?: 0)
result = 31 * result + did.hashCode()
result = 31 * result + preGenerateEthAddresses.hashCode()
result = 31 * result + preGenerateTezosAddresses.hashCode()
result = 31 * result + encryptionPrivateKey.contentHashCode()
return result
}

fun getAccountDIDPrivateKey(): Bip32ECKeyPair {
return Bip32ECKeyPair.create(dIDPrivateKey, chainCode)
}
}
val creationDate: Date
)
111 changes: 28 additions & 83 deletions libauk/src/main/java/com/bitmark/libauk/storage/SecureFileStorage.kt
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
package com.bitmark.libauk.storage

import com.bitmark.libauk.util.BiometricUtil
import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.fragment.app.FragmentActivity
import androidx.security.crypto.EncryptedFile
import androidx.security.crypto.MasterKey
import io.reactivex.Completable
import io.reactivex.Single
import java.io.ByteArrayOutputStream
import java.io.File
import java.security.KeyStore
import java.util.UUID
import java.util.*
import android.os.Build

internal interface SecureFileStorage {

fun writeOnFilesDir(name: String, data: ByteArray, isPrivate: Boolean)
fun writeOnFilesDir(name: String, data: ByteArray)

fun readOnFilesDir(name: String): Single<ByteArray>

fun readOnFilesDirWithoutAuthentication(name: String): ByteArray
fun readOnFilesDir(name: String): ByteArray

fun isExistingOnFilesDir(name: String): Boolean

fun deleteOnFilesDir(name: String): Boolean

fun readFiles(names: List<String>): Single<Map<String, ByteArray>>
}

internal class SecureFileStorageImpl(
Expand All @@ -43,22 +38,21 @@ internal class SecureFileStorageImpl(
value?.let { sharedPreferences.edit().putString(KEY_MASTER_KEY_ALIAS, it).apply() }
}

private fun write(path: String, name: String, data: ByteArray, isPrivate: Boolean) {
val file = getEncryptedFile("$path/$name", false, isPrivate)
private fun write(path: String, name: String, data: ByteArray) {
val file = getEncryptedFile("$path/$name", false)
file.openFileOutput().apply {
write(data)
flush()
close()
}
}

override fun writeOnFilesDir(name: String, data: ByteArray, isPrivate: Boolean) {
write(context.filesDir.absolutePath, "$alias-$name", data, isPrivate)
override fun writeOnFilesDir(name: String, data: ByteArray) {
write(context.filesDir.absolutePath, "$alias-$name", data)
}

private fun read(path: String, isPrivate: Boolean): ByteArray {

val file = getEncryptedFile(path, true, isPrivate)
private fun read(path: String): ByteArray {
val file = getEncryptedFile(path, true)
if (File(path).length() == 0L) return byteArrayOf()
val inputStream = file.openFileInput()
val os = ByteArrayOutputStream()
Expand All @@ -70,54 +64,8 @@ internal class SecureFileStorageImpl(
return os.toByteArray()
}

override fun readOnFilesDir(name: String): Single<ByteArray> {
val isAuthenRequired = BiometricUtil.isAuthenReuired(listOf(name), context)
return if (isAuthenRequired) {
if (context is FragmentActivity) {
return BiometricUtil.withAuthenticate<ByteArray>(activity = context,
onAuthenticationSucceeded = { result ->
read(
File(context.filesDir, "$alias-$name").absolutePath,
isAuthenRequired
)
},
onAuthenticationError = { _, _ -> byteArrayOf() },
onAuthenticationFailed = { byteArrayOf() }
)
} else {
Single.error(IllegalStateException("Context is not an instance of FragmentActivity"))
}
} else {
return Single.fromCallable { read(File(context.filesDir, "$alias-$name").absolutePath, isAuthenRequired) }
}
}

override fun readOnFilesDirWithoutAuthentication(name: String): ByteArray {
return read(File(context.filesDir, "$alias-$name").absolutePath, false)
}

override fun readFiles(names: List<String>): Single<Map<String, ByteArray>> {
val isAuthenRequired = BiometricUtil.isAuthenReuired(names, context)

fun readFileContents(): Map<String, ByteArray> = names.associateWith { name ->
read(File(context.filesDir, "$alias-$name").absolutePath, isAuthenRequired)
}

return if (isAuthenRequired) {
if (context is FragmentActivity) {
BiometricUtil.withAuthenticate<Map<String, ByteArray>>(
activity = context,
onAuthenticationSucceeded = { readFileContents() },
onAuthenticationError = { _, _ -> emptyMap() },
onAuthenticationFailed = { emptyMap() }
)
} else {
Single.error(IllegalStateException("Context is not an instance of FragmentActivity"))
}
} else {
Single.fromCallable { readFileContents() }
}
}
override fun readOnFilesDir(name: String): ByteArray =
read(File(context.filesDir, "$alias-$name").absolutePath)

private fun isExisting(path: String): Boolean = File(path).exists()

Expand All @@ -136,48 +84,45 @@ internal class SecureFileStorageImpl(
override fun deleteOnFilesDir(name: String): Boolean =
delete(File(context.filesDir, "$alias-$name").absolutePath)

private fun getEncryptedFile(path: String, read: Boolean, isPrivate: Boolean) = File(path).let { f ->
private fun getEncryptedFile(path: String, read: Boolean) = File(path).let { f ->
if (f.isDirectory) throw IllegalArgumentException("do not support directory")
if (read && !f.exists() && !f.createNewFile()) {
throw IllegalStateException("cannot create new file for reading")
} else if (!read && f.exists() && !f.delete()) {
throw IllegalStateException("cannot delete file before writing")
}
getEncryptedFileBuilder(f, isPrivate).build()
getEncryptedFileBuilder(f).build()
}

private fun getEncryptedFileBuilder(f: File, isPrivate: Boolean) = EncryptedFile.Builder(
private fun getEncryptedFileBuilder(f: File) = EncryptedFile.Builder(
context,
f,
getMasterKey(isPrivate),
getMasterKey(),
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
)

private fun getMasterKey(isPrivate: Boolean): MasterKey {
private fun getMasterKey(): MasterKey {
keyStore.load(null)

val keyAlias = masterKeyAlias ?: UUID.randomUUID().toString().also { masterKeyAlias = it }
val authenticationTimeoutInSeconds = 5
val parameterSpecBuilder = KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT).apply {

val parameterSpec = KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setKeySize(256)
setDigests(KeyProperties.DIGEST_SHA512)
setUserAuthenticationRequired(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setUnlockedDeviceRequired(true)
}
setRandomizedEncryptionRequired(true)
setInvalidatedByBiometricEnrollment(true)
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
}
// if android version is higher than 28
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
parameterSpecBuilder.apply {
setUnlockedDeviceRequired(true)
setIsStrongBoxBacked(true)
}
}

val parameterSpec = parameterSpecBuilder.build()
}.build()

return MasterKey.Builder(context, keyAlias)
.setKeyGenParameterSpec(parameterSpec)
.setUserAuthenticationRequired(isPrivate, authenticationTimeoutInSeconds)
.build()
}

Expand Down
Loading

0 comments on commit 44f4088

Please sign in to comment.