aboutsummaryrefslogtreecommitdiffstats
path: root/Sources/Shared/Keychain.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Sources/Shared/Keychain.swift')
-rw-r--r--Sources/Shared/Keychain.swift114
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
+ }
+}