mirror of
https://github.com/apple/container.git
synced 2026-10-15 18:49:20 +00:00
Adds access group to keychain helper APIs. (#553)
- Will be used by the fix for apple/container#1253. - We use a common keychain ID between the `container CLI and `container-core-images`, but right now keychain entries created by the CLI must be opted-in for `container-core-images`. - Using an access group with a notarized application should give both apps automatic access to the keychain.
This commit is contained in:
1 parent
6f9ae02933
commit
eff935e35f
3 files changed
+116
-34
No files matched your search
@@ -30,15 +30,28 @@ public struct KeychainQuery {
|
||||
public init() {}
|
||||
|
||||
/// Save a value to the keychain.
|
||||
public func save(securityDomain: String, hostname: String, username: String, password: String) throws {
|
||||
if try exists(securityDomain: securityDomain, hostname: hostname) {
|
||||
try delete(securityDomain: securityDomain, hostname: hostname)
|
||||
/// - Parameters:
|
||||
/// - securityDomain: The security domain used to fetch keychain entries.
|
||||
/// - accessGroup: If present, the access group used to fetch keychain entries.
|
||||
/// - hostname: The hostname for the authenticating server.
|
||||
/// - username: The username to present to the server.
|
||||
/// - password: The password to present to the server.
|
||||
/// - Throws: An error if the keychain query fails or returns unexpected data.
|
||||
public func save(
|
||||
securityDomain: String,
|
||||
accessGroup: String? = nil,
|
||||
hostname: String,
|
||||
username: String,
|
||||
password: String
|
||||
) throws {
|
||||
if try exists(securityDomain: securityDomain, accessGroup: accessGroup, hostname: hostname) {
|
||||
try delete(securityDomain: securityDomain, accessGroup: accessGroup, hostname: hostname)
|
||||
}
|
||||
|
||||
guard let passwordEncoded = password.data(using: String.Encoding.utf8) else {
|
||||
throw Self.Error.invalidPasswordConversion
|
||||
}
|
||||
let query: [String: Any] = [
|
||||
var query: [String: Any] = [
|
||||
kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrSecurityDomain as String: securityDomain,
|
||||
kSecAttrServer as String: hostname,
|
||||
@@ -47,18 +60,30 @@ public struct KeychainQuery {
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
||||
kSecAttrSynchronizable as String: false,
|
||||
]
|
||||
if let accessGroup {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup
|
||||
}
|
||||
|
||||
let status = SecItemAdd(query as CFDictionary, nil)
|
||||
guard status == errSecSuccess else { throw Self.Error.unhandledError(status: status) }
|
||||
}
|
||||
|
||||
/// Delete a value from the keychain.
|
||||
public func delete(securityDomain: String, hostname: String) throws {
|
||||
let query: [String: Any] = [
|
||||
/// - Parameters:
|
||||
/// - securityDomain: The security domain used to fetch keychain entries.
|
||||
/// - accessGroup: If present, the access group used to fetch keychain entries.
|
||||
/// - hostname: The hostname for the authenticating server.
|
||||
/// - Throws: An error if the keychain query fails or returns unexpected data.
|
||||
public func delete(securityDomain: String, accessGroup: String? = nil, hostname: String) throws {
|
||||
var query: [String: Any] = [
|
||||
kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrSecurityDomain as String: securityDomain,
|
||||
kSecAttrServer as String: hostname,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
]
|
||||
if let accessGroup {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup
|
||||
}
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
guard status == errSecSuccess || status == errSecItemNotFound else {
|
||||
throw Self.Error.unhandledError(status: status)
|
||||
@@ -66,8 +91,14 @@ public struct KeychainQuery {
|
||||
}
|
||||
|
||||
/// Retrieve a value from the keychain.
|
||||
public func get(securityDomain: String, hostname: String) throws -> KeychainQueryResult? {
|
||||
let query: [String: Any] = [
|
||||
/// - Parameters:
|
||||
/// - securityDomain: The security domain used to fetch keychain entries.
|
||||
/// - accessGroup: If present, the access group used to fetch keychain entries.
|
||||
/// - hostname: The hostname for the authenticating server.
|
||||
/// - Returns: The keychain entry.
|
||||
/// - Throws: An error if the keychain query fails or returns unexpected data.
|
||||
public func get(securityDomain: String, accessGroup: String? = nil, hostname: String) throws -> KeychainQueryResult? {
|
||||
var query: [String: Any] = [
|
||||
kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrSecurityDomain as String: securityDomain,
|
||||
kSecAttrServer as String: hostname,
|
||||
@@ -75,6 +106,9 @@ public struct KeychainQuery {
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecReturnData as String: true,
|
||||
]
|
||||
if let accessGroup {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup
|
||||
}
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
let exists = try isQuerySuccessful(status)
|
||||
@@ -108,18 +142,23 @@ public struct KeychainQuery {
|
||||
)
|
||||
}
|
||||
|
||||
/// List all registry entries in the keychain for a domain.
|
||||
/// - Parameter securityDomain: The security domain used to fetch registry entries in the keychain.
|
||||
/// - Returns: An array of registry metadata for each matching entry, or an empty array if none are found.
|
||||
/// List all keychain entries for a domain.
|
||||
/// - Parameters:
|
||||
/// - securityDomain: The security domain used to fetch keychain entries.
|
||||
/// - accessGroup: If present, the access group used to fetch keychain entries.
|
||||
/// - Returns: An array of keychain metadata for each matching entry, or an empty array if none are found.
|
||||
/// - Throws: An error if the keychain query fails or returns unexpected data.
|
||||
public func list(securityDomain: String) throws -> [RegistryInfo] {
|
||||
let query: [String: Any] = [
|
||||
public func list(securityDomain: String, accessGroup: String? = nil) throws -> [RegistryInfo] {
|
||||
var query: [String: Any] = [
|
||||
kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrSecurityDomain as String: securityDomain,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecReturnData as String: false,
|
||||
kSecMatchLimit as String: kSecMatchLimitAll,
|
||||
]
|
||||
if let accessGroup {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup
|
||||
}
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
let exists = try isQuerySuccessful(status)
|
||||
@@ -154,6 +193,30 @@ public struct KeychainQuery {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a value exists in the keychain.
|
||||
/// - Parameters:
|
||||
/// - securityDomain: The security domain used to fetch keychain entries.
|
||||
/// - accessGroup: If present, the access group used to fetch keychain entries.
|
||||
/// - hostname: The hostname for the authenticating server.
|
||||
/// - Returns: `true` if the entry exists, `false` otherwise.
|
||||
/// - Throws: An error if the keychain query fails.
|
||||
public func exists(securityDomain: String, accessGroup: String? = nil, hostname: String) throws -> Bool {
|
||||
var query: [String: Any] = [
|
||||
kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrSecurityDomain as String: securityDomain,
|
||||
kSecAttrServer as String: hostname,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecReturnData as String: false,
|
||||
]
|
||||
if let accessGroup {
|
||||
query[kSecAttrAccessGroup as String] = accessGroup
|
||||
}
|
||||
|
||||
let status = SecItemCopyMatching(query as CFDictionary, nil)
|
||||
return try isQuerySuccessful(status)
|
||||
}
|
||||
|
||||
private func isQuerySuccessful(_ status: Int32) throws -> Bool {
|
||||
guard status != errSecItemNotFound else {
|
||||
return false
|
||||
@@ -163,21 +226,6 @@ public struct KeychainQuery {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// Check if a value exists in the keychain.
|
||||
public func exists(securityDomain: String, hostname: String) throws -> Bool {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassInternetPassword,
|
||||
kSecAttrSecurityDomain as String: securityDomain,
|
||||
kSecAttrServer as String: hostname,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecReturnData as String: false,
|
||||
]
|
||||
|
||||
let status = SecItemCopyMatching(query as CFDictionary, nil)
|
||||
return try isQuerySuccessful(status)
|
||||
}
|
||||
}
|
||||
|
||||
extension KeychainQuery {
|
||||
|
||||
Reference in new issue
Block a user