diff --git a/CHANGELOG.md b/CHANGELOG.md index d5aa04c..987743e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.1] - 2024-01-05 +### Changed +- System keychain is located using Security framework API. + ## [1.1.0] - 2023-09-14 ### Added - Import and export capability for certificates, keys, and identities. diff --git a/Sources/Haversack/Security/KeychainFile.swift b/Sources/Haversack/Security/KeychainFile.swift index ff25daa..5a3f265 100644 --- a/Sources/Haversack/Security/KeychainFile.swift +++ b/Sources/Haversack/Security/KeychainFile.swift @@ -31,10 +31,27 @@ public class KeychainFile { public static let systemRootCertificates = KeychainFile(at: rootCertificatesKeychainPath) /// The path to the system keychain. - static let systemKeychainPath = "/Library/Keychains/System.keychain" + static let systemKeychainPath = system.path /// An instance of ``KeychainFile`` that points at the system keychain - public static let system = KeychainFile(at: systemKeychainPath) + public static let system: KeychainFile = { + let legacySystemKeychainPath = "/Library/Keychains/System.keychain" + var searchList: CFArray? + let status = withUnsafeMutablePointer(to: &searchList) { + SecKeychainCopyDomainSearchList(.system, UnsafeMutablePointer($0)) + } + + guard status == errSecSuccess else { + // attempt to use traditional path, may fail later + return KeychainFile(at: legacySystemKeychainPath) + } + + guard let searchList = searchList as? [SecKeychain], let systemKeychain = searchList.first else { + return KeychainFile(at: legacySystemKeychainPath) + } + + return KeychainFile(reference: systemKeychain) + }() /// The full path to the keychain file. public let path: FilePath @@ -55,6 +72,29 @@ public class KeychainFile { self.passwordProvider = passwordProvider } + /// Create an instance from an existing keychain reference + /// - Parameters: + /// - reference: A reference to a `SecKeychain`. + init(reference: SecKeychain) { + passwordProvider = nil + self.reference = reference + + var pathLength = UInt32(PATH_MAX) + let pathName = UnsafeMutablePointer.allocate(capacity: Int(pathLength)) + let status = withUnsafeMutablePointer(to: &pathLength) { pathLength in + SecKeychainGetPath(reference, pathLength, pathName) + } + + if status == errSecSuccess { + path = FileManager().string(withFileSystemRepresentation: pathName, length: Int(pathLength)) + } else { + // should never happen + path = "" + } + + pathName.deallocate() + } + /// Try to open and unlock the keychain file, or create the keychain if it does not yet exist. /// - Throws: A ``HaversackError`` entity public func attemptToOpenOrCreate() throws {