// CPasswordDlg.cpp: 实现文件 // #include "stdafx.h" #include "CPasswordDlg.h" #include "CLicenseDlg.h" #include "afxdialogex.h" #include "pwd_gen.h" #include "2015Remote.h" #include "common/skCrypter.h" #include "2015RemoteDlg.h" #include "LicenseFile.h" #include "generated_hash.h" #include "InputDlg.h" #include "FrpsForSubDlg.h" #include "UIBranding.h" // 外部函数声明 extern std::vector splitString(const std::string& str, char delimiter); extern std::string GetFirstMasterIP(const std::string& master); // CPasswordDlg 对话框 IMPLEMENT_DYNAMIC(CPasswordDlg, CDialogEx) void WriteHash(const char* pwdHash, const char* upperHash) { if (strlen(pwdHash) == 0 || strlen(upperHash) == 0) { return; } memcpy(g_UpperHash + 100, upperHash, 64); std::string id = genHMACbyHash(pwdHash, upperHash); Validation verify(365, "", 0, id.c_str()); WritePwdHash(g_MasterID, pwdHash, verify); } std::string getUpperHash() { return std::string(g_UpperHash + 100, 64); } std::string GetUpperHash() { // Check if upper hash is set (first byte not null) if (g_UpperHash[100] == '\0') { return GetMasterHash(); } return std::string(g_UpperHash + 100, 64); } std::string GetPwdHash() { return std::string(g_MasterID, 64); } const Validation * GetValidation(int offset) { return (Validation*)(g_MasterID + offset); } std::string GetMasterId() { auto id = std::string(g_MasterID).substr(0, 16); return id; } std::string GetHMAC(int offset) { const Validation * v= (Validation*)(g_MasterID + offset); std::string hmac(v->Checksum, 16); if (hmac.c_str()[0] == 0) hmac = THIS_CFG.GetStr("settings", "HMAC"); return hmac; } void SetHMAC(const std::string str, int offset) { Validation* v = (Validation*)(g_MasterID + offset); std::string hmac(v->Checksum, 16); if (hmac.c_str()[0] == 0) { memcpy(v->Checksum, str.c_str(), min(16, str.length())); THIS_CFG.SetStr("settings", "HMAC", str); } } extern "C" void shrink64to32(const char* input64, char* output32); // output32 必须至少 33 字节 extern "C" void shrink32to4(const char* input32, char* output4); // output4 必须至少 5 字节 // 获取授权信息存储路径 std::string GetLicensesPath() { std::string dbPath = GetDbPath(); // GetDbPath() 返回完整文件路径如 "C:\...\YAMA\YAMA.db" // 需要去掉末尾文件名,保留目录部分 size_t pos = dbPath.find_last_of("\\/"); if (pos != std::string::npos) { return dbPath.substr(0, pos + 1) + "licenses.ini"; } return "licenses.ini"; } // 保存授权信息到 INI 文件 bool SaveLicenseInfo(const std::string& deviceID, const std::string& passcode, const std::string& hmac, const std::string& remark, const std::string& authorization, const std::string& frpConfig) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); // 以 DeviceID 为 section 名 cfg.SetStr(deviceID, "SerialNumber", deviceID); cfg.SetStr(deviceID, "Passcode", passcode); cfg.SetStr(deviceID, "HMAC", hmac); cfg.SetStr(deviceID, "Remark", remark); cfg.SetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE); // 默认状态为有效 // 保存 Authorization(多层授权) // 注意:authorization 参数传入时已经是混淆后的格式,直接保存 if (!authorization.empty()) { cfg.SetStr(deviceID, "Authorization", authorization); } // 保存 FRP 配置(用于为下级提供 FRP 代理) if (!frpConfig.empty()) { cfg.SetStr(deviceID, "FrpConfig", frpConfig); } // 保存创建时间 SYSTEMTIME st; GetLocalTime(&st); char timeStr[32]; sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); cfg.SetStr(deviceID, "CreateTime", timeStr); // 初始化扩展字段 cfg.SetStr(deviceID, "IP", ""); cfg.SetStr(deviceID, "Location", ""); cfg.SetStr(deviceID, "LastActiveTime", ""); return true; } // 加载授权信息 bool LoadLicenseInfo(const std::string& deviceID, std::string& passcode, std::string& hmac, std::string& remark) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); passcode = cfg.GetStr(deviceID, "Passcode", ""); if (passcode.empty()) return false; hmac = cfg.GetStr(deviceID, "HMAC", ""); remark = cfg.GetStr(deviceID, "Remark", ""); return true; } // 加载授权的 FRP 配置 std::string LoadLicenseFrpConfig(const std::string& deviceID) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); return cfg.GetStr(deviceID, "FrpConfig", ""); } // 加载授权的 Authorization(用于 V2 授权返回给第一层) std::string LoadLicenseAuthorization(const std::string& deviceID) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); return cfg.GetStr(deviceID, "Authorization", ""); } // 更新授权的 Authorization(V2 续期时更新) bool UpdateLicenseAuthorization(const std::string& deviceID, const std::string& authorization) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); // 检查授权记录是否存在 std::string existingPasscode = cfg.GetStr(deviceID, "Passcode", ""); if (existingPasscode.empty()) { return false; // 授权记录不存在 } cfg.SetStr(deviceID, "Authorization", authorization); return true; } // IP 列表管理常量 #define MAX_IP_HISTORY 500 // 最多保留 几 个不同的 IP // 解析 IP 列表字符串为 vector> // 格式: "192.168.1.1|260218, 10.0.0.1|260215" (yyMMdd) static std::vector> ParseIPList(const std::string& ipListStr) { std::vector> result; if (ipListStr.empty()) return result; size_t start = 0; while (start < ipListStr.length()) { // 找到下一个逗号 size_t end = ipListStr.find(',', start); if (end == std::string::npos) end = ipListStr.length(); // 提取单个 IP 条目 std::string entry = ipListStr.substr(start, end - start); // 去除前后空格 size_t first = entry.find_first_not_of(' '); size_t last = entry.find_last_not_of(' '); if (first != std::string::npos && last != std::string::npos) { entry = entry.substr(first, last - first + 1); } // 解析 IP|时间戳 size_t pipePos = entry.find('|'); if (pipePos != std::string::npos) { std::string ip = entry.substr(0, pipePos); std::string ts = entry.substr(pipePos + 1); result.push_back({ ip, ts }); } else if (!entry.empty()) { // 兼容旧格式(无时间戳) result.push_back({ entry, "" }); } start = end + 1; } return result; } // 将 IP 列表序列化为字符串 static std::string SerializeIPList(const std::vector>& ipList) { std::string result; for (size_t i = 0; i < ipList.size(); ++i) { if (i > 0) result += ", "; result += ipList[i].first; if (!ipList[i].second.empty()) { result += "|"; result += ipList[i].second; } } return result; } // 更新 IP 列表:添加新 IP 或更新已有 IP 的时间戳 // 格式: IP(机器名)|时间戳,例如 "1.2.3.4(PC01)|260219" (yyMMdd 格式) // 返回更新后的 IP 列表字符串 static std::string UpdateIPList(const std::string& existingIPList, const std::string& newIP, const std::string& machineName = "") { if (newIP.empty()) return existingIPList; // 构造 IP 标识:如果有机器名则为 "IP(机器名)",否则为 "IP" std::string ipKey = newIP; if (!machineName.empty()) { // 机器名可能包含 "/"(如 "PC01/GroupName"),只取第一部分 std::string shortName = machineName; size_t slashPos = machineName.find('/'); if (slashPos != std::string::npos) { shortName = machineName.substr(0, slashPos); } ipKey = newIP + "(" + shortName + ")"; } // 获取当前时间戳 (yyMMdd 格式,6位) SYSTEMTIME st; GetLocalTime(&st); char timestamp[8]; sprintf_s(timestamp, "%02d%02d%02d", st.wYear % 100, st.wMonth, st.wDay); // 解析现有 IP 列表 auto ipList = ParseIPList(existingIPList); // 提取纯 IP(去掉括号内的机器名) auto extractIP = [](const std::string& s) -> std::string { size_t pos = s.find('('); return pos != std::string::npos ? s.substr(0, pos) : s; }; // 查找是否已存在该 IP(只比较 IP 部分,兼容旧格式) bool found = false; for (auto& entry : ipList) { if (extractIP(entry.first) == newIP) { entry.first = ipKey; // 更新为新格式(带机器名) entry.second = timestamp; // 更新时间戳 found = true; break; } } if (!found) { // 新 IP,添加到开头(最近的在前) ipList.insert(ipList.begin(), { ipKey, timestamp }); // 限制数量 if (ipList.size() > MAX_IP_HISTORY) { ipList.resize(MAX_IP_HISTORY); } } return SerializeIPList(ipList); } // 获取 IP 列表中的 IP 数量 static int GetIPCount(const std::string& ipListStr) { if (ipListStr.empty()) return 0; auto ipList = ParseIPList(ipListStr); return (int)ipList.size(); } // 更新授权活跃信息 bool UpdateLicenseActivity(const std::string& deviceID, const std::string& passcode, const std::string& hmac, const std::string& ip, const std::string& location, const std::string& machineName) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); // 检查该授权是否存在 std::string existingPasscode = cfg.GetStr(deviceID, "Passcode", ""); if (existingPasscode.empty()) { // 授权不存在,但验证成功了,说明是既往授权,自动添加记录 cfg.SetStr(deviceID, "SerialNumber", deviceID); cfg.SetStr(deviceID, "Passcode", passcode); cfg.SetStr(deviceID, "HMAC", hmac); cfg.SetStr(deviceID, "Remark", "既往授权自动加入"); cfg.SetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE); // 新记录默认为有效 } else { // 授权已存在,更新 passcode(续期后 passcode 会变化) cfg.SetStr(deviceID, "Passcode", passcode); cfg.SetStr(deviceID, "HMAC", hmac); } // 更新最后活跃时间 SYSTEMTIME st; GetLocalTime(&st); char timeStr[32]; sprintf_s(timeStr, "%04d-%02d-%02d %02d:%02d:%02d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); cfg.SetStr(deviceID, "LastActiveTime", timeStr); // 如果是新添加的记录,设置创建时间 if (existingPasscode.empty()) { cfg.SetStr(deviceID, "CreateTime", timeStr); } // 更新 IP 列表(追加新 IP 或更新已有 IP 的时间戳) // 格式: IP(机器名)|yyMMdd if (!ip.empty()) { std::string existingIPList = cfg.GetStr(deviceID, "IP", ""); std::string newIPList = UpdateIPList(existingIPList, ip, machineName); cfg.SetStr(deviceID, "IP", newIPList); } if (!location.empty()) { cfg.SetStr(deviceID, "Location", location); } return true; } // 检查授权是否已被撤销 bool IsLicenseRevoked(const std::string& deviceID) { std::string iniPath = GetLicensesPath(); config cfg(iniPath); std::string status = cfg.GetStr(deviceID, "Status", LICENSE_STATUS_ACTIVE); return (status == LICENSE_STATUS_REVOKED); } #ifdef _WIN64 #pragma comment(lib, "lib/shrink_x64.lib") #else #pragma comment(lib, "lib/shrink.lib") #endif std::string GetFinderString(const char* buf) { char output32[100] = {}; memcpy(output32, buf, 64); if (GetPwdHash() == GetMasterHash()) { return std::string(output32, 100); } shrink64to32(buf, output32+64); return std::string(output32, 96); } bool WritePwdHash(char* target, const std::string & pwdHash, const Validation& verify) { char output32[33], output4[5]; shrink64to32(pwdHash.c_str(), output32); shrink32to4(output32, output4); if (output32[0] == 0 || output4[0] == 0) return false; memcpy(target, pwdHash.c_str(), pwdHash.length()); memcpy(target + 64, output32, 32); memcpy(target + 96, output4, 4); #ifdef _DEBUG ASSERT(IsPwdHashValid(target)); #endif memcpy(target+100, &verify, sizeof(verify)); return true; } bool IsPwdHashValid(const char* hash) { const char* ptr = hash ? hash : g_MasterID; if (ptr == GetMasterHash()) return true; std::string pwdHash(ptr, 64), s1(ptr +64, 32), s2(ptr +96, 4); char output32[33], output4[5]; shrink64to32(pwdHash.c_str(), output32); shrink32to4(output32, output4); if (memcmp(output32, s1.c_str(), 32) || memcmp(output4, s2.c_str(), 4)) { // 哈希校验失败,尝试离线校验 Authorization std::string authObf = THIS_CFG.GetStr("settings", "Authorization", ""); if (!authObf.empty() && TcpClient::IsAuthorizationValid(authObf)) { return true; // Authorization 签名有效 } return false; } return true; } CPasswordDlg::CPasswordDlg(CWnd* pParent /*=nullptr*/) : CDialogLangEx(IDD_DIALOG_PASSWORD, pParent) , m_sDeviceID(_T("")) , m_sPassword(_T("")) , m_sPasscodeHmac(THIS_CFG.GetStr("settings", "PwdHmac", "").c_str()) , m_nBindType(THIS_CFG.GetInt("settings", "BindType", 0)) { m_hIcon = nullptr; } CPasswordDlg::~CPasswordDlg() { } void CPasswordDlg::DoDataExchange(CDataExchange* pDX) { __super::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT_DEVICEID, m_EditDeviceID); DDX_Control(pDX, IDC_EDIT_DEVICEPWD, m_EditPassword); DDX_Text(pDX, IDC_EDIT_DEVICEID, m_sDeviceID); DDV_MaxChars(pDX, m_sDeviceID, 19); DDX_Text(pDX, IDC_EDIT_DEVICEPWD, m_sPassword); DDV_MaxChars(pDX, m_sPassword, 42); DDX_Control(pDX, IDC_COMBO_BIND, m_ComboBinding); DDX_Control(pDX, IDC_EDIT_PASSCODE_HMAC, m_EditPasscodeHmac); DDX_Text(pDX, IDC_EDIT_PASSCODE_HMAC, m_sPasscodeHmac); DDX_Control(pDX, IDC_EDIT_ROOT_CERT, m_EditRootCert); DDX_Text(pDX, IDC_EDIT_ROOT_CERT, m_sRootCert); DDX_CBIndex(pDX, IDC_COMBO_BIND, m_nBindType); } BEGIN_MESSAGE_MAP(CPasswordDlg, CDialogEx) ON_CBN_SELCHANGE(IDC_COMBO_BIND, &CPasswordDlg::OnCbnSelchangeComboBind) END_MESSAGE_MAP() BOOL CPasswordDlg::OnInitDialog() { __super::OnInitDialog(); // 多语言翻译 - Static控件 SetDlgItemText(IDC_STATIC_PASSWORD_SERIAL, _TR("序 列 号:")); SetDlgItemText(IDC_STATIC_PASSWORD_TOKEN, _TR("授权口令:")); SetDlgItemText(IDC_STATIC_PASSWORD_METHOD, _TR("授权方式:")); SetDlgItemText(IDC_STATIC_PASSWORD_VERIFY, _TR("验 证 码:")); SetDlgItemText(IDC_STATIC_ROOT_CERT, _TR("根 凭 证:")); // 设置对话框标题和控件文本(解决英语系统乱码问题) SetWindowText(_TR("口令")); SetDlgItemText(IDOK, _TR("确定")); SetDlgItemText(IDCANCEL, _TR("取消")); // TODO: 在此添加额外的初始化 m_hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_PASSWORD)); SetIcon(m_hIcon, FALSE); m_ComboBinding.InsertStringL(0, "计算机硬件信息"); m_ComboBinding.InsertStringL(1, "主控IP或域名信息"); m_ComboBinding.SetCurSel(m_nBindType); // 加载已保存的根凭证 std::string savedAuth = THIS_CFG.GetStr("settings", "Authorization", ""); if (!savedAuth.empty()) { m_sRootCert = savedAuth.c_str(); m_EditRootCert.SetWindowText(m_sRootCert); } return TRUE; // return TRUE unless you set the focus to a control // 异常: OCX 属性页应返回 FALSE } void CPasswordDlg::OnCbnSelchangeComboBind() { m_nBindType = m_ComboBinding.GetCurSel(); std::string hardwareID = CMy2015RemoteDlg::GetHardwareID(m_nBindType); m_sDeviceID = getFixedLengthID(hashSHA256(hardwareID)).c_str(); m_EditDeviceID.SetWindowTextA(m_sDeviceID); auto master = THIS_CFG.GetStr("settings", "master", ""); if (m_nBindType == 1) { MessageBoxL(_L("请确认是否正确设置公网地址(IP或域名)?\r\n" "绑定IP后主控只能使用指定IP,绑定域名后\r\n" "主控只能使用指定域名。当前公网地址: \r\n")+ + CString(master.empty() ? _L("未设置") : master.c_str()), "提示", MB_OK | MB_ICONWARNING); } } void CPasswordDlg::OnOK() { UpdateData(TRUE); if (!m_sDeviceID.IsEmpty()) { THIS_CFG.SetInt("settings", "BindType", m_nBindType); THIS_CFG.SetStr("settings", "SN", m_sDeviceID.GetString()); // 切换绑定方式时同步保存 SN THIS_CFG.SetStr("settings", "PwdHmac", m_sPasscodeHmac.GetString()); // 保存根凭证(可选,用于多层授权) if (!m_sRootCert.IsEmpty()) { THIS_CFG.SetStr("settings", "Authorization", m_sRootCert.GetString()); } } __super::OnOK(); } // CPasswordDlg 消息处理程序 IMPLEMENT_DYNAMIC(CPwdGenDlg, CDialogEx) CPwdGenDlg::CPwdGenDlg(CWnd* pParent /*=nullptr*/) : CDialogLangEx(IDD_DIALOG_KEYGEN, pParent) , m_sDeviceID(_T("")) , m_sPassword(_T("")) , m_sUserPwd(_T("")) , m_sHMAC(_T("")) , m_ExpireTm(COleDateTime::GetCurrentTime()) , m_StartTm(COleDateTime::GetCurrentTime()) , m_nHostNum(2) , m_nAuthHostNum(2) // 默认与连接数相同 , m_bAuthHostNumManual(FALSE) // 未手动修改 , m_bIsLocalDevice(FALSE) , m_nVersion(0) , m_sPrivateKeyPath(_T("")) , m_nFrpRemotePort(0) { } CPwdGenDlg::~CPwdGenDlg() { } void CPwdGenDlg::DoDataExchange(CDataExchange* pDX) { __super::DoDataExchange(pDX); DDX_Control(pDX, IDC_EDIT_DEVICEID, m_EditDeviceID); DDX_Control(pDX, IDC_EDIT_DEVICEPWD, m_EditPassword); DDX_Control(pDX, IDC_EDIT_USERPWD, m_EditUserPwd); DDX_Text(pDX, IDC_EDIT_DEVICEID, m_sDeviceID); DDV_MaxChars(pDX, m_sDeviceID, 19); DDX_Text(pDX, IDC_EDIT_DEVICEPWD, m_sPassword); DDV_MaxChars(pDX, m_sPassword, 42); DDX_Text(pDX, IDC_EDIT_USERPWD, m_sUserPwd); DDV_MaxChars(pDX, m_sUserPwd, 24); DDX_Control(pDX, IDC_EXPIRE_DATE, m_PwdExpireDate); DDX_DateTimeCtrl(pDX, IDC_EXPIRE_DATE, m_ExpireTm); DDX_Control(pDX, IDC_START_DATE, m_StartDate); DDX_DateTimeCtrl(pDX, IDC_START_DATE, m_StartTm); DDX_Control(pDX, IDC_EDIT_HOSTNUM, m_EditHostNum); DDX_Text(pDX, IDC_EDIT_HOSTNUM, m_nHostNum); DDV_MinMaxInt(pDX, m_nHostNum, 2, 10000); DDX_Control(pDX, IDC_EDIT_HMAC, m_EditHMAC); DDX_Control(pDX, IDC_EDIT_AUTHORIZATION, m_EditAuthorization); DDX_Control(pDX, IDC_BUTTON_SAVE_LICENSE, m_BtnSaveLicense); DDX_Text(pDX, IDC_EDIT_HMAC, m_sHMAC); DDX_Text(pDX, IDC_EDIT_AUTHORIZATION, m_sAuthorization); // V2 Authorization 下级并发数 DDX_Control(pDX, IDC_EDIT_AUTH_HOSTNUM, m_EditAuthHostNum); DDX_Text(pDX, IDC_EDIT_AUTH_HOSTNUM, m_nAuthHostNum); DDV_MinMaxInt(pDX, m_nAuthHostNum, 2, 10000); DDX_Control(pDX, IDC_COMBO_VERSION, m_ComboVersion); DDX_Control(pDX, IDC_EDIT_PRIVATEKEY, m_EditPrivateKey); DDX_Control(pDX, IDC_BUTTON_BROWSE_KEY, m_BtnBrowseKey); DDX_Control(pDX, IDC_BUTTON_GEN_KEYPAIR, m_BtnGenKeyPair); DDX_CBIndex(pDX, IDC_COMBO_VERSION, m_nVersion); DDX_Text(pDX, IDC_EDIT_PRIVATEKEY, m_sPrivateKeyPath); // FRP 控件 DDX_Control(pDX, IDC_CHECK_FRP_PROXY, m_CheckFrpProxy); DDX_Control(pDX, IDC_EDIT_FRP_REMOTE_PORT, m_EditFrpRemotePort); DDX_Control(pDX, IDC_BTN_FRP_AUTO_PORT, m_BtnFrpAutoPort); DDX_Control(pDX, IDC_STATIC_FRP_INFO, m_StaticFrpInfo); DDX_Text(pDX, IDC_EDIT_FRP_REMOTE_PORT, m_nFrpRemotePort); } BEGIN_MESSAGE_MAP(CPwdGenDlg, CDialogEx) ON_BN_CLICKED(IDC_BUTTON_GENKEY, &CPwdGenDlg::OnBnClickedButtonGenkey) ON_BN_CLICKED(IDC_BUTTON_SAVE_LICENSE, &CPwdGenDlg::OnBnClickedButtonSaveLicense) ON_CBN_SELCHANGE(IDC_COMBO_VERSION, &CPwdGenDlg::OnCbnSelchangeComboVersion) ON_BN_CLICKED(IDC_BUTTON_BROWSE_KEY, &CPwdGenDlg::OnBnClickedButtonBrowseKey) ON_BN_CLICKED(IDC_BUTTON_GEN_KEYPAIR, &CPwdGenDlg::OnBnClickedButtonGenKeypair) ON_BN_CLICKED(IDC_CHECK_FRP_PROXY, &CPwdGenDlg::OnBnClickedCheckFrpProxy) ON_BN_CLICKED(IDC_BTN_FRP_AUTO_PORT, &CPwdGenDlg::OnBnClickedBtnFrpAutoPort) ON_EN_CHANGE(IDC_EDIT_HOSTNUM, &CPwdGenDlg::OnEnChangeEditHostNum) ON_EN_CHANGE(IDC_EDIT_AUTH_HOSTNUM, &CPwdGenDlg::OnEnChangeEditAuthHostNum) END_MESSAGE_MAP() // 构建 V1 Authorization(第一层/下级返回给下级) std::string BuildV1Authorization(const std::string& sn, bool heartbeat) { std::string storedAuthObf = THIS_CFG.GetStr("settings", "Authorization", ""); std::string storedAuth = TcpClient::DeobfuscateAuthorization(storedAuthObf); std::string masterIP; int masterPort; CMy2015RemoteDlg::GetEffectiveMasterAddress(masterIP, masterPort, heartbeat); std::string hmacServer = masterIP.empty() ? "" : masterIP + ":" + std::to_string(masterPort); if (storedAuth.empty() || hmacServer.empty()) { return ""; } std::string authStr; auto authParts = splitString(storedAuth, '|'); if (authParts.size() == 5) { // V2 格式(5段):第一层给第二层 // 验证 snHashPrefix 匹配(防止使用其他第一层的 Authorization) std::string storedPrefix = authParts[3]; std::string mySN = THIS_CFG.GetStr("settings", "SN", ""); std::string expectedPrefix = computeSnHashPrefix(mySN); if (storedPrefix != expectedPrefix) { Mprintf("V1 Authorization snHashPrefix 不匹配: 期望 %s, 存储 %s\n", expectedPrefix.c_str(), storedPrefix.c_str()); return ""; } // 组装完整 Authorization(V1,6段) authStr = authParts[0] + "|" + authParts[1] + "|" + authParts[2] + "|" + authParts[3] + "|" + hmacServer + "|" + authParts[4]; if (!heartbeat) Mprintf("V1 返回 Authorization: %s. AuthServer: %s\n", sn.c_str(), hmacServer.c_str()); } else if (authParts.size() == 6) { // V1 格式(6段):第二层及以下给下级 // 替换 hmacServer 为自己的地址 authStr = authParts[0] + "|" + authParts[1] + "|" + authParts[2] + "|" + authParts[3] + "|" + hmacServer + "|" + authParts[5]; if (!heartbeat) Mprintf("V1 转发 Authorization: %s. AuthServer: %s\n", sn.c_str(), hmacServer.c_str()); } return authStr.empty() ? "" : TcpClient::ObfuscateAuthorization(authStr); } void CPwdGenDlg::OnBnClickedButtonGenkey() { UpdateData(TRUE); CString strBeginDate = m_StartTm.FormatL("%Y%m%d"); CString strEndDate = m_ExpireTm.FormatL("%Y%m%d"); CString hostNum; hostNum.FormatL("%04d", m_nHostNum); std::string fixedKey; std::string pwdHmacStr; // V1: 数字字符串, V2: "v2:..." 签名 std::string hmacV1; // 只有 V1 使用 if (m_nVersion == 0) { // V1 (HMAC) 模式 if (m_sUserPwd.IsEmpty()) return; std::string pwdHash = hashSHA256(m_sUserPwd.GetString()); if (pwdHash != GetPwdHash()) { Mprintf("hashSHA256 [%s]: %s\n", m_sUserPwd, pwdHash.c_str()); MessageBoxL("您输入的密码不正确,无法生成口令!", "提示", MB_OK | MB_ICONWARNING); m_sUserPwd.Empty(); return; } // 密码形式:20250209 - 20350209: SHA256: HostNum std::string password = std::string(strBeginDate.GetString()) + " - " + strEndDate.GetBuffer() + ": " + GetPwdHash() + ": " + hostNum.GetBuffer(); std::string finalKey = deriveKey(password, m_sDeviceID.GetString()); fixedKey = strBeginDate.GetString() + std::string("-") + strEndDate.GetBuffer() + std::string("-") + hostNum.GetString() + "-" + getFixedLengthID(finalKey); m_sPassword = fixedKey.c_str(); m_EditPassword.SetWindowTextA(fixedKey.c_str()); hmacV1 = genHMAC(pwdHash, m_sUserPwd.GetString()); uint64_t pwdHmac = SignMessage(m_sUserPwd.GetString(), (BYTE*)fixedKey.c_str(), fixedKey.length()); pwdHmacStr = std::to_string(pwdHmac); m_sHMAC = pwdHmacStr.c_str(); m_EditHMAC.SetWindowTextA(m_sHMAC); // 多层授权:检查是否有 Authorization + master(公网地址) std::string fullAuth = BuildV1Authorization(m_sDeviceID.GetString()); if (!fullAuth.empty()) { m_sAuthorization = fullAuth.c_str(); m_EditAuthorization.SetWindowText(m_sAuthorization); } else { m_sAuthorization.Empty(); m_EditAuthorization.SetWindowText(_T("")); } } else { // V2 (ECDSA) 模式 if (m_sPrivateKeyPath.IsEmpty()) { MessageBoxL("请选择私钥文件!", "提示", MB_OK | MB_ICONWARNING); return; } // 检查私钥文件是否存在 if (GetFileAttributes(m_sPrivateKeyPath) == INVALID_FILE_ATTRIBUTES) { MessageBoxL("私钥文件不存在!", "错误", MB_OK | MB_ICONERROR); return; } // V2 口令格式与 V1 相同,但不需要密码验证 // 使用 deviceId + dates 作为种子生成 key std::string seed = m_sDeviceID.GetString() + std::string(strBeginDate.GetString()) + strEndDate.GetString() + hostNum.GetString(); std::string seedHash = hashSHA256(seed); fixedKey = strBeginDate.GetString() + std::string("-") + strEndDate.GetBuffer() + std::string("-") + hostNum.GetString() + "-" + getFixedLengthID(seedHash); // 使用私钥签名 pwdHmacStr = signPasswordV2(m_sDeviceID.GetString(), fixedKey, m_sPrivateKeyPath.GetString()); if (pwdHmacStr.empty()) { MessageBoxL("生成 V2 签名失败!\n请检查私钥文件是否有效。", "错误", MB_OK | MB_ICONERROR); return; } m_sPassword = fixedKey.c_str(); m_EditPassword.SetWindowTextA(fixedKey.c_str()); m_sHMAC = pwdHmacStr.c_str(); m_EditHMAC.SetWindowTextA(pwdHmacStr.c_str()); // V2 模式:生成 Authorization(使用 deviceID 计算 snHashPrefix) // Authorization 的并发数限制:使用下级并发数(已自动同步或手动设置) CString authHostNum; authHostNum.Format(_T("%04d"), m_nAuthHostNum); // 从 fixedKey 提取 license: "20260317-20270317-0256-..." → "20260317|20270317|0256" std::string license = strBeginDate.GetString() + std::string("|") + strEndDate.GetString() + "|" + authHostNum.GetString(); std::string snHashPrefix = computeSnHashPrefix(m_sDeviceID.GetString()); std::string authSig = signAuthorizationV2(license, snHashPrefix, m_sPrivateKeyPath.GetString()); if (!authSig.empty()) { // Authorization 格式(V2,5段): startDate|endDate|hostNum|snHashPrefix|signature std::string auth = license + "|" + snHashPrefix + "|" + authSig; m_sAuthorization = TcpClient::ObfuscateAuthorization(auth).c_str(); // 混淆后显示 m_EditAuthorization.SetWindowText(m_sAuthorization); GetDlgItem(IDC_STATIC_AUTHORIZATION)->ShowWindow(SW_SHOW); GetDlgItem(IDC_EDIT_AUTHORIZATION)->ShowWindow(SW_SHOW); Mprintf("V2 生成 Authorization: %s (snHashPrefix=%s, authHostNum=%s)\n", m_sDeviceID.GetString(), snHashPrefix.c_str(), authHostNum.GetString()); } else { m_sAuthorization.Empty(); m_EditAuthorization.SetWindowText(_T("")); } } // 公共部分:判断是否为本机授权 std::string hardwareID = CMy2015RemoteDlg::GetHardwareID(); std::string hashedID = hashSHA256(hardwareID); std::string deviceID = getFixedLengthID(hashedID); m_bIsLocalDevice = (deviceID == m_sDeviceID.GetString()); bool isSuperAdmin = GetUpperHash()==GetPwdHash(); if (m_bIsLocalDevice && isSuperAdmin) { // 给自己授权,自动保存 auto settings = "settings", pwdKey = "Password"; THIS_CFG.SetStr(settings, pwdKey, fixedKey.c_str()); THIS_CFG.SetStr("settings", "SN", deviceID); THIS_CFG.SetStr(settings, "HMAC", hmacV1); THIS_CFG.SetStr(settings, "PwdHmac", pwdHmacStr); m_BtnSaveLicense.EnableWindow(FALSE); } else if (m_bIsLocalDevice && !isSuperAdmin) { // 没有权限给自己生成授权 MessageBoxL("您无法给自己授权!\n生成的授权信息仅供参考,不会自动保存。", "提示", MB_OK | MB_ICONWARNING); m_BtnSaveLicense.EnableWindow(FALSE); } else { m_BtnSaveLicense.EnableWindow(TRUE); } } BOOL CPwdGenDlg::OnInitDialog() { __super::OnInitDialog(); // 设置对话框标题和控件文本(解决英语系统乱码问题) SetWindowText(_TR("生成口令")); SetDlgItemText(IDC_BUTTON_SAVE_LICENSE, _TR("保存授权")); SetDlgItemText(IDCANCEL, _TR("取消")); SetDlgItemText(IDC_STATIC_PWD_LABEL, _TR("密 码:")); SetDlgItemText(IDC_STATIC_KEYGEN_VERSION, _TR("版 本:")); SetDlgItemText(IDC_STATIC_KEYGEN_SERIAL, _TR("序列号:")); SetDlgItemText(IDC_STATIC_KEYGEN_EXPIRE, _TR("有效期:")); SetDlgItemText(IDC_STATIC_KEYGEN_CONN, _TR("连接数:")); SetDlgItemText(IDC_STATIC_AUTH_HOSTNUM, _TR("下级连接数:")); SetDlgItemText(IDC_STATIC_KEYGEN_TOKEN, _TR("口 令:")); SetDlgItemText(IDC_STATIC_KEYGEN_HMAC_2373, _TR("HMAC:")); SetDlgItemText(IDC_BUTTON_GENKEY, _TR("生成")); GetDlgItem(IDC_STATIC_KEYGEN_VERSION)->EnableWindow(GetUpperHash() == GetPwdHash()); // TODO: 在此添加额外的初始化 m_hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_PASSWORD)); SetIcon(m_hIcon, FALSE); // 初始化版本下拉框 m_ComboVersion.InsertString(0, _T("V1 (HMAC)")); m_ComboVersion.InsertString(1, _T("V2 (ECDSA)")); m_ComboVersion.SetCurSel(0); m_ComboVersion.EnableWindow(GetUpperHash() == GetPwdHash()); m_nVersion = 0; // 初始状态:V1 模式,隐藏私钥控件和下级并发数 m_EditPrivateKey.ShowWindow(SW_HIDE); m_BtnBrowseKey.ShowWindow(SW_HIDE); m_BtnGenKeyPair.ShowWindow(SW_HIDE); m_EditUserPwd.ShowWindow(SW_SHOW); // 隐藏下级并发数控件(仅 V2 模式使用) GetDlgItem(IDC_STATIC_AUTH_HOSTNUM)->ShowWindow(SW_HIDE); m_EditAuthHostNum.ShowWindow(SW_HIDE); // 初始状态禁用保存按钮 m_BtnSaveLicense.EnableWindow(FALSE); // 加载已配置的 V2 私钥路径 std::string v2Key = THIS_CFG.GetStr("settings", "V2PrivateKey", ""); if (!v2Key.empty()) { m_sPrivateKeyPath = v2Key.c_str(); m_EditPrivateKey.SetWindowText(m_sPrivateKeyPath); } // 设置根凭证标签翻译 SetDlgItemText(IDC_STATIC_AUTHORIZATION, _TR("根凭证:")); // 检查并显示当前服务端的 Authorization std::string storedAuth = THIS_CFG.GetStr("settings", "Authorization", ""); if (!storedAuth.empty()) { // 服务端已获得上级授权,在标题栏提示 CString title; title.Format(_T("%s - %s"), _TR("生成口令"), _TR("已获得上级授权")); SetWindowText(title); } // 初始化 FRP 代理控件 SetDlgItemText(IDC_CHECK_FRP_PROXY, _TR("提供 FRP 代理")); SetDlgItemText(IDC_STATIC_FRP_REMOTE_PORT, _TR("远程端口:")); SetDlgItemText(IDC_BTN_FRP_AUTO_PORT, _TR("自动")); m_nFrpRemotePort = 0; m_sFrpConfig.clear(); // 检查 FRPS 是否已配置 if (CFrpsForSubDlg::IsFrpsConfigured()) { FrpsConfig frpsConfig = CFrpsForSubDlg::GetFrpsConfig(); CString frpsInfo; frpsInfo.Format(_T("FRPS: %s:%d"), CString(frpsConfig.server.c_str()), frpsConfig.port); m_StaticFrpInfo.SetWindowText(frpsInfo); m_CheckFrpProxy.EnableWindow(TRUE); } else { m_StaticFrpInfo.SetWindowText(_TR("(未配置 FRPS,请先通过 扩展→下级FRP代理设置)")); m_CheckFrpProxy.EnableWindow(FALSE); } m_CheckFrpProxy.SetCheck(BST_UNCHECKED); UpdateFrpControlStates(); return TRUE; // return TRUE unless you set the focus to a control // 异常: OCX 属性页应返回 FALSE } void CPwdGenDlg::OnCbnSelchangeComboVersion() { m_nVersion = m_ComboVersion.GetCurSel(); // 切换版本时清理已生成的值 m_EditPassword.SetWindowText(_T("")); m_EditHMAC.SetWindowText(_T("")); m_EditAuthorization.SetWindowText(_T("")); m_sPassword.Empty(); m_sHMAC.Empty(); m_sAuthorization.Empty(); m_BtnSaveLicense.EnableWindow(FALSE); // 重置下级并发数手动标志,并同步当前连接数 m_bAuthHostNumManual = FALSE; CString strHostNum; m_EditHostNum.GetWindowText(strHostNum); int hostNum = _ttoi(strHostNum); if (hostNum >= 2) { m_nAuthHostNum = hostNum; CString strAuthHostNum; strAuthHostNum.Format(_T("%d"), m_nAuthHostNum); m_EditAuthHostNum.SetWindowText(strAuthHostNum); } // 获取"密码"标签控件和"授权"控件 CWnd* pLabel = GetDlgItem(IDC_STATIC_PWD_LABEL); CWnd* pAuthLabel = GetDlgItem(IDC_STATIC_AUTHORIZATION); CWnd* pAuthHostNumLabel = GetDlgItem(IDC_STATIC_AUTH_HOSTNUM); if (m_nVersion == 0) { // V1 (HMAC) 模式:显示密码输入框,隐藏私钥相关控件 m_EditUserPwd.ShowWindow(SW_SHOW); m_EditPrivateKey.ShowWindow(SW_HIDE); m_BtnBrowseKey.ShowWindow(SW_HIDE); m_BtnGenKeyPair.ShowWindow(SW_HIDE); if (pLabel) pLabel->SetWindowText(_TR("密 码:")); // V1 模式隐藏下级并发数 if (pAuthHostNumLabel) pAuthHostNumLabel->ShowWindow(SW_HIDE); m_EditAuthHostNum.ShowWindow(SW_HIDE); } else { // V2 (ECDSA) 模式:隐藏密码输入框,显示私钥相关控件 m_EditUserPwd.ShowWindow(SW_HIDE); m_EditPrivateKey.ShowWindow(SW_SHOW); m_BtnBrowseKey.ShowWindow(SW_SHOW); m_BtnGenKeyPair.ShowWindow(SW_SHOW); if (pLabel) pLabel->SetWindowText(_TR("私 钥:")); // V2 模式显示下级并发数 if (pAuthHostNumLabel) pAuthHostNumLabel->ShowWindow(SW_SHOW); m_EditAuthHostNum.ShowWindow(SW_SHOW); // 如果已有有效私钥,禁用浏览和生成按钮,私钥路径只读 std::string v2Key = THIS_CFG.GetStr("settings", "V2PrivateKey", ""); bool hasValidKey = !v2Key.empty() && GetFileAttributes(v2Key.c_str()) != INVALID_FILE_ATTRIBUTES; m_BtnBrowseKey.EnableWindow(!hasValidKey); m_BtnGenKeyPair.EnableWindow(!hasValidKey); m_EditPrivateKey.SetReadOnly(hasValidKey); } } void CPwdGenDlg::OnEnChangeEditHostNum() { // 如果下级并发数未手动修改,则跟随连接数变化 if (!m_bAuthHostNumManual && m_nVersion == 1) { // 仅 V2 模式需要同步 CString strHostNum; m_EditHostNum.GetWindowText(strHostNum); int hostNum = _ttoi(strHostNum); if (hostNum >= 2) { m_nAuthHostNum = hostNum; CString strAuthHostNum; strAuthHostNum.Format(_T("%d"), m_nAuthHostNum); m_EditAuthHostNum.SetWindowText(strAuthHostNum); } } } void CPwdGenDlg::OnEnChangeEditAuthHostNum() { // 检测是否为用户手动修改(而非程序同步) // 通过比较当前值与连接数来判断 CString strAuthHostNum; m_EditAuthHostNum.GetWindowText(strAuthHostNum); CString strHostNum; m_EditHostNum.GetWindowText(strHostNum); // 如果两个值不同,说明用户手动修改了 if (strAuthHostNum != strHostNum && !strAuthHostNum.IsEmpty()) { m_bAuthHostNumManual = TRUE; } } void CPwdGenDlg::OnBnClickedButtonBrowseKey() { // 检查是否有权使用 V2 私钥 if (GetUpperHash() != GetPwdHash()) { MessageBoxL("您无法使用 V2 私钥签名!", "提示", MB_OK | MB_ICONWARNING); return; } CFileDialog dlg(TRUE, _T("key"), nullptr, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, _T("Key Files (*.key;*.pem)|*.key;*.pem|All Files (*.*)|*.*||"), this); dlg.m_ofn.lpstrTitle = _T("Select Private Key"); if (dlg.DoModal() == IDOK) { m_sPrivateKeyPath = dlg.GetPathName(); m_EditPrivateKey.SetWindowText(m_sPrivateKeyPath); } } void CPwdGenDlg::OnBnClickedButtonGenKeypair() { // 检查是否有权生成 V2 密钥对 if (GetUpperHash() != GetPwdHash()) { MessageBoxL("您无法生成 V2 密钥对!", "提示", MB_OK | MB_ICONWARNING); return; } // 选择私钥保存位置 CFileDialog dlg(FALSE, _T("key"), _T("private_key.key"), OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY, _T("Key Files (*.key)|*.key|All Files (*.*)|*.*||"), this); dlg.m_ofn.lpstrTitle = _T("Save Private Key"); if (dlg.DoModal() != IDOK) { return; } CString privateKeyPath = dlg.GetPathName(); // 生成密钥对 BYTE publicKey[V2_PUBKEY_SIZE]; if (!GenerateKeyPairV2(privateKeyPath.GetString(), publicKey)) { MessageBoxL("生成密钥对失败!", "错误", MB_OK | MB_ICONERROR); return; } // 更新私钥路径(仅更新界面,不保存到配置,需通过菜单设置) m_sPrivateKeyPath = privateKeyPath; m_EditPrivateKey.SetWindowText(m_sPrivateKeyPath); // 格式化公钥为 C 代码 std::string pubKeyCode = formatPublicKeyAsCode(publicKey); // 生成公钥文件路径(与私钥同目录,扩展名改为 .pub.h) CString publicKeyPath = privateKeyPath; int dotPos = publicKeyPath.ReverseFind('.'); if (dotPos > 0) { publicKeyPath = publicKeyPath.Left(dotPos) + _T(".pub.h"); } else { publicKeyPath += _T(".pub.h"); } // 保存公钥到文件 FILE* fp = nullptr; if (fopen_s(&fp, publicKeyPath.GetString(), "w") == 0 && fp) { fprintf(fp, "// V2 License Public Key\n"); fprintf(fp, "// Generated: %s\n\n", CTime::GetCurrentTime().Format("%Y-%m-%d %H:%M:%S").GetString()); fprintf(fp, "%s\n", pubKeyCode.c_str()); fclose(fp); } // 复制公钥到剪贴板 if (OpenClipboard()) { EmptyClipboard(); size_t len = pubKeyCode.length() + 1; HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, len); if (hMem) { char* pMem = (char*)GlobalLock(hMem); if (pMem) { memcpy(pMem, pubKeyCode.c_str(), len); GlobalUnlock(hMem); SetClipboardData(CF_TEXT, hMem); } } CloseClipboard(); } // 简短提示 CString msg; msg.Format(_T("密钥对生成成功!\n\n私钥: %s\n公钥: %s\n\n公钥代码已复制到剪贴板"), privateKeyPath.GetString(), publicKeyPath.GetString()); MessageBox(msg, _TR("成功"), MB_OK | MB_ICONINFORMATION); } void CPwdGenDlg::OnBnClickedCheckFrpProxy() { UpdateFrpControlStates(); // 勾选时,如果端口为 0,自动填入下一个可用端口 if (m_CheckFrpProxy.GetCheck() == BST_CHECKED) { CString portText; m_EditFrpRemotePort.GetWindowText(portText); int currentPort = _ttoi(portText); if (currentPort <= 0) { int port = CFrpsForSubDlg::FindNextAvailablePort(); if (port > 0) { CString portStr; portStr.Format(_T("%d"), port); m_EditFrpRemotePort.SetWindowText(portStr); } } } } void CPwdGenDlg::OnBnClickedBtnFrpAutoPort() { // 自动查找下一个可用端口(不保存,等保存授权时再记录) int port = CFrpsForSubDlg::FindNextAvailablePort(); if (port > 0) { m_nFrpRemotePort = port; CString portStr; portStr.Format(_T("%d"), port); m_EditFrpRemotePort.SetWindowText(portStr); } else { MessageBoxL("端口范围已满,无法自动分配!", "提示", MB_OK | MB_ICONWARNING); } } void CPwdGenDlg::UpdateFrpControlStates() { BOOL frpEnabled = (m_CheckFrpProxy.GetCheck() == BST_CHECKED); BOOL frpsConfigured = CFrpsForSubDlg::IsFrpsConfigured(); m_EditFrpRemotePort.EnableWindow(frpEnabled && frpsConfigured); m_BtnFrpAutoPort.EnableWindow(frpEnabled && frpsConfigured); // 如果启用 FRP 且端口为空,自动分配一个 if (frpEnabled && frpsConfigured && m_nFrpRemotePort == 0) { CString portText; m_EditFrpRemotePort.GetWindowText(portText); if (portText.IsEmpty() || _ttoi(portText) == 0) { // 不自动分配,让用户点击"自动"或手动输入 } } } void CPwdGenDlg::OnBnClickedButtonSaveLicense() { UpdateData(TRUE); // 验证数据 if (m_sDeviceID.IsEmpty() || m_sPassword.IsEmpty() || m_sHMAC.IsEmpty()) { MessageBoxL("请先生成口令!", "提示", MB_OK | MB_ICONWARNING); return; } // 提示用户输入备注(可选) CString remark; // 这里可以弹出一个简单的输入对话框获取备注,或者直接保存 // 检查是否启用了 FRP 代理 std::string frpConfig; if (m_CheckFrpProxy.GetCheck() == BST_CHECKED && CFrpsForSubDlg::IsFrpsConfigured()) { // 获取远程端口 CString portText; m_EditFrpRemotePort.GetWindowText(portText); int remotePort = _ttoi(portText); if (remotePort < 1024 || remotePort > 65535) { MessageBoxL("FRP 远程端口无效(1024-65535)", "提示", MB_OK | MB_ICONWARNING); m_EditFrpRemotePort.SetFocus(); return; } // 获取 FRPS 配置 FrpsConfig frpsConfig = CFrpsForSubDlg::GetFrpsConfig(); // 生成 FRP 配置(有效期设为最大值,由 License 控制过期) // authMode: 0 = 官方 FRP (token), 1 = 自定义 FRP (privilegeKey) // 注意:expireDate 最大 20371231,因为自定义 FRP 的 timestamp 参数是 32 位 long,2038 年后会溢出 frpConfig = GenerateFrpConfig(frpsConfig.server, frpsConfig.port, remotePort, frpsConfig.token, "20371231", frpsConfig.authMode); if (!frpConfig.empty()) { // 记录端口分配 CFrpsForSubDlg::RecordPortAllocation(remotePort, m_sDeviceID.GetString()); Mprintf("[FRP] 为序列号 %s 分配端口 %d\n", m_sDeviceID.GetString(), remotePort); } } // 保存授权信息到数据库 bool success = SaveLicenseInfo( m_sDeviceID.GetString(), m_sPassword.GetString(), m_sHMAC.GetString(), remark.GetString(), m_sAuthorization.GetString(), frpConfig ); if (!success) { MessageBoxL("保存授权信息失败!", "错误", MB_OK | MB_ICONERROR); return; } // 询问是否导出文件 if (MessageBoxL("授权已保存到数据库。\n\n是否同时导出为文件?\n导出后可发送给目标设备导入使用。", "导出授权", MB_YESNO | MB_ICONQUESTION) == IDYES) { // 构建默认文件名(使用完整 SN) CString defaultName; defaultName.Format(_T(BRAND_LICENSE_PREFIX "_%s.lic"), m_sDeviceID.GetString()); // 弹出文件保存对话框 CString filter = _T(BRAND_LICENSE_DESC " (*.lic)|*.lic|All Files (*.*)|*.*||"); CString dlgTitle = _TR("保存授权文件"); CFileDialog dlg(FALSE, _T("lic"), defaultName, OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY, filter, this); dlg.m_ofn.lpstrTitle = dlgTitle; if (dlg.DoModal() == IDOK) { // 导出文件 if (ExportLicenseFile( std::string(CT2A(dlg.GetPathName().GetString())), std::string(CT2A(m_sDeviceID.GetString())), std::string(CT2A(m_sPassword.GetString())), std::string(CT2A(m_sHMAC.GetString())), std::string(CT2A(m_sAuthorization.GetString())), frpConfig)) { CString msg; msg.Format(_T("%s\n%s"), _TR("授权文件已导出到:"), dlg.GetPathName().GetString()); MessageBox(msg, _TR("导出成功"), MB_OK | MB_ICONINFORMATION); } else { MessageBoxL("无法写入文件", "导出失败", MB_OK | MB_ICONERROR); } } } else { // 只保存到数据库,显示成功信息 CString msg; if (m_sAuthorization.IsEmpty()) { msg.FormatL("授权信息已保存!\n\n序列号: %s\n口令: %s\nHMAC: %s\n\n存储位置: %s", m_sDeviceID, m_sPassword, m_sHMAC, GetLicensesPath().c_str()); } else { msg.FormatL("授权信息已保存!\n\n序列号: %s\n口令: %s\nHMAC: %s\nAuthorization: %s\n\n存储位置: %s", m_sDeviceID, m_sPassword, m_sHMAC, m_sAuthorization, GetLicensesPath().c_str()); } MessageBox(msg, _TR("保存成功"), MB_OK | MB_ICONINFORMATION); } }