2019-02-04 06:37:26 +00:00
|
|
|
// 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?
|
2020-12-02 17:09:39 +00:00
|
|
|
let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
|
|
|
|
kSecValuePersistentRef: ref,
|
|
|
|
kSecReturnData: true] as CFDictionary,
|
2019-02-04 06:37:26 +00:00
|
|
|
&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
|
2020-12-02 14:08:45 +00:00
|
|
|
guard var bundleIdentifier = Bundle.main.bundleIdentifier else {
|
2019-02-04 06:37:26 +00:00
|
|
|
wg_log(.error, staticMessage: "Unable to determine bundle identifier")
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-02 14:08:45 +00:00
|
|
|
if bundleIdentifier.hasSuffix(".network-extension") {
|
|
|
|
bundleIdentifier.removeLast(".network-extension".count)
|
2019-02-04 06:37:26 +00:00
|
|
|
}
|
2020-12-02 17:09:39 +00:00
|
|
|
var items: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
|
|
|
|
kSecAttrLabel: "WireGuard Tunnel: " + name,
|
|
|
|
kSecAttrAccount: name + ": " + UUID().uuidString,
|
|
|
|
kSecAttrDescription: "wg-quick(8) config",
|
|
|
|
kSecAttrService: bundleIdentifier,
|
|
|
|
kSecValueData: value.data(using: .utf8) as Any,
|
|
|
|
kSecReturnPersistentRef: true]
|
2019-02-04 06:37:26 +00:00
|
|
|
|
|
|
|
#if os(iOS)
|
2020-12-02 17:09:39 +00:00
|
|
|
items[kSecAttrAccessGroup] = FileManager.appGroupId
|
|
|
|
items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock
|
2019-02-04 06:37:26 +00:00
|
|
|
#elseif os(macOS)
|
2020-12-02 17:09:39 +00:00
|
|
|
items[kSecAttrSynchronizable] = false
|
|
|
|
items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
2019-02-04 06:37:26 +00:00
|
|
|
|
|
|
|
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?
|
2020-12-02 17:09:39 +00:00
|
|
|
ret = SecAccessCreate((items[kSecAttrLabel] as? String)! as CFString,
|
2019-02-04 06:37:26 +00:00
|
|
|
[extensionApp!, mainApp!] as CFArray,
|
|
|
|
&access)
|
|
|
|
if ret != errSecSuccess || access == nil {
|
|
|
|
wg_log(.error, message: "Unable to create keychain ACL object: \(ret)")
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-02 17:09:39 +00:00
|
|
|
items[kSecAttrAccess] = access!
|
2019-02-04 06:37:26 +00:00
|
|
|
#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) {
|
2020-12-02 17:09:39 +00:00
|
|
|
let ret = SecItemDelete([kSecValuePersistentRef: ref] as CFDictionary)
|
2019-02-04 06:37:26 +00:00
|
|
|
if ret != errSecSuccess {
|
|
|
|
wg_log(.error, message: "Unable to delete config from keychain: \(ret)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func deleteReferences(except whitelist: Set<Data>) {
|
|
|
|
var result: CFTypeRef?
|
2020-12-02 17:09:39 +00:00
|
|
|
let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
|
|
|
|
kSecAttrService: Bundle.main.bundleIdentifier as Any,
|
|
|
|
kSecMatchLimit: kSecMatchLimitAll,
|
|
|
|
kSecReturnPersistentRef: true] as CFDictionary,
|
2019-02-04 06:37:26 +00:00
|
|
|
&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 {
|
2020-12-02 17:09:39 +00:00
|
|
|
return SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
|
|
|
|
kSecValuePersistentRef: ref] as CFDictionary,
|
2019-10-11 19:52:55 +00:00
|
|
|
nil) != errSecItemNotFound
|
2019-02-04 06:37:26 +00:00
|
|
|
}
|
|
|
|
}
|