校验quic的ssl共钥匙
This commit is contained in:
parent
e4a0728345
commit
6a29b8bc85
@ -142,14 +142,14 @@ actor SDLContextActor {
|
||||
self.quicClient?.stop()
|
||||
|
||||
// 启动monitor
|
||||
let quicClient = SDLQUICClient(host: self.config.serverIp, port: 443)
|
||||
let quicClient = SDLQUICClient(host: self.config.serverHost, port: 443)
|
||||
quicClient.start()
|
||||
|
||||
// 等待quic准备好
|
||||
try await quicClient.waitReady()
|
||||
// 这里必须等待quic的协商完成
|
||||
try await Task.sleep(for: .seconds(0.2))
|
||||
SDLLogger.shared.log("[SDLContext] start quic client ready")
|
||||
SDLLogger.shared.log("[SDLContext] start quic client: \(self.config.serverHost)")
|
||||
|
||||
self.quicWorker = Task.detached {
|
||||
for await message in quicClient.messageStream {
|
||||
|
||||
@ -8,6 +8,8 @@
|
||||
import Foundation
|
||||
import NIOCore
|
||||
import Network
|
||||
import CryptoKit
|
||||
import Security
|
||||
|
||||
// 定义错误类型,便于上层处理
|
||||
enum SDLQUICError: Error {
|
||||
@ -47,8 +49,8 @@ final class SDLQUICClient {
|
||||
sec_protocol_options_set_verify_block(
|
||||
options.securityProtocolOptions,
|
||||
{ metadata, trust, complete in
|
||||
// 你可以自己决定是否信任
|
||||
complete(true) // true = 接受证书
|
||||
// 执行公钥校验
|
||||
complete(QUICVerifier.verify(trust: trust))
|
||||
},
|
||||
self.queue
|
||||
)
|
||||
@ -277,3 +279,50 @@ final class SDLQUICClient {
|
||||
self.messageCont.finish()
|
||||
}
|
||||
}
|
||||
|
||||
extension SDLQUICClient {
|
||||
|
||||
enum QUICVerifier {
|
||||
// 你的 Base64 公钥指纹
|
||||
static let pinnedPublicKeyHashes = [
|
||||
"Z2S0VNhlCqelkqTXxZNY39icMu622SfKhxXi3qi5fFA="
|
||||
]
|
||||
|
||||
static func verify(trust: sec_trust_t) -> Bool {
|
||||
let secTrust = sec_trust_copy_ref(trust).takeRetainedValue()
|
||||
|
||||
// --- 修复部分:使用 macOS 12+ 的新 API ---
|
||||
// SecTrustCopyCertificateChain 会返回一个 CFArray,包含整个证书链
|
||||
guard let chain = SecTrustCopyCertificateChain(secTrust) as? [SecCertificate],
|
||||
let leafCertificate = chain.first else {
|
||||
SDLLogger.shared.log("❌ 无法获取证书链或叶子证书")
|
||||
return false
|
||||
}
|
||||
|
||||
// 提取公钥
|
||||
guard let publicKey = SecCertificateCopyKey(leafCertificate) else {
|
||||
SDLLogger.shared.log("❌ 无法从证书中提取公钥")
|
||||
return false
|
||||
}
|
||||
|
||||
// 导出公钥原始数据 (SubjectPublicKeyInfo)
|
||||
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? else {
|
||||
SDLLogger.shared.log("❌ 无法导出公钥数据")
|
||||
return false
|
||||
}
|
||||
|
||||
// 计算哈希并比对
|
||||
let hash = SHA256.hash(data: publicKeyData)
|
||||
let hashBase64 = Data(hash).base64EncodedString()
|
||||
|
||||
if pinnedPublicKeyHashes.contains(hashBase64) {
|
||||
SDLLogger.shared.log("✅ 公钥校验通过")
|
||||
return true
|
||||
} else {
|
||||
SDLLogger.shared.log("⚠️ 公钥不匹配! 收到: \(hashBase64)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ public class SDLConfiguration {
|
||||
let version: Int
|
||||
|
||||
let serverIp: String
|
||||
let serverHost: String
|
||||
let stunServers: [String]
|
||||
|
||||
let noticePort: Int
|
||||
@ -69,6 +70,7 @@ public class SDLConfiguration {
|
||||
|
||||
public init(version: Int,
|
||||
serverIp: String,
|
||||
serverHost: String,
|
||||
stunServers: [String],
|
||||
clientId: String,
|
||||
networkAddress: NetworkAddress,
|
||||
@ -79,6 +81,7 @@ public class SDLConfiguration {
|
||||
|
||||
self.version = version
|
||||
self.serverIp = serverIp
|
||||
self.serverHost = serverHost
|
||||
self.stunServers = stunServers
|
||||
self.clientId = clientId
|
||||
self.networkAddress = networkAddress
|
||||
@ -95,6 +98,7 @@ extension SDLConfiguration {
|
||||
static func parse(options: [String: NSObject]) -> SDLConfiguration? {
|
||||
guard let version = options["version"] as? Int,
|
||||
let serverIp = options["server_ip"] as? String,
|
||||
let serverHost = options["server_host"] as? String,
|
||||
let stunAssistIp = options["stun_assist_ip"] as? String,
|
||||
let noticePort = options["notice_port"] as? Int,
|
||||
let accessToken = options["access_token"] as? String,
|
||||
@ -116,6 +120,7 @@ extension SDLConfiguration {
|
||||
|
||||
return SDLConfiguration(version: version,
|
||||
serverIp: serverIp,
|
||||
serverHost: serverHost,
|
||||
stunServers: stunServers,
|
||||
clientId: clientId,
|
||||
networkAddress: networkAddress,
|
||||
|
||||
@ -18,10 +18,10 @@ struct SystemConfig {
|
||||
// 渠道相关
|
||||
static let channel = "appstore"
|
||||
|
||||
static let serverHost = "punchnet.s5s8.com"
|
||||
static let serverHost = "root.punchsky.com"
|
||||
|
||||
// stun探测辅助服务器ip
|
||||
static let stunAssistHost = "punchnet.s5s8.com"
|
||||
static let stunAssistHost = "root.punchsky.com"
|
||||
|
||||
// 获取系统信息
|
||||
static let systemInfo: String = {
|
||||
@ -30,10 +30,13 @@ struct SystemConfig {
|
||||
}()
|
||||
|
||||
static func getOptions(networkId: UInt32, networkDomain: String, ip: String, maskLen: UInt8, accessToken: String, identityId: UInt32, hostname: String, noticePort: Int) -> [String: NSObject]? {
|
||||
guard let serverIp = DNSResolver.resolveAddrInfos(serverHost).first else {
|
||||
guard let serverIp = DNSResolver.resolveAddrInfos(serverHost).first,
|
||||
let stunAssistIp = DNSResolver.resolveAddrInfos(stunAssistHost).first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
NSLog("[SystemConfig] server_ip: \(serverIp), stun_assist_ip: \(stunAssistIp)")
|
||||
|
||||
let clientId = getClientId()
|
||||
let mac = getMacAddress()
|
||||
|
||||
@ -42,8 +45,9 @@ struct SystemConfig {
|
||||
"client_id": clientId as NSObject,
|
||||
"access_token": accessToken as NSObject,
|
||||
"identity_id": identityId as NSObject,
|
||||
"server_ip": "118.178.229.213" as NSObject,
|
||||
"stun_assist_ip": "118.178.229.213" as NSObject,
|
||||
"server_ip": serverIp as NSObject,
|
||||
"server_host": serverHost as NSObject,
|
||||
"stun_assist_ip": stunAssistIp as NSObject,
|
||||
"hostname": hostname as NSObject,
|
||||
"notice_port": noticePort as NSObject,
|
||||
"network_address": [
|
||||
|
||||
@ -20,7 +20,10 @@ struct SDLAPIError: Error, Decodable {
|
||||
|
||||
struct SDLAPIClient {
|
||||
|
||||
static var baseUrl: String = "https://punchnet.s5s8.com/api"
|
||||
static var baseUrl: String {
|
||||
return "https://\(SystemConfig.serverHost)/api"
|
||||
}
|
||||
|
||||
static private let token: String = "H6p*2RfEu4ITcL"
|
||||
|
||||
// 基础参数信息
|
||||
@ -73,8 +76,6 @@ struct SDLAPIClient {
|
||||
return "\(key)=\(str)"
|
||||
}.joined(separator: "&")
|
||||
|
||||
NSLog("sign qs is: \(qs), sign: \(SDLUtil.hmacMD5(key: token, data: qs))")
|
||||
|
||||
return SDLUtil.hmacMD5(key: token, data: qs)
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,9 @@ class AppContext {
|
||||
// 调用 "/connect" 之后的网络信息
|
||||
var networkContext: SDLAPIClient.NetworkContext = .default()
|
||||
|
||||
// 保存当前登陆vpn使用的配置项
|
||||
var vpnOptions: [String: NSObject]? = nil
|
||||
|
||||
// 当前app所处的场景
|
||||
var loginScene: LoginScene = .login(username: nil)
|
||||
|
||||
@ -88,6 +91,7 @@ class AppContext {
|
||||
) {
|
||||
try await VPNManager.shared.enableVpn(options: options)
|
||||
self.networkContext = context
|
||||
self.vpnOptions = options
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -89,6 +89,10 @@ struct LoginAccountView: View {
|
||||
@State private var password: String = ""
|
||||
@State private var isLoading = false
|
||||
|
||||
// 1. 引入环境操作
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
|
||||
// 错误提示
|
||||
@State private var showAlert: Bool = false
|
||||
@State private var errorMessage: String = ""
|
||||
@ -169,8 +173,12 @@ struct LoginAccountView: View {
|
||||
do {
|
||||
_ = try await appContext.loginWith(username: username, password: password)
|
||||
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
|
||||
//self.appContext.appScene = .logined
|
||||
// 2. 先打开新窗口(顺序通常不影响,但先打开更稳妥)
|
||||
openWindow(id: "logined")
|
||||
// 3. 销毁当前窗口
|
||||
dismiss()
|
||||
}
|
||||
|
||||
} catch let err as SDLAPIError {
|
||||
self.showAlert = true
|
||||
self.errorMessage = err.message
|
||||
@ -184,6 +192,10 @@ struct LoginAccountView: View {
|
||||
// MARK: - 密钥登录组件
|
||||
struct LoginTokenView: View {
|
||||
@Environment(AppContext.self) var appContext: AppContext
|
||||
|
||||
// 1. 引入环境操作
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
|
||||
@State private var token = ""
|
||||
@State private var isLoading = false
|
||||
@ -230,7 +242,10 @@ struct LoginTokenView: View {
|
||||
do {
|
||||
_ = try await appContext.loginWith(token: token)
|
||||
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
|
||||
//self.appContext.appScene = .logined
|
||||
// 2. 先打开新窗口(顺序通常不影响,但先打开更稳妥)
|
||||
openWindow(id: "logined")
|
||||
// 3. 销毁当前窗口
|
||||
dismiss()
|
||||
}
|
||||
} catch let err as SDLAPIError {
|
||||
self.showAlert = true
|
||||
|
||||
@ -36,6 +36,8 @@ struct punchnetApp: App {
|
||||
// UI 控制状态:是否显示弹窗
|
||||
@State private var showPrivacy: Bool = false
|
||||
|
||||
@State private var vpnManager = VPNManager.shared
|
||||
|
||||
init() {
|
||||
self.noticeServer = UDPNoticeCenterServer()
|
||||
self.noticeServer.start()
|
||||
@ -52,15 +54,15 @@ struct punchnetApp: App {
|
||||
}
|
||||
.sheet(isPresented: $showPrivacy) {
|
||||
PrivacyDetailView(showPrivacy: $showPrivacy)
|
||||
//.interactiveDismissDisabled() // 强制阅读
|
||||
.interactiveDismissDisabled() // 强制阅读
|
||||
}
|
||||
}
|
||||
.windowResizability(.contentSize)
|
||||
.windowToolbarStyle(.unified)
|
||||
.windowResizability(.contentSize)
|
||||
.defaultPosition(.center)
|
||||
|
||||
Window("网络", id: "logined") {
|
||||
SettingsView()
|
||||
NetworkView()
|
||||
.environment(self.appContext)
|
||||
.frame(width: 750, height: 500)
|
||||
}
|
||||
@ -74,33 +76,52 @@ struct punchnetApp: App {
|
||||
}
|
||||
.windowResizability(.contentSize)
|
||||
.defaultPosition(.center)
|
||||
|
||||
//
|
||||
// MenuBarExtra("punchnet", image: "logo_32") {
|
||||
// VStack {
|
||||
// Button(action: {
|
||||
// self.menuClick()
|
||||
// }, label: {
|
||||
// Text("启动")
|
||||
// })
|
||||
//
|
||||
// Divider()
|
||||
//
|
||||
// Button(action: {
|
||||
// NSApplication.shared.terminate(nil)
|
||||
// }, label: { Text("退出") })
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// .menuBarExtraStyle(.menu)
|
||||
|
||||
MenuBarExtra("punchnet", image: "logo_32") {
|
||||
VStack {
|
||||
|
||||
switch self.vpnManager.vpnStatus {
|
||||
case .connected:
|
||||
Button(action: {
|
||||
Task { @MainActor in
|
||||
try await vpnManager.disableVpn()
|
||||
}
|
||||
}, label: {
|
||||
Text("停止")
|
||||
})
|
||||
case .disconnected:
|
||||
Button(action: {
|
||||
Task { @MainActor in
|
||||
await self.startVPN()
|
||||
}
|
||||
}, label: {
|
||||
Text("启动")
|
||||
})
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button(action: {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}, label: {
|
||||
Text("退出应用")
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
.menuBarExtraStyle(.menu)
|
||||
}
|
||||
|
||||
private func menuClick() {
|
||||
|
||||
private func startVPN() async {
|
||||
if let options = appContext.vpnOptions {
|
||||
try? await vpnManager.enableVpn(options: options)
|
||||
} else {
|
||||
openWindow(id: "login")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 处理APP的生命周期
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@ -113,12 +134,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
Task {
|
||||
do {
|
||||
try await VPNManager.shared.disableVpn()
|
||||
NSLog("vpn disabled")
|
||||
} catch let err {
|
||||
print("退出时关闭 VPN 失败: \(err)")
|
||||
NSLog("退出时关闭 VPN 失败: \(err)")
|
||||
}
|
||||
|
||||
print("call me here termi")
|
||||
|
||||
await MainActor.run {
|
||||
sender.reply(toApplicationShouldTerminate: true)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user