校验quic的ssl共钥匙
This commit is contained in:
parent
e4a0728345
commit
6a29b8bc85
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
// stun探测辅助服务器ip
|
// stun探测辅助服务器ip
|
||||||
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": [
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user