fix: 优化启动引导、坐标处理和日志管理

This commit is contained in:
xweiba
2026-08-06 16:23:09 +08:00
parent 0e8e83fd33
commit e86909cc2f
30 changed files with 2296 additions and 857 deletions
+153 -21
View File
@@ -1,46 +1,178 @@
import Foundation
import Security
protocol CertificateAuthorityKeychain {
func load() throws -> CertificateAuthority?
func save(_ authority: CertificateAuthority) throws
func remove() throws
}
enum CertificateAuthorityStoreError: LocalizedError {
case invalidAuthority
case keychain(OSStatus)
var errorDescription: String? {
switch self {
case .invalidAuthority: return "本地 CA 证书或私钥无效"
case let .keychain(status): return "无法写入设备钥匙串(\(status))"
}
}
}
final class DeviceCertificateAuthorityKeychain: CertificateAuthorityKeychain {
private enum Item {
static let service = "com.paopaolabs.location-spoofer.certificate-authority"
static let certificateAccount = "root-ca-certificate"
static let keyAccount = "root-ca-private-key"
}
func load() throws -> CertificateAuthority? {
guard let certPEM = try load(account: Item.certificateAccount),
let keyPEM = try load(account: Item.keyAccount) else {
return nil
}
return CertificateAuthority(certPEM: certPEM, keyPEM: keyPEM)
}
func save(_ authority: CertificateAuthority) throws {
try remove()
do {
try save(authority.certPEM, account: Item.certificateAccount)
try save(authority.keyPEM, account: Item.keyAccount)
} catch {
try? remove()
throw error
}
}
func remove() throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Item.service,
kSecAttrSynchronizable as String: kCFBooleanFalse as Any,
]
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw CertificateAuthorityStoreError.keychain(status)
}
}
private func load(account: String) throws -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Item.service,
kSecAttrAccount as String: account,
kSecAttrSynchronizable as String: kCFBooleanFalse as Any,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
]
var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecItemNotFound { return nil }
guard status == errSecSuccess, let data = result as? Data,
let value = String(data: data, encoding: .utf8) else {
throw CertificateAuthorityStoreError.keychain(status)
}
return value
}
private func save(_ value: String, account: String) throws {
guard let data = value.data(using: .utf8) else {
throw CocoaError(.fileWriteInapplicableStringEncoding)
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Item.service,
kSecAttrAccount as String: account,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kSecAttrSynchronizable as String: kCFBooleanFalse as Any,
kSecValueData as String: data,
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw CertificateAuthorityStoreError.keychain(status) }
}
}
final class CertificateAuthorityStore {
private let directory: URL
private let generator: () throws -> CertificateAuthority
private let validator: (CertificateAuthority) -> Bool
private let keychain: CertificateAuthorityKeychain
private let certificateURL: URL
private let keyURL: URL
init(directory: URL = AppGroup.containerURL.appendingPathComponent("CertificateAuthority", isDirectory: true), generator: @escaping () throws -> CertificateAuthority = CoreBridge.generateCertificateAuthority) {
init(
directory: URL = AppGroup.containerURL.appendingPathComponent("CertificateAuthority", isDirectory: true),
keychain: CertificateAuthorityKeychain = DeviceCertificateAuthorityKeychain(),
generator: @escaping () throws -> CertificateAuthority = CoreBridge.generateCertificateAuthority,
validator: @escaping (CertificateAuthority) -> Bool = CoreBridge.isValidCertificateAuthority
) {
self.directory = directory
self.keychain = keychain
self.generator = generator
self.validator = validator
self.certificateURL = directory.appendingPathComponent("ca-cert.pem")
self.keyURL = directory.appendingPathComponent("ca-key.pem")
}
func ensure() throws -> CertificateAuthority {
if let current = try load() {
RuntimeLogger.debug("SHARED", "Certificate.store", "读取已有 CA 文件", details: ["directory": directory.path])
return current
if let authority = try loadValidKeychainAuthority() {
removeLegacyFilesBestEffort()
RuntimeLogger.debug("SHARED", "Certificate.store", "复用设备钥匙串中的 CA")
return authority
}
RuntimeLogger.info("SHARED", "Certificate.store", "未找到 CA 文件,开始生成", details: ["directory": directory.path])
if let legacy = try loadLegacyAuthority(), validator(legacy) {
try keychain.save(legacy)
removeLegacyFilesBestEffort()
RuntimeLogger.info("SHARED", "Certificate.store", "旧 CA 已迁移到设备钥匙串")
return legacy
}
let authority = try generator()
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
guard let certificateData = authority.certPEM.data(using: .utf8),
let keyData = authority.keyPEM.data(using: .utf8) else {
throw CocoaError(.fileWriteInapplicableStringEncoding)
}
try certificateData.write(to: certificateURL, options: .atomic)
try keyData.write(to: keyURL, options: .atomic)
applyCompleteProtection(to: certificateURL)
applyCompleteProtection(to: keyURL)
RuntimeLogger.info("SHARED", "Certificate.store", "CA 文件写入完成", details: ["directory": directory.path])
guard validator(authority) else { throw CertificateAuthorityStoreError.invalidAuthority }
try keychain.save(authority)
removeLegacyFilesBestEffort()
RuntimeLogger.info("SHARED", "Certificate.store", "已生成并保存设备专属 CA")
return authority
}
func load() throws -> CertificateAuthority? {
guard FileManager.default.fileExists(atPath: certificateURL.path), FileManager.default.fileExists(atPath: keyURL.path) else { return nil }
return CertificateAuthority(certPEM: try String(contentsOf: certificateURL, encoding: .utf8), keyPEM: try String(contentsOf: keyURL, encoding: .utf8))
try loadValidKeychainAuthority()
}
private func applyCompleteProtection(to url: URL) {
#if os(iOS)
try? FileManager.default.setAttributes([.protectionKey: FileProtectionType.complete], ofItemAtPath: url.path)
#endif
private func loadValidKeychainAuthority() throws -> CertificateAuthority? {
guard let authority = try keychain.load() else { return nil }
guard validator(authority) else {
RuntimeLogger.warning("SHARED", "Certificate.store", "钥匙串中的 CA 无效,准备回退")
try? keychain.remove()
return nil
}
return authority
}
private func loadLegacyAuthority() throws -> CertificateAuthority? {
guard FileManager.default.fileExists(atPath: certificateURL.path),
FileManager.default.fileExists(atPath: keyURL.path) else {
return nil
}
return CertificateAuthority(
certPEM: try String(contentsOf: certificateURL, encoding: .utf8),
keyPEM: try String(contentsOf: keyURL, encoding: .utf8)
)
}
private func removeLegacyFilesBestEffort() {
let fileManager = FileManager.default
// Remove private material first. Each item is retried on later launches
// when a valid Keychain authority is available.
for url in [keyURL, certificateURL] where fileManager.fileExists(atPath: url.path) {
do {
try fileManager.removeItem(at: url)
} catch {
RuntimeLogger.error("SHARED", "Certificate.store", "删除旧 CA 文件失败,将在下次启动重试", error: error)
}
}
try? fileManager.removeItem(at: directory)
}
}