1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
// SPDX-License-Identifier: MIT
// Copyright © 2018-2021 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
}
}
|