diff --git a/PiBar.xcodeproj/project.pbxproj b/PiBar.xcodeproj/project.pbxproj index 0e0775c..6008b22 100644 --- a/PiBar.xcodeproj/project.pbxproj +++ b/PiBar.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 44E390C72D87C3E2002196DC /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 44E390C62D87C3E2002196DC /* LaunchAtLogin */; }; 44E390C92D87ED53002196DC /* PiholeV6SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44E390C82D87ED49002196DC /* PiholeV6SettingsViewController.swift */; }; 44FFB092247627B100DCEDEC /* PiBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44FFB091247627B100DCEDEC /* PiBarManager.swift */; }; + 7DFE62342E0938CA0097B220 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 7DFE62332E0938CA0097B220 /* KeychainAccess */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -83,6 +84,7 @@ files = ( 44E390C72D87C3E2002196DC /* LaunchAtLogin in Frameworks */, 44E390C22D877DDE002196DC /* HotKey in Frameworks */, + 7DFE62342E0938CA0097B220 /* KeychainAccess in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -230,6 +232,7 @@ packageProductDependencies = ( 44E390C12D877DDE002196DC /* HotKey */, 44E390C62D87C3E2002196DC /* LaunchAtLogin */, + 7DFE62332E0938CA0097B220 /* KeychainAccess */, ); productName = PiBar; productReference = 449395D22471ABD600FA0C34 /* PiBar.app */; @@ -263,6 +266,7 @@ packageReferences = ( 44E390C02D877DDE002196DC /* XCRemoteSwiftPackageReference "HotKey" */, 44E390C52D87C3E2002196DC /* XCRemoteSwiftPackageReference "LaunchAtLogin-Legacy" */, + 7DFE62322E0938CA0097B220 /* XCRemoteSwiftPackageReference "KeychainAccess" */, ); productRefGroup = 449395D32471ABD600FA0C34 /* Products */; projectDirPath = ""; @@ -566,6 +570,14 @@ kind = branch; }; }; + 7DFE62322E0938CA0097B220 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess"; + requirement = { + branch = master; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -579,6 +591,11 @@ package = 44E390C52D87C3E2002196DC /* XCRemoteSwiftPackageReference "LaunchAtLogin-Legacy" */; productName = LaunchAtLogin; }; + 7DFE62332E0938CA0097B220 /* KeychainAccess */ = { + isa = XCSwiftPackageProductDependency; + package = 7DFE62322E0938CA0097B220 /* XCRemoteSwiftPackageReference "KeychainAccess" */; + productName = KeychainAccess; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 449395CA2471ABD600FA0C34 /* Project object */; diff --git a/PiBar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PiBar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1dd36b7..bc1ea03 100644 --- a/PiBar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/PiBar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "ab1eefd7a2b30c55c93e58acb6ad16334c48b48a141518af254356092869f16e", + "originHash" : "9794f416c441490423495df59b3760e5e3ab7d4596346f57bd1991bb5cd6c27f", "pins" : [ { "identity" : "hotkey", @@ -10,6 +10,15 @@ "revision" : "a3cf605d7a96f6ff50e04fcb6dea6e2613cfcbe4" } }, + { + "identity" : "keychainaccess", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kishikawakatsumi/KeychainAccess", + "state" : { + "branch" : "master", + "revision" : "e0c7eebc5a4465a3c4680764f26b7a61f567cdaf" + } + }, { "identity" : "launchatlogin-legacy", "kind" : "remoteSourceControl", diff --git a/PiBar/Data Sources/Structs.swift b/PiBar/Data Sources/Structs.swift index d618335..619a453 100644 --- a/PiBar/Data Sources/Structs.swift +++ b/PiBar/Data Sources/Structs.swift @@ -10,6 +10,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import Foundation +import KeychainAccess // MARK: - Pi-hole Connections @@ -81,37 +82,66 @@ extension PiholeConnectionV2 { } // PiBar v1.2 format -struct PiholeConnectionV3: Codable { +struct PiholeConnectionV3: Codable, Copyable { let hostname: String let port: Int let useSSL: Bool - let token: String + var token: String let passwordProtected: Bool let adminPanelURL: String let isV6: Bool } extension PiholeConnectionV3 { + + private var keychainIdentifier: String? { + get { + if self.hostname.count > 0 { + return "apitoken.\(self.hostname):\(self.port)" + } + return nil + } + } + init?(data: Data) { let jsonDecoder = JSONDecoder() do { - let object = try jsonDecoder.decode(PiholeConnectionV3.self, from: data) + var object = try jsonDecoder.decode(PiholeConnectionV3.self, from: data) + // load the token from the Keychain and add it to the connection object: + if let bundleIdentifier = Bundle.main.bundleIdentifier, let keychainIdentifier = object.keychainIdentifier { + let keychain = Keychain(service: bundleIdentifier, accessGroup: nil) + if let tokenStr = keychain[keychainIdentifier], !tokenStr.isEmpty { + object.token = tokenStr + } + } self = object } catch { Log.debug("Couldn't decode connection: \(error.localizedDescription)") return nil } } - + func encode() -> Data? { let jsonEncoder = JSONEncoder() - if let data = try? jsonEncoder.encode(self) { + // make sure the token is being securely stored to the Keychain only, and nowhere else + var copySelf = self.copy() as! PiholeConnectionV3 + if let bundleIdentifier = Bundle.main.bundleIdentifier, !copySelf.token.isEmpty, let keychainIdentifier = copySelf.keychainIdentifier { + let keychain = Keychain(service: bundleIdentifier, accessGroup: nil) + keychain[keychainIdentifier] = copySelf.token + copySelf.token = "" + } + if let data = try? jsonEncoder.encode(copySelf) { return data } else { return nil } } + func copy(with zone: NSZone? = nil) -> Any { + let copy = PiholeConnectionV3(hostname: hostname, port: port, useSSL: useSSL, token: token, passwordProtected: passwordProtected, adminPanelURL: adminPanelURL, isV6: isV6) + return copy + } + static func generateAdminPanelURL(hostname: String, port: Int, useSSL: Bool) -> String { let prefix: String = useSSL ? "https" : "http" return "\(prefix)://\(hostname):\(port)/admin/"