Files
SimpleRemoter/common/VerifyV2.h
2026-04-19 22:55:21 +02:00

277 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
// VerifyV2.h - V2 授权验证(仅验证,不含签名功能)
// 用于客户端离线验证 V2 签名
//
// ============================================================================
// 授权校验逻辑说明
// ============================================================================
//
// 授权分为 V1 和 V2 两种:
// - V1: 需要在线连接授权服务器校验
// - V2: 支持离线校验(使用 ECDSA P-256 签名)
//
// V2 离线校验流程:
// 1. 读取配置中的 SN (设备ID)、Password (授权码)、PwdHmac (签名)
// 2. 验证签名:使用内嵌公钥验证 PwdHmac 是否为 "SN|Password" 的有效签名
// 3. 检查有效期:解析 Password 中的结束日期,判断是否过期
//
// Password 格式: "YYYYMMDD-YYYYMMDD-NNNN-XXXXXXXX"
// 开始日期 结束日期 数量 校验码
//
// 授权行为:
// +---------------------------+-------------------------------------------+
// | 情况 | 行为 |
// +---------------------------+-------------------------------------------+
// | V2 + 签名有效 + 未过期 | SetAuthorized() → 离线OK无需连接服务器 |
// | V2 + 签名有效 + 已过期 | 需要连接服务器续期 |
// | V2 + 签名无效 | 需要连接服务器 |
// | V1 | 需要连接服务器 |
// | 试用版 | 保持连接WAN检测+端口限制 |
// | 未授权/连不上服务器 | 循环弹窗警告 |
// +---------------------------+-------------------------------------------+
//
// 服务器授权成功后:
// - 试用版: 保持连接,执行 WAN 检测和端口限制
// - 正式版: AuthKernelManager 退出
//
// ============================================================================
#include <windows.h>
#include <bcrypt.h>
#include <string>
#include <vector>
#pragma comment(lib, "bcrypt.lib")
// 包含公钥
#include "key.h"
// V2 签名长度 (ECDSA P-256: 64 bytes)
#define V2_SIGNATURE_SIZE 64
// V2 公钥长度 (ECDSA P-256: 64 bytes, X+Y coordinates)
#define V2_PUBKEY_SIZE 64
namespace VerifyV2
{
// Base64 解码
inline bool base64Decode(const std::string& encoded, BYTE* dataOut, size_t* lenOut)
{
static const BYTE DECODE_TABLE[256] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
};
size_t inLen = encoded.length();
if (inLen == 0 || inLen % 4 != 0) return false;
size_t outLen = inLen / 4 * 3;
if (encoded[inLen - 1] == '=') outLen--;
if (inLen >= 2 && encoded[inLen - 2] == '=') outLen--;
size_t j = 0;
for (size_t i = 0; i < inLen; i += 4) {
BYTE a = DECODE_TABLE[(BYTE)encoded[i]];
BYTE b = DECODE_TABLE[(BYTE)encoded[i + 1]];
BYTE c = DECODE_TABLE[(BYTE)encoded[i + 2]];
BYTE d = DECODE_TABLE[(BYTE)encoded[i + 3]];
if (a == 255 || b == 255) return false;
dataOut[j++] = (a << 2) | (b >> 4);
if (j < outLen) dataOut[j++] = (b << 4) | (c >> 2);
if (j < outLen) dataOut[j++] = (c << 6) | d;
}
*lenOut = outLen;
return true;
}
// 计算 SHA256 哈希 (使用 BCrypt)
inline bool computeSHA256(const BYTE* data, size_t len, BYTE* hashOut)
{
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_HASH_HANDLE hHash = nullptr;
DWORD hashObjectSize = 0, dataLen = 0;
PBYTE hashObject = nullptr;
bool success = false;
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, nullptr, 0) != 0)
return false;
if (BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashObjectSize, sizeof(DWORD), &dataLen, 0) != 0)
goto cleanup;
hashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, hashObjectSize);
if (!hashObject) goto cleanup;
if (BCryptCreateHash(hAlg, &hHash, hashObject, hashObjectSize, nullptr, 0, 0) != 0)
goto cleanup;
if (BCryptHashData(hHash, (PUCHAR)data, (ULONG)len, 0) != 0)
goto cleanup;
if (BCryptFinishHash(hHash, hashOut, 32, 0) != 0)
goto cleanup;
success = true;
cleanup:
if (hHash) BCryptDestroyHash(hHash);
if (hashObject) HeapFree(GetProcessHeap(), 0, hashObject);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
return success;
}
// 使用公钥验证 ECDSA P-256 签名
inline bool VerifySignature(const BYTE* publicKey, const BYTE* msg, int len, const BYTE* signature)
{
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
BYTE hash[32];
bool success = false;
// 计算消息的 SHA256 哈希
if (!computeSHA256(msg, len, hash))
return false;
// 构建公钥 blob
std::vector<BYTE> publicBlob(sizeof(BCRYPT_ECCKEY_BLOB) + V2_PUBKEY_SIZE);
BCRYPT_ECCKEY_BLOB* header = (BCRYPT_ECCKEY_BLOB*)publicBlob.data();
header->dwMagic = BCRYPT_ECDSA_PUBLIC_P256_MAGIC;
header->cbKey = 32;
memcpy(publicBlob.data() + sizeof(BCRYPT_ECCKEY_BLOB), publicKey, V2_PUBKEY_SIZE);
// 打开 ECDSA 算法
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_ECDSA_P256_ALGORITHM, nullptr, 0) != 0)
return false;
// 导入公钥
if (BCryptImportKeyPair(hAlg, nullptr, BCRYPT_ECCPUBLIC_BLOB, &hKey,
publicBlob.data(), (ULONG)publicBlob.size(), 0) != 0)
goto cleanup;
// 验证签名
success = (BCryptVerifySignature(hKey, nullptr, hash, 32,
(PUCHAR)signature, V2_SIGNATURE_SIZE, 0) == 0);
cleanup:
if (hKey) BCryptDestroyKey(hKey);
if (hAlg) BCryptCloseAlgorithmProvider(hAlg, 0);
return success;
}
// 验证口令签名 (V2)
// deviceId: 设备ID (如 "XXXX-XXXX-XXXX-XXXX")
// password: 口令字符串 (如 "20250301-20261231-0002-XXXXXXXX")
// hmacField: HMAC 字段值 (应以 "v2:" 开头)
// publicKey: 公钥 (64 bytes),默认使用内嵌公钥
// 返回: true 签名验证通过
inline bool verifyPasswordV2(const std::string& deviceId, const std::string& password,
const std::string& hmacField, const BYTE* publicKey = g_LicensePublicKey)
{
// 检查 v2: 前缀
if (hmacField.length() < 3 || hmacField.substr(0, 3) != "v2:") {
return false;
}
// 提取 Base64 签名
std::string sigBase64 = hmacField.substr(3);
// 解码签名
BYTE signature[V2_SIGNATURE_SIZE];
size_t sigLen = 0;
if (!base64Decode(sigBase64, signature, &sigLen) || sigLen != V2_SIGNATURE_SIZE) {
return false;
}
// 构建待验证数据: "deviceId|password"
std::string payload = deviceId + "|" + password;
// 验证签名
return VerifySignature(publicKey, (const BYTE*)payload.c_str(), (int)payload.length(), signature);
}
// 获取设备ID (从配置读取或从硬件ID计算)
// 供外部调用,简化验证流程
inline std::string getDeviceIdFromHardwareId(const std::string& hardwareId)
{
// 与 pwd_gen.cpp 中的 getDeviceID 逻辑一致
// SHA256(hardwareId) -> 取前16字节 -> 格式化为 XXXX-XXXX-XXXX-XXXX
BYTE hash[32];
if (!computeSHA256((const BYTE*)hardwareId.c_str(), hardwareId.length(), hash))
return "";
char deviceId[20];
snprintf(deviceId, sizeof(deviceId), "%02X%02X-%02X%02X-%02X%02X-%02X%02X",
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5], hash[6], hash[7]);
return deviceId;
}
// 检查 V2 密码是否过期
// password 格式: "20250301-20261231-0002-XXXXXXXX" (startDate-endDate-...)
// 返回: true 如果未过期(在有效期内)
inline bool isPasswordValid(const std::string& password)
{
// 解析结束日期 (位置 9-16, 格式 YYYYMMDD)
if (password.length() < 17 || password[8] != '-') {
return false;
}
std::string endDateStr = password.substr(9, 8);
if (endDateStr.length() != 8) {
return false;
}
// 解析 YYYYMMDD
int endYear = 0, endMonth = 0, endDay = 0;
if (sscanf(endDateStr.c_str(), "%4d%2d%2d", &endYear, &endMonth, &endDay) != 3) {
return false;
}
// 获取当前日期
SYSTEMTIME st;
GetLocalTime(&st);
// 比较日期 (转换为 YYYYMMDD 整数比较)
int endDate = endYear * 10000 + endMonth * 100 + endDay;
int currentDate = st.wYear * 10000 + st.wMonth * 100 + st.wDay;
return currentDate <= endDate;
}
}
// 便捷宏:从配置验证 V2 授权并设置授权状态
// 需要包含 iniFile.h 和 LANChecker.h
// 注意:只有签名有效且未过期才会跳过超时检测,过期则需要连接服务器续期
#define VERIFY_V2_AND_SET_AUTHORIZED() \
do { \
config* _v2cfg = IsDebug ? new config : new iniFile; \
std::string _pwdHmac = _v2cfg->GetStr("settings", "PwdHmac", ""); \
if (_pwdHmac.length() >= 3 && _pwdHmac.substr(0, 3) == "v2:") { \
std::string _deviceId = _v2cfg->GetStr("settings", "SN", ""); \
std::string _password = _v2cfg->GetStr("settings", "Password", ""); \
if (!_deviceId.empty() && !_password.empty() && \
VerifyV2::verifyPasswordV2(_deviceId, _password, _pwdHmac) && \
VerifyV2::isPasswordValid(_password)) { \
AuthTimeoutChecker::SetAuthorized(); \
} \
} \
delete _v2cfg; \
} while(0)