-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is very much a first draft as we need to workout how this and the core module work together better.
- Loading branch information
Showing
12 changed files
with
369 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
TODO Add some docs for | ||
|
||
|
||
## Protobuf | ||
### Kotlinx TODO | ||
|
||
### [Wire](https://github.com/square/wire) TODO | ||
|
||
```kotlin | ||
plugins { | ||
// TODO add https://github.com/square/wire stuff | ||
} | ||
``` | ||
|
||
### Google protobuf | ||
|
||
If you are new to Google Protobuf library it can be hard to setup. If | ||
you want a quickstart config this should get everything you need going. | ||
|
||
```kotlin | ||
import com.google.protobuf.gradle.builtins | ||
import com.google.protobuf.gradle.generateProtoTasks | ||
import com.google.protobuf.gradle.protoc | ||
|
||
plugins { | ||
id("com.android.library") | ||
// everything else… | ||
id("com.google.protobuf") version "0.8.13" | ||
} | ||
|
||
|
||
|
||
protobuf { | ||
protobuf.protoc { | ||
artifact = "com.google.protobuf:protoc:3.13.0" | ||
} | ||
protobuf.generateProtoTasks { | ||
all().forEach { task -> | ||
task.builtins { | ||
create("java").option("lite") | ||
} | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation("com.google.protobuf:protobuf-javalite:3.13.0") | ||
} | ||
``` | ||
|
||
Sample proto class. | ||
```proto | ||
syntax = "proto3"; | ||
option java_package = "at.favre.lib.armadillo.datastore"; | ||
option java_outer_classname = "EncryptedPreferencesProto"; | ||
message User { | ||
string name = 1; | ||
string email = 2; | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
plugins { | ||
id("com.android.library") | ||
id("kotlin-android") | ||
id ("org.jetbrains.kotlin.plugin.serialization") version "1.4.10" | ||
} | ||
|
||
android { | ||
val compileSdkVersion: Int by rootProject.extra | ||
val buildToolsVersion: String by rootProject.extra | ||
|
||
compileSdkVersion(compileSdkVersion) | ||
buildToolsVersion(buildToolsVersion) | ||
|
||
defaultConfig { | ||
val minSdkVersion: Int by rootProject.extra | ||
val targetSdkVersion: Int by rootProject.extra | ||
|
||
minSdkVersion(minSdkVersion) | ||
targetSdkVersion(targetSdkVersion) | ||
versionCode = 1 | ||
versionName = "0.1.0" | ||
|
||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||
consumerProguardFiles("consumer-rules.pro") | ||
} | ||
|
||
buildTypes { | ||
getByName("release") { | ||
isMinifyEnabled = false | ||
proguardFiles( | ||
getDefaultProguardFile("proguard-android-optimize.txt"), | ||
"proguard-rules.pro" | ||
) | ||
} | ||
} | ||
|
||
compileOptions { | ||
sourceCompatibility = JavaVersion.VERSION_1_8 | ||
targetCompatibility = JavaVersion.VERSION_1_8 | ||
} | ||
|
||
kotlinOptions { | ||
jvmTarget = JavaVersion.VERSION_1_8.toString() | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation(project(":armadillo")) | ||
|
||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.4.10") | ||
|
||
implementation("androidx.core:core-ktx:1.3.2") | ||
implementation("androidx.appcompat:appcompat:1.2.0") | ||
implementation("androidx.datastore:datastore-core:1.0.0-alpha01") | ||
|
||
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.0") | ||
|
||
testImplementation("junit:junit:4.13") | ||
androidTestImplementation("androidx.test.ext:junit:1.1.2") | ||
androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0") | ||
androidTestImplementation("org.bouncycastle:bcprov-jdk15on:1.60") | ||
androidTestImplementation("org.mindrot:jbcrypt:0.4") | ||
androidTestImplementation("androidx.test.ext:junit:1.1.2") | ||
androidTestImplementation("androidx.test:rules:1.3.0") | ||
} |
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions
9
armadillo-datastore/src/androidTest/java/at/favre/lib/armadillo/datastore/User.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package at.favre.lib.armadillo.datastore | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
data class User( | ||
val name: String, | ||
val email: String, | ||
) |
45 changes: 45 additions & 0 deletions
45
armadillo-datastore/src/androidTest/java/at/favre/lib/armadillo/datastore/UserStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package at.favre.lib.armadillo.datastore | ||
|
||
import android.content.Context | ||
import androidx.datastore.DataStore | ||
import androidx.datastore.createDataStore | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import kotlinx.serialization.protobuf.ProtoBuf | ||
|
||
@ExperimentalSerializationApi | ||
class UserStore(context: Context) { | ||
private val serializer = User.serializer() | ||
private val protobuf = ProtoBuf {} | ||
|
||
private val protocol = object : ProtobufProtocol<User> { | ||
|
||
override fun fromBytes(bytes: ByteArray): User = | ||
protobuf.decodeFromByteArray(serializer, bytes) | ||
|
||
override fun fromNothing(): User = | ||
User(name = "", email = "") | ||
|
||
override fun toBytes(data: User): ByteArray = | ||
protobuf.encodeToByteArray(serializer, data) | ||
} | ||
|
||
private val userSerializer: ArmadilloSerializer<User> = | ||
ArmadilloSerializer( | ||
context = context, | ||
protocol = protocol | ||
) | ||
|
||
private val store: DataStore<User> = context.createDataStore( | ||
fileName = "user.pb", | ||
serializer = userSerializer | ||
) | ||
|
||
suspend fun update(reduce: (User) -> User) { | ||
store.updateData { user -> reduce(user) } | ||
} | ||
|
||
val user: Flow<User> | ||
get() = store.data | ||
|
||
} |
41 changes: 41 additions & 0 deletions
41
armadillo-datastore/src/androidTest/java/at/favre/lib/armadillo/datastore/UserStoreTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package at.favre.lib.armadillo.datastore | ||
|
||
import androidx.test.core.app.ApplicationProvider | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import kotlinx.coroutines.flow.first | ||
import kotlinx.coroutines.runBlocking | ||
import kotlinx.serialization.ExperimentalSerializationApi | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
|
||
@ExperimentalSerializationApi | ||
@RunWith(AndroidJUnit4::class) | ||
class UserStoreTest { | ||
|
||
@Test | ||
fun canReadEmptyUserFromStore() { | ||
val store = UserStore(ApplicationProvider.getApplicationContext()) | ||
|
||
val user = runBlocking { store.user.first() } | ||
|
||
assert(user.name == "") | ||
assert(user.email == "") | ||
} | ||
|
||
|
||
@Test | ||
fun canUserStore_andReadFromStore() { | ||
val store = UserStore(ApplicationProvider.getApplicationContext()) | ||
val newName = "new name" | ||
|
||
runBlocking { | ||
store.update { | ||
it.copy(name = newName) | ||
} | ||
} | ||
val user = runBlocking { store.user.first() } | ||
|
||
assert(user.name == newName) | ||
assert(user.email == "") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest package="at.favre.lib.armadillo.datastore" /> |
142 changes: 142 additions & 0 deletions
142
armadillo-datastore/src/main/java/at/favre/lib/armadillo/datastore/ArmadilloSerializer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package at.favre.lib.armadillo.datastore | ||
|
||
import android.content.Context | ||
import android.os.Build | ||
import android.util.Log | ||
import androidx.datastore.Serializer | ||
import at.favre.lib.armadillo.* | ||
import at.favre.lib.armadillo.Armadillo.CONTENT_KEY_OUT_BYTE_LENGTH | ||
import at.favre.lib.armadillo.BuildConfig | ||
import java.io.InputStream | ||
import java.io.OutputStream | ||
import java.security.Provider | ||
import java.security.SecureRandom | ||
|
||
interface ProtobufProtocol<T> { | ||
fun toBytes(data: T): ByteArray | ||
fun fromBytes(bytes: ByteArray): T | ||
fun fromNothing(): T | ||
} | ||
|
||
class ArmadilloSerializer<T>( | ||
context: Context, | ||
private val protocol: ProtobufProtocol<T>, | ||
fingerprintData: List<String> = emptyList(), | ||
secureRandom: SecureRandom = SecureRandom(), | ||
additionalDecryptionConfigs: List<EncryptionProtocolConfig> = listOf(), | ||
enabledKitkatSupport: Boolean = false, | ||
provider: Provider? = null, | ||
preferencesSalt: ByteArray = BuildConfig.PREF_SALT | ||
) : Serializer<T> { | ||
|
||
private val password: ByteArrayRuntimeObfuscator? | ||
private val encryptionProtocol: EncryptionProtocol | ||
private val fingerprint: EncryptionFingerprint = EncryptionFingerprintFactory.create( | ||
context, | ||
buildString { fingerprintData.forEach(::append) } | ||
) | ||
|
||
init { | ||
val defaultConfig = EncryptionProtocolConfig.newDefaultConfig() | ||
|
||
val stringMessageDigest = HkdfMessageDigest( | ||
BuildConfig.PREF_SALT, | ||
CONTENT_KEY_OUT_BYTE_LENGTH | ||
) | ||
val kitKatConfig = takeIf { enabledKitkatSupport }?.run { | ||
@Suppress("DEPRECATION") | ||
EncryptionProtocolConfig.newBuilder(defaultConfig.build()) | ||
.authenticatedEncryption(AesCbcEncryption(secureRandom, provider)) | ||
.protocolVersion(Armadillo.KITKAT_PROTOCOL_VERSION) | ||
.build() | ||
} | ||
val config = | ||
if (kitKatConfig != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | ||
kitKatConfig | ||
} else { | ||
EncryptionProtocolConfig | ||
.newBuilder(defaultConfig.build()) | ||
.authenticatedEncryption(AesGcmEncryption(secureRandom, provider)) | ||
.build() | ||
}.also { checkKitKatSupport(it.authenticatedEncryption) } | ||
|
||
val factory = DefaultEncryptionProtocol.Factory( | ||
config, | ||
fingerprint, | ||
stringMessageDigest, | ||
secureRandom, | ||
false, // enableDerivedPasswordCache, | ||
if (enabledKitkatSupport) { | ||
additionalDecryptionConfigs + kitKatConfig | ||
} else { | ||
additionalDecryptionConfigs | ||
}, | ||
) | ||
|
||
encryptionProtocol = factory.create(preferencesSalt) | ||
password = null // TODO Add password config factory.obfuscatePassword() | ||
} | ||
|
||
|
||
private fun checkKitKatSupport(authenticatedEncryption: AuthenticatedEncryption) { | ||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && authenticatedEncryption.javaClass == AesGcmEncryption::class.java) { | ||
throw UnsupportedOperationException("aes gcm is not supported with KitKat, add support " + | ||
"manually with Armadillo.Builder.enableKitKatSupport()") | ||
} | ||
} | ||
|
||
companion object { | ||
private const val CRYPTO_KEY = "ArmadilloStore" | ||
} | ||
|
||
|
||
private fun encrypt(content: ByteArray): ByteArray = | ||
try { | ||
encryptionProtocol | ||
.encrypt( | ||
encryptionProtocol.deriveContentKey(CRYPTO_KEY), | ||
encryptionProtocol.deobfuscatePassword(password), | ||
content | ||
) | ||
} catch (e: Throwable) { | ||
throw IllegalStateException(e) | ||
} | ||
|
||
|
||
private fun decrypt(encrypted: ByteArray): ByteArray? { | ||
if (encrypted.isEmpty()) { | ||
return null | ||
} | ||
try { | ||
return encryptionProtocol | ||
.decrypt( | ||
encryptionProtocol.deriveContentKey(CRYPTO_KEY), | ||
encryptionProtocol.deobfuscatePassword(password), | ||
encrypted | ||
) | ||
} catch (e: Throwable) { | ||
Log.e("DataStrore", "decetyp", e) | ||
// recoveryPolicy.handleBrokenConte(e, keyHash, base64Encrypted, password != null, this) | ||
// TODO handle this | ||
} | ||
return null | ||
} | ||
|
||
override fun readFrom(input: InputStream): T = | ||
input | ||
.readBytes() | ||
.let(::decrypt) | ||
.let { | ||
val bytes = it ?: byteArrayOf() | ||
if (bytes.isEmpty()) protocol.fromNothing() | ||
else protocol.fromBytes(bytes) | ||
} | ||
|
||
|
||
override fun writeTo(t: T, output: OutputStream) { | ||
protocol | ||
.toBytes(t) | ||
.let(::encrypt) | ||
.also(output::write) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
include ':app', ':armadillo' | ||
include ':app', ':armadillo', ':armadillo-datastore' |