Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use haversack for keychain access #125

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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