kernel:Add Dynamic Signature Configuration

This commit is contained in:
ShirkNeko
2025-07-05 16:21:29 +08:00
parent 6a60b72e21
commit dd6d695020
13 changed files with 690 additions and 12 deletions

View File

@@ -4,6 +4,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/version.h> #include <linux/version.h>
#include <linux/workqueue.h>
#ifdef CONFIG_KSU_DEBUG #ifdef CONFIG_KSU_DEBUG
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#endif #endif
@@ -17,13 +18,295 @@
#include "apk_sign.h" #include "apk_sign.h"
#include "klog.h" // IWYU pragma: keep #include "klog.h" // IWYU pragma: keep
#include "kernel_compat.h" #include "kernel_compat.h"
#include "manager_sign.h"
// Expected sizes and hashes for various APK signatures
#define DYNAMIC_SIGN_FILE_MAGIC 0x7f445347 // 'DSG', u32
#define DYNAMIC_SIGN_FILE_VERSION 1 // u32
#define KERNEL_SU_DYNAMIC_SIGN "/data/adb/ksu/.dynamic_sign"
static struct dynamic_sign_config dynamic_sign = {
.size = 0x300,
.hash = "0000000000000000000000000000000000000000000000000000000000000000",
.is_set = 0
};
static DEFINE_SPINLOCK(dynamic_sign_lock);
static struct work_struct ksu_save_dynamic_sign_work;
static struct work_struct ksu_load_dynamic_sign_work;
static struct work_struct ksu_clear_dynamic_sign_work;
static void do_save_dynamic_sign(struct work_struct *work)
{
u32 magic = DYNAMIC_SIGN_FILE_MAGIC;
u32 version = DYNAMIC_SIGN_FILE_VERSION;
struct dynamic_sign_config config_to_save;
loff_t off = 0;
unsigned long flags;
struct file *fp;
spin_lock_irqsave(&dynamic_sign_lock, flags);
config_to_save = dynamic_sign;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
if (!config_to_save.is_set) {
pr_info("Dynamic sign config not set, skipping save\n");
return;
}
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("save_dynamic_sign create file failed: %ld\n", PTR_ERR(fp));
return;
}
if (ksu_kernel_write_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic)) {
pr_err("save_dynamic_sign write magic failed.\n");
goto exit;
}
if (ksu_kernel_write_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("save_dynamic_sign write version failed.\n");
goto exit;
}
if (ksu_kernel_write_compat(fp, &config_to_save, sizeof(config_to_save), &off) != sizeof(config_to_save)) {
pr_err("save_dynamic_sign write config failed.\n");
goto exit;
}
pr_info("Dynamic sign config saved successfully\n");
exit:
filp_close(fp, 0);
}
// Loading dynamic signatures from persistent storage
static void do_load_dynamic_sign(struct work_struct *work)
{
loff_t off = 0;
ssize_t ret = 0;
struct file *fp = NULL;
u32 magic;
u32 version;
struct dynamic_sign_config loaded_config;
unsigned long flags;
int i;
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_RDONLY, 0);
if (IS_ERR(fp)) {
if (PTR_ERR(fp) == -ENOENT) {
pr_info("No saved dynamic sign config found\n");
} else {
pr_err("load_dynamic_sign open file failed: %ld\n", PTR_ERR(fp));
}
return;
}
if (ksu_kernel_read_compat(fp, &magic, sizeof(magic), &off) != sizeof(magic) ||
magic != DYNAMIC_SIGN_FILE_MAGIC) {
pr_err("dynamic sign file invalid magic: %x!\n", magic);
goto exit;
}
if (ksu_kernel_read_compat(fp, &version, sizeof(version), &off) != sizeof(version)) {
pr_err("dynamic sign read version failed\n");
goto exit;
}
pr_info("dynamic sign file version: %d\n", version);
ret = ksu_kernel_read_compat(fp, &loaded_config, sizeof(loaded_config), &off);
if (ret <= 0) {
pr_info("load_dynamic_sign read err: %zd\n", ret);
goto exit;
}
if (ret != sizeof(loaded_config)) {
pr_err("load_dynamic_sign read incomplete config: %zd/%zu\n", ret, sizeof(loaded_config));
goto exit;
}
if (loaded_config.size < 0x100 || loaded_config.size > 0x1000) {
pr_err("Invalid saved config size: 0x%x\n", loaded_config.size);
goto exit;
}
if (strlen(loaded_config.hash) != 64) {
pr_err("Invalid saved config hash length: %zu\n", strlen(loaded_config.hash));
goto exit;
}
for (i = 0; i < 64; i++) {
char c = loaded_config.hash[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
pr_err("Invalid saved config hash character at position %d: %c\n", i, c);
goto exit;
}
}
spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_sign = loaded_config;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
pr_info("Dynamic sign config loaded: size=0x%x, hash=%.16s...\n",
loaded_config.size, loaded_config.hash);
exit:
filp_close(fp, 0);
}
static bool persistent_dynamic_sign(void)
{
return ksu_queue_work(&ksu_save_dynamic_sign_work);
}
// Clear dynamic sign config file using the same method as do_save_dynamic_sign
static void do_clear_dynamic_sign_file(struct work_struct *work)
{
loff_t off = 0;
struct file *fp;
char zero_buffer[512];
memset(zero_buffer, 0, sizeof(zero_buffer));
fp = ksu_filp_open_compat(KERNEL_SU_DYNAMIC_SIGN, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (IS_ERR(fp)) {
pr_err("clear_dynamic_sign create file failed: %ld\n", PTR_ERR(fp));
return;
}
// Write null bytes to overwrite the file content
if (ksu_kernel_write_compat(fp, zero_buffer, sizeof(zero_buffer), &off) != sizeof(zero_buffer)) {
pr_err("clear_dynamic_sign write null bytes failed.\n");
} else {
pr_info("Dynamic sign config file cleared successfully\n");
}
filp_close(fp, 0);
}
static bool clear_dynamic_sign_file(void)
{
return ksu_queue_work(&ksu_clear_dynamic_sign_work);
}
int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config)
{
unsigned long flags;
int ret = 0;
int i;
if (!config) {
return -EINVAL;
}
switch (config->operation) {
case DYNAMIC_SIGN_OP_SET:
if (config->size < 0x100 || config->size > 0x1000) {
pr_err("invalid size: 0x%x\n", config->size);
return -EINVAL;
}
if (strlen(config->hash) != 64) {
pr_err("invalid hash length: %zu\n", strlen(config->hash));
return -EINVAL;
}
for (i = 0; i < 64; i++) {
char c = config->hash[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
pr_err("invalid hash character at position %d: %c\n", i, c);
return -EINVAL;
}
}
spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_sign.size = config->size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash));
#else
strlcpy(dynamic_sign.hash, config->hash, sizeof(dynamic_sign.hash));
#endif
dynamic_sign.is_set = 1;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
persistent_dynamic_sign();
pr_info("dynamic sign updated: size=0x%x, hash=%.16s...\n", config->size, config->hash);
break;
case DYNAMIC_SIGN_OP_GET:
// Getting Dynamic Signatures
spin_lock_irqsave(&dynamic_sign_lock, flags);
if (dynamic_sign.is_set) {
config->size = dynamic_sign.size;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
strscpy(config->hash, dynamic_sign.hash, sizeof(config->hash));
#else
strlcpy(config->hash, dynamic_sign.hash, sizeof(config->hash));
#endif
ret = 0;
} else {
ret = -ENODATA;
}
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
break;
case DYNAMIC_SIGN_OP_CLEAR:
// Clearing dynamic signatures
spin_lock_irqsave(&dynamic_sign_lock, flags);
dynamic_sign.size = 0x300;
strcpy(dynamic_sign.hash, "0000000000000000000000000000000000000000000000000000000000000000");
dynamic_sign.is_set = 0;
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
// Clear file using the same method as save
clear_dynamic_sign_file();
pr_info("Dynamic sign config cleared\n");
break;
default:
pr_err("Invalid dynamic sign operation: %d\n", config->operation);
return -EINVAL;
}
return ret;
}
bool ksu_load_dynamic_sign(void)
{
return ksu_queue_work(&ksu_load_dynamic_sign_work);
}
void ksu_dynamic_sign_init(void)
{
INIT_WORK(&ksu_save_dynamic_sign_work, do_save_dynamic_sign);
INIT_WORK(&ksu_load_dynamic_sign_work, do_load_dynamic_sign);
INIT_WORK(&ksu_clear_dynamic_sign_work, do_clear_dynamic_sign_file);
pr_info("Dynamic sign initialized with persistent storage\n");
}
void ksu_dynamic_sign_exit(void)
{
do_save_dynamic_sign(NULL);
pr_info("Dynamic sign exited with persistent storage\n");
}
struct sdesc { struct sdesc {
struct shash_desc shash; struct shash_desc shash;
char ctx[]; char ctx[];
}; };
static struct apk_sign_key {
unsigned size;
const char *sha256;
} apk_sign_keys[] = {
{EXPECTED_SIZE, EXPECTED_HASH},
{EXPECTED_SIZE_SHIRKNEKO, EXPECTED_HASH_SHIRKNEKO}, // ShirkNeko/SukiSU
{EXPECTED_SIZE_OTHER, EXPECTED_HASH_OTHER}, // Dynamic Sign
};
static struct sdesc *init_sdesc(struct crypto_shash *alg) static struct sdesc *init_sdesc(struct crypto_shash *alg)
{ {
struct sdesc *sdesc; struct sdesc *sdesc;
@@ -71,9 +354,11 @@ static int ksu_sha256(const unsigned char *data, unsigned int datalen,
return ret; return ret;
} }
static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset, static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset)
unsigned expected_size, const char *expected_sha256)
{ {
int i;
struct apk_sign_key sign_key;
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer-sequence length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length ksu_kernel_read_compat(fp, size4, 0x4, pos); // signer length
ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length ksu_kernel_read_compat(fp, size4, 0x4, pos); // signed data length
@@ -89,7 +374,21 @@ static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset,
ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length ksu_kernel_read_compat(fp, size4, 0x4, pos); // certificate length
*offset += 0x4 * 2; *offset += 0x4 * 2;
if (*size4 == expected_size) { for (i = 0; i < ARRAY_SIZE(apk_sign_keys); i++) {
sign_key = apk_sign_keys[i];
if (i == 2) {
unsigned long flags;
spin_lock_irqsave(&dynamic_sign_lock, flags);
if (dynamic_sign.is_set) {
sign_key.size = dynamic_sign.size;
sign_key.sha256 = dynamic_sign.hash;
}
spin_unlock_irqrestore(&dynamic_sign_lock, flags);
}
if (*size4 != sign_key.size)
continue;
*offset += *size4; *offset += *size4;
#define CERT_MAX_LENGTH 1024 #define CERT_MAX_LENGTH 1024
@@ -110,8 +409,8 @@ static bool check_block(struct file *fp, u32 *size4, loff_t *pos, u32 *offset,
bin2hex(hash_str, digest, SHA256_DIGEST_SIZE); bin2hex(hash_str, digest, SHA256_DIGEST_SIZE);
pr_info("sha256: %s, expected: %s\n", hash_str, pr_info("sha256: %s, expected: %s\n", hash_str,
expected_sha256); sign_key.sha256);
if (strcmp(expected_sha256, hash_str) == 0) { if (strcmp(sign_key.sha256, hash_str) == 0) {
return true; return true;
} }
} }
@@ -171,9 +470,7 @@ static bool has_v1_signature_file(struct file *fp)
return false; return false;
} }
static __always_inline bool check_v2_signature(char *path, static __always_inline bool check_v2_signature(char *path)
unsigned expected_size,
const char *expected_sha256)
{ {
unsigned char buffer[0x11] = { 0 }; unsigned char buffer[0x11] = { 0 };
u32 size4; u32 size4;
@@ -244,9 +541,7 @@ static __always_inline bool check_v2_signature(char *path,
offset = 4; offset = 4;
if (id == 0x7109871au) { if (id == 0x7109871au) {
v2_signing_blocks++; v2_signing_blocks++;
v2_signing_valid = v2_signing_valid = check_block(fp, &size4, &pos, &offset);
check_block(fp, &size4, &pos, &offset,
expected_size, expected_sha256);
} else if (id == 0xf05368c0u) { } else if (id == 0xf05368c0u) {
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73 // http://aospxref.com/android-14.0.0_r2/xref/frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java#73
v3_signing_exist = true; v3_signing_exist = true;
@@ -316,5 +611,5 @@ module_param_cb(ksu_debug_manager_uid, &expected_size_ops,
bool is_manager_apk(char *path) bool is_manager_apk(char *path)
{ {
return check_v2_signature(path, EXPECTED_SIZE, EXPECTED_HASH); return check_v2_signature(path);
} }

View File

@@ -2,7 +2,19 @@
#define __KSU_H_APK_V2_SIGN #define __KSU_H_APK_V2_SIGN
#include <linux/types.h> #include <linux/types.h>
#include "ksu.h"
bool is_manager_apk(char *path); bool is_manager_apk(char *path);
struct dynamic_sign_config {
unsigned int size;
char hash[65];
int is_set;
};
int ksu_handle_dynamic_sign(struct dynamic_sign_user_config *config);
void ksu_dynamic_sign_init(void);
void ksu_dynamic_sign_exit(void);
bool ksu_load_dynamic_sign(void);
#endif #endif

View File

@@ -334,6 +334,36 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
return 0; return 0;
} }
// Allow the root manager to configure dynamic signatures
if (arg2 == CMD_DYNAMIC_SIGN) {
if (!from_root && !from_manager) {
return 0;
}
struct dynamic_sign_user_config config;
if (copy_from_user(&config, (void __user *)arg3, sizeof(config))) {
pr_err("copy dynamic sign config failed\n");
return 0;
}
int ret = ksu_handle_dynamic_sign(&config);
if (ret == 0 && config.operation == DYNAMIC_SIGN_OP_GET) {
if (copy_to_user((void __user *)arg3, &config, sizeof(config))) {
pr_err("copy dynamic sign config back failed\n");
return 0;
}
}
if (ret == 0) {
if (copy_to_user(result, &reply_ok, sizeof(reply_ok))) {
pr_err("dynamic_sign: prctl reply error\n");
}
}
return 0;
}
if (arg2 == CMD_REPORT_EVENT) { if (arg2 == CMD_REPORT_EVENT) {
if (!from_root) { if (!from_root) {
return 0; return 0;
@@ -345,6 +375,10 @@ int ksu_handle_prctl(int option, unsigned long arg2, unsigned long arg3,
post_fs_data_lock = true; post_fs_data_lock = true;
pr_info("post-fs-data triggered\n"); pr_info("post-fs-data triggered\n");
on_post_fs_data(); on_post_fs_data();
// Initializing Dynamic Signatures
ksu_dynamic_sign_init();
ksu_load_dynamic_sign();
pr_info("Dynamic sign config loaded during post-fs-data\n");
} }
break; break;
} }

View File

@@ -2,6 +2,7 @@
#define __KSU_H_KSU_CORE #define __KSU_H_KSU_CORE
#include <linux/init.h> #include <linux/init.h>
#include "apk_sign.h"
void __init ksu_core_init(void); void __init ksu_core_init(void);
void ksu_core_exit(void); void ksu_core_exit(void);

View File

@@ -27,6 +27,7 @@
#define CMD_GET_FULL_VERSION 0xC0FFEE1A #define CMD_GET_FULL_VERSION 0xC0FFEE1A
#define CMD_ENABLE_KPM 100 #define CMD_ENABLE_KPM 100
#define CMD_DYNAMIC_SIGN 103
#define EVENT_POST_FS_DATA 1 #define EVENT_POST_FS_DATA 1
#define EVENT_BOOT_COMPLETED 2 #define EVENT_BOOT_COMPLETED 2
@@ -44,6 +45,16 @@
#endif #endif
#define KSU_FULL_VERSION_STRING 255 #define KSU_FULL_VERSION_STRING 255
#define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_SIGN_OP_CLEAR 2
struct dynamic_sign_user_config {
unsigned int operation;
unsigned int size;
char hash[65];
};
struct root_profile { struct root_profile {
int32_t uid; int32_t uid;
int32_t gid; int32_t gid;

13
kernel/manager_sign.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef MANAGER_SIGN_H
#define MANAGER_SIGN_H
// ShirkNeko/SukiSU
#define EXPECTED_SIZE_SHIRKNEKO 0x35c
#define EXPECTED_HASH_SHIRKNEKO "947ae944f3de4ed4c21a7e4f7953ecf351bfa2b36239da37a34111ad29993eef"
// Dynamic Sign
#define EXPECTED_SIZE_OTHER 0x300
#define EXPECTED_HASH_OTHER "0000000000000000000000000000000000000000000000000000000000000000"
#endif /* MANAGER_SIGN_H */

View File

@@ -351,4 +351,47 @@ NativeBridgeNP(getFullVersion, jstring) {
char buff[255] = { 0 }; char buff[255] = { 0 };
get_full_version((char *) &buff); get_full_version((char *) &buff);
return GetEnvironment()->NewStringUTF(env, buff); return GetEnvironment()->NewStringUTF(env, buff);
}
NativeBridge(setDynamicSign, jboolean, jint size, jstring hash) {
if (!hash) {
LogDebug("setDynamicSign: hash is null");
return false;
}
const char* chash = GetEnvironment()->GetStringUTFChars(env, hash, nullptr);
bool result = set_dynamic_sign((unsigned int)size, chash);
GetEnvironment()->ReleaseStringUTFChars(env, hash, chash);
LogDebug("setDynamicSign: size=0x%x, result=%d", size, result);
return result;
}
NativeBridgeNP(getDynamicSign, jobject) {
struct dynamic_sign_user_config config;
bool result = get_dynamic_sign(&config);
if (!result) {
LogDebug("getDynamicSign: failed to get dynamic sign config");
return NULL;
}
jclass cls = GetEnvironment()->FindClass(env, "com/sukisu/ultra/Natives$DynamicSignConfig");
jmethodID constructor = GetEnvironment()->GetMethodID(env, cls, "<init>", "()V");
jobject obj = GetEnvironment()->NewObject(env, cls, constructor);
jfieldID sizeField = GetEnvironment()->GetFieldID(env, cls, "size", "I");
jfieldID hashField = GetEnvironment()->GetFieldID(env, cls, "hash", "Ljava/lang/String;");
GetEnvironment()->SetIntField(env, obj, sizeField, (jint)config.size);
GetEnvironment()->SetObjectField(env, obj, hashField, GetEnvironment()->NewStringUTF(env, config.hash));
LogDebug("getDynamicSign: size=0x%x, hash=%.16s...", config.size, config.hash);
return obj;
}
NativeBridgeNP(clearDynamicSign, jboolean) {
bool result = clear_dynamic_sign();
LogDebug("clearDynamicSign: result=%d", result);
return result;
} }

View File

@@ -36,6 +36,11 @@
#define CMD_ENABLE_KPM 100 #define CMD_ENABLE_KPM 100
#define CMD_HOOK_TYPE 101 #define CMD_HOOK_TYPE 101
#define CMD_GET_SUSFS_FEATURE_STATUS 102 #define CMD_GET_SUSFS_FEATURE_STATUS 102
#define CMD_DYNAMIC_SIGN 103
#define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_SIGN_OP_CLEAR 2
static bool ksuctl(int cmd, void* arg1, void* arg2) { static bool ksuctl(int cmd, void* arg1, void* arg2) {
int32_t result = 0; int32_t result = 0;
@@ -140,3 +145,32 @@ bool get_susfs_feature_status(struct susfs_feature_status* status) {
return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL); return ksuctl(CMD_GET_SUSFS_FEATURE_STATUS, status, NULL);
} }
bool set_dynamic_sign(unsigned int size, const char* hash) {
if (hash == NULL) {
return false;
}
struct dynamic_sign_user_config config;
config.operation = DYNAMIC_SIGN_OP_SET;
config.size = size;
strncpy(config.hash, hash, sizeof(config.hash) - 1);
config.hash[sizeof(config.hash) - 1] = '\0';
return ksuctl(CMD_DYNAMIC_SIGN, &config, NULL);
}
bool get_dynamic_sign(struct dynamic_sign_user_config* config) {
if (config == NULL) {
return false;
}
config->operation = DYNAMIC_SIGN_OP_GET;
return ksuctl(CMD_DYNAMIC_SIGN, config, NULL);
}
bool clear_dynamic_sign() {
struct dynamic_sign_user_config config;
config.operation = DYNAMIC_SIGN_OP_CLEAR;
return ksuctl(CMD_DYNAMIC_SIGN, &config, NULL);
}

View File

@@ -28,6 +28,16 @@ bool is_lkm_mode();
#define KSU_MAX_GROUPS 32 #define KSU_MAX_GROUPS 32
#define KSU_SELINUX_DOMAIN 64 #define KSU_SELINUX_DOMAIN 64
#define DYNAMIC_SIGN_OP_SET 0
#define DYNAMIC_SIGN_OP_GET 1
#define DYNAMIC_SIGN_OP_CLEAR 2
struct dynamic_sign_user_config {
unsigned int operation;
unsigned int size;
char hash[65];
};
// SUSFS Functional State Structures // SUSFS Functional State Structures
struct susfs_feature_status { struct susfs_feature_status {
bool status_sus_path; bool status_sus_path;
@@ -109,4 +119,10 @@ bool get_hook_type(char* hook_type, size_t size);
bool get_susfs_feature_status(struct susfs_feature_status* status); bool get_susfs_feature_status(struct susfs_feature_status* status);
bool set_dynamic_sign(unsigned int size, const char* hash);
bool get_dynamic_sign(struct dynamic_sign_user_config* config);
bool clear_dynamic_sign();
#endif //KERNELSU_KSU_H #endif //KERNELSU_KSU_H

View File

@@ -95,6 +95,27 @@ object Natives {
*/ */
external fun getSusfsFeatureStatus(): SusfsFeatureStatus? external fun getSusfsFeatureStatus(): SusfsFeatureStatus?
/**
* Set dynamic signature configuration
* @param size APK signature size
* @param hash APK signature hash (64 character hex string)
* @return true if successful, false otherwise
*/
external fun setDynamicSign(size: Int, hash: String): Boolean
/**
* Get current dynamic signature configuration
* @return DynamicSignConfig object containing current configuration, or null if not set
*/
external fun getDynamicSign(): DynamicSignConfig?
/**
* Clear dynamic signature configuration
* @return true if successful, false otherwise
*/
external fun clearDynamicSign(): Boolean
private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$" private const val NON_ROOT_DEFAULT_PROFILE_KEY = "$"
private const val NOBODY_UID = 9999 private const val NOBODY_UID = 9999
@@ -147,6 +168,21 @@ object Natives {
val statusSusSu: Boolean = false val statusSusSu: Boolean = false
) : Parcelable ) : Parcelable
@Immutable
@Parcelize
@Keep
data class DynamicSignConfig(
val size: Int = 0,
val hash: String = ""
) : Parcelable {
constructor() : this(0, "")
fun isValid(): Boolean {
return size > 0 && hash.length == 64 && hash.all {
it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F'
}
}
}
@Immutable @Immutable
@Parcelize @Parcelize

View File

@@ -145,6 +145,13 @@ fun MoreSettingsScreen(
var showDpiConfirmDialog by remember { mutableStateOf(false) } var showDpiConfirmDialog by remember { mutableStateOf(false) }
var showImageEditor by remember { mutableStateOf(false) } var showImageEditor by remember { mutableStateOf(false) }
// 动态签名配置状态
var dynamicSignConfig by remember { mutableStateOf<Natives.DynamicSignConfig?>(null) }
var isDynamicSignEnabled by remember { mutableStateOf(false) }
var dynamicSignSize by remember { mutableStateOf("") }
var dynamicSignHash by remember { mutableStateOf("") }
var showDynamicSignDialog by remember { mutableStateOf(false) }
// 主题模式选项 // 主题模式选项
val themeOptions = listOf( val themeOptions = listOf(
stringResource(R.string.theme_follow_system), stringResource(R.string.theme_follow_system),
@@ -652,6 +659,147 @@ fun MoreSettingsScreen(
) )
} }
LaunchedEffect(Unit) {
// 初始化动态签名配置
dynamicSignConfig = Natives.getDynamicSign()
dynamicSignConfig?.let { config ->
if (config.isValid()) {
isDynamicSignEnabled = true
dynamicSignSize = config.size.toString()
dynamicSignHash = config.hash
}
}
}
// 动态签名配置对话框
if (showDynamicSignDialog) {
AlertDialog(
onDismissRequest = { showDynamicSignDialog = false },
title = { Text(stringResource(R.string.dynamic_sign_title)) },
text = {
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
// 启用开关
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { isDynamicSignEnabled = !isDynamicSignEnabled }
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Switch(
checked = isDynamicSignEnabled,
onCheckedChange = { isDynamicSignEnabled = it }
)
Spacer(modifier = Modifier.width(12.dp))
Text(stringResource(R.string.enable_dynamic_sign))
}
Spacer(modifier = Modifier.height(16.dp))
// 签名大小输入
androidx.compose.material3.OutlinedTextField(
value = dynamicSignSize,
onValueChange = { dynamicSignSize = it },
label = { Text(stringResource(R.string.signature_size)) },
enabled = isDynamicSignEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
keyboardType = androidx.compose.ui.text.input.KeyboardType.Number
)
)
Spacer(modifier = Modifier.height(12.dp))
// 签名哈希输入
androidx.compose.material3.OutlinedTextField(
value = dynamicSignHash,
onValueChange = { hash ->
// 只允许十六进制字符
if (hash.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) {
dynamicSignHash = hash
}
},
label = { Text(stringResource(R.string.signature_hash)) },
enabled = isDynamicSignEnabled,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
supportingText = {
Text(stringResource(R.string.hash_must_be_64_chars))
},
isError = isDynamicSignEnabled && dynamicSignHash.isNotEmpty() && dynamicSignHash.length != 64
)
}
},
confirmButton = {
Button(
onClick = {
if (isDynamicSignEnabled) {
val size = dynamicSignSize.toIntOrNull()
if (size != null && size > 0 && dynamicSignHash.length == 64) {
val success = Natives.setDynamicSign(size, dynamicSignHash)
if (success) {
dynamicSignConfig = Natives.DynamicSignConfig(size, dynamicSignHash)
Toast.makeText(
context,
context.getString(R.string.dynamic_sign_set_success),
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
context.getString(R.string.dynamic_sign_set_failed),
Toast.LENGTH_SHORT
).show()
}
} else {
Toast.makeText(
context,
context.getString(R.string.invalid_sign_config),
Toast.LENGTH_SHORT
).show()
return@Button
}
} else {
val success = Natives.clearDynamicSign()
if (success) {
dynamicSignConfig = null
dynamicSignSize = ""
dynamicSignHash = ""
Toast.makeText(
context,
context.getString(R.string.dynamic_sign_disabled_success),
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
context,
context.getString(R.string.dynamic_sign_clear_failed),
Toast.LENGTH_SHORT
).show()
return@Button
}
}
showDynamicSignDialog = false
},
enabled = if (isDynamicSignEnabled) {
dynamicSignSize.toIntOrNull()?.let { it > 0 } == true &&
dynamicSignHash.length == 64
} else true
) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton(onClick = { showDynamicSignDialog = false }) {
Text(stringResource(R.string.cancel))
}
}
)
}
Scaffold( Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
@@ -1226,6 +1374,17 @@ fun MoreSettingsScreen(
} }
) )
} }
// 动态签名设置
SettingItem(
icon = Icons.Filled.Security,
title = stringResource(R.string.dynamic_sign_title),
subtitle = if (isDynamicSignEnabled) {
stringResource(R.string.dynamic_sign_enabled_summary, dynamicSignSize)
} else {
stringResource(R.string.dynamic_sign_disabled)
},
onClick = { showDynamicSignDialog = true }
)
} }
} }
} }

