校验quic的ssl共钥匙

This commit is contained in:
anlicheng 2026-03-24 22:01:31 +08:00
parent e4a0728345
commit 6a29b8bc85
8 changed files with 140 additions and 42 deletions

View File

@ -142,14 +142,14 @@ actor SDLContextActor {
self.quicClient?.stop() self.quicClient?.stop()
// monitor // monitor
let quicClient = SDLQUICClient(host: self.config.serverIp, port: 443) let quicClient = SDLQUICClient(host: self.config.serverHost, port: 443)
quicClient.start() quicClient.start()
// quic // quic
try await quicClient.waitReady() try await quicClient.waitReady()
// quic // quic
try await Task.sleep(for: .seconds(0.2)) 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 { self.quicWorker = Task.detached {
for await message in quicClient.messageStream { for await message in quicClient.messageStream {

View File

@ -8,6 +8,8 @@
import Foundation import Foundation
import NIOCore import NIOCore
import Network import Network
import CryptoKit
import Security
// 便 // 便
enum SDLQUICError: Error { enum SDLQUICError: Error {
@ -47,8 +49,8 @@ final class SDLQUICClient {
sec_protocol_options_set_verify_block( sec_protocol_options_set_verify_block(
options.securityProtocolOptions, options.securityProtocolOptions,
{ metadata, trust, complete in { metadata, trust, complete in
// //
complete(true) // true = complete(QUICVerifier.verify(trust: trust))
}, },
self.queue self.queue
) )
@ -277,3 +279,50 @@ final class SDLQUICClient {
self.messageCont.finish() 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
}
}
}
}

View File

@ -42,6 +42,7 @@ public class SDLConfiguration {
let version: Int let version: Int
let serverIp: String let serverIp: String
let serverHost: String
let stunServers: [String] let stunServers: [String]
let noticePort: Int let noticePort: Int
@ -69,6 +70,7 @@ public class SDLConfiguration {
public init(version: Int, public init(version: Int,
serverIp: String, serverIp: String,
serverHost: String,
stunServers: [String], stunServers: [String],
clientId: String, clientId: String,
networkAddress: NetworkAddress, networkAddress: NetworkAddress,
@ -79,6 +81,7 @@ public class SDLConfiguration {
self.version = version self.version = version
self.serverIp = serverIp self.serverIp = serverIp
self.serverHost = serverHost
self.stunServers = stunServers self.stunServers = stunServers
self.clientId = clientId self.clientId = clientId
self.networkAddress = networkAddress self.networkAddress = networkAddress
@ -95,6 +98,7 @@ extension SDLConfiguration {
static func parse(options: [String: NSObject]) -> SDLConfiguration? { static func parse(options: [String: NSObject]) -> SDLConfiguration? {
guard let version = options["version"] as? Int, guard let version = options["version"] as? Int,
let serverIp = options["server_ip"] as? String, let serverIp = options["server_ip"] as? String,
let serverHost = options["server_host"] as? String,
let stunAssistIp = options["stun_assist_ip"] as? String, let stunAssistIp = options["stun_assist_ip"] as? String,
let noticePort = options["notice_port"] as? Int, let noticePort = options["notice_port"] as? Int,
let accessToken = options["access_token"] as? String, let accessToken = options["access_token"] as? String,
@ -116,6 +120,7 @@ extension SDLConfiguration {
return SDLConfiguration(version: version, return SDLConfiguration(version: version,
serverIp: serverIp, serverIp: serverIp,
serverHost: serverHost,
stunServers: stunServers, stunServers: stunServers,
clientId: clientId, clientId: clientId,
networkAddress: networkAddress, networkAddress: networkAddress,

View File

@ -18,10 +18,10 @@ struct SystemConfig {
// //
static let channel = "appstore" static let channel = "appstore"
static let serverHost = "punchnet.s5s8.com" static let serverHost = "root.punchsky.com"
// stunip // stunip
static let stunAssistHost = "punchnet.s5s8.com" static let stunAssistHost = "root.punchsky.com"
// //
static let systemInfo: String = { 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]? { 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 return nil
} }
NSLog("[SystemConfig] server_ip: \(serverIp), stun_assist_ip: \(stunAssistIp)")
let clientId = getClientId() let clientId = getClientId()
let mac = getMacAddress() let mac = getMacAddress()
@ -42,8 +45,9 @@ struct SystemConfig {
"client_id": clientId as NSObject, "client_id": clientId as NSObject,
"access_token": accessToken as NSObject, "access_token": accessToken as NSObject,
"identity_id": identityId as NSObject, "identity_id": identityId as NSObject,
"server_ip": "118.178.229.213" as NSObject, "server_ip": serverIp as NSObject,
"stun_assist_ip": "118.178.229.213" as NSObject, "server_host": serverHost as NSObject,
"stun_assist_ip": stunAssistIp as NSObject,
"hostname": hostname as NSObject, "hostname": hostname as NSObject,
"notice_port": noticePort as NSObject, "notice_port": noticePort as NSObject,
"network_address": [ "network_address": [

View File

@ -20,7 +20,10 @@ struct SDLAPIError: Error, Decodable {
struct SDLAPIClient { 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" static private let token: String = "H6p*2RfEu4ITcL"
// //
@ -73,8 +76,6 @@ struct SDLAPIClient {
return "\(key)=\(str)" return "\(key)=\(str)"
}.joined(separator: "&") }.joined(separator: "&")
NSLog("sign qs is: \(qs), sign: \(SDLUtil.hmacMD5(key: token, data: qs))")
return SDLUtil.hmacMD5(key: token, data: qs) return SDLUtil.hmacMD5(key: token, data: qs)
} }

View File

@ -15,6 +15,9 @@ class AppContext {
// "/connect" // "/connect"
var networkContext: SDLAPIClient.NetworkContext = .default() var networkContext: SDLAPIClient.NetworkContext = .default()
// vpn使
var vpnOptions: [String: NSObject]? = nil
// app // app
var loginScene: LoginScene = .login(username: nil) var loginScene: LoginScene = .login(username: nil)
@ -88,6 +91,7 @@ class AppContext {
) { ) {
try await VPNManager.shared.enableVpn(options: options) try await VPNManager.shared.enableVpn(options: options)
self.networkContext = context self.networkContext = context
self.vpnOptions = options
} }
} }

View File

@ -89,6 +89,10 @@ struct LoginAccountView: View {
@State private var password: String = "" @State private var password: String = ""
@State private var isLoading = false @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 showAlert: Bool = false
@State private var errorMessage: String = "" @State private var errorMessage: String = ""
@ -169,8 +173,12 @@ struct LoginAccountView: View {
do { do {
_ = try await appContext.loginWith(username: username, password: password) _ = try await appContext.loginWith(username: username, password: password)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
//self.appContext.appScene = .logined // 2.
openWindow(id: "logined")
// 3.
dismiss()
} }
} catch let err as SDLAPIError { } catch let err as SDLAPIError {
self.showAlert = true self.showAlert = true
self.errorMessage = err.message self.errorMessage = err.message
@ -184,6 +192,10 @@ struct LoginAccountView: View {
// MARK: - // MARK: -
struct LoginTokenView: View { struct LoginTokenView: View {
@Environment(AppContext.self) var appContext: AppContext @Environment(AppContext.self) var appContext: AppContext
// 1.
@Environment(\.dismiss) private var dismiss
@Environment(\.openWindow) private var openWindow
@State private var token = "" @State private var token = ""
@State private var isLoading = false @State private var isLoading = false
@ -230,7 +242,10 @@ struct LoginTokenView: View {
do { do {
_ = try await appContext.loginWith(token: token) _ = try await appContext.loginWith(token: token)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
//self.appContext.appScene = .logined // 2.
openWindow(id: "logined")
// 3.
dismiss()
} }
} catch let err as SDLAPIError { } catch let err as SDLAPIError {
self.showAlert = true self.showAlert = true

View File

@ -36,6 +36,8 @@ struct punchnetApp: App {
// UI // UI
@State private var showPrivacy: Bool = false @State private var showPrivacy: Bool = false
@State private var vpnManager = VPNManager.shared
init() { init() {
self.noticeServer = UDPNoticeCenterServer() self.noticeServer = UDPNoticeCenterServer()
self.noticeServer.start() self.noticeServer.start()
@ -52,15 +54,15 @@ struct punchnetApp: App {
} }
.sheet(isPresented: $showPrivacy) { .sheet(isPresented: $showPrivacy) {
PrivacyDetailView(showPrivacy: $showPrivacy) PrivacyDetailView(showPrivacy: $showPrivacy)
//.interactiveDismissDisabled() // .interactiveDismissDisabled() //
} }
} }
.windowResizability(.contentSize)
.windowToolbarStyle(.unified) .windowToolbarStyle(.unified)
.windowResizability(.contentSize)
.defaultPosition(.center) .defaultPosition(.center)
Window("网络", id: "logined") { Window("网络", id: "logined") {
SettingsView() NetworkView()
.environment(self.appContext) .environment(self.appContext)
.frame(width: 750, height: 500) .frame(width: 750, height: 500)
} }
@ -74,33 +76,52 @@ struct punchnetApp: App {
} }
.windowResizability(.contentSize) .windowResizability(.contentSize)
.defaultPosition(.center) .defaultPosition(.center)
// MenuBarExtra("punchnet", image: "logo_32") {
// MenuBarExtra("punchnet", image: "logo_32") { VStack {
// VStack {
// Button(action: { switch self.vpnManager.vpnStatus {
// self.menuClick() case .connected:
// }, label: { Button(action: {
// Text("") Task { @MainActor in
// }) try await vpnManager.disableVpn()
// }
// Divider() }, label: {
// Text("停止")
// Button(action: { })
// NSApplication.shared.terminate(nil) case .disconnected:
// }, label: { Text("退") }) Button(action: {
// Task { @MainActor in
// } await self.startVPN()
// } }
// .menuBarExtraStyle(.menu) }, 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 // APP
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
@ -113,12 +134,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
Task { Task {
do { do {
try await VPNManager.shared.disableVpn() try await VPNManager.shared.disableVpn()
NSLog("vpn disabled")
} catch let err { } catch let err {
print("退出时关闭 VPN 失败: \(err)") NSLog("退出时关闭 VPN 失败: \(err)")
} }
print("call me here termi")
await MainActor.run { await MainActor.run {
sender.reply(toApplicationShouldTerminate: true) sender.reply(toApplicationShouldTerminate: true)
} }