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..b6fe2fc 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. @@ -186,7 +161,7 @@ extension SmartCard.Store { var untyped: CFTypeRef? SecItemCopyMatching(attributes, &untyped) guard let typed = untyped as? [[CFString: Any]] else { return } - let wrapped: [SmartCard.Secret] = typed.map { + let wrapped = typed.map { let name = $0[kSecAttrLabel] as? String ?? "Unnamed" let tokenID = $0[kSecAttrApplicationLabel] as! Data let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber) @@ -225,24 +200,11 @@ extension SmartCard.Store { var encryptError: SecurityError? let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError) guard let untypedSafe = untyped else { - throw SmartCard.KeychainError(statusCode: errSecSuccess) + throw 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 { - throw SmartCard.SigningError(error: encryptError) + guard let signature = SecKeyCreateEncryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else { + throw SigningError(error: encryptError) } return signature as Data } @@ -269,28 +231,30 @@ extension SmartCard.Store { var untyped: CFTypeRef? let status = SecItemCopyMatching(attributes, &untyped) if status != errSecSuccess { - throw SmartCard.KeychainError(statusCode: status) + throw KeychainError(statusCode: status) } guard let untypedSafe = untyped else { - throw SmartCard.KeychainError(statusCode: errSecSuccess) + throw KeychainError(statusCode: errSecSuccess) } let key = untypedSafe as! SecKey var encryptError: SecurityError? - let signatureAlgorithm: SecKeyAlgorithm + guard let signature = SecKeyCreateDecryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else { + throw SigningError(error: encryptError) + } + return signature as Data + } + + private func encryptionAlgorithm(for secret: SecretType) -> SecKeyAlgorithm { switch (secret.algorithm, secret.keySize) { case (.ellipticCurve, 256): - signatureAlgorithm = .eciesEncryptionStandardX963SHA256AESGCM + return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM case (.ellipticCurve, 384): - signatureAlgorithm = .eciesEncryptionStandardX963SHA384AESGCM + return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM case (.rsa, 1024), (.rsa, 2048): - signatureAlgorithm = .rsaEncryptionOAEPSHA512AESGCM + return .rsaEncryptionOAEPSHA512AESGCM default: fatalError() } - guard let signature = SecKeyCreateDecryptedData(key, signatureAlgorithm, data as CFData, &encryptError) else { - throw SmartCard.SigningError(error: encryptError) - } - return signature as Data } } @@ -303,19 +267,3 @@ extension TKTokenWatcher { } } - -extension SmartCard { - - /// A wrapper around an error code reported by a Keychain API. - public struct KeychainError: Error { - /// The status code involved. - 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? - } - -} 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