aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/Shared
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--WireGuard/Shared/FileManager+Extension.swift7
-rw-r--r--WireGuard/Shared/Keychain.swift117
-rw-r--r--WireGuard/Shared/Model/LegacyConfigMigration.swift32
-rw-r--r--WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift38
4 files changed, 171 insertions, 23 deletions
diff --git a/WireGuard/Shared/FileManager+Extension.swift b/WireGuard/Shared/FileManager+Extension.swift
index 2155683..edd764f 100644
--- a/WireGuard/Shared/FileManager+Extension.swift
+++ b/WireGuard/Shared/FileManager+Extension.swift
@@ -5,7 +5,7 @@ import Foundation
import os.log
extension FileManager {
- private static var sharedFolderURL: URL? {
+ static var appGroupId: String? {
#if os(iOS)
let appGroupIdInfoDictionaryKey = "com.wireguard.ios.app_group_id"
#elseif os(macOS)
@@ -13,7 +13,10 @@ extension FileManager {
#else
#error("Unimplemented")
#endif
- guard let appGroupId = Bundle.main.object(forInfoDictionaryKey: appGroupIdInfoDictionaryKey) as? String else {
+ return Bundle.main.object(forInfoDictionaryKey: appGroupIdInfoDictionaryKey) as? String
+ }
+ private static var sharedFolderURL: URL? {
+ guard let appGroupId = FileManager.appGroupId else {
os_log("Cannot obtain app group ID from bundle", log: OSLog.default, type: .error)
return nil
}
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<Data>) {
+ 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
+ }
+}
diff --git a/WireGuard/Shared/Model/LegacyConfigMigration.swift b/WireGuard/Shared/Model/LegacyConfigMigration.swift
index 16792fa..583e914 100644
--- a/WireGuard/Shared/Model/LegacyConfigMigration.swift
+++ b/WireGuard/Shared/Model/LegacyConfigMigration.swift
@@ -174,20 +174,32 @@ final class LegacyTunnelConfiguration: LegacyModel {
extension NETunnelProviderProtocol {
@discardableResult
- func migrateConfigurationIfNeeded() -> Bool {
- guard let configurationVersion = providerConfiguration?["tunnelConfigurationVersion"] as? Int else { return false }
- if configurationVersion == 1 {
- migrateFromConfigurationV1()
- } else {
- fatalError("No migration from configuration version \(configurationVersion) exists.")
+ func migrateConfigurationIfNeeded(called name: String) -> Bool {
+ var ret = false
+ if migrateFromConfigurationV1() {
+ ret = true
}
+ if migrateFromConfigurationV2(called: name) {
+ ret = true
+ }
+ return ret
+ }
+
+ private func migrateFromConfigurationV1() -> Bool {
+ guard let configurationVersion = providerConfiguration?["tunnelConfigurationVersion"] as? Int else { return false }
+ guard configurationVersion == 1 else { return false }
+ guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return false }
+ guard let configuration = try? JSONDecoder().decode(LegacyTunnelConfiguration.self, from: serializedTunnelConfiguration) else { return false }
+ providerConfiguration = ["WgQuickConfig": configuration.migrated.asWgQuickConfig()]
return true
}
- private func migrateFromConfigurationV1() {
- guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return }
- guard let configuration = try? JSONDecoder().decode(LegacyTunnelConfiguration.self, from: serializedTunnelConfiguration) else { return }
- providerConfiguration = [Keys.wgQuickConfig.rawValue: configuration.migrated.asWgQuickConfig()]
+ private func migrateFromConfigurationV2(called name: String) -> Bool {
+ guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
+ providerConfiguration = nil
+ guard passwordReference == nil else { return true }
+ passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
+ return true
}
}
diff --git a/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift b/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift
index 7b3142e..3b7cd1e 100644
--- a/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift
+++ b/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift
@@ -12,17 +12,16 @@ enum PacketTunnelProviderError: String, Error {
}
extension NETunnelProviderProtocol {
-
- enum Keys: String {
- case wgQuickConfig = "WgQuickConfig"
- }
-
- convenience init?(tunnelConfiguration: TunnelConfiguration) {
+ convenience init?(tunnelConfiguration: TunnelConfiguration, previouslyFrom old: NEVPNProtocol? = nil) {
self.init()
- let appId = Bundle.main.bundleIdentifier!
+ guard let name = tunnelConfiguration.name else { return nil }
+ guard let appId = Bundle.main.bundleIdentifier else { return nil }
providerBundleIdentifier = "\(appId).network-extension"
- providerConfiguration = [Keys.wgQuickConfig.rawValue: tunnelConfiguration.asWgQuickConfig()]
+ passwordReference = Keychain.makeReference(containing: tunnelConfiguration.asWgQuickConfig(), called: name, previouslyReferencedBy: old?.passwordReference)
+ if passwordReference == nil {
+ return nil
+ }
let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
if endpoints.count == 1 {
@@ -35,9 +34,26 @@ extension NETunnelProviderProtocol {
}
func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? {
- migrateConfigurationIfNeeded()
- guard let serializedConfig = providerConfiguration?[Keys.wgQuickConfig.rawValue] as? String else { return nil }
- return try? TunnelConfiguration(fromWgQuickConfig: serializedConfig, called: name)
+ migrateConfigurationIfNeeded(called: name ?? "unknown")
+ //TODO: in the case where migrateConfigurationIfNeeded is called by the network extension,
+ // before the app has started, and when there is, in fact, configuration that needs to be
+ // put into the keychain, this will generate one new keychain item every time it is started,
+ // until finally the app is open. Would it be possible to call saveToPreferences here? Or is
+ // that generally not available to network extensions? In which case, what should our
+ // behavior be?
+
+ guard let passwordReference = passwordReference else { return nil }
+ guard let config = Keychain.openReference(called: passwordReference) else { return nil }
+ return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
}
+ func destroyConfigurationReference() {
+ guard let ref = passwordReference else { return }
+ Keychain.deleteReference(called: ref)
+ }
+
+ func verifyConfigurationReference() -> Data? {
+ guard let ref = passwordReference else { return nil }
+ return Keychain.verifyReference(called: ref) ? ref : nil
+ }
}