View File

@@ -566,4 +566,16 @@
<string name="selected_apps_count">%1$d 个已选应用</string> <string name="selected_apps_count">%1$d 个已选应用</string>
<string name="already_added_apps_count">%1$d 个已添加应用</string> <string name="already_added_apps_count">%1$d 个已添加应用</string>
<string name="all_apps_already_added">所有应用均已添加</string> <string name="all_apps_already_added">所有应用均已添加</string>
<string name="dynamic_sign_title">动态签名配置</string>
<string name="dynamic_sign_enabled_summary">已启用 (大小: %s)</string>
<string name="dynamic_sign_disabled">未启用</string>
<string name="enable_dynamic_sign">启用动态签名</string>
<string name="signature_size">签名大小</string>
<string name="signature_hash">签名哈希值</string>
<string name="hash_must_be_64_chars">哈希值必须是64位十六进制字符</string>
<string name="dynamic_sign_set_success">动态签名配置设置成功</string>
<string name="dynamic_sign_set_failed">动态签名配置设置失败</string>
<string name="invalid_sign_config">无效的签名配置</string>
<string name="dynamic_sign_disabled_success">动态签名已禁用</string>
<string name="dynamic_sign_clear_failed">清除动态签名错误</string>
</resources> </resources>

View File

@@ -568,4 +568,16 @@
<string name="selected_apps_count">%1$d apps selected</string> <string name="selected_apps_count">%1$d apps selected</string>
<string name="already_added_apps_count">%1$d apps already added</string> <string name="already_added_apps_count">%1$d apps already added</string>
<string name="all_apps_already_added">All apps have been added</string> <string name="all_apps_already_added">All apps have been added</string>
<string name="dynamic_sign_title">Dynamic Signature Configuration</string>
<string name="dynamic_sign_enabled_summary">Enabled (Size: %s)</string>
<string name="dynamic_sign_disabled">Disabled</string>
<string name="enable_dynamic_sign">Enable Dynamic Signature</string>
<string name="signature_size">Signature Size</string>
<string name="signature_hash">Signature Hash</string>
<string name="hash_must_be_64_chars">Hash must be 64 hexadecimal characters</string>
<string name="dynamic_sign_set_success">Dynamic signature configuration set successfully</string>
<string name="dynamic_sign_set_failed">Failed to set dynamic signature configuration</string>
<string name="invalid_sign_config">Invalid signature configuration</string>
<string name="dynamic_sign_disabled_success">Dynamic signature disabled</string>
<string name="dynamic_sign_clear_failed">Failed to clear dynamic signature</string>
</resources> </resources>