From a192c6728f749e74f3e1d725a03873fb28ded5df Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 11 Mar 2023 17:46:47 -0800 Subject: [PATCH] Factor out some common keychain functionality --- .../Documentation.docc/Documentation.md | 6 ++ .../SecretKit/KeychainDictionary.swift | 5 -- .../Sources/SecretKit/KeychainTypes.swift | 71 +++++++++++++++ .../Sources/SecretKit/Types/SecretStore.swift | 12 --- .../SecureEnclaveStore.swift | 19 +--- .../Documentation.docc/SmartCard.md | 6 -- .../SmartCardSecretKit/SmartCardStore.swift | 90 +++++++------------ .../Tests/SecretAgentKitTests/StubStore.swift | 26 +----- 8 files changed, 114 insertions(+), 121 deletions(-) delete mode 100644 Sources/Packages/Sources/SecretKit/KeychainDictionary.swift create mode 100644 Sources/Packages/Sources/SecretKit/KeychainTypes.swift diff --git a/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md b/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md index 3c608d2..a7fed06 100644 --- a/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md +++ b/Sources/Packages/Sources/SecretKit/Documentation.docc/Documentation.md @@ -32,3 +32,9 @@ SecretKit is a collection of protocols describing secrets and stores. ### Authentication Persistence - ``PersistedAuthenticationContext`` + +### Errors + +- ``KeychainError`` +- ``SigningError`` +- ``SecurityError`` diff --git a/Sources/Packages/Sources/SecretKit/KeychainDictionary.swift b/Sources/Packages/Sources/SecretKit/KeychainDictionary.swift deleted file mode 100644 index 460af92..0000000 --- a/Sources/Packages/Sources/SecretKit/KeychainDictionary.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public func KeychainDictionary(_ dictionary: [CFString: Any]) -> CFDictionary { - dictionary as CFDictionary -} diff --git a/Sources/Packages/Sources/SecretKit/KeychainTypes.swift b/Sources/Packages/Sources/SecretKit/KeychainTypes.swift new file mode 100644 index 0000000..cfea466 --- /dev/null +++ b/Sources/Packages/Sources/SecretKit/KeychainTypes.swift @@ -0,0 +1,71 @@ +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 { + + /// The CFError returned when a verification operation fails. + static let verifyError = CFErrorCreate(nil, NSOSStatusErrorDomain as CFErrorDomain, CFIndex(errSecVerifyFailed), nil)! + + /// Equality operation that only considers domain and code. + static func ~=(lhs: CFError, rhs: CFError) -> Bool { + CFErrorGetDomain(lhs) == CFErrorGetDomain(rhs) && CFErrorGetCode(lhs) == CFErrorGetCode(rhs) + } + +} + +/// A wrapper around an error code reported by a Keychain API. +public struct KeychainError: Error { + /// The status code involved, if one was reported. + public let statusCode: OSStatus? + + /// Initializes a KeychainError with an optional error code. + /// - Parameter statusCode: The status code returned by the keychain operation, if one is applicable. + public init(statusCode: OSStatus?) { + self.statusCode = statusCode + } +} + +/// A signing-related error. +public struct SigningError: Error { + /// The underlying error reported by the API, if one was returned. + public let error: SecurityError? + + /// Initializes a SigningError with an optional SecurityError. + /// - Parameter statusCode: The SecurityError, if one is applicable. + public init(error: SecurityError?) { + self.error = error + } + +} + +public extension SecretStore { + + /// Returns the appropriate keychian signature algorithm to use for a given secret. + /// - Parameters: + /// - secret: The secret which will be used for signing. + /// - allowRSA: Whether or not RSA key types should be permited. + /// - Returns: The appropriate algorithm. + func signatureAlgorithm(for secret: SecretType, allowRSA: Bool = false) -> SecKeyAlgorithm { + switch (secret.algorithm, secret.keySize) { + case (.ellipticCurve, 256): + return .ecdsaSignatureMessageX962SHA256 + case (.ellipticCurve, 384): + return .ecdsaSignatureMessageX962SHA384 + case (.rsa, 1024), (.rsa, 2048): + guard allowRSA else { fatalError() } + return .rsaSignatureMessagePKCS1v15SHA512 + default: + fatalError() + } + + } + +} diff --git a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift index 954fe21..f780201 100644 --- a/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift +++ b/Sources/Packages/Sources/SecretKit/Types/SecretStore.swift @@ -79,15 +79,3 @@ extension NSNotification.Name { public static let secretStoreReloaded = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.reloaded") } - -public typealias SecurityError = Unmanaged - -extension CFError { - - public static let verifyError = CFErrorCreate(nil, NSOSStatusErrorDomain as CFErrorDomain, CFIndex(errSecVerifyFailed), nil)! - - static public func ~=(lhs: CFError, rhs: CFError) -> Bool { - CFErrorGetDomain(lhs) == CFErrorGetDomain(rhs) && CFErrorGetCode(lhs) == CFErrorGetCode(rhs) - } - -} diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 4cd32f9..5a853c4 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -295,29 +295,12 @@ extension SecureEnclave.Store { ]) let status = SecItemAdd(attributes, nil) if status != errSecSuccess { - throw SecureEnclave.KeychainError(statusCode: status) + throw KeychainError(statusCode: status) } } } -extension SecureEnclave { - - /// A wrapper around an error code reported by a Keychain API. - public struct KeychainError: Error { - /// The status code involved, if one was reported. - public let statusCode: OSStatus? - } - - /// A signing-related error. - public struct SigningError: Error { - /// The underlying error reported by the API, if one was returned. - public let error: SecurityError? - } - -} - - extension SecureEnclave { enum Constants { diff --git a/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/SmartCard.md b/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/SmartCard.md index 79fd58e..6a386f5 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/SmartCard.md +++ b/Sources/Packages/Sources/SmartCardSecretKit/Documentation.docc/SmartCard.md @@ -6,9 +6,3 @@ - ``Secret`` - ``Store`` - -### Errors - -- ``KeychainError`` -- ``SigningError`` -- ``SecurityError`` diff --git a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift index 156bc99..200a5b4 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift +++ b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift @@ -68,24 +68,12 @@ extension SmartCard { } let key = untypedSafe as! SecKey var signError: SecurityError? - let signatureAlgorithm: SecKeyAlgorithm - switch (secret.algorithm, secret.keySize) { - case (.ellipticCurve, 256): - signatureAlgorithm = .ecdsaSignatureMessageX962SHA256 - case (.ellipticCurve, 384): - signatureAlgorithm = .ecdsaSignatureMessageX962SHA384 - case (.rsa, 1024): - signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512 - case (.rsa, 2048): - signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512 - default: - fatalError() - } - guard let signature = SecKeyCreateSignature(key, signatureAlgorithm, data as CFData, &signError) else { + guard let signature = SecKeyCreateSignature(key, signatureAlgorithm(for: secret, allowRSA: true), data as CFData, &signError) else { throw SigningError(error: signError) } return signature as Data } + public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool { let attributes = KeychainDictionary([ kSecAttrKeyType: secret.algorithm.secAttrKeyType, @@ -98,20 +86,7 @@ extension SmartCard { throw KeychainError(statusCode: errSecSuccess) } let key = untypedSafe as! SecKey - let signatureAlgorithm: SecKeyAlgorithm - switch (secret.algorithm, secret.keySize) { - case (.ellipticCurve, 256): - signatureAlgorithm = .ecdsaSignatureMessageX962SHA256 - case (.ellipticCurve, 384): - signatureAlgorithm = .ecdsaSignatureMessageX962SHA384 - case (.rsa, 1024): - signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512 - case (.rsa, 2048): - signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512 - default: - fatalError() - } - let verified = SecKeyVerifySignature(key, signatureAlgorithm, data as CFData, signature as CFData, &verifyError) + let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret, allowRSA: true), data as CFData, signature as CFData, &verifyError) if !verified, let verifyError { if verifyError.takeUnretainedValue() ~= .verifyError { return false @@ -122,11 +97,11 @@ extension SmartCard { return verified } - public func existingPersistedAuthenticationContext(secret: SmartCard.Secret) -> PersistedAuthenticationContext? { + public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? { nil } - public func persistAuthentication(secret: SmartCard.Secret, forDuration: TimeInterval) throws { + public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws { } /// Reloads all secrets from the store. @@ -200,6 +175,20 @@ extension SmartCard.Store { secrets.append(contentsOf: wrapped) } + private func signatureAlgorithm(for secret: SmartCard.Secret) -> SecKeyAlgorithm { + switch (secret.algorithm, secret.keySize) { + case (.ellipticCurve, 256): + return .ecdsaSignatureMessageX962SHA256 + case (.ellipticCurve, 384): + return .ecdsaSignatureMessageX962SHA384 + case (.rsa, 1024), (.rsa, 2048): + return .rsaSignatureMessagePKCS1v15SHA512 + default: + fatalError() + } + + } + } @@ -228,20 +217,7 @@ extension SmartCard.Store { throw SmartCard.KeychainError(statusCode: errSecSuccess) } let key = untypedSafe as! SecKey - let signatureAlgorithm: SecKeyAlgorithm - switch (secret.algorithm, secret.keySize) { - case (.ellipticCurve, 256): - signatureAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM - case (.ellipticCurve, 384): - signatureAlgorithm = .eciesEncryptionCofactorVariableIVX963SHA256AESGCM - case (.rsa, 1024): - signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM - case (.rsa, 2048): - signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM - default: - fatalError() - } - guard let signature = SecKeyCreateEncryptedData(key, signatureAlgorithm, data as CFData, &encryptError) else { + guard let signature = SecKeyCreateEncryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else { throw SmartCard.SigningError(error: encryptError) } return signature as Data @@ -276,23 +252,25 @@ extension SmartCard.Store { } let key = untypedSafe as! SecKey var encryptError: SecurityError? - let signatureAlgorithm: SecKeyAlgorithm - switch (secret.algorithm, secret.keySize) { - case (.ellipticCurve, 256): - signatureAlgorithm = .eciesEncryptionStandardX963SHA256AESGCM - case (.ellipticCurve, 384): - signatureAlgorithm = .eciesEncryptionStandardX963SHA384AESGCM - case (.rsa, 1024), (.rsa, 2048): - signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM - default: - fatalError() - } - guard let signature = SecKeyCreateDecryptedData(key, signatureAlgorithm, data as CFData, &encryptError) else { + guard let signature = SecKeyCreateDecryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else { throw SmartCard.SigningError(error: encryptError) } return signature as Data } + private func encryptionAlgorithm(for secret: SmartCard.Secret) -> SecKeyAlgorithm { + switch (secret.algorithm, secret.keySize) { + case (.ellipticCurve, 256): + return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM + case (.ellipticCurve, 384): + return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM + case (.rsa, 1024), (.rsa, 2048): + return .rsaEncryptionOAEPSHA512AESGCM + default: + fatalError() + } + } + } extension TKTokenWatcher { diff --git a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift index acbda1d..f155706 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift @@ -58,16 +58,7 @@ extension Stub { kSecAttrKeyClass: kSecAttrKeyClassPrivate ]) , nil)! - let signatureAlgorithm: SecKeyAlgorithm - switch secret.keySize { - case 256: - signatureAlgorithm = .ecdsaSignatureMessageX962SHA256 - case 384: - signatureAlgorithm = .ecdsaSignatureMessageX962SHA384 - default: - fatalError() - } - return SecKeyCreateSignature(privateKey, signatureAlgorithm, 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 { @@ -82,20 +73,7 @@ extension Stub { throw NSError(domain: "test", code: 0, userInfo: nil) } let key = untypedSafe as! SecKey - let signatureAlgorithm: SecKeyAlgorithm - switch (secret.algorithm, secret.keySize) { - case (.ellipticCurve, 256): - signatureAlgorithm = .ecdsaSignatureMessageX962SHA256 - case (.ellipticCurve, 384): - signatureAlgorithm = .ecdsaSignatureMessageX962SHA384 - case (.rsa, 1024): - signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512 - case (.rsa, 2048): - signatureAlgorithm = .rsaSignatureMessagePKCS1v15SHA512 - default: - fatalError() - } - let verified = SecKeyVerifySignature(key, signatureAlgorithm, data as CFData, signature as CFData, &verifyError) + let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret), data as CFData, signature as CFData, &verifyError) if let verifyError { if verifyError.takeUnretainedValue() ~= .verifyError { return false