feat(sdk-bugcollect): Gradle plugin 支持从 config.xuqm 自动解密读取配置
Mode A 项目放置了 assets/xuqm/config.xuqm(PBKDF2+AES-256-GCM 加密), Gradle plugin 现可直接解密读取 appKey / platformUrl,无需额外的 xuqm.config.json。优先级:config.xuqm > xuqm.config.json > gradle.properties。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
dff226ae71
当前提交
8db0d353de
@ -4,9 +4,55 @@ import com.android.build.gradle.AppExtension
|
|||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.Base64
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.SecretKeyFactory
|
||||||
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
import javax.crypto.spec.PBEKeySpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
class XuqmBugCollectPlugin : Plugin<Project> {
|
class XuqmBugCollectPlugin : Plugin<Project> {
|
||||||
|
|
||||||
|
private fun base64UrlDecode(s: String): ByteArray {
|
||||||
|
val padded = s.replace('-', '+').replace('_', '/')
|
||||||
|
.let { it + "=".repeat((4 - it.length % 4) % 4) }
|
||||||
|
return Base64.getDecoder().decode(padded)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read appKey/platformUrl from the SDK Mode A encrypted config file:
|
||||||
|
* app/src/main/assets/xuqm/config.xuqm
|
||||||
|
* Format: XUQM-CONFIG-V1.{salt_b64url}.{iv_b64url}.{ciphertext_b64url}
|
||||||
|
* Decrypted JSON contains: appKey, baseUrl, serverUrl, ...
|
||||||
|
*/
|
||||||
|
private fun readEncryptedConfigFile(projectDir: File): Pair<String?, String?> {
|
||||||
|
val f = File(projectDir, "src/main/assets/xuqm/config.xuqm").takeIf { it.exists() }
|
||||||
|
?: return null to null
|
||||||
|
val content = runCatching { f.readText().trim() }.getOrElse { return null to null }
|
||||||
|
val parts = content.split(".")
|
||||||
|
if (parts.size != 4 || parts[0] != "XUQM-CONFIG-V1") return null to null
|
||||||
|
|
||||||
|
return runCatching {
|
||||||
|
val salt = base64UrlDecode(parts[1])
|
||||||
|
val iv = base64UrlDecode(parts[2])
|
||||||
|
val ciphertext = base64UrlDecode(parts[3])
|
||||||
|
val passphrase = "xuqm-config-file-v1.2026.internal"
|
||||||
|
|
||||||
|
val keySpec = PBEKeySpec(passphrase.toCharArray(), salt, 120_000, 256)
|
||||||
|
val keyBytes = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keySpec).encoded
|
||||||
|
val aesKey = SecretKeySpec(keyBytes, "AES")
|
||||||
|
|
||||||
|
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, aesKey, GCMParameterSpec(128, iv))
|
||||||
|
val json = String(cipher.doFinal(ciphertext))
|
||||||
|
|
||||||
|
val appKey = Regex(""""appKey"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1)
|
||||||
|
val platformUrl = Regex(""""baseUrl"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1)
|
||||||
|
?: Regex(""""serverUrl"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1)
|
||||||
|
appKey to platformUrl
|
||||||
|
}.getOrElse { null to null }
|
||||||
|
}
|
||||||
|
|
||||||
/** Read appKey/platformUrl from xuqm.config.json at project root (optional). */
|
/** Read appKey/platformUrl from xuqm.config.json at project root (optional). */
|
||||||
private fun readConfigFile(rootDir: File): Pair<String?, String?> {
|
private fun readConfigFile(rootDir: File): Pair<String?, String?> {
|
||||||
val f = File(rootDir, "xuqm.config.json").takeIf { it.exists() } ?: return null to null
|
val f = File(rootDir, "xuqm.config.json").takeIf { it.exists() } ?: return null to null
|
||||||
@ -19,7 +65,10 @@ class XuqmBugCollectPlugin : Plugin<Project> {
|
|||||||
override fun apply(target: Project) {
|
override fun apply(target: Project) {
|
||||||
val android = target.extensions.findByType(AppExtension::class.java) ?: return
|
val android = target.extensions.findByType(AppExtension::class.java) ?: return
|
||||||
|
|
||||||
val (fileAppKey, filePlatformUrl) = readConfigFile(target.rootDir)
|
// Priority: config.xuqm (Mode A encrypted) > xuqm.config.json > gradle.properties
|
||||||
|
val (encAppKey, encPlatformUrl) = readEncryptedConfigFile(target.projectDir)
|
||||||
|
val (fileAppKey, filePlatformUrl) = if (encAppKey != null) encAppKey to encPlatformUrl
|
||||||
|
else readConfigFile(target.rootDir)
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
@ -30,7 +79,7 @@ class XuqmBugCollectPlugin : Plugin<Project> {
|
|||||||
task.group = "xuqm"
|
task.group = "xuqm"
|
||||||
task.description = "Upload ProGuard mapping to BugCollect service (${variant.name})"
|
task.description = "Upload ProGuard mapping to BugCollect service (${variant.name})"
|
||||||
|
|
||||||
// Priority: xuqm.config.json > gradle.properties XUQM_APP_KEY > gradle.properties xuqm.appKey
|
// Priority: config.xuqm > xuqm.config.json > gradle.properties XUQM_APP_KEY > gradle.properties xuqm.appKey
|
||||||
task.appKey.set(
|
task.appKey.set(
|
||||||
fileAppKey
|
fileAppKey
|
||||||
?: target.findProperty("XUQM_APP_KEY")?.toString()
|
?: target.findProperty("XUQM_APP_KEY")?.toString()
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户