From b24f78558bfb0e3c322e07f40f99fb5b36672997 Mon Sep 17 00:00:00 2001 From: Kyle Hammond Date: Thu, 14 Dec 2023 15:52:02 -0600 Subject: [PATCH 1/2] #124 Using Haversack to simplify keychain access. --- PPPC Utility.xcodeproj/project.pbxproj | 27 +++++ .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 23 ++++ Source/SecurityWrapper.swift | 101 +++++++----------- 4 files changed, 87 insertions(+), 66 deletions(-) create mode 100644 PPPC Utility.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/PPPC Utility.xcodeproj/project.pbxproj b/PPPC Utility.xcodeproj/project.pbxproj index 3403476..a2deafc 100644 --- a/PPPC Utility.xcodeproj/project.pbxproj +++ b/PPPC Utility.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ C07B1FB82AF596D80075E38B /* UploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07B1FB72AF596D80075E38B /* UploadManager.swift */; }; C0A2B5422B1A5D5C0007F510 /* JamfProAPIClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A2B5412B1A5D5C0007F510 /* JamfProAPIClientTests.swift */; }; C0A85DB5279873C600086283 /* TestTCCUnsignedProfile-allLower.mobileconfig in Resources */ = {isa = PBXBuildFile; fileRef = C0A85DB4279873C600086283 /* TestTCCUnsignedProfile-allLower.mobileconfig */; }; + C0DC2BB92B2263FC003A4474 /* Haversack in Frameworks */ = {isa = PBXBuildFile; productRef = C0DC2BB82B2263FC003A4474 /* Haversack */; }; C0E0383F27A30C7100A23FA2 /* PPPCServiceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E0383D27A30C7100A23FA2 /* PPPCServiceInfo.swift */; }; C0E0384027A30C7100A23FA2 /* PPPCServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0E0383E27A30C7100A23FA2 /* PPPCServicesManager.swift */; }; C0E0384227A30D1D00A23FA2 /* PPPCServices.json in Resources */ = {isa = PBXBuildFile; fileRef = C0E0384127A30D1D00A23FA2 /* PPPCServices.json */; }; @@ -143,6 +144,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C0DC2BB92B2263FC003A4474 /* Haversack in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -377,6 +379,9 @@ dependencies = ( ); name = "PPPC Utility"; + packageProductDependencies = ( + C0DC2BB82B2263FC003A4474 /* Haversack */, + ); productName = TCCUtility; productReference = 6EC409DA214D65BC00BE4F17 /* PPPC Utility.app */; productType = "com.apple.product-type.application"; @@ -419,6 +424,9 @@ Base, ); mainGroup = 6EC409D1214D65BC00BE4F17; + packageReferences = ( + C0DC2BB72B2263FC003A4474 /* XCRemoteSwiftPackageReference "Haversack" */, + ); productRefGroup = 6EC409DB214D65BC00BE4F17 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -797,6 +805,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + C0DC2BB72B2263FC003A4474 /* XCRemoteSwiftPackageReference "Haversack" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jamf/Haversack.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C0DC2BB82B2263FC003A4474 /* Haversack */ = { + isa = XCSwiftPackageProductDependency; + package = C0DC2BB72B2263FC003A4474 /* XCRemoteSwiftPackageReference "Haversack" */; + productName = Haversack; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 6EC409D2214D65BC00BE4F17 /* Project object */; } diff --git a/PPPC Utility.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PPPC Utility.xcodeproj/project.xcworkspace/contents.xcworkspacedata index ccdc494..919434a 100644 --- a/PPPC Utility.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/PPPC Utility.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/PPPC Utility.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/PPPC Utility.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..90ca261 --- /dev/null +++ b/PPPC Utility.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "haversack", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jamf/Haversack.git", + "state" : { + "revision" : "840fd566ca709fe0932b231df63833d50488e127", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307", + "version" : "1.0.5" + } + } + ], + "version" : 2 +} diff --git a/Source/SecurityWrapper.swift b/Source/SecurityWrapper.swift index 8fd88d2..ad83621 100644 --- a/Source/SecurityWrapper.swift +++ b/Source/SecurityWrapper.swift @@ -4,7 +4,7 @@ // // MIT License // -// Copyright (c) 2018 Jamf Software +// Copyright (c) 2023 Jamf Software // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,7 @@ // import Foundation +import Haversack struct SecurityWrapper { @@ -37,64 +38,36 @@ struct SecurityWrapper { } static func saveCredentials(username: String, password: String, server: String) throws { + let haversack = Haversack() + let item = InternetPasswordEntity() + item.server = server + item.account = username + item.passwordData = password.data(using: .utf8) - do { - let possibleResult = try loadCredentials(server: server) - if let old = possibleResult, username == old.username && password == old.password { - return - } else { - let dict = [ - kSecClass as String: kSecClassInternetPassword, - kSecAttrServer as String: server - ] as CFDictionary - try execute { SecItemDelete(dict) } - } - } catch {} - - let dict = [ - kSecClass as String: kSecClassInternetPassword, - kSecAttrServer as String: server, - kSecAttrAccount as String: username, - kSecValueData as String: password - ] as CFDictionary - try execute { SecItemAdd(dict, nil) } + try haversack.save(item, itemSecurity: .standard, updateExisting: true) } static func removeCredentials(server: String, username: String) throws { - let dict = [ - kSecClass as String: kSecClassInternetPassword, - kSecAttrServer as String: server, - kSecAttrAccount as String: username - ] as CFDictionary - try execute { SecItemDelete(dict) } + let haversack = Haversack() + let query = InternetPasswordQuery(server: server) + .matching(account: username) + + try haversack.delete(where: query, treatNotFoundAsSuccess: true) } static func loadCredentials(server: String) throws -> (username: String, password: String)? { - let dict = [ - kSecClass as String: kSecClassInternetPassword, - kSecAttrServer as String: server, - kSecMatchLimit as String: kSecMatchLimitOne, - kSecReturnAttributes as String: true, - kSecReturnData as String: true - ] as CFDictionary - - var item: CFTypeRef? - try execute { - let status = SecItemCopyMatching(dict, &item) - // Check if success or not found, thrown error is a "real" error - if status == errSecSuccess || status == errSecItemNotFound { - return errSecSuccess - } - return status - } - guard let existingItem = item as? [String: Any], - let passwordData = existingItem[kSecValueData as String] as? Data, - let password = String(data: passwordData, encoding: .utf8), - let username = existingItem[kSecAttrAccount as String] as? String - else { - return nil - } - return (username: username, password: password) + let haversack = Haversack() + let query = InternetPasswordQuery(server: server) + .returning([.attributes, .data]) + + if let item = try? haversack.first(where: query), + let username = item.account, + let passwordData = item.passwordData, + let password = String(data: passwordData, encoding: .utf8) { + return (username: username, password: password) + } + + return nil } static func copyDesignatedRequirement(url: URL) throws -> String { @@ -124,22 +97,20 @@ struct SecurityWrapper { } static func loadSigningIdentities() throws -> [SigningIdentity] { + let haversack = Haversack() + let query = IdentityQuery().matching(mustBeValidOnDate: Date()).returning(.reference) - let dict = [ - kSecClass as String: kSecClassIdentity, - kSecReturnRef as String: true, - kSecMatchLimit as String: kSecMatchLimitAll - ] as CFDictionary + let identities = try haversack.search(where: query) - var result: AnyObject? - try execute { SecItemCopyMatching(dict, &result) } + return identities.compactMap { + guard let secIdentity = $0.reference else { + return nil + } - guard let secIdentities = result as? [SecIdentity] else { return [] } - - return secIdentities.map { - let name = try? getCertificateCommonName(for: $0) - return SigningIdentity(name: name ?? "Unknown \($0.hashValue)", reference: $0) - } + let name = try? getCertificateCommonName(for: secIdentity) + return SigningIdentity(name: name ?? "Unknown \(secIdentity.hashValue)", + reference: secIdentity) + } } static func getCertificateCommonName(for identity: SecIdentity) throws -> String { From 0ce5ac67580bf9afd2b10eab3470df5fe7661951 Mon Sep 17 00:00:00 2001 From: Kyle Hammond Date: Thu, 14 Dec 2023 15:54:47 -0600 Subject: [PATCH 2/2] #124 Updated changelog, fixed a comment spacing issue, and removed an unneeded #available check. --- CHANGELOG.md | 1 + Source/Networking/URLSessionAsyncCompatibility.swift | 2 +- Source/SwiftUI/UploadInfoView.swift | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2904691..bae8504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Connection to Jamf Pro can now use client credentials with Jamf Pro v10.49+ ([Issue #120](https://github.com/jamf/PPPC-Utility/issues/120)) [@macblazer](https://github.com/macblazer). ### Changed +- Now using [Haversack](https://github.com/jamf/Haversack) for simplified access to the keychain ([Issue #124](https://github.com/jamf/PPPC-Utility/issues/124)) [@macblazer](https://github.com/macblazer). - PPPC Utility now requires macOS 11+ to run. It can still produce profiles usable on older versions of macOS. ## [1.5.0] - 2022-10-04 diff --git a/Source/Networking/URLSessionAsyncCompatibility.swift b/Source/Networking/URLSessionAsyncCompatibility.swift index f73dcb1..5f38e53 100644 --- a/Source/Networking/URLSessionAsyncCompatibility.swift +++ b/Source/Networking/URLSessionAsyncCompatibility.swift @@ -1,5 +1,5 @@ // -// URLSessionAsyncCompatibility.swift +// URLSessionAsyncCompatibility.swift // Based on AsyncCompatibilityKit's URLSession+Async.swift which is // Copyright (c) John Sundell 2021 // MIT license, see LICENSE.md file for details diff --git a/Source/SwiftUI/UploadInfoView.swift b/Source/SwiftUI/UploadInfoView.swift index 20b01f6..a02b20e 100644 --- a/Source/SwiftUI/UploadInfoView.swift +++ b/Source/SwiftUI/UploadInfoView.swift @@ -103,10 +103,8 @@ struct UploadInfoView: View { HStack { Text(networkInfo) .font(.headline) - if #available(macOS 11.0, *) { - ProgressView() - .padding(.leading) - } + ProgressView() + .padding(.leading) } }