From 23670ded3a9713d1254b3945b873f47b44210063 Mon Sep 17 00:00:00 2001 From: DatLag Date: Mon, 9 Dec 2024 17:16:34 +0100 Subject: [PATCH] added non-native decryption --- .../generator/native/NativeGenerator.kt | 23 ++++++- .../generator/native/NativeJNIGenerator.kt | 22 ++++++- .../generator/nonNative/CommonGenerator.kt | 27 +++++++- .../generator/nonNative/JNIGenerator.kt | 61 ++++++++++++++++++- .../gradle/generator/nonNative/JSGenerator.kt | 23 +++++++ .../dev/datlag/sekret/gradle/helper/JNI.kt | 4 ++ .../dev/datlag/sekret/gradle/helper/Utils.kt | 3 + sekret-lib/build.gradle.kts | 2 + .../dev/datlag/sekret/EncryptedSecret.kt | 19 ++++++ .../kotlin/dev/datlag/sekret/SekretConfig.kt | 46 ++++++++++++++ 10 files changed, 224 insertions(+), 6 deletions(-) create mode 100644 sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/EncryptedSecret.kt create mode 100644 sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/SekretConfig.kt diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeGenerator.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeGenerator.kt index b34e1b9..160e21b 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeGenerator.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeGenerator.kt @@ -22,9 +22,30 @@ class NativeGenerator( encodedProperties.forEach { (key, secret) -> typeSpec = typeSpec.addFunction( FunSpec.builder(key) - .addAnnotation(Utils.optInAnnotation(C.experimentalForeignApi, C.experimentalNativeApi)) .addModifiers(KModifier.ACTUAL) .addParameter("key", String::class) + .addParameter( + ParameterSpec.builder( + name = "config", + LambdaTypeName.get( + receiver = Utils.sekretConfig.nestedClass("Builder"), + returnType = Unit::class.asTypeName() + ) + ).build() + ) + .returns(String::class.asTypeName().copy(nullable = true)) + .addStatement("return $key(key, %T.Builder().apply(config).build())", Utils.sekretConfig) + .build() + ).addFunction( + FunSpec.builder(key) + .addModifiers(KModifier.ACTUAL) + .addParameter("key", String::class) + .addParameter( + ParameterSpec.builder( + name = "config", + Utils.sekretConfig + ).build() + ) .returns(String::class.asClassName().copy(nullable = true)) .addStatement("val obfuscatedSecret = intArrayOf(%L)", secret) .addStatement("return %M(obfuscatedSecret, key)", JNI.getNativeValue(JNI_PACKAGE_NAME)) diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeJNIGenerator.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeJNIGenerator.kt index 29ef6b5..5a62887 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeJNIGenerator.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/native/NativeJNIGenerator.kt @@ -22,11 +22,11 @@ class NativeJNIGenerator( encodedProperties.forEach { (key, secret) -> spec.addFunction( - FunSpec.builder(key) + FunSpec.builder("${key}Decrypted") .addAnnotation(Utils.optInAnnotation(C.experimentalForeignApi, C.experimentalNativeApi)) .addAnnotation( AnnotationSpec.builder(C.cname) - .addMember("%S", "Java_${Utils.packageNameCSave(settings.packageName)}_Sekret_$key") + .addMember("%S", "Java_${Utils.packageNameCSave(settings.packageName)}_Sekret_${key}Decrypted") .build() ) .addParameter("env", C.pointer.parameterizedBy(JNI.jniEnvVar(JNI_PACKAGE_NAME))) @@ -40,6 +40,24 @@ class NativeJNIGenerator( JNI.getExtensionNativeValue(JNI_PACKAGE_NAME) ) .build() + ).addFunction( + FunSpec.builder("_${key}Encrypted") + .addAnnotation(Utils.optInAnnotation(C.experimentalForeignApi, C.experimentalNativeApi)) + .addAnnotation( + AnnotationSpec.builder(C.cname) + .addMember("%S", "Java_${Utils.packageNameCSave(settings.packageName)}_Sekret__${key}Encrypted") + .build() + ) + .addParameter("env", C.pointer.parameterizedBy(JNI.jniEnvVar(JNI_PACKAGE_NAME))) + .addParameter("clazz", JNI.libraryJObject(JNI_PACKAGE_NAME).copy(nullable = true)) + .returns(JNI.libraryJIntArray(JNI_PACKAGE_NAME).copy(nullable = true)) + .addStatement("val obfuscatedSecret = intArrayOf(%L)", secret) + .addStatement( + "val target = env.%M(obfuscatedSecret.size) ?: return null", + JNI.newIntArray(JNI_PACKAGE_NAME) + ) + .addStatement("return env.%M(target, obfuscatedSecret)", JNI.fillTarget(JNI_PACKAGE_NAME)) + .build() ) } diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/CommonGenerator.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/CommonGenerator.kt index 6e5199a..d5fc210 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/CommonGenerator.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/CommonGenerator.kt @@ -3,6 +3,7 @@ package dev.datlag.sekret.gradle.generator.nonNative import com.squareup.kotlinpoet.* import dev.datlag.sekret.gradle.EncodedProperty import dev.datlag.sekret.gradle.generator.SekretGenerator +import dev.datlag.sekret.gradle.helper.Utils import dev.datlag.sekret.gradle.keys import java.io.File @@ -22,7 +23,31 @@ class CommonGenerator( FunSpec.builder(key) .addModifiers(KModifier.EXPECT) .addParameter("key", String::class) - .returns(String::class.asClassName().copy(nullable = true)) + .addParameter( + ParameterSpec.builder( + name = "config", + LambdaTypeName.get( + receiver = Utils.sekretConfig.nestedClass("Builder"), + returnType = Unit::class.asTypeName() + ) + ).build() + ) + .returns(String::class.asTypeName().copy(nullable = true)) + .build() + ).addFunction( + FunSpec.builder(key) + .addModifiers(KModifier.EXPECT) + .addParameter("key", String::class) + .addParameter( + ParameterSpec.builder( + name = "config", + Utils.sekretConfig + ).defaultValue( + "%T()", + Utils.sekretConfig + ).build() + ) + .returns(String::class.asTypeName().copy(nullable = true)) .build() ) } diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JNIGenerator.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JNIGenerator.kt index d2b7865..d9b81e5 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JNIGenerator.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JNIGenerator.kt @@ -1,8 +1,10 @@ package dev.datlag.sekret.gradle.generator.nonNative import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.MemberName.Companion.member import dev.datlag.sekret.gradle.EncodedProperty import dev.datlag.sekret.gradle.generator.SekretGenerator +import dev.datlag.sekret.gradle.helper.Utils import dev.datlag.sekret.gradle.keys import java.io.File @@ -20,9 +22,64 @@ class JNIGenerator( typeSpec = typeSpec.addFunction( FunSpec.builder(key) .addAnnotation(JvmStatic::class) - .addModifiers(KModifier.ACTUAL, KModifier.EXTERNAL) + .addModifiers(KModifier.ACTUAL) .addParameter("key", String::class) - .returns(String::class.asClassName().copy(nullable = true)) + .addParameter( + ParameterSpec.builder( + name = "config", + LambdaTypeName.get( + receiver = Utils.sekretConfig.nestedClass("Builder"), + returnType = Unit::class.asTypeName() + ) + ).build() + ) + .returns(String::class.asTypeName().copy(nullable = true)) + .addStatement("return $key(key, %T.Builder().apply(config).build())", Utils.sekretConfig) + .build() + ).addFunction( + FunSpec.builder(key) + .addAnnotation(JvmStatic::class) + .addModifiers(KModifier.ACTUAL) + .addParameter("key", String::class) + .addParameter("config", Utils.sekretConfig) + .returns(String::class.asTypeName().copy(nullable = true)) + .beginControlFlow("return if (config.jni.decryptDirectly)") + .addStatement("${key}Decrypted(key)") + .nextControlFlow("else") + .addStatement("${key}Encrypted()?.decrypt(key) ?:") + .beginControlFlow("if (config.jni.fallbackToDirectDecryption)") + .addStatement("${key}Decrypted(key)") + .nextControlFlow("else") + .addStatement("null") + .endControlFlow() + .endControlFlow() + .build() + ).addFunction( + FunSpec.builder(key) + .addAnnotation(JvmStatic::class) + .addParameter("key", String::class) + .returns(String::class.asTypeName().copy(nullable = true)) + .addStatement("return $key(key, %T())", Utils.sekretConfig) + .build() + ).addFunction( + FunSpec.builder("${key}Decrypted") + .addAnnotation(JvmStatic::class) + .addModifiers(KModifier.PRIVATE, KModifier.EXTERNAL) + .addParameter("key", String::class) + .returns(String::class.asTypeName().copy(nullable = true)) + .build() + ).addFunction( + FunSpec.builder("_${key}Encrypted") + .addAnnotation(JvmStatic::class) + .addModifiers(KModifier.PRIVATE, KModifier.EXTERNAL) + .returns(IntArray::class.asTypeName().copy(nullable = true)) + .build() + ).addFunction( + FunSpec.builder("${key}Encrypted") + .addAnnotation(JvmStatic::class) + .addModifiers(KModifier.PRIVATE) + .returns(Utils.encryptedSecret.copy(nullable = true)) + .addStatement("return _${key}Encrypted()?.let(%L)", Utils.encryptedSecret.member("invoke")) .build() ) } diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JSGenerator.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JSGenerator.kt index a0fb192..a85cf81 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JSGenerator.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/generator/nonNative/JSGenerator.kt @@ -5,6 +5,7 @@ import dev.datlag.sekret.gradle.EncodedProperty import dev.datlag.sekret.gradle.generator.SekretGenerator import dev.datlag.sekret.gradle.generator.SekretGenerator.JNI_PACKAGE_NAME import dev.datlag.sekret.gradle.helper.JNI +import dev.datlag.sekret.gradle.helper.Utils import java.io.File class JSGenerator( @@ -22,6 +23,28 @@ class JSGenerator( FunSpec.builder(key) .addModifiers(KModifier.ACTUAL) .addParameter("key", String::class) + .addParameter( + ParameterSpec.builder( + name = "config", + LambdaTypeName.get( + receiver = Utils.sekretConfig.nestedClass("Builder"), + returnType = Unit::class.asTypeName() + ) + ).build() + ) + .returns(String::class.asTypeName().copy(nullable = true)) + .addStatement("return $key(key, %T.Builder().apply(config).build())", Utils.sekretConfig) + .build() + ).addFunction( + FunSpec.builder(key) + .addModifiers(KModifier.ACTUAL) + .addParameter("key", String::class) + .addParameter( + ParameterSpec.builder( + name = "config", + Utils.sekretConfig + ).build() + ) .returns(String::class.asClassName().copy(nullable = true)) .addStatement("val obfuscatedSecret = intArrayOf(%L)", secret) .addStatement("return %M(obfuscatedSecret, key)", JNI.getNativeValue(JNI_PACKAGE_NAME)) diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/JNI.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/JNI.kt index d50ce51..a67e82b 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/JNI.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/JNI.kt @@ -12,6 +12,7 @@ object JNI { private const val LIBRARY_J_STRING = "jString" private const val LIBRARY_J_OBJECT = "jObject" + private const val LIBRARY_J_IntArray = "jIntArray" private const val LIBRARY_SEKRET_HELPER = "SekretHelper" @@ -24,9 +25,12 @@ object JNI { fun getNativeValue(packageName: String) = MemberName(sekretHelper(packageName), "getNativeValue") fun getExtensionNativeValue(packageName: String) = MemberName(packageName, "getNativeValue") + fun newIntArray(packageName: String) = MemberName(packageName, "newIntArray") + fun fillTarget(packageName: String) = MemberName(packageName, "fill") fun libraryJString(packageName: String) = ClassName(packageName, LIBRARY_J_STRING) fun libraryJObject(packageName: String) = ClassName(packageName, LIBRARY_J_OBJECT) + fun libraryJIntArray(packageName: String) = ClassName(packageName, LIBRARY_J_IntArray) sealed class Error(private val msg: String) { object NewString : Error("Could not find NewString method in JNI") diff --git a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/Utils.kt b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/Utils.kt index 213d2d5..cf9521e 100644 --- a/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/Utils.kt +++ b/sekret-gradle-plugin/src/main/java/dev/datlag/sekret/gradle/helper/Utils.kt @@ -8,6 +8,9 @@ import java.util.* object Utils { + val encryptedSecret = ClassName("dev.datlag.sekret", "EncryptedSecret") + val sekretConfig = ClassName("dev.datlag.sekret", "SekretConfig") + fun packageNameToFolderStructure(packageName: String): String { return packageName.replace(".", File.pathSeparator?.ifBlank { null } ?: "/") } diff --git a/sekret-lib/build.gradle.kts b/sekret-lib/build.gradle.kts index 4bad2e8..04e369b 100644 --- a/sekret-lib/build.gradle.kts +++ b/sekret-lib/build.gradle.kts @@ -8,6 +8,7 @@ plugins { signing alias(libs.plugins.vanniktech.publish) alias(libs.plugins.osdetector) + alias(libs.plugins.serialization) } val artifact = VersionCatalog.artifactName() @@ -106,6 +107,7 @@ kotlin { sourceSets { commonMain.dependencies { api(project(":sekret-annotations")) + implementation(libs.serialization) } val jniNativeMain by creating { diff --git a/sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/EncryptedSecret.kt b/sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/EncryptedSecret.kt new file mode 100644 index 0000000..3018e4c --- /dev/null +++ b/sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/EncryptedSecret.kt @@ -0,0 +1,19 @@ +package dev.datlag.sekret + +class EncryptedSecret private constructor( + private val data: IntArray +) { + fun decrypt(key: String): String { + return SekretHelper.getNativeValue(data, key) + } + + companion object { + operator fun invoke(data: IntArray?): EncryptedSecret? { + return if (data == null || data.isEmpty()) { + null + } else { + EncryptedSecret(data) + } + } + } +} \ No newline at end of file diff --git a/sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/SekretConfig.kt b/sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/SekretConfig.kt new file mode 100644 index 0000000..8578e2b --- /dev/null +++ b/sekret-lib/src/commonMain/kotlin/dev/datlag/sekret/SekretConfig.kt @@ -0,0 +1,46 @@ +package dev.datlag.sekret + +import kotlinx.serialization.Serializable + +/** + * Configure Sekret decryption behavior. + * + * @param jni specify JNI related configuration (Android and Desktop JVM) + */ +@Serializable +data class SekretConfig( + val jni: JNI = JNI() +) { + /** + * Configure decryption behavior on JNI. + * + * @param decryptDirectly if true decrypts directly in native code, might expose keys to JNI tracing. + * @param fallbackToDirectDecryption if true decrypts in native code if non-native decryption fails. + * See [decryptDirectly] + */ + @Serializable + data class JNI( + val decryptDirectly: Boolean = false, + val fallbackToDirectDecryption: Boolean = true, + ) { + class Builder { + var decryptDirectly: Boolean = false + var fallbackToDirectDecryption: Boolean = true + + fun build(): JNI = JNI(decryptDirectly, fallbackToDirectDecryption) + } + } + + class Builder { + var jni: JNI = JNI() + private set + + fun jni(block: JNI.Builder.() -> Unit) = apply { + jni = JNI.Builder().apply(block).build() + } + + fun build(): SekretConfig = SekretConfig(jni) + } +} + +fun SekretConfig(block: SekretConfig.Builder.() -> Unit) = SekretConfig.Builder().apply(block).build() \ No newline at end of file