-
Notifications
You must be signed in to change notification settings - Fork 633
Expand file tree
/
Copy pathKeychain.swift
More file actions
130 lines (119 loc) · 5.63 KB
/
Keychain.swift
File metadata and controls
130 lines (119 loc) · 5.63 KB
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 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 getReferenceName(called ref: Data) -> String? {
var result: CFTypeRef?
let ret = SecItemCopyMatching([kSecValuePersistentRef: ref,
kSecReturnAttributes: true] as CFDictionary,
&result)
if ret != errSecSuccess || result == nil {
wg_log(.error, message: "Unable to get config attributes from keychain: \(ret)")
return nil
}
guard let attrs = result as? [CFString: AnyObject] else { return nil }
guard let secAttrAccount = attrs[kSecAttrAccount] as? String else { return nil }
let name = secAttrAccount.split(separator: ":", maxSplits: 1)[0]
return String(name)
}
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
}
}