Skip to content

Commit

Permalink
Merge pull request #286 from luckyloogn/feat-add-signature-and-permis…
Browse files Browse the repository at this point in the history
…sion-configuration

新增打包签名配置、签名密钥管理、打包权限设置
  • Loading branch information
SuperMonster003 authored Jan 1, 2025
2 parents 09c1069 + c907d10 commit 2f8a888
Show file tree
Hide file tree
Showing 40 changed files with 2,978 additions and 2 deletions.
8 changes: 8 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ dependencies /* Unclassified */ {
// Toaster
implementation("com.github.getActivity:Toaster:12.6")
implementation("com.github.getActivity:EasyWindow:10.3")

// apksigner
implementation("com.github.TimScriptov:apksigner:1.2.0")

// room
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
ksp("androidx.room:room-compiler:2.6.1")
}

dependencies /* MIME */ {
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@

<activity android:name="org.autojs.autojs.ui.project.ProjectConfigActivity" />

<activity
android:name="org.autojs.autojs.ui.keystore.ManageKeyStoreActivity" />

<activity
android:name="org.autojs.autojs.ui.log.LogActivity"
android:exported="true"
Expand Down
Binary file added app/src/main/assets/default_key_store.bks
Binary file not shown.
55 changes: 55 additions & 0 deletions app/src/main/java/org/autojs/autojs/apkbuilder/ApkBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.util.Log
import com.mcal.apksigner.ApkSigner
import com.reandroid.arsc.chunk.TableBlock
import org.apache.commons.io.FileUtils.copyFile
import org.apache.commons.io.FileUtils.copyInputStreamToFile
import org.autojs.autojs.apkbuilder.keystore.AESUtils
import org.autojs.autojs.app.GlobalAppContext
import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard
import org.autojs.autojs.pio.PFiles
import org.autojs.autojs.project.BuildInfo
import org.autojs.autojs.project.ProjectConfig
import org.autojs.autojs.script.EncryptedScriptFileHeader.writeHeader
import org.autojs.autojs.script.JavaScriptFileSource
import org.autojs.autojs.apkbuilder.keystore.KeyStore
import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT
import org.autojs.autojs.util.MD5Utils
import org.autojs.autojs6.BuildConfig
Expand Down Expand Up @@ -269,6 +274,40 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
val fos = FileOutputStream(outApkFile)
TinySign.sign(File(workspacePath), fos)
fos.close()

val defaultKeyStoreFile = File(workspacePath, "default_key_store.bks")
val tmpOutputApk = File(workspacePath, "temp.apk")
copyInputStreamToFile(GlobalAppContext.get().assets.open("default_key_store.bks"), defaultKeyStoreFile)

val signer = ApkSigner(outApkFile, tmpOutputApk)
signer.useDefaultSignatureVersion = false
signer.v1SigningEnabled = mAppConfig.signatureSchemes.contains("V1")
signer.v2SigningEnabled = mAppConfig.signatureSchemes.contains("V2")
signer.v3SigningEnabled = mAppConfig.signatureSchemes.contains("V3")
signer.v4SigningEnabled = mAppConfig.signatureSchemes.contains("V4")

var keyStoreFile = defaultKeyStoreFile
var password = "AutoJs6"
var alias = "AutoJs6"
var aliasPassword = "AutoJs6"

mAppConfig.keyStore?.let {
keyStoreFile = File(it.absolutePath)
password = AESUtils.decrypt(it.password)
alias = it.alias
aliasPassword = AESUtils.decrypt(it.aliasPassword)
}

// 使用 ApkSigner 重新签名
if (!signer.signRelease(keyStoreFile, password, alias, aliasPassword)) {
throw java.lang.RuntimeException("Failed to re-sign using ApkSigner")
}

try {
copyFile(tmpOutputApk, outApkFile)
} catch (e: java.lang.Exception) {
throw java.lang.RuntimeException(e)
}
}

fun cleanWorkspace() = also {
Expand Down Expand Up @@ -321,6 +360,12 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
private set
var libs: List<String> = emptyList()
private set
var keyStore: KeyStore? = null
private set
var signatureSchemes: String = "V1 + V2"
private set
var permissions: List<String> = emptyList()
private set

fun ignoreDir(dir: File) = also { ignoredDirs.add(dir) }

Expand All @@ -342,6 +387,12 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File

fun setLibs(libs: List<String>) = also { this.libs = libs }

fun setKeyStore(keyStore: KeyStore?) = also { this.keyStore = keyStore }

fun setSignatureSchemes(signatureSchemes: String) = also { this.signatureSchemes = signatureSchemes }

fun setPermissions(permissions: List<String>) = also { this.permissions = permissions }

companion object {
@JvmStatic
fun fromProjectConfig(projectDir: String?, projectConfig: ProjectConfig) = AppConfig()
Expand All @@ -365,6 +416,10 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
}
}

override fun isPermissionRequired(permissionName: String): Boolean {
return mAppConfig.permissions.contains(permissionName)
}
}

private fun copyLibrariesByConfig(config: AppConfig) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ public void onAttr(AxmlWriter.Attr attr) {
}
}

public boolean isPermissionRequired(String permissionName) {
return true;
}

