refactoring KeychainDictionary

from CFDictionary to NSDictionary
high-level API, supports ARC, which simplifies development and reduces the likelihood of memory leaks.
This commit is contained in:
Dmitrii Taradai 2024-09-09 10:44:16 +03:00
parent ad56019901
commit fd7a9c2f7a
4 changed files with 42 additions and 46 deletions

View File

@ -2,12 +2,6 @@ import Foundation
public typealias SecurityError = Unmanaged<CFError> public typealias SecurityError = Unmanaged<CFError>
/// Wraps a Swift dictionary in a CFDictionary.
/// - Parameter dictionary: The Swift dictionary to wrap.
/// - Returns: A CFDictionary containing the keys and values.
public func KeychainDictionary(_ dictionary: [CFString: Any]) -> CFDictionary {
dictionary as CFDictionary
}
public extension CFError { public extension CFError {

View File

@ -49,7 +49,7 @@ extension SecureEnclave {
throw error.takeRetainedValue() as Error throw error.takeRetainedValue() as Error
} }
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecAttrLabel: name, kSecAttrLabel: name,
kSecAttrKeyType: Constants.keyType, kSecAttrKeyType: Constants.keyType,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
@ -58,7 +58,7 @@ extension SecureEnclave {
kSecAttrIsPermanent: true, kSecAttrIsPermanent: true,
kSecAttrAccessControl: access kSecAttrAccessControl: access
] ]
]) ]
var createKeyError: SecurityError? var createKeyError: SecurityError?
let keypair = SecKeyCreateRandomKey(attributes, &createKeyError) let keypair = SecKeyCreateRandomKey(attributes, &createKeyError)
@ -73,10 +73,10 @@ extension SecureEnclave {
} }
public func delete(secret: Secret) throws { public func delete(secret: Secret) throws {
let deleteAttributes = KeychainDictionary([ let deleteAttributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData kSecAttrApplicationLabel: secret.id
]) ]
let status = SecItemDelete(deleteAttributes) let status = SecItemDelete(deleteAttributes)
if status != errSecSuccess { if status != errSecSuccess {
throw KeychainError(statusCode: status) throw KeychainError(statusCode: status)
@ -85,14 +85,14 @@ extension SecureEnclave {
} }
public func update(secret: Secret, name: String) throws { public func update(secret: Secret, name: String) throws {
let updateQuery = KeychainDictionary([ let updateQuery : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData kSecAttrApplicationLabel: secret.id
]) ]
let updatedAttributes = KeychainDictionary([ let updatedAttributes : NSDictionary = [
kSecAttrLabel: name, kSecAttrLabel: name,
]) ]
let status = SecItemUpdate(updateQuery, updatedAttributes) let status = SecItemUpdate(updateQuery, updatedAttributes)
if status != errSecSuccess { if status != errSecSuccess {
@ -111,16 +111,16 @@ extension SecureEnclave {
context = newContext context = newContext
} }
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id as CFData, kSecAttrApplicationLabel: secret.id,
kSecAttrKeyType: Constants.keyType, kSecAttrKeyType: Constants.keyType,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecAttrApplicationTag: Constants.keyTag, kSecAttrApplicationTag: Constants.keyTag,
kSecUseAuthenticationContext: context, kSecUseAuthenticationContext: context,
kSecReturnRef: true kSecReturnRef: true
]) ]
var untyped: CFTypeRef? var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped) let status = SecItemCopyMatching(attributes, &untyped)
if status != errSecSuccess { if status != errSecSuccess {
@ -142,16 +142,16 @@ extension SecureEnclave {
let context = LAContext() let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)") context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id as CFData, kSecAttrApplicationLabel: secret.id,
kSecAttrKeyType: Constants.keyType, kSecAttrKeyType: Constants.keyType,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecAttrApplicationTag: Constants.keyTag, kSecAttrApplicationTag: Constants.keyTag,
kSecUseAuthenticationContext: context, kSecUseAuthenticationContext: context,
kSecReturnRef: true kSecReturnRef: true
]) ]
var verifyError: SecurityError? var verifyError: SecurityError?
var untyped: CFTypeRef? var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped) let status = SecItemCopyMatching(attributes, &untyped)
@ -225,7 +225,7 @@ extension SecureEnclave.Store {
/// Loads all secrets from the store. /// Loads all secrets from the store.
private func loadSecrets() { private func loadSecrets() {
let publicAttributes = KeychainDictionary([ let publicAttributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyType: SecureEnclave.Constants.keyType, kSecAttrKeyType: SecureEnclave.Constants.keyType,
kSecAttrApplicationTag: SecureEnclave.Constants.keyTag, kSecAttrApplicationTag: SecureEnclave.Constants.keyTag,
@ -233,11 +233,11 @@ extension SecureEnclave.Store {
kSecReturnRef: true, kSecReturnRef: true,
kSecMatchLimit: kSecMatchLimitAll, kSecMatchLimit: kSecMatchLimitAll,
kSecReturnAttributes: true kSecReturnAttributes: true
]) ]
var publicUntyped: CFTypeRef? var publicUntyped: CFTypeRef?
SecItemCopyMatching(publicAttributes, &publicUntyped) SecItemCopyMatching(publicAttributes, &publicUntyped)
guard let publicTyped = publicUntyped as? [[CFString: Any]] else { return } guard let publicTyped = publicUntyped as? [[CFString: Any]] else { return }
let privateAttributes = KeychainDictionary([ let privateAttributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyType: SecureEnclave.Constants.keyType, kSecAttrKeyType: SecureEnclave.Constants.keyType,
kSecAttrApplicationTag: SecureEnclave.Constants.keyTag, kSecAttrApplicationTag: SecureEnclave.Constants.keyTag,
@ -245,7 +245,7 @@ extension SecureEnclave.Store {
kSecReturnRef: true, kSecReturnRef: true,
kSecMatchLimit: kSecMatchLimitAll, kSecMatchLimit: kSecMatchLimitAll,
kSecReturnAttributes: true kSecReturnAttributes: true
]) ]
var privateUntyped: CFTypeRef? var privateUntyped: CFTypeRef?
SecItemCopyMatching(privateAttributes, &privateUntyped) SecItemCopyMatching(privateAttributes, &privateUntyped)
guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return } guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return }
@ -283,7 +283,7 @@ extension SecureEnclave.Store {
/// - publicKey: The public key to save. /// - publicKey: The public key to save.
/// - name: A user-facing name for the key. /// - name: A user-facing name for the key.
private func savePublicKey(_ publicKey: SecKey, name: String) throws { private func savePublicKey(_ publicKey: SecKey, name: String) throws {
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyType: SecureEnclave.Constants.keyType, kSecAttrKeyType: SecureEnclave.Constants.keyType,
kSecAttrKeyClass: kSecAttrKeyClassPublic, kSecAttrKeyClass: kSecAttrKeyClassPublic,
@ -292,7 +292,7 @@ extension SecureEnclave.Store {
kSecAttrIsPermanent: true, kSecAttrIsPermanent: true,
kSecReturnData: true, kSecReturnData: true,
kSecAttrLabel: name kSecAttrLabel: name
]) ]
let status = SecItemAdd(attributes, nil) let status = SecItemAdd(attributes, nil)
if status != errSecSuccess { if status != errSecSuccess {
throw KeychainError(statusCode: status) throw KeychainError(statusCode: status)

View File

@ -52,14 +52,15 @@ extension SmartCard {
let context = LAContext() let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id as CFData, kSecAttrApplicationLabel: secret.id,
kSecAttrTokenID: tokenID, kSecAttrTokenID: tokenID,
kSecUseAuthenticationContext: context, kSecUseAuthenticationContext: context,
kSecReturnRef: true kSecReturnRef: true
]) ]
var untyped: CFTypeRef? var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped) let status = SecItemCopyMatching(attributes, &untyped)
if status != errSecSuccess { if status != errSecSuccess {
@ -77,11 +78,12 @@ extension SmartCard {
} }
public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool { public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool {
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeyType: secret.algorithm.secAttrKeyType,
kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeySizeInBits: secret.keySize,
kSecAttrKeyClass: kSecAttrKeyClassPublic kSecAttrKeyClass: kSecAttrKeyClassPublic
]) ]
var verifyError: SecurityError? var verifyError: SecurityError?
let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError) let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError)
guard let untypedSafe = untyped else { guard let untypedSafe = untyped else {
@ -145,13 +147,13 @@ extension SmartCard.Store {
name = fallbackName name = fallbackName
} }
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrTokenID: tokenID, kSecAttrTokenID: tokenID,
kSecReturnRef: true, kSecReturnRef: true,
kSecMatchLimit: kSecMatchLimitAll, kSecMatchLimit: kSecMatchLimitAll,
kSecReturnAttributes: true kSecReturnAttributes: true
]) ]
var untyped: CFTypeRef? var untyped: CFTypeRef?
SecItemCopyMatching(attributes, &untyped) SecItemCopyMatching(attributes, &untyped)
guard let typed = untyped as? [[CFString: Any]] else { return } guard let typed = untyped as? [[CFString: Any]] else { return }
@ -185,12 +187,12 @@ extension SmartCard.Store {
let context = LAContext() let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_encrypt_description_\(secret.name)") context.localizedReason = String(localized: "auth_context_request_encrypt_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeyType: secret.algorithm.secAttrKeyType,
kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeySizeInBits: secret.keySize,
kSecAttrKeyClass: kSecAttrKeyClassPublic, kSecAttrKeyClass: kSecAttrKeyClassPublic,
kSecUseAuthenticationContext: context kSecUseAuthenticationContext: context
]) ]
var encryptError: SecurityError? var encryptError: SecurityError?
let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError) let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError)
guard let untypedSafe = untyped else { guard let untypedSafe = untyped else {
@ -214,14 +216,14 @@ extension SmartCard.Store {
let context = LAContext() let context = LAContext()
context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)") context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)")
context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button")
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecClass: kSecClassKey, kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrApplicationLabel: secret.id as CFData, kSecAttrApplicationLabel: secret.id,
kSecAttrTokenID: tokenID, kSecAttrTokenID: tokenID,
kSecUseAuthenticationContext: context, kSecUseAuthenticationContext: context,
kSecReturnRef: true kSecReturnRef: true
]) ]
var untyped: CFTypeRef? var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped) let status = SecItemCopyMatching(attributes, &untyped)
if status != errSecSuccess { if status != errSecSuccess {

View File

@ -27,7 +27,7 @@ extension Stub {
flags, flags,
nil) as Any nil) as Any
let attributes = KeychainDictionary([ let attributes : NSDictionary = [
kSecAttrLabel: name, kSecAttrLabel: name,
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: size, kSecAttrKeySizeInBits: size,
@ -35,7 +35,7 @@ extension Stub {
kSecAttrIsPermanent: true, kSecAttrIsPermanent: true,
kSecAttrAccessControl: access kSecAttrAccessControl: access
] ]
]) ]
let privateKey = SecKeyCreateRandomKey(attributes, nil)! let privateKey = SecKeyCreateRandomKey(attributes, nil)!
let publicKey = SecKeyCopyPublicKey(privateKey)! let publicKey = SecKeyCopyPublicKey(privateKey)!
@ -52,21 +52,21 @@ extension Stub {
guard !shouldThrow else { guard !shouldThrow else {
throw NSError(domain: "test", code: 0, userInfo: nil) throw NSError(domain: "test", code: 0, userInfo: nil)
} }
let privateKey = SecKeyCreateWithData(secret.privateKey as CFData, KeychainDictionary([ let privateKey = SecKeyCreateWithData(secret.privateKey as CFData, [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeySizeInBits: secret.keySize,
kSecAttrKeyClass: kSecAttrKeyClassPrivate kSecAttrKeyClass: kSecAttrKeyClassPrivate
]) ] as CFDictionary
, nil)! , nil)!
return SecKeyCreateSignature(privateKey, signatureAlgorithm(for: secret), data as CFData, nil)! as Data return SecKeyCreateSignature(privateKey, signatureAlgorithm(for: secret), data as CFData, nil)! as Data
} }
public func verify(signature: Data, for data: Data, with secret: Stub.Secret) throws -> Bool { public func verify(signature: Data, for data: Data, with secret: Stub.Secret) throws -> Bool {
let attributes = KeychainDictionary([ let attributes: NSDictionary = [
kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeyType: secret.algorithm.secAttrKeyType,
kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeySizeInBits: secret.keySize,
kSecAttrKeyClass: kSecAttrKeyClassPublic kSecAttrKeyClass: kSecAttrKeyClassPublic
]) ]
var verifyError: Unmanaged<CFError>? var verifyError: Unmanaged<CFError>?
let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError) let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError)
guard let untypedSafe = untyped else { guard let untypedSafe = untyped else {