├── CODEOWNERS ├── Locker-github.jpg ├── .gitignore ├── Example ├── Pods │ ├── Target Support Files │ │ ├── Locker │ │ │ ├── Locker-Unit-Tests-frameworks-output-files.xcfilelist │ │ │ ├── Locker-Unit-Tests-resources-output-files.xcfilelist │ │ │ ├── Locker.modulemap │ │ │ ├── Locker-dummy.m │ │ │ ├── Locker-Unit-Tests-frameworks-input-files.xcfilelist │ │ │ ├── Locker-Unit-Tests-resources-input-files.xcfilelist │ │ │ ├── Locker-prefix.pch │ │ │ ├── Locker-Unit-Tests-prefix.pch │ │ │ ├── Locker-umbrella.h │ │ │ ├── Locker-Unit-Tests-Info.plist │ │ │ ├── ResourceBundle-Locker_Locker-Locker-Info.plist │ │ │ ├── Locker-Info.plist │ │ │ ├── Locker.debug.xcconfig │ │ │ ├── Locker.release.xcconfig │ │ │ ├── Locker.unit-tests.debug.xcconfig │ │ │ ├── Locker.unit-tests.release.xcconfig │ │ │ ├── Locker-Unit-Tests-resources.sh │ │ │ └── Locker-Unit-Tests-frameworks.sh │ │ ├── Pods-TouchID │ │ │ ├── Pods-TouchID.modulemap │ │ │ ├── Pods-TouchID-dummy.m │ │ │ ├── Pods-TouchID-umbrella.h │ │ │ ├── Pods-TouchID-Info.plist │ │ │ ├── Pods-TouchID.debug.xcconfig │ │ │ ├── Pods-TouchID.release.xcconfig │ │ │ ├── Pods-TouchID-frameworks.sh │ │ │ ├── Pods-TouchID-acknowledgements.markdown │ │ │ └── Pods-TouchID-acknowledgements.plist │ │ └── Pods-TouchIDTests │ │ │ ├── Pods-TouchIDTests.modulemap │ │ │ ├── Pods-TouchIDTests-dummy.m │ │ │ ├── Pods-TouchIDTests-acknowledgements.markdown │ │ │ ├── Pods-TouchIDTests-umbrella.h │ │ │ ├── Pods-TouchIDTests.debug.xcconfig │ │ │ ├── Pods-TouchIDTests.release.xcconfig │ │ │ ├── Pods-TouchIDTests-acknowledgements.plist │ │ │ └── Pods-TouchIDTests-Info.plist │ ├── Manifest.lock │ └── Local Podspecs │ │ └── Locker.podspec.json ├── build │ └── Pods.build │ │ └── Release-iphoneos │ │ └── Locker.build │ │ └── dgph ├── TouchID.xcworkspace │ ├── xcuserdata │ │ ├── ivanvecko.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── xcdebugger │ │ │ │ └── Breakpoints_v2.xcbkptlist │ │ └── barbaravujicic.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── TouchID.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── ivanvecko.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcuserdata │ │ ├── ivanvecko.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ └── barbaravujicic.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── TouchID.xcscheme ├── Podfile ├── Podfile.lock ├── TouchID │ ├── AppDelegate.swift │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── ViewController.swift └── TouchIDTests │ ├── Info.plist │ └── TouchIDTests.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Sources └── Locker │ ├── Core │ ├── BiometricsType.swift │ ├── LockerError.swift │ ├── Locker+ObjectiveC.swift │ └── Locker.swift │ ├── Models │ ├── Constants.swift │ └── DeviceResponse.swift │ ├── SupportingFiles │ └── PrivacyInfo.xcprivacy │ ├── Helpers │ ├── DeviceManager.swift │ ├── BundleHelpers.swift │ ├── LockerHelpers.swift │ └── BiometryAvailabilityDeviceList.json │ └── Tests │ └── LockerHelpersTests.swift ├── .github └── PULL_REQUEST_TEMPLATE.md. ├── SECURITY.md ├── Locker.podspec ├── Package.swift ├── Locker.xcodeproj ├── xcshareddata │ └── xcschemes │ │ ├── LockerTests.xcscheme │ │ └── Locker.xcscheme └── project.pbxproj ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── .swiflint.yml ├── README.md └── LICENSE /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @gitsino -------------------------------------------------------------------------------- /Locker-github.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Locker/HEAD/Locker-github.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Example/.DS_Store 3 | *.xcuserstate 4 | project.xcworkspace/ 5 | xcuserdata/ 6 | .build/ 7 | Carthage/Build -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-frameworks-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Locker.framework -------------------------------------------------------------------------------- /Example/build/Pods.build/Release-iphoneos/Locker.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05/Users zvonimirmedak DocumentsLockerExamplePods -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-resources-output-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Locker_Locker.bundle -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker.modulemap: -------------------------------------------------------------------------------- 1 | framework module Locker { 2 | umbrella header "Locker-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Locker : NSObject 3 | @end 4 | @implementation PodsDummy_Locker 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_TouchID { 2 | umbrella header "Pods-TouchID-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-frameworks-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Locker/Locker-Unit-Tests-frameworks.sh 2 | ${BUILT_PRODUCTS_DIR}/Locker/Locker.framework -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_TouchID : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_TouchID 5 | @end 6 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-resources-input-files.xcfilelist: -------------------------------------------------------------------------------- 1 | ${PODS_ROOT}/Target Support Files/Locker/Locker-Unit-Tests-resources.sh 2 | ${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker_Locker.bundle -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_TouchIDTests { 2 | umbrella header "Pods-TouchIDTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/xcuserdata/ivanvecko.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Locker/HEAD/Example/TouchID.xcworkspace/xcuserdata/ivanvecko.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_TouchIDTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_TouchIDTests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/xcuserdata/barbaravujicic.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Locker/HEAD/Example/TouchID.xcworkspace/xcuserdata/barbaravujicic.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/TouchID.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/TouchID.xcodeproj/project.xcworkspace/xcuserdata/ivanvecko.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/infinum/Locker/HEAD/Example/TouchID.xcodeproj/project.xcworkspace/xcuserdata/ivanvecko.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | 3 | target 'TouchID' do 4 | use_frameworks! 5 | 6 | pod 'Locker', :path => '../Locker.podspec', :testspecs => ['Tests'] 7 | # Pods for TouchID 8 | 9 | target 'TouchIDTests' do 10 | inherit! :search_paths 11 | # Pods for testing 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/Locker/Core/BiometricsType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BiometricsType.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 19.10.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc 12 | public enum BiometricsType: Int { 13 | case none, touchID, faceID 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Locker/Models/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 21.10.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Constants { 12 | static let devicesURL = URL(string: "https://private-984c5-lockerdevices.apiary-mock.com/devices") 13 | } 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double LockerVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char LockerVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Locker (3.0.5) 3 | - Locker/Tests (3.0.5) 4 | 5 | DEPENDENCIES: 6 | - Locker (from `../Locker.podspec`) 7 | - Locker/Tests (from `../Locker.podspec`) 8 | 9 | EXTERNAL SOURCES: 10 | Locker: 11 | :path: "../Locker.podspec" 12 | 13 | SPEC CHECKSUMS: 14 | Locker: 3c9daabe0fdf8c16a136c799ee30fc347ba66408 15 | 16 | PODFILE CHECKSUM: 4e54ee4f3c4f512d80ae1f8574c0ca8e06d2dda8 17 | 18 | COCOAPODS: 1.16.2 19 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_TouchIDVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_TouchIDVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Sources/Locker/Models/DeviceResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceResponse.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 21.10.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct DeviceResponse: Codable { 12 | let faceIdDevices: [Device] 13 | let touchIdDevices: [Device] 14 | } 15 | 16 | struct Device: Codable { 17 | let id: String 18 | let name: String 19 | } 20 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Locker (3.0.5) 3 | - Locker/Tests (3.0.5) 4 | 5 | DEPENDENCIES: 6 | - Locker (from `../Locker.podspec`) 7 | - Locker/Tests (from `../Locker.podspec`) 8 | 9 | EXTERNAL SOURCES: 10 | Locker: 11 | :path: "../Locker.podspec" 12 | 13 | SPEC CHECKSUMS: 14 | Locker: 3c9daabe0fdf8c16a136c799ee30fc347ba66408 15 | 16 | PODFILE CHECKSUM: 4e54ee4f3c4f512d80ae1f8574c0ca8e06d2dda8 17 | 18 | COCOAPODS: 1.16.2 19 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_TouchIDTestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_TouchIDTestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/TouchID.xcodeproj/xcuserdata/ivanvecko.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TouchID.xcscheme 8 | 9 | orderHint 10 | 3 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Example/TouchID.xcodeproj/xcuserdata/barbaravujicic.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TouchID.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/Locker/SupportingFiles/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyCollectedDataTypes 6 | 7 | NSPrivacyAccessedAPITypes 8 | 9 | 10 | NSPrivacyAccessedAPIType 11 | NSPrivacyAccessedAPICategoryUserDefaults 12 | NSPrivacyAccessedAPITypeReasons 13 | 14 | CA92.1 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Example/TouchID/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TouchID 4 | // 5 | // Created by Ivan Vecko on 02/03/2018. 6 | // Copyright © 2018 Infinum Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Locker 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application( 18 | _ application: UIApplication, 19 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 20 | ) -> Bool { 21 | // Override point for customization after application launch. 22 | Locker.enableDeviceListSync = true 23 | return true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md.: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 6 | 7 | **Related task(s):** 8 | 9 | * [Productive/Jira/... Ticket](url-to-ticket) 10 | 11 | 12 | ### Additional notes 13 | 14 | 17 | 18 | ### Design changes 19 | 20 | 23 | -------------------------------------------------------------------------------- /Example/TouchIDTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "LocalAuthentication" -framework "Locker" -framework "Security" -framework "UIKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "LocalAuthentication" -framework "Locker" -framework "Security" -framework "UIKit" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 12 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/ResourceBundle-Locker_Locker-Locker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 3.0.6 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0.6 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/xcuserdata/ivanvecko.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Locker 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "LocalAuthentication" -framework "Security" -framework "UIKit" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Locker 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_LDFLAGS = $(inherited) -framework "LocalAuthentication" -framework "Security" -framework "UIKit" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/TouchID.xcworkspace/xcuserdata/barbaravujicic.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchIDTests/Pods-TouchIDTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | ${PODS_DEVELOPMENT_LANGUAGE} 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## Reporting security issues 4 | 5 | At Infinum we are committed to ensuring the security of our software. If you have discovered a security vulnerability or have concerns regarding the security of our project, we encourage you to report it to us in a responsible manner. 6 | 7 | If you discover a security vulnerability, please report it to us by emailing us at opensource@infinum.com. We will review your report, and if the issue is confirmed, we will work to resolve the issue as soon as possible and coordinate the release of a security patch. 8 | 9 | ## Responsible disclosure 10 | 11 | We request that you practice responsible disclosure by allowing us time to investigate and address any reported vulnerabilities before making them public. We believe this approach helps protect our users and provides a better outcome for everyone involved. 12 | 13 | ## Preferred languages 14 | 15 | We prefer all communication to be in English. 16 | 17 | ## Contributions 18 | 19 | We greatly appreciate your help in keeping Infinum projects secure. Your efforts contribute to the ongoing improvement of our project's security. 20 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "LocalAuthentication" -framework "Locker" -framework "Security" -framework "UIKit" 9 | OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/Locker" 10 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 11 | PODS_BUILD_DIR = ${BUILD_DIR} 12 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 13 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 14 | PODS_ROOT = ${SRCROOT}/Pods 15 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "LocalAuthentication" -framework "Locker" -framework "Security" -framework "UIKit" 9 | OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/Locker" 10 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 11 | PODS_BUILD_DIR = ${BUILD_DIR} 12 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 13 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 14 | PODS_ROOT = ${SRCROOT}/Pods 15 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker.unit-tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "LocalAuthentication" -framework "Locker" -framework "Security" -framework "UIKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 11 | PODS_ROOT = ${SRCROOT} 12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 15 | SKIP_INSTALL = YES 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker.unit-tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Locker" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "LocalAuthentication" -framework "Locker" -framework "Security" -framework "UIKit" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 11 | PODS_ROOT = ${SRCROOT} 12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 15 | SKIP_INSTALL = YES 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Sources/Locker/Core/LockerError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LockerError.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 20.10.2021.. 6 | // 7 | 8 | import Foundation 9 | 10 | @objc 11 | public enum LockerError: Int, Error { 12 | /// Access control couldn't be initialized while trying to save the secret 13 | case accessControl = -99 14 | /// Conversion from secret to data failed 15 | case invalidData = -72 16 | 17 | /// NSError representation used in Obj-C methods 18 | var asNSError: NSError { 19 | convertToNSError() 20 | } 21 | } 22 | 23 | // MARK: - Public extension - 24 | 25 | extension LockerError: LocalizedError { 26 | 27 | public var errorDescription: String? { 28 | switch self { 29 | case .accessControl: 30 | return "Unable to initialize access control" 31 | case .invalidData: 32 | return "Invalid storing data" 33 | } 34 | } 35 | } 36 | 37 | // MARK: - Private extension - 38 | 39 | private extension LockerError { 40 | func convertToNSError() -> NSError { 41 | return NSError( 42 | domain: "com.infinum.locker", 43 | code: self.rawValue, 44 | userInfo: ["NSLocalizedDescriptionKey": self.errorDescription ?? "Something went wrong"] 45 | ) 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Locker/Core/Locker+ObjectiveC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Locker+ObjectiveC.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 09.11.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Locker { 12 | 13 | /** 14 | Used for storing value to Keychain with unique identifier. 15 | 16 | If Locker is run on the Simulator, the secret will not be stored securely in the keychain. 17 | Instead, the UserDefaults storage will be used. 18 | 19 | - Parameters: 20 | - secret: value to store to Keychain 21 | - uniqueIdentifier: unique key used for storing secret 22 | - completed: completion block returning an error if something went wrong 23 | */ 24 | @available(swift, obsoleted: 1.0) 25 | @objc(setSecret:forUniqueIdentifier:completion:) 26 | static func setSecret( 27 | _ secret: String, 28 | for uniqueIdentifier: String, 29 | completed: ((NSError?) -> Void)? = nil 30 | ) { 31 | #if targetEnvironment(simulator) 32 | Locker.userDefaults?.set(secret, forKey: uniqueIdentifier) 33 | #else 34 | setSecretForDevice(secret, for: uniqueIdentifier, completion: { error in 35 | DispatchQueue.main.async { 36 | completed?(error?.asNSError) 37 | } 38 | }) 39 | #endif 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Locker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Locker" 3 | s.version = "3.0.6" 4 | s.summary = "Securely lock your secrets under the watch of TouchID or FaceID keeper 🔒" 5 | s.description = <<-DESC 6 | Lightweight manager for saving, fetching and updating secrets (string value) in Keychain using Biometric Authentication. 7 | Includes methods for checking general changes in Biometric settings and device biometric type info (FaceID / TouchID / None). 8 | DESC 9 | s.homepage = "https://github.com/infinum/Locker.git" 10 | s.license = "MIT" 11 | s.author = { 12 | "Jasmin Abou Aldan" => "jasmin.aboualdan@infinum.com", 13 | "Siniša Abramović" => "sinisa.abramovic@infinum.com", 14 | "Nikola Šimunko" => "nikola.simunko@infinum.com" 15 | } 16 | s.platform = :ios, "10.0" 17 | s.swift_version = "5.5" 18 | s.source = { :git => "https://github.com/infinum/Locker.git", :tag => "#{s.version}" } 19 | s.source_files = "Sources/Locker/**/*.swift" 20 | s.exclude_files = 'Sources/Locker/Tests/' 21 | 22 | s.resource_bundles = { 'Locker_Locker' => ['Sources/Locker/**/*.json', 'Sources/Locker/SupportingFiles/PrivacyInfo.xcprivacy'] } 23 | s.frameworks = "UIKit", "LocalAuthentication", "Security" 24 | 25 | s.test_spec 'Tests' do |test_spec| 26 | test_spec.source_files = 'Sources/Locker/Tests/**/*.swift' 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/Locker.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Locker", 3 | "version": "3.0.6", 4 | "summary": "Securely lock your secrets under the watch of TouchID or FaceID keeper 🔒", 5 | "description": "Lightweight manager for saving, fetching and updating secrets (string value) in Keychain using Biometric Authentication. \nIncludes methods for checking general changes in Biometric settings and device biometric type info (FaceID / TouchID / None).", 6 | "homepage": "https://github.com/infinum/Locker.git", 7 | "license": "MIT", 8 | "authors": { 9 | "Barbara Vujičić": "barbara.vujicic@infinum.com", 10 | "Jasmin Abou Aldan": "jasmin.aboualdan@infinum.com", 11 | "Zvonimir Medak": "zvonimir.medak@infinum.com" 12 | }, 13 | "platforms": { 14 | "ios": "10.0" 15 | }, 16 | "swift_versions": "5.6", 17 | "source": { 18 | "git": "https://github.com/infinum/Locker.git", 19 | "tag": "3.0.6" 20 | }, 21 | "source_files": "Sources/Locker/**/*.swift", 22 | "exclude_files": "Sources/Locker/Tests/", 23 | "resource_bundles": { 24 | "Locker_Locker": [ 25 | "Sources/Locker/**/*.json", 26 | "Sources/Locker/SupportingFiles/PrivacyInfo.xcprivacy" 27 | ] 28 | }, 29 | "frameworks": [ 30 | "UIKit", 31 | "LocalAuthentication", 32 | "Security" 33 | ], 34 | "testspecs": [ 35 | { 36 | "name": "Tests", 37 | "test_type": "unit", 38 | "source_files": "Sources/Locker/Tests/**/*.swift" 39 | } 40 | ], 41 | "swift_version": "5.6" 42 | } 43 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Locker", 8 | platforms: [ 9 | .iOS(.v10) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "Locker", 15 | targets: ["Locker"] 16 | ) 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "Locker", 27 | path: "Sources/Locker", 28 | exclude: ["Tests"], // exclude test sources so Locker module doesn't depend on XCTest 29 | resources: [ 30 | .process("Helpers/BiometryAvailabilityDeviceList.json"), 31 | .copy("SupportingFiles/PrivacyInfo.xcprivacy") 32 | ] 33 | ), 34 | .testTarget( 35 | name: "LockerTests", 36 | dependencies: ["Locker"], 37 | path: "Sources/Locker/Tests" // use the existing test folder inside Locker 38 | ) 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /Sources/Locker/Helpers/DeviceManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceManager.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 20.10.2021.. 6 | // 7 | 8 | import Foundation 9 | 10 | class DeviceManager { 11 | 12 | // MARK: - Singleton creation - 13 | 14 | static let shared = DeviceManager() 15 | 16 | private init() {} 17 | } 18 | 19 | // MARK: - Internal extension - 20 | 21 | extension DeviceManager { 22 | 23 | func fetchDevices() { 24 | guard let url = Constants.devicesURL else { return } 25 | URLSession.shared.dataTask(with: url) { data, _, error in 26 | guard let data = data, 27 | error == nil, 28 | let path = BundleHelpers.getFileURL(for: "BiometryAvailabilityDeviceList", with: "json") 29 | else { return } 30 | BundleHelpers.write(data, to: path) 31 | }.resume() 32 | } 33 | 34 | func isDeviceInFaceIDList(device: String) -> Bool { 35 | let deviceResponse = readDataFromDevices() 36 | return deviceResponse?.faceIdDevices.contains { $0.id == device } ?? false 37 | } 38 | 39 | func isDeviceInTouchIDList(device: String) -> Bool { 40 | let deviceResponse = readDataFromDevices() 41 | return deviceResponse?.touchIdDevices.contains { $0.id == device } ?? false 42 | } 43 | } 44 | 45 | // MARK: - Private extension 46 | 47 | private extension DeviceManager { 48 | 49 | func readDataFromDevices() -> DeviceResponse? { 50 | guard let data = BundleHelpers.readFromJSON("BiometryAvailabilityDeviceList") else { 51 | return nil 52 | } 53 | return try? BundleHelpers.decoder.decode(DeviceResponse.self, from: data) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/TouchID/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSFaceIDUsageDescription 24 | Allow Face ID. 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Example/TouchID/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/Locker/Helpers/BundleHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleHelpers.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 20.10.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class BundleHelpers { 12 | 13 | // MARK: - Public properties 14 | 15 | static var bundleResource: Bundle? { 16 | guard let resourceBundleURL = Bundle(for: Self.self).url(forResource: "Locker_Locker", withExtension: "bundle"), 17 | let resourceBundle = Bundle(url: resourceBundleURL) 18 | else { return nil } 19 | return resourceBundle 20 | } 21 | 22 | static var decoder: JSONDecoder { 23 | let decoder = JSONDecoder() 24 | decoder.keyDecodingStrategy = .convertFromSnakeCase 25 | return decoder 26 | } 27 | } 28 | 29 | // MARK: - Internal extension - 30 | 31 | extension BundleHelpers { 32 | 33 | static func write(_ data: Data, to url: URL) { 34 | // we'll have periodic updates to the library so we don't want to throw errors 35 | guard let decodedResponse = try? BundleHelpers.decoder.decode(DeviceResponse.self, from: data), 36 | let encodedData = try? JSONEncoder().encode(decodedResponse) 37 | else { return } 38 | try? encodedData.write(to: url) 39 | } 40 | 41 | static func readFromJSON(_ name: String) -> Data? { 42 | guard let path = BundleHelpers.bundleResource?.path(forResource: name, ofType: "json"), 43 | let data = try? String(contentsOfFile: path).data(using: .utf8) 44 | else { return nil } 45 | return data 46 | } 47 | 48 | static func getFileURL(for name: String, with nameExtension: String) -> URL? { 49 | return BundleHelpers.bundleResource?.url(forResource: name, withExtension: nameExtension) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/TouchID/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Locker.xcodeproj/xcshareddata/xcschemes/LockerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | Welcome to our project! We appreciate your interest in helping us improve it. 4 | 5 | ## How can I contribute? 6 | 7 | There are multiple ways in which you can help us make this project even better. 8 | 9 | - Reporting bugs or suggesting new features 10 | - Contributing code improvements or new features 11 | - Writing, updating, or fixing tests 12 | - Improving documentation, including inline comments, user manuals, and developer guides 13 | 14 | ## Issue reporting 15 | 16 | If you found a bug or have an idea for a new feature, please open an issue. Be sure to include a clear and descriptive title, as well as a detailed description of the bug or feature. 17 | 18 | To avoid duplicate issues, please check if a similar issue has already been created. 19 | 20 | ## Making changes 21 | 22 | To make changes to the project, please follow these steps: 23 | 24 | 1. Fork the project repository. 25 | 2. Create a new branch for your changes, based on the project's main branch. 26 | 3. Make your changes. Ensure you've followed the coding style and standards. 27 | 4. Test your changes thoroughly, ensuring all existing tests pass and new tests cover your changes where appropriate. 28 | 5. Commit your changes with a clear and descriptive commit message. 29 | 6. Push your changes to your fork. 30 | 7. Create a pull request to the project's main branch. 31 | 32 | Once we check everything, we will merge the changes into the main branch and include it in the next release. 33 | 34 | ## Guidelines for pull requests 35 | 36 | When submitting a pull request, please ensure that: 37 | 38 | - Your pull request is concise and well-scoped 39 | - Your code is properly tested 40 | - Your code adheres to the project's coding standards and style guidelines 41 | - Your commit message is clear and descriptive 42 | - Your pull request includes a description of the changes you have made and why you have made them 43 | 44 | Try to avoid creating large pull requests that include multiple unrelated changes. Instead, break them down into smaller, more focused pull requests. This will make it easier for us to review and merge your changes. 45 | 46 | ## Code of conduct 47 | 48 | We want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [code of conduct](/CODE_OF_CONDUCT.md). 49 | 50 | ## License 51 | 52 | By submitting a pull request you agree to release that code under the project's [license](/LICENSE). 53 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | ## Our pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@infinum.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 71 | available [here](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). 72 | 73 | For answers to common questions about this code of conduct, visit 74 | the [FAQ](https://www.contributor-covenant.org/faq) page. 75 | -------------------------------------------------------------------------------- /Locker.xcodeproj/xcshareddata/xcschemes/Locker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Sources/Locker/Tests/LockerHelpersTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LockerHelpersTests.swift 3 | // Locker-Locker_Locker 4 | // 5 | // Created by Nikola Simunko on 01.08.2025.. 6 | // 7 | 8 | import XCTest 9 | 10 | @testable import Locker 11 | 12 | final class LockerHelpersTests: XCTestCase { 13 | 14 | private var bundleId: String { Bundle.main.bundleIdentifier ?? "" } 15 | 16 | // MARK: - Setup before & after each test 17 | 18 | override func setUp() { 19 | super.setUp() 20 | let defaults = UserDefaults.standard 21 | defaults.removeObject(forKey: LockerHelpers.keyCustomKeychainService) 22 | defaults.removeObject(forKey: LockerHelpers.keyLAPolicyDomainState) 23 | } 24 | 25 | override func tearDown() { 26 | let defaults = UserDefaults.standard 27 | defaults.removeObject(forKey: LockerHelpers.keyCustomKeychainService) 28 | defaults.removeObject(forKey: LockerHelpers.keyLAPolicyDomainState) 29 | super.tearDown() 30 | } 31 | 32 | // MARK: - Keychain 33 | 34 | func testKeyKeychainAccountNameForUniqueIdentifier() { 35 | let uid = "abc123" 36 | let expected = "\(bundleId)_KeychainAccount_\(uid)" 37 | let got = LockerHelpers.keyKeychainAccountNameForUniqueIdentifier(uid) 38 | 39 | XCTAssertEqual(got, expected) 40 | } 41 | 42 | func testKeyDidAskToUseBiometricsIDForUniqueIdentifier() { 43 | let uid = "abc123" 44 | let expected = "\(bundleId)_UserDefaultsDidAskToUseTouchID_\(uid)" 45 | let got = LockerHelpers.keyDidAskToUseBiometricsIDForUniqueIdentifier(uid) 46 | 47 | XCTAssertEqual(got, expected) 48 | } 49 | 50 | func testKeyBiometricsIDActivatedForUniqueIdentifier() { 51 | let uid = "abc123" 52 | let expected = "\(bundleId)_UserDefaultsKeyTouchIDActivated_\(uid)" 53 | let got = LockerHelpers.keyBiometricsIDActivatedForUniqueIdentifier(uid) 54 | 55 | XCTAssertEqual(got, expected) 56 | } 57 | 58 | func testKeyShouldAddSecretToKeychainOnNextLoginForUniqueIdentifier() { 59 | let uid = "abc123" 60 | let expected = "\(bundleId)_UserDefaultsShouldAddPasscodeToKeychainOnNextLogin_\(uid)" 61 | let got = LockerHelpers.keyShouldAddSecretToKeychainOnNextLoginForUniqueIdentifier(uid) 62 | 63 | XCTAssertEqual(got, expected) 64 | } 65 | 66 | func testCustomKeychainService() { 67 | let expected = "\(bundleId)_UserDefaultsCustomKeychainService" 68 | XCTAssertEqual(LockerHelpers.keyCustomKeychainService, expected) 69 | } 70 | 71 | func testLAPolicyDomainState() { 72 | let expected = "\(bundleId)_UserDefaultsLAPolicyDomainState" 73 | XCTAssertEqual(LockerHelpers.keyLAPolicyDomainState, expected) 74 | } 75 | 76 | func testKeychainServiceNameReturnsDefaultWhenNoOverride() { 77 | let expected = "\(bundleId)_KeychainService" 78 | let got = LockerHelpers.keyKeychainServiceName 79 | XCTAssertEqual(got, expected) 80 | } 81 | 82 | func testKeychainServiceNameOverrideFromUserDefaults() { 83 | let override = "CustomService" 84 | UserDefaults.standard.set(override, forKey: LockerHelpers.keyCustomKeychainService) 85 | let got = LockerHelpers.keyKeychainServiceName 86 | XCTAssertEqual(got, override) 87 | } 88 | 89 | // MARK: - Device 90 | 91 | func testIsSimulator() { 92 | // Matches compilation environment 93 | #if targetEnvironment(simulator) 94 | XCTAssertTrue(LockerHelpers.isSimulator) 95 | #else 96 | XCTAssertFalse(LockerHelpers.isSimulator) 97 | #endif 98 | } 99 | 100 | func testDeviceCodeIsNonEmpty() { 101 | // getDeviceIdentifierFromSystem() returns machine identifier (e.g., "arm64", "x86_64", "iPhone15,3", etc.) 102 | let code = LockerHelpers.deviceCode 103 | XCTAssertFalse(code.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) 104 | } 105 | 106 | // MARK: - Fetch new device list (no-op on simulator) 107 | 108 | func testFetchNewDeviceList() { 109 | // On simulator the method body is compiled out; this just ensures calling it won't explode. 110 | LockerHelpers.fetchNewDeviceList() 111 | XCTAssertTrue(true) 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /Example/TouchID/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TouchID 4 | // 5 | // Created by Ivan Vecko on 02/03/2018. 6 | // Copyright © 2018 Infinum Ltd. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Locker 11 | 12 | public final class ViewController: UIViewController { 13 | 14 | // MARK: - Public properties - 15 | 16 | let topSecret = "My Secret!" 17 | 18 | // MARK: - Private properties - 19 | 20 | @IBOutlet private weak var storeResultLabel: UILabel! 21 | @IBOutlet private weak var readResultLabel: UILabel! 22 | 23 | private let identifier = "TouchIDSampleApp" 24 | 25 | public override func viewDidLoad() { 26 | super.viewDidLoad() 27 | // Locker.setKeychainService("someCustomService") 28 | // shouldUseAuthWithBiometrics = true 29 | } 30 | 31 | 32 | @IBAction private func storeSecretAction() { 33 | storeSecret() 34 | } 35 | 36 | @IBAction private func readSecretAction() { 37 | readSecret { [weak self] secret in 38 | self?.readResultLabel.text = "Read: \(secret)" 39 | } failure: { [weak self] status in 40 | self?.readResultLabel.text = "Failed to read with status: \(status)" 41 | } 42 | } 43 | 44 | @IBAction private func resetEverythingAction() { 45 | resetUserDefaults() 46 | resetEverything() 47 | readResultLabel.text = "--" 48 | storeResultLabel.text = "--" 49 | } 50 | } 51 | 52 | // MARK: - Locker usage - 53 | 54 | // MARK: Read Write Delete 55 | 56 | extension ViewController { 57 | 58 | func storeSecret() { 59 | Locker.setSecret(topSecret, for: identifier, completed: { [weak self] error in 60 | guard let self else { return } 61 | 62 | guard let error 63 | else { 64 | self.storeResultLabel.text = "Stored: \(self.topSecret)" 65 | return 66 | } 67 | 68 | self.storeResultLabel.text = "Failed to store: \(error)" 69 | }) 70 | } 71 | 72 | func readSecret(success: @escaping (String) -> Void, failure: @escaping (OSStatus) -> Void) { 73 | Locker.retrieveCurrentSecret( 74 | for: identifier, 75 | operationPrompt: "Unlock locker!", 76 | success: success, failure: failure 77 | ) 78 | } 79 | 80 | func deleteSecret() { 81 | Locker.deleteSecret(for: identifier) 82 | } 83 | } 84 | 85 | // MARK: Device settings 86 | 87 | extension ViewController { 88 | 89 | var settingsChanged: Bool { 90 | return Locker.biometricsSettingsDidChange 91 | } 92 | 93 | var runningFromTheSimulator: Bool { 94 | return Locker.isRunningFromTheSimulator 95 | } 96 | 97 | var supportedBiometricAuthentication: BiometricsType { 98 | return Locker.supportedBiometricsAuthentication 99 | } 100 | 101 | var configuredBiometricsAuthentication: BiometricsType { 102 | return Locker.configuredBiometricsAuthentication 103 | } 104 | } 105 | 106 | // MARK: User defaults 107 | 108 | extension ViewController { 109 | 110 | func setCustomUserDefaults() { 111 | guard let userDefaults = UserDefaults(suiteName: "customDomain") else { 112 | return 113 | } 114 | Locker.userDefaults = userDefaults 115 | } 116 | 117 | func resetUserDefaults() { 118 | Locker.userDefaults = nil 119 | } 120 | } 121 | 122 | // MARK: Helpers 123 | 124 | extension ViewController { 125 | 126 | var shouldUseAuthWithBiometrics: Bool { 127 | get { return Locker.shouldUseAuthenticationWithBiometrics(for: identifier) } 128 | set (newValue) { Locker.setShouldUseAuthenticationWithBiometrics(newValue, for: identifier) } 129 | } 130 | 131 | // swiftlint:disable unused_setter_value 132 | var didAskToUseAuthWithBiometrics: Bool { 133 | get { return Locker.didAskToUseAuthenticationWithBiometrics(for: identifier) } 134 | set { Locker.setDidAskToUseAuthenticationWithBiometrics(true, for: identifier) } 135 | } 136 | 137 | var shouldAddSecretToKeychainOnNextLogin: Bool { 138 | get { return Locker.shouldAddSecretToKeychainOnNextLogin(for: identifier) } 139 | set { Locker.setShouldAddSecretToKeychainOnNextLogin(true, for: identifier) } 140 | } 141 | } 142 | 143 | // MARK: Reseting 144 | 145 | extension ViewController { 146 | func resetEverything() { 147 | Locker.reset(for: identifier) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Example/TouchID.xcodeproj/xcshareddata/xcschemes/TouchID.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 52 | 58 | 59 | 60 | 61 | 62 | 72 | 74 | 80 | 81 | 82 | 83 | 89 | 91 | 97 | 98 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /.swiflint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - . 3 | analyzer_rules: 4 | - unused_capture_list 5 | - unused_closure_parameter 6 | - unused_control_flow_label 7 | - unused_declaration 8 | - unused_enumerated 9 | - unused_import 10 | - unused_optional_binding 11 | - unused_setter_value 12 | 13 | only_rules: 14 | - array_init 15 | - attributes 16 | - block_based_kvo 17 | - closing_brace 18 | - closure_body_length 19 | - closure_end_indentation 20 | - closure_parameter_position 21 | - closure_spacing 22 | - collection_alignment 23 | - comma 24 | - comment_spacing 25 | - compiler_protocol_init 26 | - computed_accessors_order 27 | - contains_over_filter_count 28 | - contains_over_filter_is_empty 29 | - contains_over_first_not_nil 30 | - contains_over_range_nil_comparison 31 | - control_statement 32 | - convenience_type 33 | - deployment_target 34 | - discarded_notification_center_observer 35 | - discouraged_assert 36 | - discouraged_direct_init 37 | - discouraged_object_literal 38 | - duplicate_imports 39 | - dynamic_inline 40 | - empty_collection_literal 41 | - empty_count 42 | - empty_enum_arguments 43 | - empty_parameters 44 | - empty_parentheses_with_trailing_closure 45 | - empty_string 46 | - explicit_init 47 | - extension_access_modifier 48 | - fatal_error_message 49 | - file_length 50 | - file_name_no_space 51 | - first_where 52 | - flatmap_over_map_reduce 53 | - for_where 54 | - force_try 55 | - function_body_length 56 | - function_parameter_count 57 | - generic_type_name 58 | - identical_operands 59 | - identifier_name 60 | - implicit_getter 61 | - inert_defer 62 | - is_disjoint 63 | - joined_default_parameter 64 | - large_tuple 65 | - last_where 66 | - leading_whitespace 67 | - legacy_cggeometry_functions 68 | - legacy_constant 69 | - legacy_constructor 70 | - legacy_hashing 71 | - legacy_multiple 72 | - legacy_nsgeometry_functions 73 | - legacy_random 74 | - line_length 75 | - literal_expression_end_indentation 76 | - lower_acl_than_parent 77 | - mark 78 | - modifier_order 79 | - multiline_arguments 80 | - multiline_arguments_brackets 81 | - multiline_function_chains 82 | - multiline_literal_brackets 83 | - multiline_parameters 84 | - multiline_parameters_brackets 85 | - multiple_closures_with_trailing_closure 86 | - no_fallthrough_only 87 | - no_space_in_method_call 88 | - notification_center_detachment 89 | - nslocalizedstring_require_bundle 90 | - nsobject_prefer_isequal 91 | - number_separator 92 | - opening_brace 93 | - operator_usage_whitespace 94 | - operator_whitespace 95 | - optional_enum_case_matching 96 | - orphaned_doc_comment 97 | - overridden_super_call 98 | - prefer_self_type_over_type_of_self 99 | - prefer_zero_over_explicit_init 100 | - private_action 101 | - private_outlet 102 | - private_over_fileprivate 103 | - private_subject 104 | - prohibited_super_call 105 | - protocol_property_accessors_order 106 | - reduce_boolean 107 | - reduce_into 108 | - redundant_discardable_let 109 | - redundant_nil_coalescing 110 | - redundant_objc_attribute 111 | - redundant_optional_initialization 112 | - redundant_set_access_control 113 | - redundant_string_enum_value 114 | - redundant_type_annotation 115 | - redundant_void_return 116 | - required_enum_case 117 | - return_arrow_whitespace 118 | - shorthand_operator 119 | - sorted_first_last 120 | - statement_position 121 | - static_operator 122 | - superfluous_disable_command 123 | - switch_case_alignment 124 | - syntactic_sugar 125 | - todo 126 | - toggle_bool 127 | - trailing_comma 128 | - trailing_newline 129 | - trailing_semicolon 130 | - type_body_length 131 | - type_name 132 | - unavailable_function 133 | - unneeded_break_in_switch 134 | - unneeded_parentheses_in_closure_argument 135 | - untyped_error_in_catch 136 | - valid_ibinspectable 137 | - vertical_parameter_alignment 138 | - vertical_parameter_alignment_on_call 139 | - vertical_whitespace 140 | - void_return 141 | - weak_delegate 142 | - yoda_condition 143 | 144 | file_length: 145 | warning: 600 146 | error: 1000 147 | ignore_comment_only_lines: true 148 | function_body_length: 149 | warning: 100 150 | error: 150 151 | function_parameter_count: 152 | warning: 8 153 | error: 12 154 | identifier_name: 155 | min_length: 156 | error: 1 157 | warning: 2 158 | max_length: 159 | warning: 60 160 | error: 100 161 | excluded: 162 | - id 163 | - Id 164 | - ID 165 | - on 166 | - On 167 | - ON 168 | - x 169 | - y 170 | - z 171 | - i 172 | - j 173 | type_name: 174 | excluded: 175 | - id 176 | - Id 177 | - ID 178 | - on 179 | - On 180 | - ON 181 | line_length: 182 | warning: 150 183 | error: 250 184 | ignores_comments: true 185 | number_separator: 186 | minimum_length: 5 187 | type_body_length: 188 | warning: 300 189 | error: 450 190 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then 12 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # resources to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 18 | 19 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 20 | > "$RESOURCES_TO_COPY" 21 | 22 | XCASSET_FILES=() 23 | 24 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 25 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 26 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 27 | 28 | case "${TARGETED_DEVICE_FAMILY:-}" in 29 | 1,2) 30 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 31 | ;; 32 | 1) 33 | TARGET_DEVICE_ARGS="--target-device iphone" 34 | ;; 35 | 2) 36 | TARGET_DEVICE_ARGS="--target-device ipad" 37 | ;; 38 | 3) 39 | TARGET_DEVICE_ARGS="--target-device tv" 40 | ;; 41 | 4) 42 | TARGET_DEVICE_ARGS="--target-device watch" 43 | ;; 44 | *) 45 | TARGET_DEVICE_ARGS="--target-device mac" 46 | ;; 47 | esac 48 | 49 | install_resource() 50 | { 51 | if [[ "$1" = /* ]] ; then 52 | RESOURCE_PATH="$1" 53 | else 54 | RESOURCE_PATH="${PODS_ROOT}/$1" 55 | fi 56 | if [[ ! -e "$RESOURCE_PATH" ]] ; then 57 | cat << EOM 58 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. 59 | EOM 60 | exit 1 61 | fi 62 | case $RESOURCE_PATH in 63 | *.storyboard) 64 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 65 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 66 | ;; 67 | *.xib) 68 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true 69 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} 70 | ;; 71 | *.framework) 72 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 73 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 74 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true 75 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 76 | ;; 77 | *.xcdatamodel) 78 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true 79 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" 80 | ;; 81 | *.xcdatamodeld) 82 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true 83 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" 84 | ;; 85 | *.xcmappingmodel) 86 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true 87 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" 88 | ;; 89 | *.xcassets) 90 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" 91 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 92 | ;; 93 | *) 94 | echo "$RESOURCE_PATH" || true 95 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" 96 | ;; 97 | esac 98 | } 99 | if [[ "$CONFIGURATION" == "Debug" ]]; then 100 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker_Locker.bundle" 101 | fi 102 | if [[ "$CONFIGURATION" == "Release" ]]; then 103 | install_resource "${PODS_CONFIGURATION_BUILD_DIR}/Locker/Locker_Locker.bundle" 104 | fi 105 | 106 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 107 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 108 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 109 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 110 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 111 | fi 112 | rm -f "$RESOURCES_TO_COPY" 113 | 114 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] 115 | then 116 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 117 | OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) 118 | while read line; do 119 | if [[ $line != "${PODS_ROOT}*" ]]; then 120 | XCASSET_FILES+=("$line") 121 | fi 122 | done <<<"$OTHER_XCASSETS" 123 | 124 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then 125 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 126 | else 127 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" 128 | fi 129 | fi 130 | -------------------------------------------------------------------------------- /Example/TouchIDTests/TouchIDTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchIDTests.swift 3 | // TouchIDTests 4 | // 5 | // Created by Ivan Vecko on 02/03/2018. 6 | // Copyright © 2018 Infinum Ltd. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TouchID 11 | 12 | class TouchIDTests: XCTestCase { 13 | 14 | private var containerViewController: ViewController! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | containerViewController = ViewController() 20 | } 21 | 22 | override func tearDown() { 23 | containerViewController.resetUserDefaults() 24 | super.tearDown() 25 | } 26 | 27 | // MARK: - Store Retrieve and Delete - 28 | func testStoreAndRetrieveSecret() { 29 | 30 | // Store 31 | containerViewController.storeSecret() 32 | 33 | // Retrieve 34 | let asyncExpectation = expectation(description: "Async block executed") 35 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 36 | self.containerViewController.readSecret(success: { (secret) in 37 | XCTAssertEqual(secret, self.containerViewController.topSecret) 38 | asyncExpectation.fulfill() 39 | }, failure: { _ in 40 | XCTFail("Error while retrieve the secret") 41 | asyncExpectation.fulfill() 42 | }) 43 | } 44 | 45 | waitForExpectations(timeout: 3, handler: nil) 46 | } 47 | 48 | func testStoreAndRetrieveSecretWithCustomUserDefaults() { 49 | 50 | containerViewController.setCustomUserDefaults() 51 | 52 | // Store 53 | containerViewController.storeSecret() 54 | 55 | // Retrieve 56 | let asyncExpectation = expectation(description: "Async block executed") 57 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 58 | self.containerViewController.readSecret(success: { (secret) in 59 | XCTAssertEqual(secret, self.containerViewController.topSecret) 60 | asyncExpectation.fulfill() 61 | }, failure: { _ in 62 | XCTFail("Error while retrieve the secret") 63 | asyncExpectation.fulfill() 64 | }) 65 | } 66 | 67 | waitForExpectations(timeout: 3, handler: nil) 68 | } 69 | 70 | func testDeleteSecret() { 71 | 72 | // Store 73 | containerViewController.storeSecret() 74 | 75 | // Delete 76 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 77 | self.containerViewController.deleteSecret() 78 | } 79 | 80 | // Retrieve 81 | let asyncExpectation = expectation(description: "Async block executed") 82 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 83 | self.containerViewController.readSecret(success: { _ in 84 | XCTFail("Data should not be available") 85 | asyncExpectation.fulfill() 86 | }, failure: { _ in 87 | XCTAssertTrue(true, "Data succesfully deleted") 88 | asyncExpectation.fulfill() 89 | }) 90 | } 91 | 92 | waitForExpectations(timeout: 3, handler: nil) 93 | } 94 | 95 | // MARK: - Device settings - 96 | 97 | func testSettingsChanged() { 98 | XCTAssertFalse(containerViewController.settingsChanged) 99 | } 100 | 101 | func testRunningFromTheSimulator() { 102 | XCTAssertTrue(containerViewController.runningFromTheSimulator) 103 | } 104 | 105 | func testDeviceSupportsAuthenticationWithBiometrics() { 106 | XCTAssertTrue(containerViewController.supportedBiometricAuthentication == .none) 107 | } 108 | 109 | func testConfiguredBiometricsAuthentication() { 110 | XCTAssertTrue(containerViewController.configuredBiometricsAuthentication == .none) 111 | } 112 | 113 | // MARK: - Helpers - 114 | 115 | func testShouldUseAuthFlag() { 116 | containerViewController.shouldUseAuthWithBiometrics = true 117 | XCTAssertTrue(containerViewController.shouldUseAuthWithBiometrics) 118 | } 119 | 120 | func testShouldUseAuthFlagWithCustomUserDefaults() { 121 | containerViewController.setCustomUserDefaults() 122 | containerViewController.shouldUseAuthWithBiometrics = true 123 | XCTAssertTrue(containerViewController.shouldUseAuthWithBiometrics) 124 | } 125 | 126 | func testDidAskToUseAuthWithBiometrics() { 127 | containerViewController.didAskToUseAuthWithBiometrics = true 128 | XCTAssertTrue(containerViewController.didAskToUseAuthWithBiometrics) 129 | } 130 | 131 | func testDidAskToUseAuthWithBiometricsWithCustomUserDefaults() { 132 | containerViewController.setCustomUserDefaults() 133 | containerViewController.didAskToUseAuthWithBiometrics = true 134 | XCTAssertTrue(containerViewController.didAskToUseAuthWithBiometrics) 135 | } 136 | 137 | func testShouldAddSecretToKeychainOnNextLogin() { 138 | containerViewController.shouldAddSecretToKeychainOnNextLogin = true 139 | XCTAssertTrue(containerViewController.shouldAddSecretToKeychainOnNextLogin) 140 | } 141 | 142 | func testShouldAddSecretToKeychainOnNextLoginWithCustomUserDefaults() { 143 | containerViewController.setCustomUserDefaults() 144 | containerViewController.shouldAddSecretToKeychainOnNextLogin = true 145 | XCTAssertTrue(containerViewController.shouldAddSecretToKeychainOnNextLogin) 146 | } 147 | 148 | // MARK: - Reseting - 149 | 150 | func testResetAll() { 151 | 152 | // Store some data 153 | containerViewController.shouldUseAuthWithBiometrics = true 154 | containerViewController.didAskToUseAuthWithBiometrics = true 155 | containerViewController.shouldAddSecretToKeychainOnNextLogin = true 156 | containerViewController.storeSecret() 157 | 158 | // Reset locker 159 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 160 | self.containerViewController.resetEverything() 161 | } 162 | 163 | // Check if everything is reseted 164 | let asyncExpectation = expectation(description: "Async block executed") 165 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 166 | self.containerViewController.readSecret(success: { _ in 167 | XCTFail("Data should not be available") 168 | asyncExpectation.fulfill() 169 | }, failure: { _ in 170 | 171 | let shouldUseAuth = self.containerViewController.shouldUseAuthWithBiometrics 172 | let didAskToUseAuth = self.containerViewController.didAskToUseAuthWithBiometrics 173 | let shouldAddSecretOnNextLogin = self.containerViewController.shouldAddSecretToKeychainOnNextLogin 174 | 175 | let allReseted = shouldUseAuth || didAskToUseAuth || shouldAddSecretOnNextLogin 176 | 177 | XCTAssertFalse(allReseted, "Locker succesfully reseted") 178 | 179 | asyncExpectation.fulfill() 180 | }) 181 | } 182 | waitForExpectations(timeout: 3, handler: nil) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /Example/TouchID/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 33 | 41 | 47 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Sources/Locker/Helpers/LockerHelpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LockerHelpers.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 19.10.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LocalAuthentication 11 | 12 | class LockerHelpers { 13 | 14 | // MARK: - Public properties 15 | 16 | static var biometricsSettingsChanged: Bool { 17 | return checkIfBiometricsSettingsChanged() 18 | } 19 | 20 | static var supportedBiometricAuthentication: BiometricsType { 21 | if LockerHelpers.deviceSupportsAuthenticationWithFaceID { 22 | return .faceID 23 | } 24 | 25 | return deviceSupportsAuthenticationWithTouchID ? .touchID : .none 26 | } 27 | 28 | static var configuredBiometricsAuthentication: BiometricsType { 29 | return getConfiguredBiometricsAuthenticationType() 30 | } 31 | 32 | static var keyLAPolicyDomainState: String { 33 | return "\(LockerHelpers.bundleIdentifier)_UserDefaultsLAPolicyDomainState" 34 | } 35 | 36 | static var canUseAuthenticationWithFaceID: Bool { 37 | return faceIDEnabled 38 | } 39 | 40 | static var canUseAuthenticationWithTouchID: Bool { 41 | return touchIDEnabled 42 | } 43 | 44 | static var deviceCode: String { 45 | return getDeviceIdentifierFromSystem() 46 | } 47 | 48 | static var deviceSupportsAuthenticationWithFaceID: Bool { 49 | if LockerHelpers.canUseAuthenticationWithFaceID { 50 | return true 51 | } 52 | 53 | return deviceManager.isDeviceInFaceIDList(device: LockerHelpers.deviceCode) 54 | } 55 | 56 | static var deviceSupportsAuthenticationWithTouchID: Bool { 57 | if LockerHelpers.canUseAuthenticationWithTouchID { 58 | return true 59 | } 60 | 61 | return deviceManager.isDeviceInTouchIDList(device: LockerHelpers.deviceCode) 62 | } 63 | 64 | static var isSimulator: Bool { 65 | #if targetEnvironment(simulator) 66 | return true 67 | #else 68 | return false 69 | #endif 70 | } 71 | 72 | // MARK: - Private properties 73 | 74 | static private var currentLAPolicyDomainState: Data? { 75 | let context = LAContext() 76 | context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) 77 | return context.evaluatedPolicyDomainState 78 | } 79 | 80 | static private var savedLAPolicyDomainState: Data? { 81 | UserDefaults.standard.object(forKey: LockerHelpers.keyLAPolicyDomainState) as? Data 82 | } 83 | 84 | private static var faceIDEnabled: Bool { 85 | return checkFaceIdState() 86 | } 87 | 88 | private static var touchIDEnabled: Bool { 89 | let context = LAContext() 90 | var error: NSError? 91 | 92 | if !context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { 93 | // When user removes all fingers for TouchID, error code will be `notEnrolled`. 94 | // In that case, we want to return that device supports TouchID. 95 | // In case lib is used on simulator, error code will always be `notEnrolled` and only then 96 | // we want to return that biometrics is not supported as we don't know what simulator is used. 97 | if let error = error, 98 | error.code == biometryNotAvailableCode || (error.code == biometryNotEnrolledCode && isSimulator) { 99 | return false 100 | } 101 | } 102 | 103 | return true 104 | } 105 | 106 | private static let bundleIdentifier = Bundle.main.bundleIdentifier ?? "" 107 | 108 | private static var biometryNotAvailableCode: Int { 109 | if #available(iOS 11.0, *) { 110 | return LAError.biometryNotAvailable.rawValue 111 | } else { 112 | return Int(kLAErrorBiometryNotAvailable) 113 | } 114 | } 115 | 116 | private static var biometryNotEnrolledCode: Int { 117 | if #available(iOS 11, *) { 118 | return LAError.biometryNotEnrolled.rawValue 119 | } else { 120 | return Int(kLAErrorBiometryNotEnrolled) 121 | } 122 | } 123 | 124 | private static let deviceManager: DeviceManager = .shared 125 | } 126 | 127 | // MARK: - Internal extension 128 | 129 | extension LockerHelpers { 130 | 131 | // MARK: - User defaults keys help methods 132 | 133 | static func keyKeychainAccountNameForUniqueIdentifier(_ uniqueIdentifier: String) -> String { 134 | let keychainAccountName = "\(LockerHelpers.bundleIdentifier)_KeychainAccount" 135 | return "\(keychainAccountName)_\(uniqueIdentifier)" 136 | } 137 | 138 | static func keyDidAskToUseBiometricsIDForUniqueIdentifier(_ uniqueIdentifier: String) -> String { 139 | let userDefaultsDidAskToUseBiometricsID = "\(LockerHelpers.bundleIdentifier)_UserDefaultsDidAskToUseTouchID" 140 | return "\(userDefaultsDidAskToUseBiometricsID)_\(uniqueIdentifier)" 141 | } 142 | 143 | static func keyBiometricsIDActivatedForUniqueIdentifier(_ uniqueIdentifier: String) -> String { 144 | let userDefaultsKeyBiometricsIDActivated = "\(LockerHelpers.bundleIdentifier)_UserDefaultsKeyTouchIDActivated" 145 | return "\(userDefaultsKeyBiometricsIDActivated)_\(uniqueIdentifier)" 146 | } 147 | 148 | static func keyShouldAddSecretToKeychainOnNextLoginForUniqueIdentifier(_ uniqueIdentifier: String) -> String { 149 | // swiftlint:disable:next line_length 150 | let shouldAddSecretToKeychainOnNextLogin = "\(LockerHelpers.bundleIdentifier)_UserDefaultsShouldAddPasscodeToKeychainOnNextLogin" 151 | return "\(shouldAddSecretToKeychainOnNextLogin)_\(uniqueIdentifier)" 152 | } 153 | 154 | static var keyCustomKeychainService: String { 155 | return "\(LockerHelpers.bundleIdentifier)_UserDefaultsCustomKeychainService" 156 | } 157 | 158 | // MARK: - Biometric helpers 159 | 160 | static func storeCurrentLAPolicyDomainState() { 161 | let newDomainState = LockerHelpers.currentLAPolicyDomainState 162 | LockerHelpers.setLAPolicyDomainState(with: newDomainState) 163 | } 164 | 165 | static var keyKeychainServiceName: String { 166 | guard let service = UserDefaults.standard.object(forKey: keyCustomKeychainService) as? String else { 167 | return "\(LockerHelpers.bundleIdentifier)_KeychainService" 168 | } 169 | 170 | return service 171 | } 172 | 173 | // MARK: - Device list 174 | 175 | static func fetchNewDeviceList() { 176 | #if !targetEnvironment(simulator) 177 | if !deviceSupportsAuthenticationWithTouchID && !deviceSupportsAuthenticationWithFaceID { 178 | deviceManager.fetchDevices() 179 | } 180 | #endif 181 | } 182 | } 183 | 184 | // MARK: - Private extension 185 | 186 | private extension LockerHelpers { 187 | 188 | static func checkIfBiometricsSettingsChanged() -> Bool { 189 | let oldDomainState = LockerHelpers.savedLAPolicyDomainState 190 | let newDomainState = LockerHelpers.currentLAPolicyDomainState 191 | 192 | guard oldDomainState != nil || newDomainState != nil, 193 | oldDomainState != newDomainState 194 | else { return false } 195 | 196 | LockerHelpers.setLAPolicyDomainState(with: LockerHelpers.currentLAPolicyDomainState) 197 | return true 198 | } 199 | 200 | static func getConfiguredBiometricsAuthenticationType() -> BiometricsType { 201 | let canUse = LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) 202 | 203 | if canUse && LockerHelpers.canUseAuthenticationWithFaceID { 204 | return .faceID 205 | } else if canUse { 206 | return .touchID 207 | } else { 208 | return .none 209 | } 210 | } 211 | 212 | static func checkFaceIdState() -> Bool { 213 | let context = LAContext() 214 | var error: NSError? 215 | 216 | if #available(iOS 11.0, *) { 217 | if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { 218 | if context.biometryType == .faceID { 219 | return true 220 | } 221 | } 222 | } 223 | return false 224 | } 225 | 226 | static func getDeviceIdentifierFromSystem() -> String { 227 | var systemInfo = utsname() 228 | uname(&systemInfo) 229 | let machineMirror = Mirror(reflecting: systemInfo.machine) 230 | let identifier = machineMirror.children.reduce("") { identifier, element in 231 | guard let value = element.value as? Int8, value != 0 else { return identifier } 232 | return identifier + String(UnicodeScalar(UInt8(value))) 233 | } 234 | return identifier 235 | } 236 | 237 | static func setLAPolicyDomainState(with domainState: Data?) { 238 | UserDefaults.standard.set(domainState, forKey: LockerHelpers.keyLAPolicyDomainState) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Locker/Locker-Unit-Tests-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink -f "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/Locker/Locker.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/Locker/Locker.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink -f "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/Locker/Locker.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/Locker/Locker.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Locker 🔒** 2 | 3 | ![Bitrise](https://img.shields.io/bitrise/eb8928b119a9df30?token=753GPSmElQJ1aRfplKYJLw) ![GitHub](https://img.shields.io/github/license/infinum/Locker) ![Cocoapods](https://img.shields.io/cocoapods/v/Locker) ![Cocoapods platforms](https://img.shields.io/cocoapods/p/Locker) 4 | 5 | 6 | 7 |

8 | Locker 9 |

10 | 11 | ## Description 12 | 13 | Lightweight library for handling sensitive data (`String` type) in Keychain using iOS Biometric features. 14 | 15 | * Save data in Keychain. 16 | * Fetch data from Keychain with Biometric ID. 17 | * Delete data from Keychain. 18 | * There are additional methods that help you with saving and fetching some additional info regarding the authentication with biometric usage. 19 | * Detect changes in Biometric settings. 20 | * Check if device has support for certain Biometric ID. 21 | * Detect and supports Simulator. 22 | * Update the supported devices list without updating the library 23 | 24 | Locker does not collect any user data. We have provided a [privacy manifest](https://github.com/infinum/Locker/blob/master/Sources/Locker/SupportingFiles/PrivacyInfo.xcprivacy) file that can be included in your app. 25 | 26 | ## Table of contents 27 | 28 | * [Requirements](#requirements) 29 | * [Getting started](#getting-started) 30 | * [Usage](#usage) 31 | * [Contributing](#contributing) 32 | * [License](#license) 33 | * [Credits](#credits) 34 | 35 | 36 | ## Requirements 37 | 38 | - iOS 10.0 + 39 | 40 | ## Getting started 41 | 42 | Locker supports CocoaPods, Swift Package Manager and Carthage. 43 | 44 | 45 | #### CocoaPods 46 | 47 | See installation instructions for [CocoaPods](http://cocoapods.org) if not already installed 48 | 49 | To integrate the library into your Xcode project specify the pod dependency to your `Podfile`: 50 | 51 | ```ruby 52 | platform :ios, '10.0' 53 | use_frameworks! 54 | 55 | pod 'Locker' 56 | ``` 57 | 58 | run pod install 59 | 60 | ```bash 61 | pod install 62 | ``` 63 | 64 | 65 | #### Swift Package Manager 66 | 67 | To install Locker from the Swift Package Manager, you should: 68 | * In Xcode 11+ select File → Packages → Add Package Dependency 69 | * Enter project's URL: https://github.com/infinum/Locker.git 70 | 71 | For more information, check [Swift Package Manager](https://swift.org/package-manager/). 72 | 73 | 74 | #### Carthage 75 | 76 | For the Carthage installation and usage instruction, you can check official [quick start documentation](https://github.com/Carthage/Carthage#quick-start). 77 | 78 | To integrate the library into your Xcode project, specify it in your `Cartfile`: 79 | 80 | ``` 81 | github "infinum/Locker" 82 | ``` 83 | 84 | Run `carthage update`. 85 | 86 | ## Usage 87 | 88 | ##### 0. Setup Xcode project `Info.plist` with required permission for **Face ID** usage. 89 | - add following to your **Info.plist** from drop down menu 90 | - Privacy - Face ID usage description 91 | - after new field was added, provide meaningful description 92 | - e.g. (when viewed as source code) 93 | ```xml 94 | NSFaceIDUsageDescription 95 | ** Add your Face ID Usage Description ** 96 | ``` 97 | 98 | ##### 1. Save Your data with `setSecret: forUniqueIdentifier: completed:` method. 99 | For `uniqueIdentifier` pass `String` value You will later use to fetch Your data. 100 | The `completed` is a closure that is called upon finished secret storage. If the error occurs upon storing, info will be passed through the completion block. 101 | 102 | ```objective-c 103 | // Objective-C 104 | [Locker setSecret:@"passcode" forUniqueIdentifier:@"kUniqueIdentifier" completed: ^(NSError *error) { 105 | //handle error 106 | }]; 107 | ``` 108 | 109 | ```swift 110 | // Swift 111 | Locker.setSecret("passcode", for: "UniqueIdentifier", completed: { error in 112 | // handle error 113 | }) 114 | ``` 115 | 116 | > If Locker is run from the Simulator, instead of storing it into the Keychain, Locker will store data to the `UserDefaults`. You can check if Locker is running from the simulator with `isRunningFromTheSimulator` property. 117 | 118 | ##### 2. Fetch Your data with `retrieveCurrentSecretForUniqueIdentifier: operationPrompt: success: failure:`. 119 | 120 | `operationPrompt` is `String` value which will be displayed as message on system Touch ID dialog. 121 | You'll get Your data in `success` completion block. If, for some reason, Your data is not found in Keychain, You'll get error status in `failure` completion block. 122 | 123 | ```objective-c 124 | // Objective-C 125 | [Locker retrieveCurrentSecretForUniqueIdentifier:@"kUniqueIdentifier" 126 | operationPrompt:@"Touch ID description" success:^(NSString *secret) { 127 | // do sth with secret 128 | } failure:^(OSStatus failureStatus) { 129 | // handle failure 130 | }]; 131 | ``` 132 | 133 | ```swift 134 | // Swift 135 | Locker.retrieveCurrentSecret( 136 | for: "kUniqueIdentifier", 137 | operationPrompt: "Touch ID description", 138 | success: { (secret) in 139 | // do sth with secret 140 | }, failure: { (failureStatus) in 141 | // handle failure 142 | } 143 | ) 144 | ``` 145 | 146 | ##### 3. Delete data with `deleteSecretForUniqueIdentifier:` method. 147 | 148 | ```objective-c 149 | // Objective-C 150 | [Locker deleteSecretForUniqueIdentifier:@"kUniqueIdentifier"]; 151 | ``` 152 | 153 | ```swift 154 | // Swift 155 | Locker.deleteSecret(for: "kUniqueIdentifier") 156 | ``` 157 | 158 | ##### 4. If You need to update Your saved data, just call `setSecret: forUniqueIdentifier: completed:`. This method first deletes old value, if there is one, and then saves new one. 159 | 160 | ##### 5. There are some additional methods that may help You with handling the authentication with Biometric usage. 161 | 162 | Use `setShouldUseAuthenticationWithBiometrics: forUniqueIdentifier:` method to save if Biometric ID should be used for fetching data from Keychain. 163 | Use `shouldUseAuthenticationWithBiometricsForUniqueIdentifier:` method to fetch that info. 164 | 165 | Use `setDidAskToUseAuthenticationWithBiometrics: forUniqueIdentifier:` method to save if user was asked to use Biometric ID for certain data. 166 | Use `didAskToUseAuthenticationWithBiometricsForUniqueIdentifier:` method to fetch that info. 167 | 168 | Use `setShouldAddSecretToKeychainOnNextLogin: forUniqueIdentifier:` method to save if data should be saved to Keychain on next user entering. 169 | Use `shouldAddSecretToKeychainOnNextLoginForUniqueIdentifier:` method to fetch that info. 170 | 171 | Note: This methods are here because they were used on some of our projects. 172 | You should probably want to use the first two, `setShouldUseAuthenticationWithBiometrics: forUniqueIdentifier` and `shouldUseAuthenticationWithBiometricsForUniqueIdentifier`. 173 | The other ones will be useful if Your app has certain behaviour. 174 | 175 | ##### 6. You can check for Biometrics settings changes with `biometricsSettingsDidChange`. 176 | It will return `true` if Biometric settings are changed since Your last calling this method or last saving in Keychain. 177 | 178 | ```objective-c 179 | // Objective-C 180 | BOOL biometrySettingsChanged = Locker.biometricsSettingsDidChange; 181 | BOOL usingBiometry = [Locker shouldUseAuthenticationWithBiometricsForUniqueIdentifier:@"kUniqueIdentifier"]; 182 | if (biometrySettingsChanged && usingBiometry) { 183 | // handle case when settings are changed and biometry should be used 184 | } 185 | ``` 186 | 187 | ```swift 188 | // Swift 189 | let biometrySettingsChanged = Locker.biometricsSettingsDidChange 190 | let usingBiometry = Locker.shouldUseAuthenticationWithBiometrics(for: "kUniqueIdentifier") 191 | if biometrySettingsChanged && usingBiometry { 192 | // handle case when settings are changed and biometry should be used 193 | } 194 | ``` 195 | 196 | ##### 7. There are `supportedBiometricsAuthentication` and `configuredBiometricsAuthentication` computed properties which return `BiometricsType` enum (`BiometricsTypeNone`, `BiometricsTypeTouchID`, `BiometricsTypeFaceID`). 197 | `supportedBiometricsAuthentication` checks if the device has support for some Biometric type. 198 | `configuredBiometricsAuthentication` checks if the device has support for some Biometrics type and if that Biometric is enabled in device settings. 199 | 200 | #### 8. There is a local JSON file that contains every iPhone and iPad model which has FaceID or TouchID. That way we can check if the user's device can use FaceID or TouchID. If you want to allow the JSON file to sync itself with a server, you can set `enableDeviceListSync` to `true`. 201 | `enableDeviceListSync` when enabled, if the device is not present on the local list, it syncs the list with a list from the server and writes it down to the local JSON file. 202 | 203 | 204 | ## Contributing 205 | 206 | Feedback and code contributions are very much welcome. Just make a pull request with a short description of your changes. By making contributions to this project you give permission for your code to be used under the same [license](https://github.com/infinum/Locker/blob/master/LICENSE). 207 | 208 | ## License 209 | 210 | ```text 211 | Copyright 2024 Infinum 212 | 213 | Licensed under the Apache License, Version 2.0 (the "License"); 214 | you may not use this file except in compliance with the License. 215 | You may obtain a copy of the License at 216 | 217 | http://www.apache.org/licenses/LICENSE-2.0 218 | 219 | Unless required by applicable law or agreed to in writing, software 220 | distributed under the License is distributed on an "AS IS" BASIS, 221 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 222 | See the License for the specific language governing permissions and 223 | limitations under the License. 224 | ``` 225 | 226 | ## Credits 227 | 228 | Maintained and sponsored by [Infinum](https://infinum.com). 229 | 230 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Locker 5 | 6 | Apache License 7 | Version 2.0, January 2004 8 | http://www.apache.org/licenses/ 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 1. Definitions. 13 | 14 | "License" shall mean the terms and conditions for use, reproduction, 15 | and distribution as defined by Sections 1 through 9 of this document. 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by 18 | the copyright owner that is granting the License. 19 | 20 | "Legal Entity" shall mean the union of the acting entity and all 21 | other entities that control, are controlled by, or are under common 22 | control with that entity. For the purposes of this definition, 23 | "control" means (i) the power, direct or indirect, to cause the 24 | direction or management of such entity, whether by contract or 25 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 26 | outstanding shares, or (iii) beneficial ownership of such entity. 27 | 28 | "You" (or "Your") shall mean an individual or Legal Entity 29 | exercising permissions granted by this License. 30 | 31 | "Source" form shall mean the preferred form for making modifications, 32 | including but not limited to software source code, documentation 33 | source, and configuration files. 34 | 35 | "Object" form shall mean any form resulting from mechanical 36 | transformation or translation of a Source form, including but 37 | not limited to compiled object code, generated documentation, 38 | and conversions to other media types. 39 | 40 | "Work" shall mean the work of authorship, whether in Source or 41 | Object form, made available under the License, as indicated by a 42 | copyright notice that is included in or attached to the work 43 | (an example is provided in the Appendix below). 44 | 45 | "Derivative Works" shall mean any work, whether in Source or Object 46 | form, that is based on (or derived from) the Work and for which the 47 | editorial revisions, annotations, elaborations, or other modifications 48 | represent, as a whole, an original work of authorship. For the purposes 49 | of this License, Derivative Works shall not include works that remain 50 | separable from, or merely link (or bind by name) to the interfaces of, 51 | the Work and Derivative Works thereof. 52 | 53 | "Contribution" shall mean any work of authorship, including 54 | the original version of the Work and any modifications or additions 55 | to that Work or Derivative Works thereof, that is intentionally 56 | submitted to Licensor for inclusion in the Work by the copyright owner 57 | or by an individual or Legal Entity authorized to submit on behalf of 58 | the copyright owner. For the purposes of this definition, "submitted" 59 | means any form of electronic, verbal, or written communication sent 60 | to the Licensor or its representatives, including but not limited to 61 | communication on electronic mailing lists, source code control systems, 62 | and issue tracking systems that are managed by, or on behalf of, the 63 | Licensor for the purpose of discussing and improving the Work, but 64 | excluding communication that is conspicuously marked or otherwise 65 | designated in writing by the copyright owner as "Not a Contribution." 66 | 67 | "Contributor" shall mean Licensor and any individual or Legal Entity 68 | on behalf of whom a Contribution has been received by Licensor and 69 | subsequently incorporated within the Work. 70 | 71 | 2. Grant of Copyright License. Subject to the terms and conditions of 72 | this License, each Contributor hereby grants to You a perpetual, 73 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 74 | copyright license to reproduce, prepare Derivative Works of, 75 | publicly display, publicly perform, sublicense, and distribute the 76 | Work and such Derivative Works in Source or Object form. 77 | 78 | 3. Grant of Patent License. Subject to the terms and conditions of 79 | this License, each Contributor hereby grants to You a perpetual, 80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 81 | (except as stated in this section) patent license to make, have made, 82 | use, offer to sell, sell, import, and otherwise transfer the Work, 83 | where such license applies only to those patent claims licensable 84 | by such Contributor that are necessarily infringed by their 85 | Contribution(s) alone or by combination of their Contribution(s) 86 | with the Work to which such Contribution(s) was submitted. If You 87 | institute patent litigation against any entity (including a 88 | cross-claim or counterclaim in a lawsuit) alleging that the Work 89 | or a Contribution incorporated within the Work constitutes direct 90 | or contributory patent infringement, then any patent licenses 91 | granted to You under this License for that Work shall terminate 92 | as of the date such litigation is filed. 93 | 94 | 4. Redistribution. You may reproduce and distribute copies of the 95 | Work or Derivative Works thereof in any medium, with or without 96 | modifications, and in Source or Object form, provided that You 97 | meet the following conditions: 98 | 99 | (a) You must give any other recipients of the Work or 100 | Derivative Works a copy of this License; and 101 | 102 | (b) You must cause any modified files to carry prominent notices 103 | stating that You changed the files; and 104 | 105 | (c) You must retain, in the Source form of any Derivative Works 106 | that You distribute, all copyright, patent, trademark, and 107 | attribution notices from the Source form of the Work, 108 | excluding those notices that do not pertain to any part of 109 | the Derivative Works; and 110 | 111 | (d) If the Work includes a "NOTICE" text file as part of its 112 | distribution, then any Derivative Works that You distribute must 113 | include a readable copy of the attribution notices contained 114 | within such NOTICE file, excluding those notices that do not 115 | pertain to any part of the Derivative Works, in at least one 116 | of the following places: within a NOTICE text file distributed 117 | as part of the Derivative Works; within the Source form or 118 | documentation, if provided along with the Derivative Works; or, 119 | within a display generated by the Derivative Works, if and 120 | wherever such third-party notices normally appear. The contents 121 | of the NOTICE file are for informational purposes only and 122 | do not modify the License. You may add Your own attribution 123 | notices within Derivative Works that You distribute, alongside 124 | or as an addendum to the NOTICE text from the Work, provided 125 | that such additional attribution notices cannot be construed 126 | as modifying the License. 127 | 128 | You may add Your own copyright statement to Your modifications and 129 | may provide additional or different license terms and conditions 130 | for use, reproduction, or distribution of Your modifications, or 131 | for any such Derivative Works as a whole, provided Your use, 132 | reproduction, and distribution of the Work otherwise complies with 133 | the conditions stated in this License. 134 | 135 | 5. Submission of Contributions. Unless You explicitly state otherwise, 136 | any Contribution intentionally submitted for inclusion in the Work 137 | by You to the Licensor shall be under the terms and conditions of 138 | this License, without any additional terms or conditions. 139 | Notwithstanding the above, nothing herein shall supersede or modify 140 | the terms of any separate license agreement you may have executed 141 | with Licensor regarding such Contributions. 142 | 143 | 6. Trademarks. This License does not grant permission to use the trade 144 | names, trademarks, service marks, or product names of the Licensor, 145 | except as required for reasonable and customary use in describing the 146 | origin of the Work and reproducing the content of the NOTICE file. 147 | 148 | 7. Disclaimer of Warranty. Unless required by applicable law or 149 | agreed to in writing, Licensor provides the Work (and each 150 | Contributor provides its Contributions) on an "AS IS" BASIS, 151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 152 | implied, including, without limitation, any warranties or conditions 153 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 154 | PARTICULAR PURPOSE. You are solely responsible for determining the 155 | appropriateness of using or redistributing the Work and assume any 156 | risks associated with Your exercise of permissions under this License. 157 | 158 | 8. Limitation of Liability. In no event and under no legal theory, 159 | whether in tort (including negligence), contract, or otherwise, 160 | unless required by applicable law (such as deliberate and grossly 161 | negligent acts) or agreed to in writing, shall any Contributor be 162 | liable to You for damages, including any direct, indirect, special, 163 | incidental, or consequential damages of any character arising as a 164 | result of this License or out of the use or inability to use the 165 | Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all 167 | other commercial damages or losses), even if such Contributor 168 | has been advised of the possibility of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing 171 | the Work or Derivative Works thereof, You may choose to offer, 172 | and charge a fee for, acceptance of support, warranty, indemnity, 173 | or other liability obligations and/or rights consistent with this 174 | License. However, in accepting such obligations, You may act only 175 | on Your own behalf and on Your sole responsibility, not on behalf 176 | of any other Contributor, and only if You agree to indemnify, 177 | defend, and hold each Contributor harmless for any liability 178 | incurred by, or claims asserted against, such Contributor by reason 179 | of your accepting any such warranty or additional liability. 180 | 181 | END OF TERMS AND CONDITIONS 182 | 183 | APPENDIX: How to apply the Apache License to your work. 184 | 185 | To apply the Apache License to your work, attach the following 186 | boilerplate notice, with the fields enclosed by brackets "[]" 187 | replaced with your own identifying information. (Don't include 188 | the brackets!) The text should be enclosed in the appropriate 189 | comment syntax for the file format. We also recommend that a 190 | file or class name and description of purpose be included on the 191 | same "printed page" as the copyright notice for easier 192 | identification within third-party archives. 193 | 194 | Copyright [yyyy] [name of copyright owner] 195 | 196 | Licensed under the Apache License, Version 2.0 (the "License"); 197 | you may not use this file except in compliance with the License. 198 | You may obtain a copy of the License at 199 | 200 | http://www.apache.org/licenses/LICENSE-2.0 201 | 202 | Unless required by applicable law or agreed to in writing, software 203 | distributed under the License is distributed on an "AS IS" BASIS, 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | See the License for the specific language governing permissions and 206 | limitations under the License. 207 | Generated by CocoaPods - https://cocoapods.org 208 | -------------------------------------------------------------------------------- /Sources/Locker/Helpers/BiometryAvailabilityDeviceList.json: -------------------------------------------------------------------------------- 1 | { 2 | "face_id_devices": [ 3 | { 4 | "id": "iPhone10,3", 5 | "name": "iPhone X Global" 6 | }, 7 | { 8 | "id": "iPhone10,6", 9 | "name": "iPhone X GSM" 10 | }, 11 | { 12 | "id": "iPhone11,2", 13 | "name": "iPhone XS" 14 | }, 15 | { 16 | "id": "iPhone11,4", 17 | "name": "iPhone XS Max" 18 | }, 19 | { 20 | "id": "iPhone11,6", 21 | "name": "iPhone XS Max Global" 22 | }, 23 | { 24 | "id": "iPhone11,8", 25 | "name": "iPhone XR" 26 | }, 27 | { 28 | "id": "iPhone12,1", 29 | "name": "iPhone 11" 30 | }, 31 | { 32 | "id": "iPhone12,3", 33 | "name": "iPhone 11 Pro" 34 | }, 35 | { 36 | "id": "iPhone12,5", 37 | "name": "iPhone 11 Pro Max" 38 | }, 39 | { 40 | "id": "iPhone13,1", 41 | "name": "iPhone 12 Mini" 42 | }, 43 | { 44 | "id": "iPhone13,2", 45 | "name": "iPhone 12" 46 | }, 47 | { 48 | "id": "iPhone13,3", 49 | "name": "iPhone 12 Pro" 50 | }, 51 | { 52 | "id": "iPhone13,4", 53 | "name": "iPhone 12 Pro Max" 54 | }, 55 | { 56 | "id": "iPhone14,2", 57 | "name": "iPhone 13 Mini" 58 | }, 59 | { 60 | "id": "iPhone14,3", 61 | "name": "iPhone 13" 62 | }, 63 | { 64 | "id": "iPhone14,4", 65 | "name": "iPhone 13 Pro" 66 | }, 67 | { 68 | "id": "iPhone14,5", 69 | "name": "iPhone 13 Pro Max" 70 | }, 71 | { 72 | "id": "iPhone14,7", 73 | "name": "iPhone 14" 74 | }, 75 | { 76 | "id": "iPhone14,8", 77 | "name": "iPhone 14 Plus" 78 | }, 79 | { 80 | "id": "iPhone15,2", 81 | "name": "iPhone 14 Pro" 82 | }, 83 | { 84 | "id": "iPhone15,3", 85 | "name": "iPhone 14 Pro Max" 86 | }, 87 | { 88 | "id": "iPhone15,4", 89 | "name": "iPhone 15" 90 | }, 91 | { 92 | "id": "iPhone15,5", 93 | "name": "iPhone 15 Plus" 94 | }, 95 | { 96 | "id": "iPhone16,1", 97 | "name": "iPhone 15 Pro" 98 | }, 99 | { 100 | "id": "iPhone16,2", 101 | "name": "iPhone 15 Pro Max" 102 | }, 103 | { 104 | "id": "iPhone17,1", 105 | "name": "iPhone 16 Pro" 106 | }, 107 | { 108 | "id": "iPhone17,2", 109 | "name": "iPhone 16 Pro Max" 110 | }, 111 | { 112 | "id": "iPhone17,3", 113 | "name": "iPhone 16" 114 | }, 115 | { 116 | "id": "iPhone17,4", 117 | "name": "iPhone 16 Plus" 118 | }, 119 | { 120 | "id": "iPhone17,5", 121 | "name": "iPhone 16e" 122 | }, 123 | { 124 | "id": "iPhone18,4", 125 | "name": "iPhone Air" 126 | }, 127 | { 128 | "id": "iPhone18,3", 129 | "name": "iPhone 17" 130 | }, 131 | { 132 | "id": "iPhone18,1", 133 | "name": "iPhone 17 Pro" 134 | }, 135 | { 136 | "id": "iPhone18,2", 137 | "name": "iPhone 17 Pro Max" 138 | }, 139 | { 140 | "id": "iPad8,1", 141 | "name": "iPad Pro 11 inch 3rd Gen (WiFi)" 142 | }, 143 | { 144 | "id": "iPad8,2", 145 | "name": "iPad Pro 11 inch 3rd Gen (1TB, WiFi)" 146 | }, 147 | { 148 | "id": "iPad8,3", 149 | "name": "iPad Pro 11 inch 3rd Gen (WiFi+Cellular)" 150 | }, 151 | { 152 | "id": "iPad8,4", 153 | "name": "iPad Pro 11 inch 3rd Gen (1TB, WiFi+Cellular)" 154 | }, 155 | { 156 | "id": "iPad8,5", 157 | "name": "iPad Pro 12.9 inch 3rd Gen (WiFi)" 158 | }, 159 | { 160 | "id": "iPad8,6", 161 | "name": "iPad Pro 12.9 inch 3rd Gen (1TB, WiFi)" 162 | }, 163 | { 164 | "id": "iPad8,7", 165 | "name": "iPad Pro 12.9 inch 3rd Gen (WiFi+Cellular)" 166 | }, 167 | { 168 | "id": "iPad8,8", 169 | "name": "iPad Pro 12.9 inch 3rd Gen (1TB, WiFi+Cellular)" 170 | }, 171 | { 172 | "id": "iPad8,9", 173 | "name": "iPad Pro 11 inch 4th Gen (WiFi)" 174 | }, 175 | { 176 | "id": "iPad8,10", 177 | "name": "iPad Pro 11 inch 4th Gen (WiFi+Cellular)" 178 | }, 179 | { 180 | "id": "iPad8,11", 181 | "name": "iPad Pro 12.9 inch 4th Gen (WiFi)" 182 | }, 183 | { 184 | "id": "iPad8,12", 185 | "name": "iPad Pro 12.9 inch 4th Gen (WiFi+Cellular)" 186 | }, 187 | { 188 | "id": "iPad16,3", 189 | "name": "iPad Pro 11 inch 5th Gen" 190 | }, 191 | { 192 | "id": "iPad16,4", 193 | "name": "iPad Pro 11 inch 5th Gen" 194 | }, 195 | { 196 | "id": "iPad14,5", 197 | "name": "iPad Pro 12.9 inch 6th Gen" 198 | }, 199 | { 200 | "id": "iPad14,6", 201 | "name": "iPad Pro 12.9 inch 6th Gen" 202 | }, 203 | { 204 | "id": "iPad16,5", 205 | "name": "iPad Pro 12.9 inch 7th Gen" 206 | }, 207 | { 208 | "id": "iPad16,6", 209 | "name": "iPad Pro 12.9 inch 7th Gen" 210 | } 211 | ], 212 | 213 | "touch_id_devices": [ 214 | { 215 | "id": "iPhone6,1", 216 | "name": "iPhone 5S (GSM)" 217 | }, 218 | { 219 | "id": "iPhone6,2", 220 | "name": "iPhone 5S (Global)" 221 | }, 222 | { 223 | "id": "iPhone7,1", 224 | "name": "iPhone 6 Plus" 225 | }, 226 | { 227 | "id": "iPhone7,2", 228 | "name": "iPhone 6" 229 | }, 230 | { 231 | "id": "iPhone8,1", 232 | "name": "iPhone 6s" 233 | }, 234 | { 235 | "id": "iPhone8,2", 236 | "name": "iPhone 6s Plus" 237 | }, 238 | { 239 | "id": "iPhone8,4", 240 | "name": "iPhone SE (GSM)" 241 | }, 242 | { 243 | "id": "iPhone9,1", 244 | "name": "iPhone 7" 245 | }, 246 | { 247 | "id": "iPhone9,2", 248 | "name": "iPhone 7 Plus" 249 | }, 250 | { 251 | "id": "iPhone9,3", 252 | "name": "iPhone 7" 253 | }, 254 | { 255 | "id": "iPhone9,4", 256 | "name": "iPhone 7 Plus" 257 | }, 258 | { 259 | "id": "iPhone10,1", 260 | "name": "iPhone 8" 261 | }, 262 | { 263 | "id": "iPhone10,2", 264 | "name": "iPhone 8 Plus" 265 | }, 266 | { 267 | "id": "iPhone12,8", 268 | "name": "iPhone SE 2nd Gen" 269 | }, 270 | { 271 | "id": "iPad4,7", 272 | "name": "iPad mini 3 (WiFi)" 273 | }, 274 | { 275 | "id": "iPad4,8", 276 | "name": "iPad mini 3 (GSM+CDMA)" 277 | }, 278 | { 279 | "id": "iPad4,9", 280 | "name": "iPad Mini 3 (China)" 281 | }, 282 | { 283 | "id": "iPad5,1", 284 | "name": "iPad mini 4 (WiFi)" 285 | }, 286 | { 287 | "id": "iPad5,2", 288 | "name": "4th Gen iPad mini (WiFi+Cellular)" 289 | }, 290 | { 291 | "id": "iPad5,3", 292 | "name": "iPad Air 2 (WiFi)" 293 | }, 294 | { 295 | "id": "iPad5,4", 296 | "name": "iPad Air 2 (Cellular)" 297 | }, 298 | { 299 | "id": "iPad6,3", 300 | "name": "iPad Pro (9.7 inch, WiFi)" 301 | }, 302 | { 303 | "id": "iPad6,4", 304 | "name": "iPad Pro (9.7 inch, WiFi+LTE)" 305 | }, 306 | { 307 | "id": "iPad6,7", 308 | "name": "iPad Pro (12.9 inch, WiFi)" 309 | }, 310 | { 311 | "id": "iPad6,8", 312 | "name": "iPad Pro (12.9 inch, WiFi+LTE)" 313 | }, 314 | { 315 | "id": "iPad6,11", 316 | "name": "iPad (2017)" 317 | }, 318 | { 319 | "id": "iPad6,12", 320 | "name": "iPad (2017)" 321 | }, 322 | { 323 | "id": "iPad7,1", 324 | "name": "iPad Pro 2nd Gen (WiFi)" 325 | }, 326 | { 327 | "id": "iPad7,2", 328 | "name": "iPad Pro 2nd Gen (WiFi+Cellular)" 329 | }, 330 | { 331 | "id": "iPad7,3", 332 | "name": "iPad Pro 10.5-inch 2nd Gen" 333 | }, 334 | { 335 | "id": "iPad7,4", 336 | "name": "iPad Pro 10.5-inch 2nd Gen" 337 | }, 338 | { 339 | "id": "iPad7,5", 340 | "name": "iPad 6th Gen (WiFi)" 341 | }, 342 | { 343 | "id": "iPad7,6", 344 | "name": "iPad 6th Gen (WiFi+Cellular)" 345 | }, 346 | { 347 | "id": "iPad7,11", 348 | "name": "iPad 7th Gen 10.2-inch (WiFi)" 349 | }, 350 | { 351 | "id": "iPad7,12", 352 | "name": "iPad 7th Gen 10.2-inch (WiFi+Cellular)" 353 | }, 354 | { 355 | "id": "iPad11,1", 356 | "name": "iPad mini 5th Gen (WiFi)" 357 | }, 358 | { 359 | "id": "iPad11,2", 360 | "name": "iPad mini 5th Gen" 361 | }, 362 | { 363 | "id": "iPad11,3", 364 | "name": "iPad Air 3rd Gen (WiFi)" 365 | }, 366 | { 367 | "id": "iPad11,4", 368 | "name": "iPad Air 3rd Gen" 369 | }, 370 | { 371 | "id": "iPad11,6", 372 | "name": "iPad 8th Gen (WiFi)" 373 | }, 374 | { 375 | "id": "iPad11,7", 376 | "name": "iPad 8th Gen (WiFi+Cellular)" 377 | }, 378 | { 379 | "id": "iPad12,1", 380 | "name": "iPad 9th Gen (WiFi)" 381 | }, 382 | { 383 | "id": "iPad12,2", 384 | "name": "iPad 9th Gen (WiFi+Cellular)" 385 | }, 386 | { 387 | "id": "iPad13,18", 388 | "name": "iPad 10th Gen (WiFi)" 389 | }, 390 | { 391 | "id": "iPad13,19", 392 | "name": "iPad 10th Gen (WiFi+Cellular)" 393 | }, 394 | { 395 | "id": "iPad13,1", 396 | "name": "iPad Air 4th Gen (WiFi)" 397 | }, 398 | { 399 | "id": "iPad13,2", 400 | "name": "iPad Air 4th Gen (WiFi+Cellular)" 401 | }, 402 | { 403 | "id": "iPad13,16", 404 | "name": "iPad Air 5th Gen (WiFi)" 405 | }, 406 | { 407 | "id": "iPad13,17", 408 | "name": "iPad Air 5th Gen (WiFi+Cellular)" 409 | }, 410 | { 411 | "id": "iPad14,8", 412 | "name": "iPad Air 6th Gen (WiFi)" 413 | }, 414 | { 415 | "id": "iPad14,9", 416 | "name": "iPad Air 6th Gen (WiFi+Cellular)" 417 | }, 418 | { 419 | "id": "iPad14,10", 420 | "name": "iPad Air 7th Gen (WiFi)" 421 | }, 422 | { 423 | "id": "iPad14,11", 424 | "name": "iPad Air 7th Gen (WiFi+Cellular)" 425 | }, 426 | { 427 | "id": "iPad14,1", 428 | "name": "iPad mini 6th Gen (WiFi)" 429 | }, 430 | { 431 | "id": "iPad14,2", 432 | "name": "iPad mini 6th Gen (WiFi+Cellular)" 433 | } 434 | ] 435 | } 436 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-TouchID/Pods-TouchID-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Apache License 18 | Version 2.0, January 2004 19 | http://www.apache.org/licenses/ 20 | 21 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 22 | 23 | 1. Definitions. 24 | 25 | "License" shall mean the terms and conditions for use, reproduction, 26 | and distribution as defined by Sections 1 through 9 of this document. 27 | 28 | "Licensor" shall mean the copyright owner or entity authorized by 29 | the copyright owner that is granting the License. 30 | 31 | "Legal Entity" shall mean the union of the acting entity and all 32 | other entities that control, are controlled by, or are under common 33 | control with that entity. For the purposes of this definition, 34 | "control" means (i) the power, direct or indirect, to cause the 35 | direction or management of such entity, whether by contract or 36 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 37 | outstanding shares, or (iii) beneficial ownership of such entity. 38 | 39 | "You" (or "Your") shall mean an individual or Legal Entity 40 | exercising permissions granted by this License. 41 | 42 | "Source" form shall mean the preferred form for making modifications, 43 | including but not limited to software source code, documentation 44 | source, and configuration files. 45 | 46 | "Object" form shall mean any form resulting from mechanical 47 | transformation or translation of a Source form, including but 48 | not limited to compiled object code, generated documentation, 49 | and conversions to other media types. 50 | 51 | "Work" shall mean the work of authorship, whether in Source or 52 | Object form, made available under the License, as indicated by a 53 | copyright notice that is included in or attached to the work 54 | (an example is provided in the Appendix below). 55 | 56 | "Derivative Works" shall mean any work, whether in Source or Object 57 | form, that is based on (or derived from) the Work and for which the 58 | editorial revisions, annotations, elaborations, or other modifications 59 | represent, as a whole, an original work of authorship. For the purposes 60 | of this License, Derivative Works shall not include works that remain 61 | separable from, or merely link (or bind by name) to the interfaces of, 62 | the Work and Derivative Works thereof. 63 | 64 | "Contribution" shall mean any work of authorship, including 65 | the original version of the Work and any modifications or additions 66 | to that Work or Derivative Works thereof, that is intentionally 67 | submitted to Licensor for inclusion in the Work by the copyright owner 68 | or by an individual or Legal Entity authorized to submit on behalf of 69 | the copyright owner. For the purposes of this definition, "submitted" 70 | means any form of electronic, verbal, or written communication sent 71 | to the Licensor or its representatives, including but not limited to 72 | communication on electronic mailing lists, source code control systems, 73 | and issue tracking systems that are managed by, or on behalf of, the 74 | Licensor for the purpose of discussing and improving the Work, but 75 | excluding communication that is conspicuously marked or otherwise 76 | designated in writing by the copyright owner as "Not a Contribution." 77 | 78 | "Contributor" shall mean Licensor and any individual or Legal Entity 79 | on behalf of whom a Contribution has been received by Licensor and 80 | subsequently incorporated within the Work. 81 | 82 | 2. Grant of Copyright License. Subject to the terms and conditions of 83 | this License, each Contributor hereby grants to You a perpetual, 84 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 85 | copyright license to reproduce, prepare Derivative Works of, 86 | publicly display, publicly perform, sublicense, and distribute the 87 | Work and such Derivative Works in Source or Object form. 88 | 89 | 3. Grant of Patent License. Subject to the terms and conditions of 90 | this License, each Contributor hereby grants to You a perpetual, 91 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 92 | (except as stated in this section) patent license to make, have made, 93 | use, offer to sell, sell, import, and otherwise transfer the Work, 94 | where such license applies only to those patent claims licensable 95 | by such Contributor that are necessarily infringed by their 96 | Contribution(s) alone or by combination of their Contribution(s) 97 | with the Work to which such Contribution(s) was submitted. If You 98 | institute patent litigation against any entity (including a 99 | cross-claim or counterclaim in a lawsuit) alleging that the Work 100 | or a Contribution incorporated within the Work constitutes direct 101 | or contributory patent infringement, then any patent licenses 102 | granted to You under this License for that Work shall terminate 103 | as of the date such litigation is filed. 104 | 105 | 4. Redistribution. You may reproduce and distribute copies of the 106 | Work or Derivative Works thereof in any medium, with or without 107 | modifications, and in Source or Object form, provided that You 108 | meet the following conditions: 109 | 110 | (a) You must give any other recipients of the Work or 111 | Derivative Works a copy of this License; and 112 | 113 | (b) You must cause any modified files to carry prominent notices 114 | stating that You changed the files; and 115 | 116 | (c) You must retain, in the Source form of any Derivative Works 117 | that You distribute, all copyright, patent, trademark, and 118 | attribution notices from the Source form of the Work, 119 | excluding those notices that do not pertain to any part of 120 | the Derivative Works; and 121 | 122 | (d) If the Work includes a "NOTICE" text file as part of its 123 | distribution, then any Derivative Works that You distribute must 124 | include a readable copy of the attribution notices contained 125 | within such NOTICE file, excluding those notices that do not 126 | pertain to any part of the Derivative Works, in at least one 127 | of the following places: within a NOTICE text file distributed 128 | as part of the Derivative Works; within the Source form or 129 | documentation, if provided along with the Derivative Works; or, 130 | within a display generated by the Derivative Works, if and 131 | wherever such third-party notices normally appear. The contents 132 | of the NOTICE file are for informational purposes only and 133 | do not modify the License. You may add Your own attribution 134 | notices within Derivative Works that You distribute, alongside 135 | or as an addendum to the NOTICE text from the Work, provided 136 | that such additional attribution notices cannot be construed 137 | as modifying the License. 138 | 139 | You may add Your own copyright statement to Your modifications and 140 | may provide additional or different license terms and conditions 141 | for use, reproduction, or distribution of Your modifications, or 142 | for any such Derivative Works as a whole, provided Your use, 143 | reproduction, and distribution of the Work otherwise complies with 144 | the conditions stated in this License. 145 | 146 | 5. Submission of Contributions. Unless You explicitly state otherwise, 147 | any Contribution intentionally submitted for inclusion in the Work 148 | by You to the Licensor shall be under the terms and conditions of 149 | this License, without any additional terms or conditions. 150 | Notwithstanding the above, nothing herein shall supersede or modify 151 | the terms of any separate license agreement you may have executed 152 | with Licensor regarding such Contributions. 153 | 154 | 6. Trademarks. This License does not grant permission to use the trade 155 | names, trademarks, service marks, or product names of the Licensor, 156 | except as required for reasonable and customary use in describing the 157 | origin of the Work and reproducing the content of the NOTICE file. 158 | 159 | 7. Disclaimer of Warranty. Unless required by applicable law or 160 | agreed to in writing, Licensor provides the Work (and each 161 | Contributor provides its Contributions) on an "AS IS" BASIS, 162 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 163 | implied, including, without limitation, any warranties or conditions 164 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 165 | PARTICULAR PURPOSE. You are solely responsible for determining the 166 | appropriateness of using or redistributing the Work and assume any 167 | risks associated with Your exercise of permissions under this License. 168 | 169 | 8. Limitation of Liability. In no event and under no legal theory, 170 | whether in tort (including negligence), contract, or otherwise, 171 | unless required by applicable law (such as deliberate and grossly 172 | negligent acts) or agreed to in writing, shall any Contributor be 173 | liable to You for damages, including any direct, indirect, special, 174 | incidental, or consequential damages of any character arising as a 175 | result of this License or out of the use or inability to use the 176 | Work (including but not limited to damages for loss of goodwill, 177 | work stoppage, computer failure or malfunction, or any and all 178 | other commercial damages or losses), even if such Contributor 179 | has been advised of the possibility of such damages. 180 | 181 | 9. Accepting Warranty or Additional Liability. While redistributing 182 | the Work or Derivative Works thereof, You may choose to offer, 183 | and charge a fee for, acceptance of support, warranty, indemnity, 184 | or other liability obligations and/or rights consistent with this 185 | License. However, in accepting such obligations, You may act only 186 | on Your own behalf and on Your sole responsibility, not on behalf 187 | of any other Contributor, and only if You agree to indemnify, 188 | defend, and hold each Contributor harmless for any liability 189 | incurred by, or claims asserted against, such Contributor by reason 190 | of your accepting any such warranty or additional liability. 191 | 192 | END OF TERMS AND CONDITIONS 193 | 194 | APPENDIX: How to apply the Apache License to your work. 195 | 196 | To apply the Apache License to your work, attach the following 197 | boilerplate notice, with the fields enclosed by brackets "[]" 198 | replaced with your own identifying information. (Don't include 199 | the brackets!) The text should be enclosed in the appropriate 200 | comment syntax for the file format. We also recommend that a 201 | file or class name and description of purpose be included on the 202 | same "printed page" as the copyright notice for easier 203 | identification within third-party archives. 204 | 205 | Copyright [yyyy] [name of copyright owner] 206 | 207 | Licensed under the Apache License, Version 2.0 (the "License"); 208 | you may not use this file except in compliance with the License. 209 | You may obtain a copy of the License at 210 | 211 | http://www.apache.org/licenses/LICENSE-2.0 212 | 213 | Unless required by applicable law or agreed to in writing, software 214 | distributed under the License is distributed on an "AS IS" BASIS, 215 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 216 | See the License for the specific language governing permissions and 217 | limitations under the License. 218 | License 219 | MIT 220 | Title 221 | Locker 222 | Type 223 | PSGroupSpecifier 224 | 225 | 226 | FooterText 227 | Generated by CocoaPods - https://cocoapods.org 228 | Title 229 | 230 | Type 231 | PSGroupSpecifier 232 | 233 | 234 | StringsTable 235 | Acknowledgements 236 | Title 237 | Acknowledgements 238 | 239 | 240 | -------------------------------------------------------------------------------- /Sources/Locker/Core/Locker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Locker.swift 3 | // Locker 4 | // 5 | // Created by Zvonimir Medak on 19.10.2021.. 6 | // Copyright © 2021 Infinum. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | @objcMembers 13 | public class Locker: NSObject { 14 | 15 | // MARK: - Public properties 16 | 17 | /** 18 | User defaults used for storing shouldUseAuthenticationWithBiometrics, askToUseAuthenticationWithBiometrics and shouldAddPasscodeToKeychainOnNextLogin values 19 | 20 | Should be set once before using any other Locker methods. 21 | If not set, standard user defaults will be used. 22 | */ 23 | public static var userDefaults: UserDefaults? { 24 | get { 25 | return currentUserDefaults == nil ? UserDefaults.standard : currentUserDefaults 26 | } 27 | set { 28 | currentUserDefaults = newValue ?? .standard 29 | } 30 | } 31 | 32 | /** 33 | Boolean value that indicates if biometric settings have changed 34 | */ 35 | public static var biometricsSettingsDidChange: Bool { 36 | LockerHelpers.biometricsSettingsChanged 37 | } 38 | 39 | /** 40 | Boolean value that indicates if Locker is running from the simulator 41 | 42 | As Simulator does not support Keychain storage, Locker run from the simulator 43 | will use UserDefaults storage instead. 44 | */ 45 | public static var isRunningFromTheSimulator: Bool { 46 | #if targetEnvironment(simulator) 47 | return true 48 | #else 49 | return false 50 | #endif 51 | } 52 | 53 | /** 54 | The biometrics type that the device supports (None, TouchID, FaceID). 55 | */ 56 | public static var supportedBiometricsAuthentication: BiometricsType { 57 | LockerHelpers.supportedBiometricAuthentication 58 | } 59 | 60 | /** 61 | The biometrics type that the device supports which is enabled and configured in the device settings. 62 | */ 63 | public static var configuredBiometricsAuthentication: BiometricsType { 64 | LockerHelpers.configuredBiometricsAuthentication 65 | } 66 | 67 | /** 68 | Boolean value that indicates if Locker should sync its local JSON device list with the API 69 | 70 | If the sync is enabled, Locker will check if the device is already contained in the list. If the 71 | device is not found in the local list, Locker will updated the local JSON device list. 72 | 73 | If you're using a simulator Locker will not sync the list. 74 | */ 75 | public static var enableDeviceListSync: Bool = false { 76 | didSet { 77 | guard enableDeviceListSync else { return } 78 | LockerHelpers.fetchNewDeviceList() 79 | } 80 | } 81 | 82 | // MARK: - Private properties 83 | 84 | private static var currentUserDefaults: UserDefaults? 85 | 86 | // MARK: - Handle secrets (store, delete, fetch) 87 | 88 | /** 89 | Used for storing value to Keychain with unique identifier. 90 | 91 | If Locker is run on the Simulator, the secret will not be stored securely in the keychain. 92 | Instead, the UserDefaults storage will be used. 93 | 94 | - Parameters: 95 | - secret: value to store to Keychain 96 | - uniqueIdentifier: unique key used for storing secret 97 | - completed: completion block returning an error if something went wrong 98 | */ 99 | public static func setSecret( 100 | _ secret: String, 101 | for uniqueIdentifier: String, 102 | completed: ((LockerError?) -> Void)? = nil 103 | ) { 104 | #if targetEnvironment(simulator) 105 | Locker.userDefaults?.set(secret, forKey: uniqueIdentifier) 106 | #else 107 | setSecretForDevice(secret, for: uniqueIdentifier, completion: { error in 108 | DispatchQueue.main.async { 109 | completed?(error) 110 | } 111 | }) 112 | #endif 113 | } 114 | 115 | /** 116 | Used for retrieving secret from Keychain with unique identifier. 117 | If operation is successfull, secret is returned. Otherwise, failure status is returned. 118 | 119 | - Parameters: 120 | - uniqueIdentifier: unique key used for fetching secret 121 | - operationPrompt: message showed to the user on TouchID dialog 122 | - success: completion block returning secret 123 | - failure: failure block returning failure status 124 | */ 125 | @objc(retreiveCurrentSecretForUniqueIdentifier:operationPrompt:success:failure:) 126 | public static func retrieveCurrentSecret( 127 | for uniqueIdentifier: String, 128 | operationPrompt: String, 129 | success: ((String) -> Void)?, 130 | failure: ((OSStatus) -> Void)? 131 | ) { 132 | 133 | #if targetEnvironment(simulator) 134 | let simulatorSecret = Locker.userDefaults?.string(forKey: uniqueIdentifier) 135 | guard let simulatorSecret = simulatorSecret else { 136 | DispatchQueue.main.async { 137 | failure?(errSecItemNotFound) 138 | } 139 | return 140 | } 141 | DispatchQueue.main.async { 142 | success?(simulatorSecret) 143 | } 144 | #else 145 | let query: [CFString: Any] = [ 146 | kSecClass: kSecClassGenericPassword, 147 | kSecAttrService: LockerHelpers.keyKeychainServiceName, 148 | kSecAttrAccount: LockerHelpers.keyKeychainAccountNameForUniqueIdentifier(uniqueIdentifier), 149 | kSecMatchLimit: kSecMatchLimitOne, 150 | kSecReturnData: true, 151 | kSecUseOperationPrompt: operationPrompt 152 | ] 153 | 154 | DispatchQueue.global(qos: .default).async { 155 | var dataTypeRef: CFTypeRef? 156 | 157 | let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) 158 | if status == errSecSuccess { 159 | guard let resultData = dataTypeRef as? Data, 160 | let result = String(data: resultData, encoding: .utf8) else { 161 | DispatchQueue.main.async { 162 | failure?(errSecItemNotFound) 163 | } 164 | return 165 | } 166 | 167 | DispatchQueue.main.async { 168 | success?(result) 169 | } 170 | } else { 171 | DispatchQueue.main.async { 172 | failure?(status) 173 | } 174 | } 175 | } 176 | #endif 177 | } 178 | 179 | /** 180 | Used for deleting secret from Keychain with unique identifier. 181 | 182 | - Parameter uniqueIdentifier: unique key used for deleting secret 183 | */ 184 | @objc(deleteSecretForUniqueIdentifier:) 185 | public static func deleteSecret(for uniqueIdentifier: String) { 186 | 187 | #if targetEnvironment(simulator) 188 | Locker.userDefaults?.removeObject(forKey: uniqueIdentifier) 189 | #else 190 | let query: [CFString: Any] = [ 191 | kSecClass: kSecClassGenericPassword, 192 | kSecAttrService: LockerHelpers.keyKeychainServiceName, 193 | kSecAttrAccount: LockerHelpers.keyKeychainAccountNameForUniqueIdentifier(uniqueIdentifier) 194 | ] 195 | 196 | DispatchQueue.global(qos: .default).async { 197 | SecItemDelete(query as CFDictionary) 198 | } 199 | #endif 200 | } 201 | } 202 | 203 | // MARK: - Additional helpers 204 | 205 | public extension Locker { 206 | 207 | /** 208 | Used for setting a custom keychain service key. 209 | 210 | If this is not set, Locker will use a combination of the bundle identifier and a string constant to set the keychain service. 211 | 212 | - Parameter service: the custom keychain service key you want to use to interact with the Keychain 213 | */ 214 | @objc(setKeychainService:) 215 | static func setKeychainService(_ service: String) { 216 | Locker.userDefaults?.set(service, forKey: LockerHelpers.keyCustomKeychainService) 217 | } 218 | 219 | /** 220 | Used for fetching whether user enabled authentication with biometrics. 221 | 222 | - Parameter uniqueIdentifier: used for fetching shouldUseAuthenticationWithBiometrics value 223 | 224 | - Returns: used to determine whether user enabled authentication with biometrics 225 | */ 226 | @objc(shouldUseAuthenticationWithBiometricsForUniqueIdentifier:) 227 | static func shouldUseAuthenticationWithBiometrics(for uniqueIdentifier: String) -> Bool { 228 | return Locker.userDefaults?.bool( 229 | forKey: LockerHelpers.keyBiometricsIDActivatedForUniqueIdentifier(uniqueIdentifier) 230 | ) ?? false 231 | } 232 | 233 | /** 234 | Used for saving whether user enabled authentication with biometrics. 235 | 236 | - Parameters: 237 | - shouldUse: used to determine whether user enabled authentication with biometrics 238 | - uniqueIdentifier: used for saving shouldUseAuthenticationWithBiometrics value 239 | */ 240 | @objc(setShouldUseAuthenticationWithBiometrics:forUniqueIdentifier:) 241 | static func setShouldUseAuthenticationWithBiometrics(_ shouldUse: Bool, for uniqueIdentifier: String) { 242 | if !shouldUse && Locker.shouldAddSecretToKeychainOnNextLogin(for: uniqueIdentifier) { 243 | Locker.setShouldAddSecretToKeychainOnNextLogin(false, for: uniqueIdentifier) 244 | } 245 | Locker.userDefaults?.set( 246 | shouldUse, 247 | forKey: LockerHelpers.keyBiometricsIDActivatedForUniqueIdentifier(uniqueIdentifier) 248 | ) 249 | } 250 | 251 | /** 252 | Used for fetching whether user was asked to use authentication with biometrics. 253 | 254 | - Parameter uniqueIdentifier: used for fetching askToUseAuthenticationWithBiometrics value 255 | 256 | - Returns: used to determine whether user was asked to use authentication with biometrics 257 | */ 258 | @objc(didAskToUseAuthenticationWithBiometricsForUniqueIdentifier:) 259 | static func didAskToUseAuthenticationWithBiometrics(for uniqueIdentifier: String) -> Bool { 260 | Locker.userDefaults?.bool( 261 | forKey: LockerHelpers.keyDidAskToUseBiometricsIDForUniqueIdentifier(uniqueIdentifier) 262 | ) ?? false 263 | } 264 | 265 | /** 266 | Used for saving whether user was asked to use authentication with 267 | - Parameters: 268 | - useAuthenticationBiometrics: used to determine whether user was asked to use authentication with biometrics 269 | - uniqueIdentifier: used for saving askToUseAuthenticationWithBiometrics value 270 | */ 271 | @objc(setDidAskToUseAuthenticationWithBiometrics:forUniqueIdentifier:) 272 | static func setDidAskToUseAuthenticationWithBiometrics( 273 | _ useAuthenticationBiometrics: Bool, 274 | for uniqueIdentifier: String 275 | ) { 276 | Locker.userDefaults?.set( 277 | useAuthenticationBiometrics, 278 | forKey: LockerHelpers.keyDidAskToUseBiometricsIDForUniqueIdentifier(uniqueIdentifier) 279 | ) 280 | } 281 | 282 | /** 283 | Used for fetching whether secret should be stored to Keychain on next login. 284 | 285 | - Parameter uniqueIdentifier: used for fetching shouldAddSecretToKeychainOnNextLogin value 286 | 287 | - Returns: used to determine whether secret should be stored to Keychain on next login 288 | */ 289 | @objc(shouldAddSecretToKeychainOnNextLoginForUniqueIdentifier:) 290 | static func shouldAddSecretToKeychainOnNextLogin(for uniqueIdentifier: String) -> Bool { 291 | Locker.userDefaults?.bool( 292 | forKey: LockerHelpers.keyShouldAddSecretToKeychainOnNextLoginForUniqueIdentifier(uniqueIdentifier) 293 | ) ?? false 294 | } 295 | 296 | /** 297 | Used for saving whether secret should be stored to Keychain on next login. 298 | 299 | - Parameters: 300 | - shouldAdd: used to determine whether secret should be stored to Keychain on next login 301 | - uniqueIdentifier: used for saving shouldAddSecretToKeychainOnNextLogin value 302 | */ 303 | @objc(setShouldAddSecretToKeychainOnNextLogin:forUniqueIdentifier:) 304 | static func setShouldAddSecretToKeychainOnNextLogin(_ shouldAdd: Bool, for uniqueIdentifier: String) { 305 | Locker.userDefaults?.set( 306 | shouldAdd, 307 | forKey: LockerHelpers.keyShouldAddSecretToKeychainOnNextLoginForUniqueIdentifier(uniqueIdentifier) 308 | ) 309 | } 310 | } 311 | 312 | // MARK: - Data reset 313 | 314 | public extension Locker { 315 | 316 | /** 317 | Used for deleting all stored data for unique identifier. 318 | 319 | - Parameter uniqueIdentifier: unique key used for deleting all stored data 320 | */ 321 | @objc(resetForUniqueIdentifier:) 322 | static func reset(for uniqueIdentifier: String) { 323 | Locker.userDefaults?.removeObject( 324 | forKey: LockerHelpers.keyDidAskToUseBiometricsIDForUniqueIdentifier(uniqueIdentifier) 325 | ) 326 | Locker.userDefaults?.removeObject( 327 | forKey: LockerHelpers.keyShouldAddSecretToKeychainOnNextLoginForUniqueIdentifier(uniqueIdentifier) 328 | ) 329 | Locker.userDefaults?.removeObject( 330 | forKey: LockerHelpers.keyBiometricsIDActivatedForUniqueIdentifier(uniqueIdentifier) 331 | ) 332 | Locker.deleteSecret(for: uniqueIdentifier) 333 | } 334 | } 335 | 336 | // MARK: - Internal extension 337 | 338 | extension Locker { 339 | static func setSecretForDevice( 340 | _ secret: String, 341 | for uniqueIdentifier: String, 342 | completion: ((LockerError?) -> Void)? = nil 343 | ) { 344 | let query: [CFString: Any] = [ 345 | kSecClass: kSecClassGenericPassword, 346 | kSecAttrService: LockerHelpers.keyKeychainServiceName, 347 | kSecAttrAccount: LockerHelpers.keyKeychainAccountNameForUniqueIdentifier(uniqueIdentifier) 348 | ] 349 | 350 | DispatchQueue.global(qos: .default).async { 351 | // First delete the previous item if it exists 352 | SecItemDelete(query as CFDictionary) 353 | 354 | // Then store it 355 | let errorRef: UnsafeMutablePointer?>? = nil 356 | var flags: SecAccessControlCreateFlags 357 | if #available(iOS 11.3, *) { 358 | flags = .biometryCurrentSet 359 | } else { 360 | flags = .touchIDCurrentSet 361 | } 362 | let sac = SecAccessControlCreateWithFlags( 363 | kCFAllocatorDefault, 364 | kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, 365 | flags, 366 | errorRef 367 | ) 368 | 369 | guard let sacObject = sac, errorRef == nil, let secretData = secret.data(using: .utf8) else { 370 | if errorRef != nil { 371 | DispatchQueue.main.async { 372 | completion?(.accessControl) 373 | } 374 | } else { 375 | DispatchQueue.main.async { 376 | completion?(.invalidData) 377 | } 378 | } 379 | return 380 | } 381 | addSecItem(for: uniqueIdentifier, secretData, sacObject: sacObject, completion: completion) 382 | } 383 | } 384 | 385 | private static func addSecItem( 386 | for uniqueIdentifier: String, 387 | _ secretData: Data, sacObject: SecAccessControl, 388 | completion: ((LockerError?) -> Void)? = nil 389 | ) { 390 | let attributes: [CFString: Any] = [ 391 | kSecClass: kSecClassGenericPassword, 392 | kSecAttrService: LockerHelpers.keyKeychainServiceName, 393 | kSecAttrAccount: LockerHelpers.keyKeychainAccountNameForUniqueIdentifier(uniqueIdentifier), 394 | kSecValueData: secretData, 395 | kSecUseAuthenticationUI: false, 396 | kSecAttrAccessControl: sacObject 397 | ] 398 | 399 | DispatchQueue.global(qos: .default).async { 400 | SecItemAdd(attributes as CFDictionary, nil) 401 | 402 | // Store current LA policy domain state 403 | LockerHelpers.storeCurrentLAPolicyDomainState() 404 | DispatchQueue.main.async { 405 | completion?(nil) 406 | } 407 | } 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /Locker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3755F109272072F800D62955 /* BundleHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3755F108272072F800D62955 /* BundleHelpers.swift */; }; 11 | 375E9682271EE79600896845 /* LockerHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375E9681271EE79600896845 /* LockerHelpers.swift */; }; 12 | 375E9684271EE86200896845 /* BiometricsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375E9683271EE86200896845 /* BiometricsType.swift */; }; 13 | 375E9686271EF02300896845 /* Locker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375E9685271EF02300896845 /* Locker.swift */; }; 14 | 376B905127214C270021B54F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376B905027214C270021B54F /* Constants.swift */; }; 15 | 376B905327214C370021B54F /* DeviceResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376B905227214C370021B54F /* DeviceResponse.swift */; }; 16 | 377D681627202257002C31FE /* LockerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D681527202257002C31FE /* LockerError.swift */; }; 17 | 377D68212720395D002C31FE /* DeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D681F2720395D002C31FE /* DeviceManager.swift */; }; 18 | 37D08735273D20E10025F45F /* Locker+ObjectiveC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D08734273D20E10025F45F /* Locker+ObjectiveC.swift */; }; 19 | DF8FB52F1E8DAFFF00EC2740 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF8FB52E1E8DAFFF00EC2740 /* UIKit.framework */; }; 20 | DF8FB5311E8DB00600EC2740 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF8FB5301E8DB00600EC2740 /* LocalAuthentication.framework */; }; 21 | DF8FB5351E8DB03600EC2740 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF8FB5341E8DB03600EC2740 /* Security.framework */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | DF8FB4FE1E8D119C00EC2740 /* CopyFiles */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 2147483647; 28 | dstPath = "include/$(PRODUCT_NAME)"; 29 | dstSubfolderSpec = 16; 30 | files = ( 31 | ); 32 | runOnlyForDeploymentPostprocessing = 0; 33 | }; 34 | /* End PBXCopyFilesBuildPhase section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 3755F108272072F800D62955 /* BundleHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleHelpers.swift; sourceTree = ""; }; 38 | 375E9681271EE79600896845 /* LockerHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockerHelpers.swift; sourceTree = ""; }; 39 | 375E9683271EE86200896845 /* BiometricsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricsType.swift; sourceTree = ""; }; 40 | 375E9685271EF02300896845 /* Locker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locker.swift; sourceTree = ""; }; 41 | 376B905027214C270021B54F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 42 | 376B905227214C370021B54F /* DeviceResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceResponse.swift; sourceTree = ""; }; 43 | 377D681527202257002C31FE /* LockerError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockerError.swift; sourceTree = ""; }; 44 | 377D681F2720395D002C31FE /* DeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceManager.swift; sourceTree = ""; }; 45 | 377D682627203AD8002C31FE /* BiometryAvailabilityDeviceList.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = BiometryAvailabilityDeviceList.json; sourceTree = ""; }; 46 | 37D08734273D20E10025F45F /* Locker+ObjectiveC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Locker+ObjectiveC.swift"; sourceTree = ""; }; 47 | DF8FB5001E8D119C00EC2740 /* libLocker.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLocker.a; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | DF8FB52E1E8DAFFF00EC2740 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 49 | DF8FB5301E8DB00600EC2740 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; 50 | DF8FB5321E8DB00E00EC2740 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 51 | DF8FB5341E8DB03600EC2740 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | DF8FB4FD1E8D119C00EC2740 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | DF8FB5351E8DB03600EC2740 /* Security.framework in Frameworks */, 60 | DF8FB5311E8DB00600EC2740 /* LocalAuthentication.framework in Frameworks */, 61 | DF8FB52F1E8DAFFF00EC2740 /* UIKit.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 06FB9482236B264700988BA1 /* Sources */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 06FB9484236B264700988BA1 /* Locker */, 72 | ); 73 | path = Sources; 74 | sourceTree = ""; 75 | }; 76 | 06FB9484236B264700988BA1 /* Locker */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 376B905627218E480021B54F /* Core */, 80 | 06FB9487236B264700988BA1 /* Helpers */, 81 | 3755F103272071F100D62955 /* Models */, 82 | ); 83 | path = Locker; 84 | sourceTree = ""; 85 | }; 86 | 06FB9487236B264700988BA1 /* Helpers */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 377D682627203AD8002C31FE /* BiometryAvailabilityDeviceList.json */, 90 | 377D681F2720395D002C31FE /* DeviceManager.swift */, 91 | 375E9681271EE79600896845 /* LockerHelpers.swift */, 92 | 3755F108272072F800D62955 /* BundleHelpers.swift */, 93 | ); 94 | path = Helpers; 95 | sourceTree = ""; 96 | }; 97 | 3755F103272071F100D62955 /* Models */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 376B905027214C270021B54F /* Constants.swift */, 101 | 376B905227214C370021B54F /* DeviceResponse.swift */, 102 | ); 103 | path = Models; 104 | sourceTree = ""; 105 | }; 106 | 376B905627218E480021B54F /* Core */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 375E9685271EF02300896845 /* Locker.swift */, 110 | 37D08734273D20E10025F45F /* Locker+ObjectiveC.swift */, 111 | 375E9683271EE86200896845 /* BiometricsType.swift */, 112 | 377D681527202257002C31FE /* LockerError.swift */, 113 | ); 114 | path = Core; 115 | sourceTree = ""; 116 | }; 117 | DF8FB4F71E8D119C00EC2740 = { 118 | isa = PBXGroup; 119 | children = ( 120 | 06FB9482236B264700988BA1 /* Sources */, 121 | DF8FB5011E8D119C00EC2740 /* Products */, 122 | DF8FB52D1E8DAFFF00EC2740 /* Frameworks */, 123 | ); 124 | sourceTree = ""; 125 | }; 126 | DF8FB5011E8D119C00EC2740 /* Products */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | DF8FB5001E8D119C00EC2740 /* libLocker.a */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | DF8FB52D1E8DAFFF00EC2740 /* Frameworks */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | DF8FB5341E8DB03600EC2740 /* Security.framework */, 138 | DF8FB5321E8DB00E00EC2740 /* SystemConfiguration.framework */, 139 | DF8FB5301E8DB00600EC2740 /* LocalAuthentication.framework */, 140 | DF8FB52E1E8DAFFF00EC2740 /* UIKit.framework */, 141 | ); 142 | name = Frameworks; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | DF8FB4FF1E8D119C00EC2740 /* Locker */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = DF8FB5091E8D119C00EC2740 /* Build configuration list for PBXNativeTarget "Locker" */; 151 | buildPhases = ( 152 | 377D68142720168C002C31FE /* SwiftLint */, 153 | DF8FB4FC1E8D119C00EC2740 /* Sources */, 154 | DF8FB4FD1E8D119C00EC2740 /* Frameworks */, 155 | DF8FB4FE1E8D119C00EC2740 /* CopyFiles */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = Locker; 162 | productName = TouchIDManager; 163 | productReference = DF8FB5001E8D119C00EC2740 /* libLocker.a */; 164 | productType = "com.apple.product-type.library.static"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | DF8FB4F81E8D119C00EC2740 /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastUpgradeCheck = 1030; 173 | ORGANIZATIONNAME = Infinum; 174 | TargetAttributes = { 175 | DF8FB4FF1E8D119C00EC2740 = { 176 | CreatedOnToolsVersion = 8.1; 177 | DevelopmentTeam = 3GN7X2W9W5; 178 | LastSwiftMigration = 1300; 179 | ProvisioningStyle = Automatic; 180 | }; 181 | }; 182 | }; 183 | buildConfigurationList = DF8FB4FB1E8D119C00EC2740 /* Build configuration list for PBXProject "Locker" */; 184 | compatibilityVersion = "Xcode 3.2"; 185 | developmentRegion = English; 186 | hasScannedForEncodings = 0; 187 | knownRegions = ( 188 | English, 189 | en, 190 | ); 191 | mainGroup = DF8FB4F71E8D119C00EC2740; 192 | productRefGroup = DF8FB5011E8D119C00EC2740 /* Products */; 193 | projectDirPath = ""; 194 | projectRoot = ""; 195 | targets = ( 196 | DF8FB4FF1E8D119C00EC2740 /* Locker */, 197 | ); 198 | }; 199 | /* End PBXProject section */ 200 | 201 | /* Begin PBXShellScriptBuildPhase section */ 202 | 377D68142720168C002C31FE /* SwiftLint */ = { 203 | isa = PBXShellScriptBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | ); 207 | inputFileListPaths = ( 208 | ); 209 | inputPaths = ( 210 | ); 211 | name = SwiftLint; 212 | outputFileListPaths = ( 213 | ); 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 219 | }; 220 | /* End PBXShellScriptBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | DF8FB4FC1E8D119C00EC2740 /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 377D68212720395D002C31FE /* DeviceManager.swift in Sources */, 228 | 375E9682271EE79600896845 /* LockerHelpers.swift in Sources */, 229 | 3755F109272072F800D62955 /* BundleHelpers.swift in Sources */, 230 | 375E9684271EE86200896845 /* BiometricsType.swift in Sources */, 231 | 377D681627202257002C31FE /* LockerError.swift in Sources */, 232 | 37D08735273D20E10025F45F /* Locker+ObjectiveC.swift in Sources */, 233 | 375E9686271EF02300896845 /* Locker.swift in Sources */, 234 | 376B905327214C370021B54F /* DeviceResponse.swift in Sources */, 235 | 376B905127214C270021B54F /* Constants.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXSourcesBuildPhase section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | DF8FB5071E8D119C00EC2740 /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_NONNULL = YES; 247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 248 | CLANG_CXX_LIBRARY = "libc++"; 249 | CLANG_ENABLE_MODULES = YES; 250 | CLANG_ENABLE_OBJC_ARC = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = dwarf; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | ENABLE_TESTABILITY = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu99; 278 | GCC_DYNAMIC_NO_PIC = NO; 279 | GCC_NO_COMMON_BLOCKS = YES; 280 | GCC_OPTIMIZATION_LEVEL = 0; 281 | GCC_PREPROCESSOR_DEFINITIONS = ( 282 | "DEBUG=1", 283 | "$(inherited)", 284 | ); 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 287 | GCC_WARN_UNDECLARED_SELECTOR = YES; 288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 289 | GCC_WARN_UNUSED_FUNCTION = YES; 290 | GCC_WARN_UNUSED_VARIABLE = YES; 291 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 292 | MTL_ENABLE_DEBUG_INFO = YES; 293 | ONLY_ACTIVE_ARCH = YES; 294 | SDKROOT = iphoneos; 295 | }; 296 | name = Debug; 297 | }; 298 | DF8FB5081E8D119C00EC2740 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | ALWAYS_SEARCH_USER_PATHS = NO; 302 | CLANG_ANALYZER_NONNULL = YES; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 304 | CLANG_CXX_LIBRARY = "libc++"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_COMMA = YES; 310 | CLANG_WARN_CONSTANT_CONVERSION = YES; 311 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 314 | CLANG_WARN_EMPTY_BODY = YES; 315 | CLANG_WARN_ENUM_CONVERSION = YES; 316 | CLANG_WARN_INFINITE_RECURSION = YES; 317 | CLANG_WARN_INT_CONVERSION = YES; 318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 323 | CLANG_WARN_STRICT_PROTOTYPES = YES; 324 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 331 | ENABLE_NS_ASSERTIONS = NO; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu99; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 342 | MTL_ENABLE_DEBUG_INFO = NO; 343 | SDKROOT = iphoneos; 344 | VALIDATE_PRODUCT = YES; 345 | }; 346 | name = Release; 347 | }; 348 | DF8FB50A1E8D119C00EC2740 /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | CLANG_ENABLE_MODULES = YES; 352 | DEVELOPMENT_TEAM = 3GN7X2W9W5; 353 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 354 | OTHER_LDFLAGS = "-ObjC"; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | SKIP_INSTALL = YES; 357 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 358 | SWIFT_VERSION = 5.0; 359 | }; 360 | name = Debug; 361 | }; 362 | DF8FB50B1E8D119C00EC2740 /* Release */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | CLANG_ENABLE_MODULES = YES; 366 | DEVELOPMENT_TEAM = 3GN7X2W9W5; 367 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 368 | OTHER_LDFLAGS = "-ObjC"; 369 | PRODUCT_NAME = "$(TARGET_NAME)"; 370 | SKIP_INSTALL = YES; 371 | SWIFT_VERSION = 5.0; 372 | }; 373 | name = Release; 374 | }; 375 | /* End XCBuildConfiguration section */ 376 | 377 | /* Begin XCConfigurationList section */ 378 | DF8FB4FB1E8D119C00EC2740 /* Build configuration list for PBXProject "Locker" */ = { 379 | isa = XCConfigurationList; 380 | buildConfigurations = ( 381 | DF8FB5071E8D119C00EC2740 /* Debug */, 382 | DF8FB5081E8D119C00EC2740 /* Release */, 383 | ); 384 | defaultConfigurationIsVisible = 0; 385 | defaultConfigurationName = Release; 386 | }; 387 | DF8FB5091E8D119C00EC2740 /* Build configuration list for PBXNativeTarget "Locker" */ = { 388 | isa = XCConfigurationList; 389 | buildConfigurations = ( 390 | DF8FB50A1E8D119C00EC2740 /* Debug */, 391 | DF8FB50B1E8D119C00EC2740 /* Release */, 392 | ); 393 | defaultConfigurationIsVisible = 0; 394 | defaultConfigurationName = Release; 395 | }; 396 | /* End XCConfigurationList section */ 397 | }; 398 | rootObject = DF8FB4F81E8D119C00EC2740 /* Project object */; 399 | } 400 | --------------------------------------------------------------------------------