private class MutableAxmlWriter extends AxmlWriter {
private class MutableNodeImpl extends AxmlWriter.NodeImpl {

Expand All @@ -97,6 +101,9 @@ private class MutableNodeImpl extends AxmlWriter.NodeImpl {

@Override
protected void onAttr(AxmlWriter.Attr a) {
if ("uses-permission".equals(this.name.data) && "name".equals(a.name.data) && a.value instanceof StringItem) {
this.ignore = !ManifestEditor.this.isPermissionRequired(((StringItem) a.value).data);
}
ManifestEditor.this.onAttr(a);
super.onAttr(a);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.autojs.autojs.apkbuilder.keystore

import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.KeyGenerator

object AESUtils {

private const val TRANSFORMATION = "AES/GCM/NoPadding"
private const val TAG_LENGTH = 128

private const val KEY_ALIAS = "autojs6_key_store_aes_key"

private fun getKey(): SecretKey {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)

val key = keyStore.getKey(KEY_ALIAS, null)
if (key != null) {
return key as SecretKey
}

val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val keyGenParameterSpec = KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()

keyGenerator.init(keyGenParameterSpec)
return keyGenerator.generateKey()
}

// 加密
fun encrypt(data: String): String {
val secretKey: SecretKey = getKey()
val cipher = Cipher.getInstance(TRANSFORMATION)

cipher.init(Cipher.ENCRYPT_MODE, secretKey)

val encryptedData = cipher.doFinal(data.toByteArray())
val encryptedBase64 = Base64.encodeToString(encryptedData, Base64.NO_WRAP)
val ivBase64 = Base64.encodeToString(cipher.iv, Base64.NO_WRAP)

return "$ivBase64:$encryptedBase64"
}

// 解密
fun decrypt(encryptedData: String): String {
val parts = encryptedData.split(":")
val ivBase64 = parts[0]
val encryptedBase64 = parts[1]

val iv = Base64.decode(ivBase64, Base64.NO_WRAP)
val encrypted = Base64.decode(encryptedBase64, Base64.NO_WRAP)

val secretKey: SecretKey = getKey()
val cipher = Cipher.getInstance(TRANSFORMATION)

val gcmSpec = GCMParameterSpec(TAG_LENGTH, iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec)

val decryptedData = cipher.doFinal(encrypted)

return String(decryptedData)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.autojs.autojs.apkbuilder.keystore

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey


@Entity
data class KeyStore(
@PrimaryKey val absolutePath: String, // 密钥库绝对路径
@ColumnInfo(name = "filename") val filename: String = "", // 文件名
@ColumnInfo(name = "password") val password: String = "", // 密码
@ColumnInfo(name = "alias") val alias: String = "", // 别名
@ColumnInfo(name = "alias_password") val aliasPassword: String = "", // 别名密码
@ColumnInfo(name = "verified") val verified: Boolean = false, // 验证状态
) {
override fun toString(): String = filename
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.autojs.autojs.apkbuilder.keystore

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query
import androidx.room.Upsert


@Dao
interface KeyStoreDao {

@Query("SELECT * FROM keystore WHERE absolutePath = :absolutePath LIMIT 1")
suspend fun getByAbsolutePath(absolutePath: String): KeyStore?

@Upsert
suspend fun upsert(vararg keyStores: KeyStore)

@Query("SELECT * FROM keystore")
suspend fun getAll(): List<KeyStore>

@Delete
suspend fun delete(vararg keyStores: KeyStore)

@Query("DELETE FROM keystore WHERE absolutePath = :absolutePath")
suspend fun deleteByAbsolutePath(absolutePath: String): Int

@Query("DELETE FROM keystore")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.autojs.autojs.apkbuilder.keystore

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [KeyStore::class], version = 1, exportSchema = false)
abstract class KeyStoreDatabase : RoomDatabase() {
abstract fun keyStoreDao(): KeyStoreDao

companion object {
@Volatile
private var INSTANCE: KeyStoreDatabase? = null

fun getDatabase(context: Context): KeyStoreDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
KeyStoreDatabase::class.java,
"keystore-database"
).build()
INSTANCE = instance
instance
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.autojs.autojs.apkbuilder.keystore

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class KeyStoreRepository(context: Context) {

private var dao: KeyStoreDao

init {
val keyStoreDatabase = KeyStoreDatabase.getDatabase(context)
dao = keyStoreDatabase.keyStoreDao()
}

// 获取所有 KeyStore
suspend fun getAllKeyStores(): List<KeyStore> {
return withContext(Dispatchers.IO) {
dao.getAll()
}
}

// 插入或更新 KeyStore
suspend fun upsertKeyStores(vararg keyStores: KeyStore) {
withContext(Dispatchers.IO) {
dao.upsert(*keyStores)
}
}

// 根据绝对路径获取 KeyStore
suspend fun getKeyStoreAbsolutePath(absolutePath: String): KeyStore? {
return withContext(Dispatchers.IO) {
dao.getByAbsolutePath(absolutePath)
}
}

// 删除 KeyStore
suspend fun deleteKeyStores(vararg keyStores: KeyStore) {
withContext(Dispatchers.IO) {
dao.delete(*keyStores)
}
}

// 删除所有 KeyStore
suspend fun deleteAllKeyStores() {
withContext(Dispatchers.IO) {
dao.deleteAll()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public float getTextSize() {
return /* default text size */ 14;
}

public void setTextColors(@NotNull Integer[] colors) {
public void setTextColors(Integer[] colors) {
Adapter adapter = (Adapter) mLogListRecyclerView.getAdapter();
if (adapter != null) {
adapter.setTextColors(colors);
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/autojs/autojs/core/pref/Pref.kt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ object Pref {
@ScriptInterfaceCompatible
fun getScriptDirPath() = WorkingDirectoryUtils.path

@JvmStatic
fun getKeyStorePath(): String {
return getScriptDirPath() + "/.KeyStore/"
}

@JvmStatic
fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
sPref.registerOnSharedPreferenceChangeListener(listener)
Expand Down
Loading

0 comments on commit 2f8a888

Please sign in to comment.