mirror of
https://github.com/munki/munki.git
synced 2026-04-30 09:19:31 -05:00
Add keychain and cert functions
This commit is contained in:
@@ -283,6 +283,8 @@
|
||||
C0BF62E82CEC11B10030885D /* reports.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07074DE2C33B9A000B86310 /* reports.swift */; };
|
||||
C0BF62E92CEC11CB0030885D /* prefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A6FAF2C2B22A400090743 /* prefs.swift */; };
|
||||
C0BF62EA2CEC11EB0030885D /* plistutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364422C2DD1BA008DB215 /* plistutils.swift */; };
|
||||
C0C4091D2D86549600704005 /* keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C4091C2D86548800704005 /* keychain.swift */; };
|
||||
C0C4091E2D86549600704005 /* keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C4091C2D86548800704005 /* keychain.swift */; };
|
||||
C0D00FA12C457E2B0021DA9C /* munkihash.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A98E2C39C135007F0B34 /* munkihash.swift */; };
|
||||
C0D00FA22C457E4E0021DA9C /* display.swift in Sources */ = {isa = PBXBuildFile; fileRef = C01364572C3265D6008DB215 /* display.swift */; };
|
||||
C0D00FA32C457E5F0021DA9C /* fileutils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C030A9B52C3DF6D0007F0B34 /* fileutils.swift */; };
|
||||
@@ -581,6 +583,7 @@
|
||||
C0B7FA002D288C2700CC14F0 /* authrestart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = authrestart.swift; sourceTree = "<group>"; };
|
||||
C0BF62CD2CEC00E90030885D /* repoclean */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = repoclean; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C0BF62CF2CEC00E90030885D /* repoclean.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = repoclean.swift; sourceTree = "<group>"; };
|
||||
C0C4091C2D86548800704005 /* keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = keychain.swift; sourceTree = "<group>"; };
|
||||
C0D00FA72C45814F0021DA9C /* repoutils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = repoutils.swift; sourceTree = "<group>"; };
|
||||
C0D00FAF2C458EAA0021DA9C /* version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = version.swift; sourceTree = "<group>"; };
|
||||
C0D00FB52C45BCB90021DA9C /* errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = errors.swift; sourceTree = "<group>"; };
|
||||
@@ -869,6 +872,7 @@
|
||||
C07A6FD02C2B631800090743 /* shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C0C4091C2D86548800704005 /* keychain.swift */,
|
||||
C008A0502D18BB780073ADBA /* launchd.swift */,
|
||||
C0011CB02C7A99D00004ED70 /* headers */,
|
||||
C07AED6D2C67DF4F00DE6119 /* network */,
|
||||
@@ -1522,6 +1526,7 @@
|
||||
C030A9B22C3DB88E007F0B34 /* osinstaller.swift in Sources */,
|
||||
C01364542C321FE7008DB215 /* dmgutils.swift in Sources */,
|
||||
C030A9B62C3DF6D0007F0B34 /* fileutils.swift in Sources */,
|
||||
C0C4091D2D86549600704005 /* keychain.swift in Sources */,
|
||||
C0D66AB92D2C923E009EF807 /* common.swift in Sources */,
|
||||
C01364582C3265D6008DB215 /* display.swift in Sources */,
|
||||
C07A6FD22C2B654300090743 /* utils.swift in Sources */,
|
||||
@@ -1594,6 +1599,7 @@
|
||||
C06C213E2C88CACE0023E9D9 /* SignalHandler.swift in Sources */,
|
||||
C07074DD2C33AE5F00B86310 /* munkilog.swift in Sources */,
|
||||
C07A6FBF2C2B5AF400090743 /* constants.swift in Sources */,
|
||||
C0C4091E2D86549600704005 /* keychain.swift in Sources */,
|
||||
C030A9902C39C135007F0B34 /* munkihash.swift in Sources */,
|
||||
C07074E02C33B9A000B86310 /* reports.swift in Sources */,
|
||||
C07AED6C2C66F56C00DE6119 /* manifests.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
//
|
||||
// keychain.swift
|
||||
// munki
|
||||
//
|
||||
// Created by Greg Neagle on 3/15/25.
|
||||
//
|
||||
// Copyright 2025 Greg Neagle.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import CryptoKit
|
||||
import Security
|
||||
|
||||
private let DEFAULT_KEYCHAIN_NAME = "munki.keychain"
|
||||
private let DEFAULT_KEYCHAIN_PASSWORD = "munki"
|
||||
private let KEYCHAIN_DIRECTORY = managedInstallsDir(subpath: "Keychains") as NSString
|
||||
|
||||
/// Read in a base64 pem file, return data of embedded certificate
|
||||
func pemCertData(_ certPath: String) throws -> Data {
|
||||
guard let certString = try? String(contentsOfFile: certPath, encoding: .utf8) else {
|
||||
throw MunkiError("File not decodeable as UTF-8")
|
||||
}
|
||||
guard let startIndex = certString.range(of: "-----BEGIN CERTIFICATE-----")?.upperBound,
|
||||
let endIndex = certString.range(of: "-----END CERTIFICATE-----")?.lowerBound
|
||||
else {
|
||||
throw MunkiError("File does not appear to be .pem file")
|
||||
}
|
||||
let certDataString = String(certString[startIndex ..< endIndex]).split(separator: "\n").joined()
|
||||
guard let certData = Data(base64Encoded: String(certDataString)) else {
|
||||
throw MunkiError("Could not decode cert string as base64")
|
||||
}
|
||||
return certData
|
||||
}
|
||||
|
||||
/// Return SHA1 digest for pem certificate at path
|
||||
func pemCertSha1Digest(_ certPath: String) throws -> String {
|
||||
let certData = try pemCertData(certPath)
|
||||
let hashed = Insecure.SHA1.hash(data: certData)
|
||||
return hashed.compactMap { String(format: "%02x", $0) }.joined().uppercased()
|
||||
}
|
||||
|
||||
/// Attempt to get information we need from Munki's preferences or defaults.
|
||||
/// Returns a dictionary.
|
||||
func getMunkiServerCertInfo() -> [String: String] {
|
||||
var certInfo = [
|
||||
"ca_cert_path": "",
|
||||
"ca_dir_path": "",
|
||||
]
|
||||
|
||||
// get server CA cert if it exists so we can verify the Munki server
|
||||
let default_ca_cert_path = managedInstallsDir(subpath: "certs/ca.pem")
|
||||
if pathExists(default_ca_cert_path) {
|
||||
certInfo["ca_cert_path"] = default_ca_cert_path
|
||||
}
|
||||
if let ca_path = pref("SoftwareRepoCAPath") as? String {
|
||||
if pathIsRegularFile(ca_path) {
|
||||
certInfo["ca_cert_path"] = ca_path
|
||||
} else if pathIsDirectory(ca_path) {
|
||||
certInfo["ca_cert_path"] = ""
|
||||
certInfo["ca_dir_path"] = ca_path
|
||||
}
|
||||
}
|
||||
if let ca_cert_path = pref("SoftwareRepoCACertificate") as? String {
|
||||
certInfo["ca_cert_path"] = ca_cert_path
|
||||
}
|
||||
return certInfo
|
||||
}
|
||||
|
||||
extension String {
|
||||
// remove a suffix if it exists
|
||||
func deletingSuffix(_ suffix: String) -> String {
|
||||
guard hasSuffix(suffix) else { return self }
|
||||
return String(dropLast(suffix.count))
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to get client cert and key information from Munki's preferences or defaults.
|
||||
/// Returns a dictionary.
|
||||
func getMunkiClientCertInfo() -> [String: Any] {
|
||||
var certInfo = [
|
||||
"client_cert_path": "",
|
||||
"client_key_path": "",
|
||||
"site_urls": [String](),
|
||||
] as [String: Any]
|
||||
|
||||
// should we use a client cert at all?
|
||||
if pref("UseClientCertificate") as? Bool ?? false {
|
||||
return certInfo
|
||||
}
|
||||
// get client cert if it exists
|
||||
certInfo["client_cert_path"] = pref("ClientCertificatePath") as? String ?? ""
|
||||
certInfo["client_key_path"] = pref("ClientKeyPath") as? String ?? ""
|
||||
if (certInfo["client_cert_path"] as? String ?? "").isEmpty {
|
||||
for name in ["cert.pem", "client.pem", "munki.pem"] {
|
||||
let client_cert_path = managedInstallsDir(subpath: "certs/\(name)")
|
||||
if pathExists(client_cert_path) {
|
||||
certInfo["client_cert_path"] = client_cert_path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// get site urls
|
||||
var siteUrls = [String]()
|
||||
for key in ["SoftwareRepoURL", "PackageURL", "CatalogURL",
|
||||
"ManifestURL", "IconURL", "ClientResourceURL"]
|
||||
{
|
||||
if let url = pref(key) as? String {
|
||||
siteUrls.append(url.deletingSuffix("/") + "/")
|
||||
}
|
||||
}
|
||||
certInfo["site_urls"] = siteUrls
|
||||
return certInfo
|
||||
}
|
||||
|
||||
/// Returns the common name for the client cert, if any
|
||||
func getClientCertCommonName() -> String? {
|
||||
let certInfo = getMunkiClientCertInfo()
|
||||
if let certPath = certInfo["client_cert_path"] as? String {
|
||||
if let certData = try? pemCertData(certPath) {
|
||||
if let cert = SecCertificateCreateWithData(
|
||||
kCFAllocatorDefault, certData as CFData
|
||||
) {
|
||||
var commonName: CFString?
|
||||
if SecCertificateCopyCommonName(cert, &commonName) == errSecSuccess {
|
||||
return commonName as String?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: keychain functions
|
||||
|
||||
class SecurityError: MunkiError {}
|
||||
|
||||
/// Runs the security binary with args. Returns stdout.
|
||||
/// Raises SecurityError for a non-zero return code
|
||||
/// This version allows variadic args which look nicer
|
||||
func security(_ args: String..., environment: [String: String] = [:]) throws -> String {
|
||||
try security(args, environment: environment)
|
||||
}
|
||||
|
||||
/// Runs the security binary with args. Returns stdout.
|
||||
/// Raises SecurityError for a non-zero return code
|
||||
func security(_ args: [String], environment: [String: String] = [:]) throws -> String {
|
||||
let result = runCLI("/usr/bin/security", arguments: args, environment: environment)
|
||||
if result.exitcode != 0 {
|
||||
throw SecurityError(result.error)
|
||||
}
|
||||
if !result.output.isEmpty {
|
||||
return result.output
|
||||
} else {
|
||||
return result.error
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an absolute path for our Munki keychain
|
||||
func getKeychainPath() -> String {
|
||||
var keychainName = pref("KeychainName") as? String ?? DEFAULT_KEYCHAIN_NAME
|
||||
// We only care about the filename, not the path
|
||||
// if we have an odd path that appears to be all directory and no
|
||||
// file name, revert to default filename
|
||||
keychainName = baseName(keychainName)
|
||||
if keychainName.isEmpty {
|
||||
keychainName = DEFAULT_KEYCHAIN_NAME
|
||||
}
|
||||
// Correct the filename to include '.keychain' if not already present
|
||||
if !["keychain", "keychain-db"].contains((keychainName as NSString).pathExtension) {
|
||||
keychainName = keychainName + ".keychain"
|
||||
}
|
||||
return getAbsolutePath(KEYCHAIN_DIRECTORY.appendingPathComponent(keychainName))
|
||||
}
|
||||
|
||||
/// Debugging output for keychain
|
||||
func debugKeychainOutput() {
|
||||
do {
|
||||
displayDebug2("***Keychain search list for common domain***")
|
||||
try displayDebug2(security("list-keychains", "-d", "common"))
|
||||
displayDebug2("***Default keychain info***")
|
||||
try displayDebug2(security("default-keychain", "-d", "common"))
|
||||
let keychainfile = getKeychainPath()
|
||||
if pathExists(keychainfile) {
|
||||
displayDebug2("***Info for \(keychainfile)***")
|
||||
try displayDebug2(security("show-keychain-info", keychainfile))
|
||||
}
|
||||
} catch {
|
||||
displayError("Error: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the keychain is in the search path. Returns boolean to indicate if the keychain was added
|
||||
func addToKeychainList(_ keychainPath: String, environment: [String: String] = [:]) -> Bool {
|
||||
var addedKeychain = false
|
||||
guard let output = try? security(
|
||||
"list-keychains", "-d", "common",
|
||||
environment: environment
|
||||
) else { return false }
|
||||
// Split the output and strip it of whitespace and leading/trailing
|
||||
// quotes, the result are absolute paths to keychains
|
||||
// Preserve the order in case we need to append to them
|
||||
var searchKeychains: [String] = []
|
||||
var quoteChar = CharacterSet()
|
||||
quoteChar.insert("\"")
|
||||
for line in output.split(separator: "\n") {
|
||||
let trimmedLine = line.trimmingCharacters(in: .whitespaces).trimmingCharacters(in: quoteChar)
|
||||
if !trimmedLine.isEmpty {
|
||||
searchKeychains.append(trimmedLine)
|
||||
}
|
||||
}
|
||||
if !searchKeychains.contains(keychainPath) {
|
||||
// Keychain is not in the search paths, let's add it
|
||||
displayDebug2("Adding client keychain to search path...")
|
||||
searchKeychains.append(keychainPath)
|
||||
do {
|
||||
let output = try security(
|
||||
["list-keychains", "-d", "common", "-s"] + searchKeychains,
|
||||
environment: environment
|
||||
)
|
||||
if !output.isEmpty {
|
||||
displayDebug2(output)
|
||||
}
|
||||
addedKeychain = true
|
||||
} catch {
|
||||
displayError("Could not add keychain \(keychainPath) to keychain list: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
if loggingLevel() > 2 {
|
||||
debugKeychainOutput()
|
||||
}
|
||||
return addedKeychain
|
||||
}
|
||||
|
||||
/// Remove keychain from the list of keychains
|
||||
func removeFromKeychainList(_ keychainPath: String, environment: [String: String] = [:]) {
|
||||
guard let output = try? security("list-keychains", "-d", "common", environment: environment) else {
|
||||
return
|
||||
}
|
||||
// Split the output and strip it of whitespace and leading/trailing
|
||||
// quotes, the result are absolute paths to keychains
|
||||
// Preserve the order in case we need to append to them
|
||||
var searchKeychains: [String] = []
|
||||
var quoteChar = CharacterSet()
|
||||
quoteChar.insert("\"")
|
||||
for line in output.split(separator: "\n") {
|
||||
let trimmedLine = line.trimmingCharacters(in: .whitespaces).trimmingCharacters(in: quoteChar)
|
||||
if !trimmedLine.isEmpty {
|
||||
searchKeychains.append(trimmedLine)
|
||||
}
|
||||
}
|
||||
if searchKeychains.contains(keychainPath) {
|
||||
// Keychain is in the search path
|
||||
displayDebug2("Removing \(keychainPath) from search path...")
|
||||
let filteredKeychains = searchKeychains.filter { $0 != keychainPath }
|
||||
do {
|
||||
let output = try security(
|
||||
["list-keychains", "-d", "common", "-s"] + filteredKeychains,
|
||||
environment: environment
|
||||
)
|
||||
if !output.isEmpty {
|
||||
displayDebug2(output)
|
||||
}
|
||||
} catch {
|
||||
displayError("Could not remove keychain \(keychainPath) from keychain list: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
if loggingLevel() > 2 {
|
||||
debugKeychainOutput()
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlocks the keychain and sets it to non-locking
|
||||
func unlockAndSetNonLocking(_ keychainPath: String, environment: [String: String] = [:]) {
|
||||
let keychainPassword = pref("KeychainPassword") as? String ?? DEFAULT_KEYCHAIN_PASSWORD
|
||||
do {
|
||||
let output = try security(
|
||||
"unlock-keychain", "-p", keychainPassword, keychainPath,
|
||||
environment: environment
|
||||
)
|
||||
if !output.isEmpty {
|
||||
displayDebug2(output)
|
||||
}
|
||||
} catch {
|
||||
// some problem unlocking the keychain
|
||||
displayError("Could not unlock \(keychainPath): \(error.localizedDescription)")
|
||||
// just delete the keychain
|
||||
do {
|
||||
try FileManager.default.removeItem(atPath: keychainPath)
|
||||
} catch {
|
||||
displayError("Could not remove \(keychainPath): \(error.localizedDescription)")
|
||||
}
|
||||
return
|
||||
}
|
||||
do {
|
||||
let output = try security(
|
||||
"set-keychain-settings", keychainPath,
|
||||
environment: environment
|
||||
)
|
||||
if !output.isEmpty {
|
||||
displayDebug2(output)
|
||||
}
|
||||
} catch {
|
||||
displayError("Could not set keychain settings for \(keychainPath): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if a client cert exists that we need to import into a keychain
|
||||
func clientCertExists() -> Bool {
|
||||
let certInfo = getMunkiClientCertInfo()
|
||||
let client_cert_path = certInfo["client_cert_path"] as? String ?? ""
|
||||
return !client_cert_path.isEmpty && pathExists(client_cert_path)
|
||||
}
|
||||
|
||||
/// Builds a client cert keychain from existing client certs
|
||||
/// If keychain was added to the search list, returns true
|
||||
func makeClientKeychain(_ certInfo: [String: Any] = [:]) -> Bool {
|
||||
var certInfo = certInfo
|
||||
if certInfo.isEmpty {
|
||||
// grab data from Munki's preferences/defaults
|
||||
certInfo = getMunkiClientCertInfo()
|
||||
}
|
||||
let client_cert_path = certInfo["client_cert_path"] as? String ?? ""
|
||||
let client_key_path = certInfo["client_key_path"] as? String ?? ""
|
||||
if client_cert_path.isEmpty {
|
||||
// no client cert, so nothing to do
|
||||
displayDebug1("No client cert info provided, so no client keychain will be created.")
|
||||
return false
|
||||
} else {
|
||||
displayDebug1("Client cert path: \(client_cert_path)")
|
||||
displayDebug1("Client key path: \(client_key_path)")
|
||||
}
|
||||
|
||||
// to do some of the following options correctly, we need to be root
|
||||
// and have root's home.
|
||||
// check to see if we're root
|
||||
if NSUserName().lowercased() != "root" {
|
||||
displayError("Can't make our client keychain unless we are root!")
|
||||
return false
|
||||
}
|
||||
// make sure HOME has root's home
|
||||
var env = ProcessInfo.processInfo.environment
|
||||
env["HOME"] = NSHomeDirectoryForUser("root") ?? "/var/root"
|
||||
|
||||
let keychainPassword = pref("KeychainPassword") as? String ?? DEFAULT_KEYCHAIN_PASSWORD
|
||||
let keychainPath = getKeychainPath()
|
||||
if pathExists(keychainPath) {
|
||||
try? FileManager.default.removeItem(atPath: keychainPath)
|
||||
}
|
||||
if !pathExists(dirName(keychainPath)) {
|
||||
let attrs = [
|
||||
FileAttributeKey.posixPermissions: 0o700,
|
||||
] as [FileAttributeKey: Any]
|
||||
try? FileManager.default.createDirectory(atPath: dirName(keychainPath), withIntermediateDirectories: true, attributes: attrs)
|
||||
}
|
||||
// create a new keychain
|
||||
displayDebug1("Creating client keychain...")
|
||||
do {
|
||||
let output = try security(
|
||||
"create-keychain", "-p", keychainPassword, keychainPath,
|
||||
environment: env
|
||||
)
|
||||
if !output.isEmpty {
|
||||
displayDebug2(output)
|
||||
}
|
||||
} catch {
|
||||
displayError("Could not create keychain \(keychainPath): \(error)")
|
||||
}
|
||||
|
||||
// Ensure the keychain is in the search path and unlocked
|
||||
let addedKeychain = addToKeychainList(keychainPath, environment: env)
|
||||
unlockAndSetNonLocking(keychainPath, environment: env)
|
||||
|
||||
// Add client cert (and optionally key)
|
||||
var client_cert_file = ""
|
||||
var combined_pem = ""
|
||||
if !client_key_path.isEmpty {
|
||||
// combine client cert and private key before we import
|
||||
if let certData = try? Data(contentsOf: URL(fileURLWithPath: client_cert_path)),
|
||||
let keyData = try? Data(contentsOf: URL(fileURLWithPath: client_key_path)),
|
||||
let tempDir = TempDir.shared.path
|
||||
{
|
||||
// write the combined data
|
||||
combined_pem = (tempDir as NSString).appendingPathComponent("combined.pem")
|
||||
let combinedData = certData + keyData
|
||||
do {
|
||||
try combinedData.write(to: URL(fileURLWithPath: combined_pem))
|
||||
client_cert_file = combined_pem
|
||||
} catch {
|
||||
displayError("Could not combine client cert and key for import!")
|
||||
}
|
||||
} else {
|
||||
displayError("Could not read client cert or key file")
|
||||
}
|
||||
} else {
|
||||
client_cert_file = client_cert_path
|
||||
}
|
||||
if !client_cert_file.isEmpty {
|
||||
// client_cert_file is combined_pem or client_cert_file
|
||||
displayDebug2("Importing client cert and key...")
|
||||
do {
|
||||
let output = try security(
|
||||
"import", client_cert_file, "-A", "-k", keychainPath,
|
||||
environment: env
|
||||
)
|
||||
if !output.isEmpty {
|
||||
displayDebug2(output)
|
||||
}
|
||||
} catch {
|
||||
displayError("Could not import \(client_cert_file): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
if !combined_pem.isEmpty {
|
||||
// we created this; we should clean it up
|
||||
try? FileManager.default.removeItem(atPath: combined_pem)
|
||||
}
|
||||
|
||||
// we're done
|
||||
// if addedKeychain {
|
||||
// removeFromKeychainList(keychainPath, environment: env)
|
||||
// }
|
||||
displayInfo("Completed creation of client keychain at \(keychainPath)")
|
||||
return addedKeychain
|
||||
}
|
||||
|
||||
/// Wrapper class for handling the client keychain
|
||||
class MunkiKeychain {
|
||||
var keychainPath = ""
|
||||
var addedKeychain = false
|
||||
|
||||
/// Unlocks the munki.keychain if it exists.
|
||||
/// Makes sure the munki.keychain is in the search list.
|
||||
/// Creates a new client keychain if needed.
|
||||
init() {
|
||||
keychainPath = getKeychainPath()
|
||||
if clientCertExists(), pathExists(keychainPath) {
|
||||
do {
|
||||
try FileManager.default.removeItem(atPath: keychainPath)
|
||||
} catch {
|
||||
displayError("Could not remove pre-existing \(keychainPath): \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
if pathExists(keychainPath) {
|
||||
// ensure existing keychain is available for use
|
||||
addedKeychain = addToKeychainList(keychainPath)
|
||||
unlockAndSetNonLocking(keychainPath)
|
||||
}
|
||||
if !pathExists(keychainPath) {
|
||||
// try making a new keychain
|
||||
addedKeychain = makeClientKeychain()
|
||||
}
|
||||
if !pathExists(keychainPath) {
|
||||
// give up
|
||||
keychainPath = ""
|
||||
addedKeychain = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Remove our keychain from the keychain list if we added it
|
||||
if addedKeychain {
|
||||
removeFromKeychainList(keychainPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user