fix(ncm): 会话密钥改用设备私有 .mkey,网卡/MAC 变化不再丢登录态(M9 启动无网根因)
根因(代码级):会话 cookie 加密密钥 = sha256(首选网卡 MAC + 盐),MAC 取自 /sys/class/net 首个非 lo 网卡。设备断网时 Wi-Fi 网卡可能从 /sys/class/net 消失, 或 readdir 顺序变化选到另一块网卡 → 派生密钥改变 → session.enc 解不开 → ncm_ctx_load_cookie 失败 → cookie 为空 → g_st.logged_in=0 → 启动直接进扫码页。 这与「把网络不可达当登录过期」是同一症状的另一条路径,且发生在登录门之前。 改动: - 新密钥优先 <data_dir>/.mkey(32B 随机,首次生成后稳定,与网络状态无关); .mkey 不可写(只读 FS/无权限)才回退旧 MAC 派生,保持极端环境可启动。 - 旧版 MAC 派生密文仍可解(cookie_decrypt_sel 二次尝试);load_session 命中旧密钥时 自动重写为 .mkey 密钥(透明迁移,不改调用方)。 - ncm_store.h 同步说明:machine_key 仅作旧密文兼容,新写入不再使用。
This commit is contained in:
1 parent
707bbd8a02
commit
e0bd503261
2 files changed
+66
-12
No files matched your search
@@ -170,6 +170,28 @@ int ncm_store_machine_key(const char *salt, unsigned char key[16]) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 设备私有密钥派生:sha256(.mkey_hex + 盐)。.mkey 是首次生成的 32 字节随机数,
|
||||||
|
* 与网络状态/网卡集合无关。 */
|
||||||
|
static int key_from_mkey(const char *salt, unsigned char key[16]) {
|
||||||
|
unsigned char mkey[32], h[32];
|
||||||
|
if (get_or_create_mkey(mkey) != 0) return -1;
|
||||||
|
char hex[65], buf[96];
|
||||||
|
ncm_hex_encode(mkey, 32, hex, 0);
|
||||||
|
snprintf(buf, sizeof buf, "%s%s", hex, salt ? salt : "");
|
||||||
|
if (ncm_sha256(buf, strlen(buf), h) != 0) return -1;
|
||||||
|
memcpy(key, h, 16);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 会话密钥(M9 根因修复):优先设备私有 .mkey,只有它不可用(只读文件系统/无权限)才回退
|
||||||
|
* 旧版 MAC 派生。旧版密钥依赖「首选网卡 MAC」——设备断网时网卡可能从 /sys/class/net 消失,
|
||||||
|
* 或 readdir 顺序变化选到另一块网卡,派生结果随之改变 → session.enc 解不开 → App 以为
|
||||||
|
* 「未登录」直接进扫码页(用户报告的「一断网就退到扫码登录」正是该路径)。 */
|
||||||
|
static int session_key(const char *salt, unsigned char key[16]) {
|
||||||
|
if (key_from_mkey(salt, key) == 0) return 0;
|
||||||
|
return ncm_store_machine_key(salt, key);
|
||||||
|
}
|
||||||
|
|
||||||
static int pkcs7_pad_len(size_t n) {
|
static int pkcs7_pad_len(size_t n) {
|
||||||
return (int)(16 - (n % 16)); /* 1..16 */
|
return (int)(16 - (n % 16)); /* 1..16 */
|
||||||
}
|
}
|
||||||
@@ -178,7 +200,7 @@ int ncm_store_cookie_encrypt(const char *plain,
|
|||||||
unsigned char *out, size_t *out_len, size_t cap) {
|
unsigned char *out, size_t *out_len, size_t cap) {
|
||||||
if (!plain || !out || !out_len) return NCM_ERR_ARG;
|
if (!plain || !out || !out_len) return NCM_ERR_ARG;
|
||||||
unsigned char key[16];
|
unsigned char key[16];
|
||||||
if (ncm_store_machine_key(SESSION_SALT, key) != 0) return NCM_ERR_CRYPTO;
|
if (session_key(SESSION_SALT, key) != 0) return NCM_ERR_CRYPTO;
|
||||||
|
|
||||||
size_t n = strlen(plain);
|
size_t n = strlen(plain);
|
||||||
int pad = pkcs7_pad_len(n);
|
int pad = pkcs7_pad_len(n);
|
||||||
@@ -212,13 +234,9 @@ int ncm_store_cookie_encrypt(const char *plain,
|
|||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len,
|
/* 用指定密钥解密已解包(魔数/iv 已剥离)的密文;成功返回 NCM_OK。 */
|
||||||
char *out, size_t out_sz) {
|
static int cookie_decrypt_key(const unsigned char *in, size_t in_len,
|
||||||
if (!in || !out || !out_sz) return NCM_ERR_ARG;
|
const unsigned char key[16], char *out, size_t out_sz) {
|
||||||
if (in_len < SESSION_MAGIC_LEN + 16 || memcmp(in, SESSION_MAGIC, SESSION_MAGIC_LEN) != 0)
|
|
||||||
return NCM_ERR_CRYPTO;
|
|
||||||
unsigned char key[16];
|
|
||||||
if (ncm_store_machine_key(SESSION_SALT, key) != 0) return NCM_ERR_CRYPTO;
|
|
||||||
const unsigned char *iv = in + SESSION_MAGIC_LEN;
|
const unsigned char *iv = in + SESSION_MAGIC_LEN;
|
||||||
size_t clen = in_len - SESSION_MAGIC_LEN - 16;
|
size_t clen = in_len - SESSION_MAGIC_LEN - 16;
|
||||||
if (clen == 0 || clen % 16) return NCM_ERR_CRYPTO;
|
if (clen == 0 || clen % 16) return NCM_ERR_CRYPTO;
|
||||||
@@ -246,6 +264,34 @@ int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len,
|
|||||||
return NCM_OK;
|
return NCM_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 解密(*legacy 可选:回填 1=命中的是旧版 MAC 派生密钥,调用方据此迁移重加密):
|
||||||
|
* ① 新密钥(.mkey 派生)——当前写入格式;
|
||||||
|
* ② 旧密钥(MAC 派生)——兼容升级前落盘的 session.enc,解出即迁移。 */
|
||||||
|
static int cookie_decrypt_sel(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_sz, int *legacy) {
|
||||||
|
if (!in || !out || !out_sz) return NCM_ERR_ARG;
|
||||||
|
if (legacy) *legacy = 0;
|
||||||
|
if (in_len < SESSION_MAGIC_LEN + 16 || memcmp(in, SESSION_MAGIC, SESSION_MAGIC_LEN) != 0)
|
||||||
|
return NCM_ERR_CRYPTO;
|
||||||
|
|
||||||
|
unsigned char key[16];
|
||||||
|
if (session_key(SESSION_SALT, key) == 0) {
|
||||||
|
int rc = cookie_decrypt_key(in, in_len, key, out, out_sz);
|
||||||
|
if (rc == NCM_OK) return NCM_OK;
|
||||||
|
if (rc == NCM_ERR_FULL || rc == NCM_ERR_OOM) return rc;
|
||||||
|
}
|
||||||
|
if (ncm_store_machine_key(SESSION_SALT, key) == 0) {
|
||||||
|
int rc = cookie_decrypt_key(in, in_len, key, out, out_sz);
|
||||||
|
if (rc == NCM_OK) { if (legacy) *legacy = 1; return NCM_OK; }
|
||||||
|
}
|
||||||
|
return NCM_ERR_CRYPTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_sz) {
|
||||||
|
return cookie_decrypt_sel(in, in_len, out, out_sz, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
int ncm_store_save_session(const ncm_store_t *st, const char *cookie) {
|
int ncm_store_save_session(const ncm_store_t *st, const char *cookie) {
|
||||||
if (!st || !cookie) return NCM_ERR_ARG;
|
if (!st || !cookie) return NCM_ERR_ARG;
|
||||||
unsigned char enc[8192];
|
unsigned char enc[8192];
|
||||||
@@ -264,7 +310,11 @@ int ncm_store_load_session(const ncm_store_t *st, char *cookie, size_t sz) {
|
|||||||
char raw[8192];
|
char raw[8192];
|
||||||
int n = ncm_store_read_file(path, raw, sizeof raw);
|
int n = ncm_store_read_file(path, raw, sizeof raw);
|
||||||
if (n <= 0) return NCM_ERR_NOENT;
|
if (n <= 0) return NCM_ERR_NOENT;
|
||||||
return ncm_store_cookie_decrypt((const unsigned char *)raw, (size_t)n, cookie, sz);
|
int legacy = 0;
|
||||||
|
int rc = cookie_decrypt_sel((const unsigned char *)raw, (size_t)n, cookie, sz, &legacy);
|
||||||
|
/* 旧版 MAC 密钥解出 → 就近迁移为 .mkey 密钥(失败不回滚:本次仍返回明文会话)。 */
|
||||||
|
if (rc == NCM_OK && legacy) (void)ncm_store_save_session(st, cookie);
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ncm_store_clear_session(const ncm_store_t *st) {
|
int ncm_store_clear_session(const ncm_store_t *st) {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
* 职责(纯 C,不依赖 UI/SDK):
|
* 职责(纯 C,不依赖 UI/SDK):
|
||||||
* - 应用数据目录推断(env MEOW_DATA_DIR 优先,否则 /tmp 回退)
|
* - 应用数据目录推断(env MEOW_DATA_DIR 优先,否则 /tmp 回退)
|
||||||
* - deviceId(52hex) 首生成 + 持久化(风控要求稳定)
|
* - deviceId(52hex) 首生成 + 持久化(风控要求稳定)
|
||||||
* - 机器绑定密钥派生:sha256(MAC + 盐) → AES-128-CBC key(防裸读,非真安全)
|
* - 设备私有密钥派生(<data_dir>/.mkey + 盐)→ AES-128-CBC key(防裸读,非真安全);
|
||||||
|
* 旧版 MAC 派生密钥保留兼容解密/自动迁移
|
||||||
* - 登录 cookie 加密落盘/读回(AES-128-CBC + 随机 IV + 魔数头)
|
* - 登录 cookie 加密落盘/读回(AES-128-CBC + 随机 IV + 魔数头)
|
||||||
* - 原子写文件、读取、配置键读取(<dir>/<key>.cfg,对齐 SDK meow_config)
|
* - 原子写文件、读取、配置键读取(<dir>/<key>.cfg,对齐 SDK meow_config)
|
||||||
*/
|
*/
|
||||||
@@ -34,10 +35,13 @@ int ncm_store_get_device_id(ncm_store_t *st, char out[NCM_STORE_ID_MAX]);
|
|||||||
int ncm_store_set_device_id(ncm_store_t *st, const char id[NCM_STORE_ID_MAX]);
|
int ncm_store_set_device_id(ncm_store_t *st, const char id[NCM_STORE_ID_MAX]);
|
||||||
|
|
||||||
/* 机器绑定密钥:sha256(mac + salt) 取前 16 字节作为 AES-128 key。
|
/* 机器绑定密钥:sha256(mac + salt) 取前 16 字节作为 AES-128 key。
|
||||||
* mac 取首选网卡 MAC 小写;NCM_MAC 环境变量可覆盖(测试用)。失败返回非 0。 */
|
* mac 取首选网卡 MAC 小写;NCM_MAC 环境变量可覆盖(测试用)。失败返回非 0。
|
||||||
|
* ⚠ 仅作旧版 session.enc 的兼容解密(见下):MAC 随网卡出现/消失而变化,勿用于新写入。 */
|
||||||
int ncm_store_machine_key(const char *salt, unsigned char key[16]);
|
int ncm_store_machine_key(const char *salt, unsigned char key[16]);
|
||||||
|
|
||||||
/* cookie 加解密(AES-128-CBC,PKCS7)。注意:这是防裸读模糊,非真安全。 */
|
/* cookie 加解密(AES-128-CBC,PKCS7)。注意:这是防裸读模糊,非真安全。
|
||||||
|
* M9 起密钥改为设备私有 <data_dir>/.mkey 派生(与网络/网卡状态无关);旧版 MAC 派生
|
||||||
|
* 密文仍可解,load_session 命中旧密钥时自动重写为新密钥(透明迁移)。 */
|
||||||
int ncm_store_cookie_encrypt(const char *plain,
|
int ncm_store_cookie_encrypt(const char *plain,
|
||||||
unsigned char *out, size_t *out_len, size_t cap);
|
unsigned char *out, size_t *out_len, size_t cap);
|
||||||
int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len,
|
int ncm_store_cookie_decrypt(const unsigned char *in, size_t in_len,
|
||||||
|
|||||||
Reference in new issue
Block a user