/* logic/pin.c — APP 启动 PIN:加盐迭代哈希 + meow_config 持久化(纯 C)。 * * 设计取舍(见 pin.h 头注释):PIN 防随手翻看,不是安全边界。本文件不引入加密库, * 只做「每次设置新随机盐 + FNV-1a 迭代 1000 轮」的轻量哈希,避免明文 PIN 直接躺在 * 配置里。配置落 TF 数据目录,卡被拔走即可读/改——这是产品口径接受的局限。 * * 依赖面:只 include include/meow_ui_config.h(get/set/remove/int 声明),不 include * meow_ui.h、不碰 UI,宿主 gcc 可直接编译 + 测试存根替换配置层。 */ #include "pin.h" #include "include/meow_ui_config.h" #include #include #include #include #include #include #define PIN_KEY_ENABLED "pin.enabled" #define PIN_KEY_HASH "pin.hash" #define PIN_KEY_SALT "pin.salt" #define PIN_HEX_LEN 16 /* 64-bit 摘要/盐的 hex 字符数 */ #define PIN_HEX_BUF 17 /* 16 + NUL */ #define PIN_ROUNDS 1000 /* 迭代轮数(防直接反查,不防暴力枚举) */ #define PIN_SALT_BYTES 8 /* 8 字节盐 → 16 位 hex */ /* ---------- 哈希 ---------- */ /* FNV-1a(64-bit)逐段吸收,返回新状态。 */ static uint64_t fnv1a_update(uint64_t h, const void *data, size_t n) { const unsigned char *p = (const unsigned char *)data; for (size_t i = 0; i < n; i++) { h ^= (uint64_t)p[i]; h *= 1099511628211ULL; } return h; } /* pin → 16 位 hex 摘要:FNV-1a(salt, pin) 起手,再做 PIN_ROUNDS 轮 * 「上一轮摘要(大端 8 字节)+ 盐」的 FNV-1a。同一 PIN 每次设置因盐不同而摘要不同。 */ static void pin_digest(const char *pin, const char *salt, char out[PIN_HEX_BUF]) { uint64_t h = 1469598103934665603ULL; /* FNV-1a 64-bit offset basis */ h = fnv1a_update(h, salt, strlen(salt)); h = fnv1a_update(h, pin, strlen(pin)); for (int r = 0; r < PIN_ROUNDS; r++) { unsigned char be[8]; for (int i = 0; i < 8; i++) be[i] = (unsigned char)((h >> (8 * (7 - i))) & 0xFF); h = fnv1a_update(h, be, sizeof be); h = fnv1a_update(h, salt, strlen(salt)); } snprintf(out, PIN_HEX_BUF, "%016llx", (unsigned long long)h); } /* 生成 16 位 hex 随机盐:优先 /dev/urandom;无设备时用时间/pid/地址/计数器做降级熵 * (宿主测试与裁剪系统仍可用——降级盐的强度如实标注:不构成密码学随机)。 */ static void pin_gen_salt(char out[PIN_HEX_BUF]) { unsigned char b[PIN_SALT_BYTES]; int ok = 0; FILE *f = fopen("/dev/urandom", "rb"); if (f) { ok = (fread(b, 1, sizeof b, f) == sizeof b); fclose(f); } if (!ok) { static uint64_t ctr; uint64_t x = (uint64_t)time(NULL) ^ ((uint64_t)clock() << 21) ^ ((uint64_t)getpid() << 40) ^ (uint64_t)(uintptr_t)&b ^ (++ctr * 0x9E3779B97F4A7C15ULL); for (int i = 0; i < PIN_SALT_BYTES; i++) b[i] = (unsigned char)((x >> (8 * i)) & 0xFF); } for (int i = 0; i < PIN_SALT_BYTES; i++) snprintf(out + i * 2, 3, "%02x", b[i]); } /* ---------- 配置层小包装 ---------- */ static int cfg_get(const char *key, char *out, int outsz) { return meow_config_get(key, out, outsz) == MEOW_CFG_OK ? 0 : -1; } static int cfg_set(const char *key, const char *val) { return meow_config_set(key, val) == MEOW_CFG_OK ? 0 : -1; } static int is_hex16(const char *s) { if (!s) return 0; for (int i = 0; i < PIN_HEX_LEN; i++) { char c = s[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) return 0; } return s[PIN_HEX_LEN] == '\0'; } /* ---------- 公共 API ---------- */ int mr_pin_valid(const char *pin) { if (!pin) return 0; int n = 0; while (pin[n]) { if (pin[n] < '0' || pin[n] > '9') return 0; n++; if (n > MR_PIN_MAX_LEN) return 0; } return n >= MR_PIN_MIN_LEN; } int mr_pin_enabled(void) { char hash[PIN_HEX_BUF + 8]; char salt[PIN_HEX_BUF + 8]; if (meow_config_int(PIN_KEY_ENABLED, 0) != 1) return 0; if (cfg_get(PIN_KEY_HASH, hash, (int)sizeof hash) != 0) return 0; if (cfg_get(PIN_KEY_SALT, salt, (int)sizeof salt) != 0) return 0; return is_hex16(hash) && is_hex16(salt); /* 配置残缺 → fail-closed */ } int mr_pin_set(const char *pin) { char salt[PIN_HEX_BUF]; char hash[PIN_HEX_BUF]; char old_salt[PIN_HEX_BUF + 8]; int had_old_salt; if (!mr_pin_valid(pin)) return MR_PIN_ERR_FORMAT; pin_gen_salt(salt); pin_digest(pin, salt, hash); had_old_salt = (cfg_get(PIN_KEY_SALT, old_salt, (int)sizeof old_salt) == 0); /* 先写盐与摘要、最后打开 enabled:中途失败只会留下「未启用」,不会出现 * enabled=1 但缺少盐/哈希的半残状态。摘要写失败时把盐还原成旧值,使已启用的旧 * PIN 仍可校验(否则「新盐+旧哈希」会让新旧 PIN 都验不过)。 */ if (cfg_set(PIN_KEY_SALT, salt) != 0) return MR_PIN_ERR_STORE; if (cfg_set(PIN_KEY_HASH, hash) != 0) { if (had_old_salt) cfg_set(PIN_KEY_SALT, old_salt); else meow_config_remove(PIN_KEY_SALT); return MR_PIN_ERR_STORE; } if (cfg_set(PIN_KEY_ENABLED, "1") != 0) { /* 重设时 enabled 本来就是 "1":新盐/哈希已一致生效,按成功返回; * 首次开启时 enabled 仍为 0 → 如实报落盘失败。 */ return meow_config_int(PIN_KEY_ENABLED, 0) == 1 ? MR_PIN_OK : MR_PIN_ERR_STORE; } return MR_PIN_OK; } int mr_pin_verify(const char *pin) { char hash[PIN_HEX_BUF + 8]; char salt[PIN_HEX_BUF + 8]; char calc[PIN_HEX_BUF]; if (!mr_pin_valid(pin)) return 0; if (!mr_pin_enabled()) return 0; if (cfg_get(PIN_KEY_HASH, hash, (int)sizeof hash) != 0) return 0; if (cfg_get(PIN_KEY_SALT, salt, (int)sizeof salt) != 0) return 0; pin_digest(pin, salt, calc); unsigned diff = 0; for (int i = 0; i < PIN_HEX_LEN; i++) diff |= (unsigned)((unsigned char)hash[i] ^ (unsigned char)calc[i]); diff |= (unsigned)(hash[PIN_HEX_LEN] != '\0'); return diff == 0; } int mr_pin_change(const char *old_pin, const char *new_pin) { if (!mr_pin_enabled()) return MR_PIN_ERR_STATE; if (!mr_pin_valid(new_pin)) return MR_PIN_ERR_FORMAT; if (!mr_pin_verify(old_pin)) return MR_PIN_ERR_WRONG; return mr_pin_set(new_pin); } int mr_pin_disable(void) { meow_config_remove(PIN_KEY_HASH); meow_config_remove(PIN_KEY_SALT); if (cfg_set(PIN_KEY_ENABLED, "0") != 0) return MR_PIN_ERR_STORE; return MR_PIN_OK; } int mr_pin_get_salt(char *out, int outsz) { if (!out || outsz <= 0) return 0; out[0] = '\0'; if (cfg_get(PIN_KEY_SALT, out, outsz) != 0) return 0; return out[0] != '\0'; } int mr_pin_get_hash(char *out, int outsz) { if (!out || outsz <= 0) return 0; out[0] = '\0'; if (cfg_get(PIN_KEY_HASH, out, outsz) != 0) return 0; return out[0] != '\0'; }