mirror of
				https://github.com/maxgoedjen/secretive.git
				synced 2025-10-30 23:10:57 +00:00 
			
		
		
		
	Factor out some common keychain functionality (#456)
* Factor out some common keychain functionality * Remove redundant * Remove redundant
This commit is contained in:
		
							parent
							
								
									93e79470b7
								
							
						
					
					
						commit
						be58ddd324
					
				| @ -32,3 +32,9 @@ SecretKit is a collection of protocols describing secrets and stores. | ||||
| ### Authentication Persistence | ||||
| 
 | ||||
| - ``PersistedAuthenticationContext`` | ||||
| 
 | ||||
| ### Errors | ||||
| 
 | ||||
| - ``KeychainError`` | ||||
| - ``SigningError`` | ||||
| - ``SecurityError`` | ||||
|  | ||||
| @ -1,5 +0,0 @@ | ||||
| import Foundation | ||||
| 
 | ||||
| public func KeychainDictionary(_ dictionary: [CFString: Any]) -> CFDictionary { | ||||
|     dictionary as CFDictionary | ||||
| } | ||||
							
								
								
									
										71
									
								
								Sources/Packages/Sources/SecretKit/KeychainTypes.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Sources/Packages/Sources/SecretKit/KeychainTypes.swift
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| import Foundation | ||||
| 
 | ||||
| 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 { | ||||
| 
 | ||||
|     /// 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() | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -79,15 +79,3 @@ extension NSNotification.Name { | ||||
|     public static let secretStoreReloaded = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.reloaded") | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| public typealias SecurityError = Unmanaged<CFError> | ||||
| 
 | ||||
| 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) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -6,9 +6,3 @@ | ||||
| 
 | ||||
| - ``Secret`` | ||||
| - ``Store`` | ||||
| 
 | ||||
| ### Errors | ||||
| 
 | ||||
| - ``KeychainError`` | ||||
| - ``SigningError`` | ||||
| - ``SecurityError`` | ||||
|  | ||||
| @ -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? | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user