From fd7a9c2f7a7daf34dab53284737f06ca6c96b69c Mon Sep 17 00:00:00 2001 From: Dmitrii Taradai Date: Mon, 9 Sep 2024 10:44:16 +0300 Subject: [PATCH] refactoring KeychainDictionary from CFDictionary to NSDictionary high-level API, supports ARC, which simplifies development and reduces the likelihood of memory leaks. --- .../Sources/SecretKit/KeychainTypes.swift | 6 --- .../SecureEnclaveStore.swift | 44 +++++++++---------- .../SmartCardSecretKit/SmartCardStore.swift | 26 ++++++----- .../Tests/SecretAgentKitTests/StubStore.swift | 12 ++--- 4 files changed, 42 insertions(+), 46 deletions(-) diff --git a/Sources/Packages/Sources/SecretKit/KeychainTypes.swift b/Sources/Packages/Sources/SecretKit/KeychainTypes.swift index cfea466..f5903a1 100644 --- a/Sources/Packages/Sources/SecretKit/KeychainTypes.swift +++ b/Sources/Packages/Sources/SecretKit/KeychainTypes.swift @@ -2,12 +2,6 @@ import Foundation public typealias SecurityError = Unmanaged -/// 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 { diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 19b6168..a07e135 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -49,7 +49,7 @@ extension SecureEnclave { throw error.takeRetainedValue() as Error } - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecAttrLabel: name, kSecAttrKeyType: Constants.keyType, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, @@ -58,7 +58,7 @@ extension SecureEnclave { kSecAttrIsPermanent: true, kSecAttrAccessControl: access ] - ]) + ] var createKeyError: SecurityError? let keypair = SecKeyCreateRandomKey(attributes, &createKeyError) @@ -73,10 +73,10 @@ extension SecureEnclave { } public func delete(secret: Secret) throws { - let deleteAttributes = KeychainDictionary([ + let deleteAttributes : NSDictionary = [ kSecClass: kSecClassKey, - kSecAttrApplicationLabel: secret.id as CFData - ]) + kSecAttrApplicationLabel: secret.id + ] let status = SecItemDelete(deleteAttributes) if status != errSecSuccess { throw KeychainError(statusCode: status) @@ -85,14 +85,14 @@ extension SecureEnclave { } public func update(secret: Secret, name: String) throws { - let updateQuery = KeychainDictionary([ + let updateQuery : NSDictionary = [ kSecClass: kSecClassKey, - kSecAttrApplicationLabel: secret.id as CFData - ]) + kSecAttrApplicationLabel: secret.id + ] - let updatedAttributes = KeychainDictionary([ + let updatedAttributes : NSDictionary = [ kSecAttrLabel: name, - ]) + ] let status = SecItemUpdate(updateQuery, updatedAttributes) if status != errSecSuccess { @@ -111,16 +111,16 @@ extension SecureEnclave { context = newContext } context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, - kSecAttrApplicationLabel: secret.id as CFData, + kSecAttrApplicationLabel: secret.id, kSecAttrKeyType: Constants.keyType, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrApplicationTag: Constants.keyTag, kSecUseAuthenticationContext: context, kSecReturnRef: true - ]) + ] var untyped: CFTypeRef? let status = SecItemCopyMatching(attributes, &untyped) if status != errSecSuccess { @@ -142,16 +142,16 @@ extension SecureEnclave { let context = LAContext() context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, - kSecAttrApplicationLabel: secret.id as CFData, + kSecAttrApplicationLabel: secret.id, kSecAttrKeyType: Constants.keyType, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, kSecAttrApplicationTag: Constants.keyTag, kSecUseAuthenticationContext: context, kSecReturnRef: true - ]) + ] var verifyError: SecurityError? var untyped: CFTypeRef? let status = SecItemCopyMatching(attributes, &untyped) @@ -225,7 +225,7 @@ extension SecureEnclave.Store { /// Loads all secrets from the store. private func loadSecrets() { - let publicAttributes = KeychainDictionary([ + let publicAttributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyType: SecureEnclave.Constants.keyType, kSecAttrApplicationTag: SecureEnclave.Constants.keyTag, @@ -233,11 +233,11 @@ extension SecureEnclave.Store { kSecReturnRef: true, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true - ]) + ] var publicUntyped: CFTypeRef? SecItemCopyMatching(publicAttributes, &publicUntyped) guard let publicTyped = publicUntyped as? [[CFString: Any]] else { return } - let privateAttributes = KeychainDictionary([ + let privateAttributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyType: SecureEnclave.Constants.keyType, kSecAttrApplicationTag: SecureEnclave.Constants.keyTag, @@ -245,7 +245,7 @@ extension SecureEnclave.Store { kSecReturnRef: true, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true - ]) + ] var privateUntyped: CFTypeRef? SecItemCopyMatching(privateAttributes, &privateUntyped) guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return } @@ -283,7 +283,7 @@ extension SecureEnclave.Store { /// - publicKey: The public key to save. /// - name: A user-facing name for the key. private func savePublicKey(_ publicKey: SecKey, name: String) throws { - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyType: SecureEnclave.Constants.keyType, kSecAttrKeyClass: kSecAttrKeyClassPublic, @@ -292,7 +292,7 @@ extension SecureEnclave.Store { kSecAttrIsPermanent: true, kSecReturnData: true, kSecAttrLabel: name - ]) + ] let status = SecItemAdd(attributes, nil) if status != errSecSuccess { throw KeychainError(statusCode: status) diff --git a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift index c8c3281..9f02326 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift +++ b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift @@ -52,14 +52,15 @@ extension SmartCard { let context = LAContext() context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, - kSecAttrApplicationLabel: secret.id as CFData, + kSecAttrApplicationLabel: secret.id, kSecAttrTokenID: tokenID, kSecUseAuthenticationContext: context, kSecReturnRef: true - ]) + ] + var untyped: CFTypeRef? let status = SecItemCopyMatching(attributes, &untyped) if status != errSecSuccess { @@ -77,11 +78,12 @@ extension SmartCard { } public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool { - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeyClass: kSecAttrKeyClassPublic - ]) + ] + var verifyError: SecurityError? let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError) guard let untypedSafe = untyped else { @@ -145,13 +147,13 @@ extension SmartCard.Store { name = fallbackName } - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrTokenID: tokenID, kSecReturnRef: true, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true - ]) + ] var untyped: CFTypeRef? SecItemCopyMatching(attributes, &untyped) guard let typed = untyped as? [[CFString: Any]] else { return } @@ -185,12 +187,12 @@ extension SmartCard.Store { let context = LAContext() context.localizedReason = String(localized: "auth_context_request_encrypt_description_\(secret.name)") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeyClass: kSecAttrKeyClassPublic, kSecUseAuthenticationContext: context - ]) + ] var encryptError: SecurityError? let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError) guard let untypedSafe = untyped else { @@ -214,14 +216,14 @@ extension SmartCard.Store { let context = LAContext() context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)") context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, - kSecAttrApplicationLabel: secret.id as CFData, + kSecAttrApplicationLabel: secret.id, kSecAttrTokenID: tokenID, kSecUseAuthenticationContext: context, kSecReturnRef: true - ]) + ] var untyped: CFTypeRef? let status = SecItemCopyMatching(attributes, &untyped) if status != errSecSuccess { diff --git a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift index f990f97..74b432a 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift @@ -27,7 +27,7 @@ extension Stub { flags, nil) as Any - let attributes = KeychainDictionary([ + let attributes : NSDictionary = [ kSecAttrLabel: name, kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits: size, @@ -35,7 +35,7 @@ extension Stub { kSecAttrIsPermanent: true, kSecAttrAccessControl: access ] - ]) + ] let privateKey = SecKeyCreateRandomKey(attributes, nil)! let publicKey = SecKeyCopyPublicKey(privateKey)! @@ -52,21 +52,21 @@ extension Stub { guard !shouldThrow else { 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, kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeyClass: kSecAttrKeyClassPrivate - ]) + ] as CFDictionary , nil)! 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 { - let attributes = KeychainDictionary([ + let attributes: NSDictionary = [ kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeyClass: kSecAttrKeyClassPublic - ]) + ] var verifyError: Unmanaged? let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError) guard let untypedSafe = untyped else {