| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 |
- import Foundation
- import Security
- import CryptoKit
- import Darwin
- // This helper's signed identity owns each Keychain item. Plaintext travels only
- // on private stdin/stdout, never in command arguments or logs.
- func fail(_ message: String) -> Never {
- FileHandle.standardError.write(Data((message + "\n").utf8)); exit(1)
- }
- #if !DEVELOPMENT_HELPER
- // The distributed helper accepts only its containing, validly signed App with
- // the same signing team. Copying the helper or invoking it from a shell fails.
- @_silgen_name("proc_pidpath")
- func processPath(_ pid: Int32, _ buffer: UnsafeMutableRawPointer, _ size: UInt32) -> Int32
- var parentPath = [CChar](repeating: 0, count: 4096)
- let pathLength = parentPath.withUnsafeMutableBytes { processPath(getppid(), $0.baseAddress!, UInt32($0.count)) }
- guard pathLength > 0 else { fail("无法核对 App 身份") }
- let parentURL = URL(fileURLWithPath: String(cString: parentPath)).resolvingSymlinksInPath()
- let expected = parentURL.deletingLastPathComponent().appendingPathComponent("../Resources/runtime/keychain-helper").standardizedFileURL.resolvingSymlinksInPath()
- guard expected.path == URL(fileURLWithPath: CommandLine.arguments[0]).resolvingSymlinksInPath().path else { fail("加密助手只允许所属 App 调用") }
- var parentCode: SecCode?, ownCode: SecCode?
- guard SecCodeCopyGuestWithAttributes(nil, [kSecGuestAttributePid as String: getppid()] as CFDictionary, [], &parentCode) == errSecSuccess,
- SecCodeCopySelf([], &ownCode) == errSecSuccess, let parentCode, let ownCode,
- SecCodeCheckValidity(parentCode, [], nil) == errSecSuccess else { fail("App 签名无效") }
- func signingInfo(_ code: SecCode) -> [String: Any] {
- var info: CFDictionary?
- var staticCode: SecStaticCode?
- guard SecCodeCopyStaticCode(code, [], &staticCode) == errSecSuccess, let staticCode else { fail("无法读取签名身份") }
- guard SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &info) == errSecSuccess else { fail("无法核对签名身份") }
- return info as? [String: Any] ?? [:]
- }
- let parentInfo = signingInfo(parentCode), ownInfo = signingInfo(ownCode)
- guard let team = ownInfo[kSecCodeInfoTeamIdentifier as String] as? String,
- parentInfo[kSecCodeInfoTeamIdentifier as String] as? String == team,
- // The client keeps the historical identifier for upgrade continuity;
- // the full dual-mode build moved to its own identifier.
- ["cn.samien.yaoyao.desktop", "cn.samien.yaoyao.desktop.full"]
- .contains(parentInfo[kSecCodeInfoIdentifier as String] as? String ?? "") else { fail("App 签名身份不匹配") }
- #endif
- guard CommandLine.arguments.count == 3 else { fail("钥匙串请求格式无效") }
- let operation = CommandLine.arguments[1], account = CommandLine.arguments[2]
- guard ["encrypt", "decrypt"].contains(operation), account.range(of: "^[a-f0-9]{64}$", options: .regularExpression) != nil else { fail("钥匙串请求格式无效") }
- let service = "cn.samien.yaoyao.runner-key.v1"
- let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account]
- func loadKey(create: Bool) -> Data {
- var result: CFTypeRef?
- let status = SecItemCopyMatching(query.merging([kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne]) { _, next in next } as CFDictionary, &result)
- if status == errSecSuccess, let data = result as? Data, data.count == 32 { return data }
- guard status == errSecItemNotFound, create else { fail("无法读取当前数据目录的加密密钥,请检查系统钥匙串") }
- var key = Data(count: 32)
- guard key.withUnsafeMutableBytes({ SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!) }) == errSecSuccess else { fail("无法创建加密密钥") }
- let added = SecItemAdd(query.merging([kSecValueData as String: key, kSecAttrLabel as String: "夭夭 Runner 配置密钥"]) { _, next in next } as CFDictionary, nil)
- if added == errSecDuplicateItem { return loadKey(create: false) }
- guard added == errSecSuccess else { fail("无法保存加密密钥,请检查系统钥匙串") }
- return key
- }
- let input = FileHandle.standardInput.readDataToEndOfFile()
- guard input.count <= 1_048_576 else { fail("配置超过加密大小上限") }
- do {
- let key = SymmetricKey(data: loadKey(create: operation == "encrypt"))
- let output: Data
- if operation == "encrypt" {
- guard let combined = try AES.GCM.seal(input, using: key).combined else { fail("配置加密失败") }
- output = combined
- } else { output = try AES.GCM.open(AES.GCM.SealedBox(combined: input), using: key) }
- FileHandle.standardOutput.write(output)
- } catch { fail("配置加解密失败,原配置保留") }
|