#include "stdafx.h" #include "TlsCertificateDlg.h" #include "LangManager.h" // _TR / MessageBoxL #include "2015Remote.h" // THIS_CFG (GetThisCfg) #include "ServerTlsSession.h" // ServerTlsIdentity::Load / IsLoadError #include "common/iniFile.h" // config::GetInt/GetStr/SetInt/SetStr #include #include #ifdef _DEBUG #define new DEBUG_NEW #endif namespace { // 构造一个无控件的 DLGTEMPLATE:cdit=0,控件由 OnInitDialog 动态创建。 // (与 McpSettingsDlg.cpp / ZstaPickerDlg.cpp 内同名函数一致) void BuildDialogTemplate(std::vector& out, LPCWSTR caption, short cx, short cy) { out.clear(); auto append = [&](const void* p, size_t n) { const BYTE* b = (const BYTE*)p; out.insert(out.end(), b, b + n); }; auto appendW = [&](WORD v) { out.push_back((BYTE)(v & 0xFF)); out.push_back((BYTE)((v >> 8) & 0xFF)); }; auto appendWStr = [&](LPCWSTR s) { size_t n = wcslen(s); append(s, (n + 1) * sizeof(WCHAR)); }; DLGTEMPLATE dt = { 0 }; dt.style = DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU; dt.dwExtendedStyle = 0; dt.cdit = 0; dt.x = 0; dt.y = 0; dt.cx = cx; dt.cy = cy; append(&dt, sizeof(dt)); appendW(0); // no menu appendW(0); // default dialog class appendWStr(caption); // caption appendW(8); // font point size appendWStr(L"MS Shell Dlg"); // typeface while (out.size() % 4) out.push_back(0); // DWORD align } } // namespace BEGIN_MESSAGE_MAP(CTlsCertificateDlg, CDialog) ON_BN_CLICKED(IDC_TLS_CERT_BROWSE, &CTlsCertificateDlg::OnBrowseCert) ON_BN_CLICKED(IDC_TLS_KEY_BROWSE, &CTlsCertificateDlg::OnBrowseKey) END_MESSAGE_MAP() CTlsCertificateDlg::CTlsCertificateDlg(CWnd* parent) : CDialog((LPCTSTR)NULL, parent) { } INT_PTR CTlsCertificateDlg::DoModal() { USES_CONVERSION; CString title = _TR("TLS证书"); BuildDialogTemplate(m_Template, T2CW(title), 340, 180); InitModalIndirect((LPCDLGTEMPLATE)m_Template.data()); return CDialog::DoModal(); } BOOL CTlsCertificateDlg::OnInitDialog() { CDialog::OnInitDialog(); CRect cli; GetClientRect(&cli); // 占位 rect,真正布局在 LayoutControls 里 CRect r0(0, 0, 10, 10); m_btnEnable.Create(_TR("启用加密"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, r0, this, IDC_TLS_ENABLE); m_lblCert.Create(_TR("证书路径"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1); m_editCert.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, r0, this, IDC_TLS_CERT); m_btnCertBrowse.Create(_TR("浏览"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, r0, this, IDC_TLS_CERT_BROWSE); m_lblKey.Create(_TR("私钥路径"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1); m_editKey.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, r0, this, IDC_TLS_KEY); m_btnKeyBrowse.Create(_TR("浏览"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, r0, this, IDC_TLS_KEY_BROWSE); m_btnOK.Create(_TR("确定"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, r0, this, IDOK); m_btnCancel.Create(_TR("取消"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, r0, this, IDCANCEL); // 子控件继承对话框字体 (DS_SETFONT) HFONT hFont = (HFONT)::SendMessage(GetSafeHwnd(), WM_GETFONT, 0, 0); if (hFont) { m_btnEnable.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_lblCert.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_editCert.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnCertBrowse.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_lblKey.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_editKey.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnKeyBrowse.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); m_btnCancel.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0)); } // 回填配置 int enabled = THIS_CFG.GetInt("tls", "Enabled", 0); std::string cert = THIS_CFG.GetStr("tls", "CertPath", ""); std::string key = THIS_CFG.GetStr("tls", "KeyPath", ""); m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED); m_editCert.SetWindowText(CString(cert.c_str())); m_editKey.SetWindowText(CString(key.c_str())); LayoutControls(cli.Width(), cli.Height()); return TRUE; } void CTlsCertificateDlg::OnOK() { CString sCert, sKey; m_editCert.GetWindowText(sCert); m_editKey.GetWindowText(sKey); bool enabled = (m_btnEnable.GetCheck() == BST_CHECKED); std::string cert = CT2A(sCert); std::string key = CT2A(sKey); // 校验:启用加密时必须填写证书/私钥路径 if (enabled && cert.empty()) { MessageBoxL("请填写证书路径", "提示", MB_ICONWARNING); return; } if (enabled && key.empty()) { MessageBoxL("请填写私钥路径", "提示", MB_ICONWARNING); return; } // 落盘 [tls] 三键 THIS_CFG.SetInt("tls", "Enabled", enabled ? 1 : 0); THIS_CFG.SetStr("tls", "CertPath", cert); THIS_CFG.SetStr("tls", "KeyPath", key); // 热重载身份(设计文档 §7.1):开关与证书/私钥路径对新建连接即时生效。 // 已知窄竞态:Load 内部 FreeIdentity 与「已取 Cred 副本、尚在握手」的连接存在竞态, // 未来热切换需引用计数 + 惰性释放(见 ServerTlsSession.cpp FreeIdentity 注释),本次不做。 ServerTlsIdentity::Load(THIS_CFG); if (ServerTlsIdentity::IsLoadError()) { MessageBoxL("证书/私钥加载失败,加密功能未生效。请检查文件路径与格式(证书 PEM、私钥 PKCS#8)。", "提示", MB_ICONWARNING); } else { MessageBox(_TR("TLS 配置已保存。") + _T("\r\n") + _TR("改动对新建连接即时生效。"), _TR("提示"), MB_ICONINFORMATION); } CDialog::OnOK(); } void CTlsCertificateDlg::OnBrowseCert() { CFileDialog dlg(TRUE, _T("crt"), nullptr, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, _T("Certificate Files (*.crt;*.pem;*.cer;*.cert)|*.crt;*.pem;*.cer;*.cert|All Files (*.*)|*.*||"), this); dlg.m_ofn.lpstrTitle = _T("Select Certificate"); if (dlg.DoModal() == IDOK) { m_editCert.SetWindowText(dlg.GetPathName()); } } void CTlsCertificateDlg::OnBrowseKey() { 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_editKey.SetWindowText(dlg.GetPathName()); } } void CTlsCertificateDlg::LayoutControls(int cx, int cy) { const int margin = 14; const int labelW = 72; const int rowH = 24; const int gap = 10; const int browseW = 64; const int btnW = 88; const int btnH = 26; int y = margin; m_btnEnable.MoveWindow(margin, y, cx - margin * 2, 22); y += 30; int editW = cx - margin * 2 - labelW - browseW - gap; m_lblCert.MoveWindow(margin, y, labelW, rowH); m_editCert.MoveWindow(margin + labelW, y - 2, editW, rowH); m_btnCertBrowse.MoveWindow(cx - margin - browseW, y - 2, browseW, rowH); y += rowH + gap; m_lblKey.MoveWindow(margin, y, labelW, rowH); m_editKey.MoveWindow(margin + labelW, y - 2, editW, rowH); m_btnKeyBrowse.MoveWindow(cx - margin - browseW, y - 2, browseW, rowH); y += rowH + gap; // 确定 / 取消按钮紧随私钥路径下方 m_btnCancel.MoveWindow(cx - margin - btnW, y, btnW, btnH); m_btnOK.MoveWindow(cx - margin - btnW * 2 - gap, y, btnW, btnH); y += btnH + margin; // 收缩对话框到内容实际高度并重新居中,消除底部空白。 if (y < cy) { CRect wnd; GetWindowRect(&wnd); SetWindowPos(NULL, 0, 0, wnd.Width(), y + (wnd.Height() - cy), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); CenterWindow(); } }