diff options
Diffstat (limited to 'Sources/Shared/Keychain.swift')
-rw-r--r-- | Sources/Shared/Keychain.swift | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/Sources/Shared/Keychain.swift b/Sources/Shared/Keychain.swift new file mode 100644 index 0000000..2e0e7f0 --- /dev/null +++ b/Sources/Shared/Keychain.swift @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved. + +import Foundation +import Security + +class Keychain { + static func openReference(called ref: Data) -> String? { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecValuePersistentRef: ref, + kSecReturnData: true] as CFDictionary, + &result) + if ret != errSecSuccess || result == nil { + wg_log(.error, message: "Unable to open config from keychain: \(ret)") + return nil + } + guard let data = result as? Data else { return nil } + return String(data: data, encoding: String.Encoding.utf8) + } + + static func makeReference(containing value: String, called name: String, previouslyReferencedBy oldRef: Data? = nil) -> Data? { + var ret: OSStatus + guard var bundleIdentifier = Bundle.main.bundleIdentifier else { + wg_log(.error, staticMessage: "Unable to determine bundle identifier") + return nil + } + if bundleIdentifier.hasSuffix(".network-extension") { + bundleIdentifier.removeLast(".network-extension".count) + } + let itemLabel = "WireGuard Tunnel: \(name)" + var items: [CFString: Any] = [kSecClass: kSecClassGenericPassword, + kSecAttrLabel: itemLabel, + kSecAttrAccount: name + ": " + UUID().uuidString, + kSecAttrDescription: "wg-quick(8) config", + kSecAttrService: bundleIdentifier, + kSecValueData: value.data(using: .utf8) as Any, + kSecReturnPersistentRef: true] + + #if os(iOS) + items[kSecAttrAccessGroup] = FileManager.appGroupId + items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock + #elseif os(macOS) + items[kSecAttrSynchronizable] = false + items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + + guard let extensionPath = Bundle.main.builtInPlugInsURL?.appendingPathComponent("WireGuardNetworkExtension.appex", isDirectory: true).path else { + wg_log(.error, staticMessage: "Unable to determine app extension path") + return nil + } + var extensionApp: SecTrustedApplication? + var mainApp: SecTrustedApplication? + ret = SecTrustedApplicationCreateFromPath(extensionPath, &extensionApp) + if ret != kOSReturnSuccess || extensionApp == nil { + wg_log(.error, message: "Unable to create keychain extension trusted application object: \(ret)") + return nil + } + ret = SecTrustedApplicationCreateFromPath(nil, &mainApp) + if ret != errSecSuccess || mainApp == nil { + wg_log(.error, message: "Unable to create keychain local trusted application object: \(ret)") + return nil + } + var access: SecAccess? + ret = SecAccessCreate(itemLabel as CFString, [extensionApp!, mainApp!] as CFArray, &access) + if ret != errSecSuccess || access == nil { + wg_log(.error, message: "Unable to create keychain ACL object: \(ret)") + return nil + } + items[kSecAttrAccess] = access! + #else + #error("Unimplemented") + #endif + + var ref: CFTypeRef? + ret = SecItemAdd(items as CFDictionary, &ref) + if ret != errSecSuccess || ref == nil { + wg_log(.error, message: "Unable to add config to keychain: \(ret)") + return nil + } + if let oldRef = oldRef { + deleteReference(called: oldRef) + } + return ref as? Data + } + + static func deleteReference(called ref: Data) { + let ret = SecItemDelete([kSecValuePersistentRef: ref] as CFDictionary) + if ret != errSecSuccess { + wg_log(.error, message: "Unable to delete config from keychain: \(ret)") + } + } + + static func deleteReferences(except whitelist: Set<Data>) { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword, + kSecAttrService: Bundle.main.bundleIdentifier as Any, + kSecMatchLimit: kSecMatchLimitAll, + kSecReturnPersistentRef: true] as CFDictionary, + &result) + if ret != errSecSuccess || result == nil { + return + } + guard let items = result as? [Data] else { return } + for item in items { + if !whitelist.contains(item) { + deleteReference(called: item) + } + } + } + + static func verifyReference(called ref: Data) -> Bool { + return SecItemCopyMatching([kSecValuePersistentRef: ref] as CFDictionary, + nil) != errSecItemNotFound + } +} |