Skip to content

Commit

Permalink
Merge pull request #125 from macblazer/124-use-haversack-for-keychain…
Browse files Browse the repository at this point in the history
…-access

Use Haversack for keychain access
  • Loading branch information
macblazer authored Jan 29, 2024
2 parents 7605083 + 0ce5ac6 commit 408f84f
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 71 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions PPPC Utility.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -143,6 +144,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C0DC2BB92B2263FC003A4474 /* Haversack in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -377,6 +379,9 @@
dependencies = (
);
name = "PPPC Utility";
packageProductDependencies = (
C0DC2BB82B2263FC003A4474 /* Haversack */,
);
productName = TCCUtility;
productReference = 6EC409DA214D65BC00BE4F17 /* PPPC Utility.app */;
productType = "com.apple.product-type.application";
Expand Down Expand Up @@ -419,6 +424,9 @@
Base,
);
mainGroup = 6EC409D1214D65BC00BE4F17;
packageReferences = (
C0DC2BB72B2263FC003A4474 /* XCRemoteSwiftPackageReference "Haversack" */,
);
productRefGroup = 6EC409DB214D65BC00BE4F17 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -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 */;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion Source/Networking/URLSessionAsyncCompatibility.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down
101 changes: 36 additions & 65 deletions Source/SecurityWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,6 +26,7 @@
//

import Foundation
import Haversack

struct SecurityWrapper {

Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 2 additions & 4 deletions Source/SwiftUI/UploadInfoView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,8 @@ struct UploadInfoView: View {
HStack {
Text(networkInfo)
.font(.headline)
if #available(macOS 11.0, *) {
ProgressView()
.padding(.leading)
}
ProgressView()
.padding(.leading)
}
}

Expand Down

0 comments on commit 408f84f

Please sign in to comment.