From 8c3557a90723c20329cbdc7eff676787bfcd5872 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 4 Feb 2019 07:37:26 +0100 Subject: Keychain: store configurations in keychain instead of providerConfig Signed-off-by: Jason A. Donenfeld --- WireGuard/Shared/Keychain.swift | 117 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 WireGuard/Shared/Keychain.swift (limited to 'WireGuard/Shared/Keychain.swift') diff --git a/WireGuard/Shared/Keychain.swift b/WireGuard/Shared/Keychain.swift new file mode 100644 index 0000000..edc546d --- /dev/null +++ b/WireGuard/Shared/Keychain.swift @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Foundation +import Security + +class Keychain { + static func openReference(called ref: Data) -> String? { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword, + kSecValuePersistentRef as String: ref, + kSecReturnData as String: 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 id = Bundle.main.bundleIdentifier else { + wg_log(.error, staticMessage: "Unable to determine bundle identifier") + return nil + } + if id.hasSuffix(".network-extension") { + id.removeLast(".network-extension".count) + } + var items: [String: Any] = [kSecClass as String: kSecClassGenericPassword, + kSecAttrLabel as String: "WireGuard Tunnel: " + name, + kSecAttrAccount as String: name + ": " + UUID().uuidString, + kSecAttrDescription as String: "wg-quick(8) config", + kSecAttrService as String: id, + kSecValueData as String: value.data(using: .utf8) as Any, + kSecReturnPersistentRef as String: true] + + #if os(iOS) + items[kSecAttrAccessGroup as String] = FileManager.appGroupId + items[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock + #elseif os(macOS) + items[kSecAttrSynchronizable as String] = false + items[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + + guard let extensionPath = Bundle.main.builtInPlugInsURL?.appendingPathComponent("WireGuardNetworkExtension.appex").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((items[kSecAttrLabel as String] as? String)! 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 as String] = 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 as String: ref] as CFDictionary) + if ret != errSecSuccess { + wg_log(.error, message: "Unable to delete config from keychain: \(ret)") + } + } + + static func deleteReferences(except whitelist: Set) { + var result: CFTypeRef? + let ret = SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: Bundle.main.bundleIdentifier as Any, + kSecMatchLimit as String: kSecMatchLimitAll, + kSecReturnPersistentRef as String: 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([kSecClass as String: kSecClassGenericPassword, + kSecValuePersistentRef as String: ref] as CFDictionary, + nil) == errSecSuccess + } +} -- cgit v1.2.3-59-g8ed1b