校验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()
// 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 {

View File

@ -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
}
}
}
}

View File

@ -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,

View File

@ -18,10 +18,10 @@ struct SystemConfig {
//
static let channel = "appstore"
static let serverHost = "punchnet.s5s8.com"
static let serverHost = "root.punchsky.com"
// stunip
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": [

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
@ -185,6 +193,10 @@ struct LoginAccountView: View {
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

View File

@ -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)
}
@ -75,32 +77,51 @@ 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)
}