From 9cbd540c6135af3d1641ad17aef5ccc5e04d146c Mon Sep 17 00:00:00 2001 From: Yeimi Moreno Date: Thu, 14 Jul 2022 14:35:36 -0500 Subject: [PATCH 01/15] Revert changes --- .../Sources/Keychain/KeychainTests.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/KeychainTests.swift b/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/KeychainTests.swift index a5516371..f7fa9c20 100644 --- a/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/KeychainTests.swift +++ b/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/KeychainTests.swift @@ -18,8 +18,6 @@ // import XCTest -import Foundation -import LocalAuthentication @testable import TwilioVerifySDK // swiftlint:disable force_cast type_body_length @@ -331,7 +329,7 @@ class KeychainTests: XCTestCase { ) } } - + func testCopyItemMatching_witMatches_shouldReturnKey() { var pair: KeyPair! var keyObject: AnyObject! @@ -415,9 +413,3 @@ private extension KeychainTests { } } } - -extension CFString { - var asString: String { - self as String - } -} From 26aa843c06fbab08cb263bcca3bb58e6d78f834c Mon Sep 17 00:00:00 2001 From: Alejandro Orozco Date: Mon, 19 Sep 2022 19:34:22 -0500 Subject: [PATCH 02/15] [26703] - Add Optional Attempts for CopyItemMatching. (#208) * [26703] - Add Optional Attemps for CopyItemMatching. - Add Optional attempt for get queries on SecureStorage & AuthenticatedSecureStorage. * [26703] - Add Optional Attemps for CopyItemMatching. - Set only 1 retry for the AuthenticatedSecureStorage biometric verification. --- .../Sources/Keychain/Keychain.swift | 34 +++++++++++++------ .../Storage/AuthenticatedSecureStorage.swift | 11 +++--- .../Sources/Keychain/Mocks/KeychainMock.swift | 5 ++- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/TwilioVerifySDK/TwilioSecurity/Sources/Keychain/Keychain.swift b/TwilioVerifySDK/TwilioSecurity/Sources/Keychain/Keychain.swift index 2fef2425..df5fdda5 100644 --- a/TwilioVerifySDK/TwilioSecurity/Sources/Keychain/Keychain.swift +++ b/TwilioVerifySDK/TwilioSecurity/Sources/Keychain/Keychain.swift @@ -25,7 +25,7 @@ protocol KeychainProtocol { func verify(withPublicKey key: SecKey, algorithm: SecKeyAlgorithm, signedData: Data, signature: Data) -> Bool func representation(forKey key: SecKey) throws -> Data func generateKeyPair(withParameters parameters: [String: Any]) throws -> KeyPair - func copyItemMatching(query: Query) throws -> AnyObject + func copyItemMatching(query: Query, attempts: Int) throws -> AnyObject func addItem(withQuery query: Query) -> OSStatus func updateItem(withQuery query: Query, attributes: CFDictionary) -> OSStatus @discardableResult func deleteItem(withQuery query: Query) -> OSStatus @@ -143,10 +143,13 @@ class Keychain: KeychainProtocol { ) } - func copyItemMatching(query: Query) throws -> AnyObject { + func copyItemMatching( + query: Query, + attempts: Int + ) throws -> AnyObject { var result: AnyObject? - let status: OSStatus = retry { + let status: OSStatus = retry(attempts: attempts) { SecItemCopyMatching(query as CFDictionary, &result) } validation: { status in status == errSecSuccess @@ -213,14 +216,15 @@ class Keychain: KeychainProtocol { } private func retry( - tries: Int = 2, + attempts: Int = Constants.defaultAttempts, block: () -> T, delay: TimeInterval = 0.1, validation: ((T) -> Bool)? = nil ) -> T { - var tries: Int = tries + guard attempts > 0 else { return block() } + var attempts: Int = attempts repeat { - tries -= 1 + attempts -= 1 let result = block() if let validation = validation { if validation(result) { @@ -232,13 +236,23 @@ class Keychain: KeychainProtocol { } else { return result } - } while tries > 0 + } while attempts > 0 return block() } +} + +private enum Constants { + static let accessControlProtection = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + static let accessControlFlags: SecAccessControlCreateFlags = .privateKeyUsage + static let defaultAttempts: Int = 2 +} - enum Constants { - static let accessControlProtection = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly - static let accessControlFlags: SecAccessControlCreateFlags = .privateKeyUsage +extension KeychainProtocol { + func copyItemMatching( + query: Query, + attempts: Int = Constants.defaultAttempts + ) throws -> AnyObject { + return try copyItemMatching(query: query, attempts: attempts) } } diff --git a/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift b/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift index 7d3e5e94..45b57cf5 100644 --- a/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift +++ b/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift @@ -20,12 +20,12 @@ import Foundation import LocalAuthentication -///:nodoc: +///: nodoc: public typealias SuccessBlock = (Data) -> () -///:nodoc: +///: nodoc: public typealias ErrorBlock = (Error) -> () -///:nodoc: +///: nodoc: public protocol AuthenticatedSecureStorageProvider { func save(_ data: Data, withKey key: String, authenticator: Authenticator, success: @escaping EmptySuccessBlock, failure: @escaping ErrorBlock, withServiceName service: String?) func get(_ key: String, authenticator: Authenticator, success: @escaping SuccessBlock, failure: @escaping ErrorBlock) @@ -33,7 +33,7 @@ public protocol AuthenticatedSecureStorageProvider { func clear(withServiceName service: String?) throws } -///:nodoc: +///: nodoc: public class AuthenticatedSecureStorage { public enum Errors: Error, LocalizedError { @@ -89,7 +89,7 @@ extension AuthenticatedSecureStorage: AuthenticatedSecureStorageProvider { Logger.shared.log(withLevel: .info, message: "Getting \(key)") let query = self.keychainQuery.getData(withKey: key, authenticationPrompt: authenticator.localizedAuthenticationPrompt) do { - let result = try self.keychain.copyItemMatching(query: query) + let result = try self.keychain.copyItemMatching(query: query, attempts: Constants.biomettricAttempts) // swiftlint:disable:next force_cast let data = result as! Data Logger.shared.log(withLevel: .debug, message: "Return value for \(key)") @@ -218,6 +218,7 @@ extension AuthenticatedSecureStorage: AuthenticatedSecureStorageProvider { static let accessControlFlagsBiometrics: SecAccessControlCreateFlags = .biometryCurrentSet static let accessControlFlags: SecAccessControlCreateFlags = .touchIDCurrentSet static let biometricsPolicyState: String = "%@.biometricsPolicyState" + static let biomettricAttempts: Int = 0 } } diff --git a/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/Mocks/KeychainMock.swift b/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/Mocks/KeychainMock.swift index 651ab2b9..93468259 100644 --- a/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/Mocks/KeychainMock.swift +++ b/TwilioVerifySDKTests/TwilioSecurity/Sources/Keychain/Mocks/KeychainMock.swift @@ -89,7 +89,10 @@ extension KeychainMock: KeychainProtocol { return keyPair } - func copyItemMatching(query: Query) throws -> AnyObject { + func copyItemMatching( + query: Query, + attempts: Int + ) throws -> AnyObject { callsToCopyItemMatching += 1 if let copyItemMitmatchingHandler = copyItemMitmatchingHandler { From 71b940dbf1bf226edd3e61aeb1fc58bf0e8c6f7b Mon Sep 17 00:00:00 2001 From: Alejandro Orozco Date: Thu, 9 Mar 2023 16:49:40 -0500 Subject: [PATCH 03/15] - Update BiometricError with KeychainError. (#209) - Make KeychainError public. Co-authored-by: Yeimi Moreno --- .../Sources/Biometrics/BiometricError.swift | 40 ++++++++++++------- .../Storage/AuthenticatedSecureStorage.swift | 2 +- .../Sources/Errors/OperationErrors.swift | 6 +-- .../AuthenticatedSecureStorageTests.swift | 23 +++++++++++ 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/TwilioVerifySDK/TwilioSecurity/Sources/Biometrics/BiometricError.swift b/TwilioVerifySDK/TwilioSecurity/Sources/Biometrics/BiometricError.swift index c9182cc9..62eed9a0 100644 --- a/TwilioVerifySDK/TwilioSecurity/Sources/Biometrics/BiometricError.swift +++ b/TwilioVerifySDK/TwilioSecurity/Sources/Biometrics/BiometricError.swift @@ -45,24 +45,36 @@ public enum BiometricError: Error, LocalizedError { // MARK: - Resolution public static func given(_ error: NSError?) -> Self? { - guard let error = error else { - return nil - } + guard let error = error else { + return nil + } - switch error.domain { - case kLAErrorDomain: - return laError(error) - case NSOSStatusErrorDomain: - return osStatusError(error) - default: return nil - } + switch error.domain { + case kLAErrorDomain: + return laError(error.code) + case NSOSStatusErrorDomain: + return osStatusError(error.code) + default: return nil + } + } + + public static func given(_ error: KeychainError) -> Self? { + switch error { + case .invalidStatusCode(let code) where error.domain == kLAErrorDomain, + .invalidProtection(let code) where error.domain == kLAErrorDomain: + return laError(code) + case .invalidStatusCode(let code) where error.domain == NSOSStatusErrorDomain, + .invalidProtection(let code) where error.domain == NSOSStatusErrorDomain: + return osStatusError(code) + default: return nil + } } // MARK: - Private Methods // swiftlint:disable:next cyclomatic_complexity - private static func laError(_ error: NSError) -> Self? { - switch LAError.Code(rawValue: error.code) { + private static func laError(_ errorCode: Int) -> Self? { + switch LAError.Code(rawValue: errorCode) { case .appCancel: return .appCancel case .authenticationFailed: return .authenticationFailed case .invalidContext: return .invalidContext @@ -78,8 +90,8 @@ public enum BiometricError: Error, LocalizedError { } } - private static func osStatusError(_ error: NSError) -> Self? { - switch error.code { + private static func osStatusError(_ errorCode: Int) -> Self? { + switch errorCode { case Int(errSecAuthFailed): return .secAuthFailed case Int(errSecNotAvailable): return .secNotAvailable case Int(errSecReadOnly): return .secReadOnly diff --git a/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift b/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift index 45b57cf5..bed161e4 100644 --- a/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift +++ b/TwilioVerifySDK/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorage.swift @@ -107,7 +107,7 @@ extension AuthenticatedSecureStorage: AuthenticatedSecureStorageProvider { } Logger.shared.log(withLevel: .error, message: error.localizedDescription) - if let biometricError = BiometricError.given(error as NSError) { + if let keychainError = error as? KeychainError, let biometricError = BiometricError.given(keychainError) { failure(biometricError) } else { failure(error) diff --git a/TwilioVerifySDK/TwilioVerify/Sources/Errors/OperationErrors.swift b/TwilioVerifySDK/TwilioVerify/Sources/Errors/OperationErrors.swift index 02c9347b..2fe0af34 100644 --- a/TwilioVerifySDK/TwilioVerify/Sources/Errors/OperationErrors.swift +++ b/TwilioVerifySDK/TwilioVerify/Sources/Errors/OperationErrors.swift @@ -24,7 +24,7 @@ public protocol OperationError: LocalizedError { var domain: String { get } } -enum KeychainError: OperationError { +public enum KeychainError: OperationError { case unexpectedError case unableToCopyItem case unableToGeneratePublicKey @@ -34,7 +34,7 @@ enum KeychainError: OperationError { case invalidProtection(code: Int) case createSignatureError(cause: Error) - var errorDescription: String? { + public var errorDescription: String? { switch self { case .invalidStatusCode(let code): return String.invalidStatusCode(code) @@ -49,7 +49,7 @@ enum KeychainError: OperationError { } } - var domain: String { + public var domain: String { switch self { case .invalidProtection, .invalidStatusCode, diff --git a/TwilioVerifySDKTests/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorageTests.swift b/TwilioVerifySDKTests/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorageTests.swift index 30b4e3ac..5176aedc 100644 --- a/TwilioVerifySDKTests/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorageTests.swift +++ b/TwilioVerifySDKTests/TwilioSecurity/Sources/Storage/AuthenticatedSecureStorageTests.swift @@ -309,6 +309,29 @@ class AuthenticatedSecureStorageTests: XCTestCase { } } + func testSave_withBiometricFailure_shouldThrowError() { + // Given + let biometricsData = "123456".data(using: .utf8) + let newBiometricsData = "123456".data(using: .utf8) + let expectedError = BiometricError.secItemNotFound + let key = "testKey" + let data = "testData".data(using: .utf8) + let mockContext = MockContext() + let authenticator = AuthenticatorMock(context: mockContext) + mockContext.evaluatedPolicyDomainStateResult = newBiometricsData + keychainMock.keys = [data, biometricsData] as [AnyObject] + keychainMock.addItemStatus = [errSecSuccess] + keychainMock.error = KeychainError.invalidStatusCode(code: Int(errSecItemNotFound)) + + // When + testSubject.get(key, authenticator: authenticator) { _ in + // Then + XCTFail("Get Data should failed, because of biometrics authentication failed") + } failure: { error in + XCTAssertEqual(expectedError.localizedDescription, error.localizedDescription) + } + } + func testSave_withSameBiometrics_shouldSucceed() { // Given let biometricsData = "123456".data(using: .utf8) From 9ef308c80687ca7c090b335231edc1db614914e4 Mon Sep 17 00:00:00 2001 From: Alejandro Orozco Date: Fri, 25 Aug 2023 15:27:19 -0500 Subject: [PATCH 04/15] chore: Xcode 15 support (#221) BREAKING CHANGE: Dropped support for iOS 11 --- AppSizer/AppSizer.xcodeproj/project.pbxproj | 4 ++-- Package.swift | 2 +- README.md | 2 +- TwilioVerify.podspec | 2 +- .../TwilioVerifyDemo.xcodeproj/project.pbxproj | 8 ++++---- TwilioVerifySDK.xcodeproj/project.pbxproj | 10 ++++++++-- fastlane/Fastfile | 6 +++--- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/AppSizer/AppSizer.xcodeproj/project.pbxproj b/AppSizer/AppSizer.xcodeproj/project.pbxproj index 9cec9f2c..f32eefd6 100644 --- a/AppSizer/AppSizer.xcodeproj/project.pbxproj +++ b/AppSizer/AppSizer.xcodeproj/project.pbxproj @@ -263,7 +263,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -318,7 +318,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; diff --git a/Package.swift b/Package.swift index c4856912..3ca02301 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "TwilioVerifySDK", platforms: [ - .iOS(.v11) + .iOS(.v12) ], products: [ .library( diff --git a/README.md b/README.md index 7e6578af..aa46fb21 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ None ## Requirements -* iOS 11+ +* iOS 12+ * Swift 5.2 * Xcode 14.x diff --git a/TwilioVerify.podspec b/TwilioVerify.podspec index af43ae84..0e4b89d1 100644 --- a/TwilioVerify.podspec +++ b/TwilioVerify.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.authors = { 'Twilio' => 'help@twilio.com' } s.source = { :git => 'https://github.com/twilio/twilio-verify-ios.git', :tag => s.version } s.documentation_url = 'https://twilio.github.io/twilio-verify-ios/latest/' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' s.swift_version = '5.2' s.source_files = 'TwilioVerifySDK/TwilioVerify/**/*.swift', 'TwilioVerifySDK/TwilioSecurity/**/*.swift' end diff --git a/TwilioVerifyDemo/TwilioVerifyDemo.xcodeproj/project.pbxproj b/TwilioVerifyDemo/TwilioVerifyDemo.xcodeproj/project.pbxproj index 712edc83..7cd90cce 100644 --- a/TwilioVerifyDemo/TwilioVerifyDemo.xcodeproj/project.pbxproj +++ b/TwilioVerifyDemo/TwilioVerifyDemo.xcodeproj/project.pbxproj @@ -533,7 +533,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = "$(DEMO_VERSION)"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -590,7 +590,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = "$(DEMO_VERSION)"; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -611,7 +611,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = TwilioVerifyDemo/Common/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -635,7 +635,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = TwilioVerifyDemo/Common/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/TwilioVerifySDK.xcodeproj/project.pbxproj b/TwilioVerifySDK.xcodeproj/project.pbxproj index e287625c..59bbf642 100644 --- a/TwilioVerifySDK.xcodeproj/project.pbxproj +++ b/TwilioVerifySDK.xcodeproj/project.pbxproj @@ -1505,7 +1505,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1562,7 +1562,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -1589,6 +1589,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = TwilioVerifySDK/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1621,6 +1622,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = TwilioVerifySDK/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1644,6 +1646,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 9EVH78F4V4; INFOPLIST_FILE = TwilioVerifySDKTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1664,6 +1667,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 9EVH78F4V4; INFOPLIST_FILE = TwilioVerifySDKTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1685,6 +1689,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 9EVH78F4V4; INFOPLIST_FILE = HostApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1705,6 +1710,7 @@ CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 9EVH78F4V4; INFOPLIST_FILE = HostApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/fastlane/Fastfile b/fastlane/Fastfile index b88aeb56..2b40d97c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,10 +1,10 @@ output_directory = './fastlane/Test Output/' coverage_directory = '/coverage' complete_suite = 'CompleteSuite' -single_ftl_device = [{ios_model_id: 'iphone8', ios_version_id: '16.3'}] +single_ftl_device = [{ios_model_id: 'iphone8', ios_version_id: '16.5'}] all_ftl_devices = [ {ios_model_id: 'iphone12pro', ios_version_id: '14.8'}, - {ios_model_id: 'iphone11pro', ios_version_id: '16.3'}, + {ios_model_id: 'iphone11pro', ios_version_id: '16.5'}, {ios_model_id: 'ipadmini4', ios_version_id: '15.4'}, {ios_model_id: 'iphone13pro', ios_version_id: '15.7'} ] @@ -62,7 +62,7 @@ platform :ios do timeout_sec: 300, devices: ftl_devices, skip_validation: true, - ios_xc_test_args: { xcodeVersion: '14.2' } + ios_xc_test_args: { xcodeVersion: '14.3' } ) end From 1dda0132c3047603bd85765ae5adbb5f28bbd552 Mon Sep 17 00:00:00 2001 From: Alejandro Orozco Date: Wed, 27 Sep 2023 12:00:18 -0500 Subject: [PATCH 05/15] feat: Allow factors migration by providing the flag allowIphoneMigration, for enabling users to migrate their factors to another iPhone or restore them during backup processes. (#218) --- README.md | 37 ++++++ .../Presenter/ChallengeDetailPresenter.swift | 2 +- .../Common/View/Base.lproj/Main.storyboard | 95 +++++++++------ .../Presenter/CreateFactorPresenter.swift | 31 +++-- .../View/CreateFactorViewController.swift | 5 +- .../Sources/Key/Signer/ECSigner.swift | 2 +- .../Key/Template/ECP256SignerTemplate.swift | 9 +- .../Sources/Key/Template/Template.swift | 5 +- .../Sources/Keychain/KeyManager.swift | 27 +++-- .../Sources/Keychain/Keychain.swift | 28 +++-- .../Sources/Keychain/KeychainQuery.swift | 35 +++--- .../Sources/Storage/SecureStorage.swift | 20 +++- .../Sources/API/ChallengeAPIClient.swift | 7 +- .../Sources/Data/JWT/JwtGenerator.swift | 12 +- .../Sources/Data/JWT/JwtSigner.swift | 12 +- .../Sources/Data/KeyStorage.swift | 33 ++++-- .../Sources/Data/Storage+AccessGroup.swift | 12 +- .../TwilioVerify/Sources/Data/Storage.swift | 13 +- .../Sources/Data/StorageProvider.swift | 18 ++- .../Domain/Challenge/ChallengeFacade.swift | 8 +- .../Challenge/ChallengeRepository.swift | 16 ++- .../Challenge/PushChallengeProcessor.swift | 24 +++- .../Sources/Domain/Factor/FactorFacade.swift | 44 +++++-- .../Sources/Domain/Factor/FactorMapper.swift | 33 +++++- .../Domain/Factor/FactorRepository.swift | 17 ++- .../Factor/Models/FactorDataPayload.swift | 5 + .../Domain/Factor/Models/PushFactor.swift | 79 +++++++++++- .../Sources/Domain/Factor/PushFactory.swift | 112 ++++++++++++------ .../Sources/Domain/Manager/TwilioVerify.swift | 12 +- .../Domain/Manager/TwilioVerifyManager.swift | 2 +- .../TwilioVerify/Sources/Logger/Logger.swift | 2 +- .../Sources/Models/Challenge.swift | 42 +++---- .../Sources/Models/ChallengeList.swift | 12 +- .../Sources/Models/ChallengeListPayload.swift | 14 +-- .../TwilioVerify/Sources/Models/Factor.swift | 34 +++--- .../Sources/Models/FactorPayload.swift | 44 ++++--- .../Sources/Models/Metadata.swift | 20 ++-- .../Models/UpdateChallengePayload.swift | 22 ++-- .../Sources/Models/UpdateFactorPayload.swift | 17 +-- .../Sources/Models/VerifyFactorPayload.swift | 8 +- .../Sources/Networking/Authentication.swift | 2 +- .../Template/ECP256SignerTemplateTests.swift | 12 +- .../Sources/Keychain/KeyManagerTests.swift | 24 ++-- .../Sources/Keychain/KeychainQueryTests.swift | 31 +++-- .../Sources/Keychain/KeychainTests.swift | 4 +- .../Sources/Keychain/Mocks/KeychainMock.swift | 2 +- .../Sources/Storage/SecureStorageTests.swift | 4 +- .../Sources/API/ChallengeAPIClientTests.swift | 1 + .../Sources/API/FactorAPIClientTests.swift | 9 +- .../API/Mocks/ChallengeAPIClientMock.swift | 9 +- .../API/Mocks/FactorAPIClientMock.swift | 26 +++- .../Sources/Data/JWT/JwtGeneratorTests.swift | 10 +- .../Sources/Data/JWT/JwtSignerTests.swift | 4 +- .../Sources/Data/KeyStorageAdapterTests.swift | 18 +-- .../Sources/Data/Mocks/JwtGeneratorMock.swift | 6 +- .../Sources/Data/Mocks/JwtSignerMock.swift | 5 +- .../Sources/Data/Mocks/KeyStorageMock.swift | 8 +- .../Data/Mocks/SecureStorageMock.swift | 2 +- .../Data/Mocks/StorageProviderMock.swift | 2 +- .../Data/Storage+AccessGroupTests.swift | 5 +- .../Sources/Data/StorageTests.swift | 51 ++++++-- .../Challenge/ChallengeFacadeTests.swift | 1 + .../Challenge/ChallengeListMapperTests.swift | 1 + .../Challenge/ChallengeRepositoryTests.swift | 1 + .../Mocks/ChallengeProviderMock.swift | 7 +- .../Mocks/PushChallengeProcessorMock.swift | 8 +- .../PushChallengeProcessorTests.swift | 2 + .../Domain/Factor/FactorFacadeTests.swift | 1 + .../Domain/Factor/FactorMapperTests.swift | 20 ++-- .../Domain/Factor/FactorRepositoryTests.swift | 13 +- .../Factor/Mocks/FactorRepositoryMock.swift | 25 +++- .../Domain/Factor/Mocks/PushFactoryMock.swift | 28 ++++- .../Domain/Factor/PushFactoryTests.swift | 3 +- .../Manager/TwilioVerifyManagerTests.swift | 1 + .../Integration/TwilioVerifyTests.swift | 1 + .../Sources/Models/Mocks/FactorMock.swift | 1 + .../Models/Mocks/FakeFactorPayload.swift | 5 + .../AuthenticationProviderTests.swift | 2 + 78 files changed, 938 insertions(+), 387 deletions(-) diff --git a/README.md b/README.md index aa46fb21..c03044e0 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,43 @@ To stop sharing existing factors created with **App Groups**, uncheck the **App > This will restrict access to the factors and will not affect the main application in which the data was initially created. +--- + +## Allow Factors migration + +By default, all factors are securely stored within the device's [Secure Enclave](https://support.apple.com/guide/security/secure-enclave-sec59b0b31ff/web), which provides a high level of protection against unauthorized access and ensures sensitive data remains encrypted. This default setting restricts the migration of these factors from one iPhone to another, enhancing the overall security of the authentication process. + +However, in certain cases, users may want to allow data migration from one iPhone to another or perform backup restoration while retaining their Verify Factors. For this purpose, you can use the **allowIphoneMigration** property within the **PushFactorPayload**. + +### Usage of allowIphoneMigration Property: + +When using the push-based factor for verifying a user, you can include the **allowIphoneMigration** property in the **PushFactorPayload**. By setting this property to `true`, you enable users to migrate their factors to another iPhone or restore them during backup processes. It's essential to carefully consider the security implications before enabling this option, as it may expose the factors to potential risks during data migration or backup restoration. + +### Important Security Considerations: + +Remember to use this option judiciously, as limiting factors to a single device enhances security. Advise users to perform migrations and backups on trusted devices and networks and use additional security measures like passcodes, Touch ID, or Face ID. + +--- + +To enable [Data migration from iPhone to iPhone](https://support.apple.com/en-us/HT210216) or [Backup restoration](https://support.apple.com/en-us/HT204184) for factors, utilize the **allowIphoneMigration** property in the **PushFactorPayload**, IE: + +```swift +let payload = PushFactorPayload( + friendlyName: friendlyName, + serviceSid: serviceSid, + identity: identity, + allowIphoneMigration: true, + pushToken: pushToken, + accessToken: accessToken, + metadata: metadata +) + +twilioVerify.createFactor(withPayload: payload, success: success, failure: failure) +``` + +**Note:** Factors created before implementing this change will not be eligible for migration. Only new factors with the **allowIphoneMigration** property set to `true` will have the capability to migrate from one iPhone to another. + +> For more information about the accessibility attribute, please refer to the [Restricting Keychain Item Accessibility](https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility) documentation --- diff --git a/TwilioVerifyDemo/TwilioVerifyDemo/ChallengeDetail/Presenter/ChallengeDetailPresenter.swift b/TwilioVerifyDemo/TwilioVerifyDemo/ChallengeDetail/Presenter/ChallengeDetailPresenter.swift index 20ec9a8a..6471c926 100644 --- a/TwilioVerifyDemo/TwilioVerifyDemo/ChallengeDetail/Presenter/ChallengeDetailPresenter.swift +++ b/TwilioVerifyDemo/TwilioVerifyDemo/ChallengeDetail/Presenter/ChallengeDetailPresenter.swift @@ -76,7 +76,7 @@ extension ChallengeDetailPresenter: ChallengeDetailPresentable { factorSid: factorSid, challengeSid: challengeSid, status: status - ) + ) twilioVerify.updateChallenge(withPayload: payload, success: { [weak self] in guard let strongSelf = self else { return } strongSelf.fetchChallengeDetails() diff --git a/TwilioVerifyDemo/TwilioVerifyDemo/Common/View/Base.lproj/Main.storyboard b/TwilioVerifyDemo/TwilioVerifyDemo/Common/View/Base.lproj/Main.storyboard index dcf161e3..21250fb5 100644 --- a/TwilioVerifyDemo/TwilioVerifyDemo/Common/View/Base.lproj/Main.storyboard +++ b/TwilioVerifyDemo/TwilioVerifyDemo/Common/View/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -39,7 +39,7 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - @@ -91,9 +113,10 @@ + - + @@ -108,7 +131,7 @@ - + @@ -141,7 +164,7 @@ - + @@ -183,7 +206,7 @@ - + @@ -335,11 +358,11 @@ - +