diff --git a/SAMKeychain.xcodeproj/project.pbxproj b/SAMKeychain.xcodeproj/project.pbxproj index 4e54734..cc2de11 100644 --- a/SAMKeychain.xcodeproj/project.pbxproj +++ b/SAMKeychain.xcodeproj/project.pbxproj @@ -31,6 +31,15 @@ 21632DD31C9282BD00C40D7D /* KeychainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21632DD21C9282BD00C40D7D /* KeychainTests.swift */; }; 21632DD41C9282BD00C40D7D /* KeychainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21632DD21C9282BD00C40D7D /* KeychainTests.swift */; }; 21632DD51C9282BD00C40D7D /* KeychainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21632DD21C9282BD00C40D7D /* KeychainTests.swift */; }; + C598E1962086415500B79528 /* SAMKeychainAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = C598E1942086415500B79528 /* SAMKeychainAccessControl.h */; }; + C598E1972086415500B79528 /* SAMKeychainAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = C598E1942086415500B79528 /* SAMKeychainAccessControl.h */; }; + C598E1982086415500B79528 /* SAMKeychainAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = C598E1942086415500B79528 /* SAMKeychainAccessControl.h */; }; + C598E1992086415500B79528 /* SAMKeychainAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = C598E1942086415500B79528 /* SAMKeychainAccessControl.h */; }; + C598E19A2086415500B79528 /* SAMKeychainAccessControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C598E1952086415500B79528 /* SAMKeychainAccessControl.m */; }; + C598E19B2086415500B79528 /* SAMKeychainAccessControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C598E1952086415500B79528 /* SAMKeychainAccessControl.m */; }; + C598E19C2086415500B79528 /* SAMKeychainAccessControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C598E1952086415500B79528 /* SAMKeychainAccessControl.m */; }; + C598E19D2086415500B79528 /* SAMKeychainAccessControl.m in Sources */ = {isa = PBXBuildFile; fileRef = C598E1952086415500B79528 /* SAMKeychainAccessControl.m */; }; + C5AF91D4208AC10C00136637 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C5AF91D3208AC10C00136637 /* LocalAuthentication.framework */; }; E8A6665B1A844D3A00287CA3 /* SAMKeychain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8A666341A844CC400287CA3 /* SAMKeychain.framework */; }; E8A6667A1A844E4100287CA3 /* SAMKeychain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8A6666F1A844E4100287CA3 /* SAMKeychain.framework */; }; /* End PBXBuildFile section */ @@ -82,6 +91,9 @@ 21CC42AE17DB874300201DDC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 21CC42C317DB874300201DDC /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 21CC42F917DB87C300201DDC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + C598E1942086415500B79528 /* SAMKeychainAccessControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAMKeychainAccessControl.h; sourceTree = ""; }; + C598E1952086415500B79528 /* SAMKeychainAccessControl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAMKeychainAccessControl.m; sourceTree = ""; }; + C5AF91D3208AC10C00136637 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; E8A666341A844CC400287CA3 /* SAMKeychain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SAMKeychain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E8A666551A844D3A00287CA3 /* SAMKeychainTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SAMKeychainTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; E8A6666F1A844E4100287CA3 /* SAMKeychain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SAMKeychain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -115,6 +127,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C5AF91D4208AC10C00136637 /* LocalAuthentication.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -151,6 +164,8 @@ 21632D7D1C92599100C40D7D /* SAMKeychain.m */, 21632D7E1C92599100C40D7D /* SAMKeychainQuery.h */, 21632D7F1C92599100C40D7D /* SAMKeychainQuery.m */, + C598E1942086415500B79528 /* SAMKeychainAccessControl.h */, + C598E1952086415500B79528 /* SAMKeychainAccessControl.m */, ); path = Sources; sourceTree = ""; @@ -204,6 +219,7 @@ 21CC42A917DB874300201DDC /* Frameworks */ = { isa = PBXGroup; children = ( + C5AF91D3208AC10C00136637 /* LocalAuthentication.framework */, 21B85B6E18EC963A009D2B98 /* Cocoa.framework */, 21B85B1E18EC9391009D2B98 /* Security.framework */, 21CC42F917DB87C300201DDC /* Security.framework */, @@ -232,6 +248,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + C598E1982086415500B79528 /* SAMKeychainAccessControl.h in Headers */, 21632DB11C925A6000C40D7D /* SAMKeychain.h in Headers */, 21632DB31C925A6000C40D7D /* SAMKeychainQuery.h in Headers */, ); @@ -241,6 +258,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + C598E1992086415500B79528 /* SAMKeychainAccessControl.h in Headers */, 21632DC51C925B3700C40D7D /* SAMKeychain.h in Headers */, 21632DC71C925B3700C40D7D /* SAMKeychainQuery.h in Headers */, ); @@ -250,6 +268,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + C598E1962086415500B79528 /* SAMKeychainAccessControl.h in Headers */, 21632D821C92599100C40D7D /* SAMKeychain.h in Headers */, 21632D841C92599100C40D7D /* SAMKeychainQuery.h in Headers */, ); @@ -259,6 +278,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + C598E1972086415500B79528 /* SAMKeychainAccessControl.h in Headers */, 21632D8F1C9259C100C40D7D /* SAMKeychain.h in Headers */, 21632D911C9259C100C40D7D /* SAMKeychainQuery.h in Headers */, ); @@ -505,6 +525,7 @@ buildActionMask = 2147483647; files = ( 21632DB41C925A6000C40D7D /* SAMKeychainQuery.m in Sources */, + C598E19C2086415500B79528 /* SAMKeychainAccessControl.m in Sources */, 21632DB21C925A6000C40D7D /* SAMKeychain.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -522,6 +543,7 @@ buildActionMask = 2147483647; files = ( 21632DC81C925B3700C40D7D /* SAMKeychainQuery.m in Sources */, + C598E19D2086415500B79528 /* SAMKeychainAccessControl.m in Sources */, 21632DC61C925B3700C40D7D /* SAMKeychain.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -531,6 +553,7 @@ buildActionMask = 2147483647; files = ( 21632D851C92599100C40D7D /* SAMKeychainQuery.m in Sources */, + C598E19A2086415500B79528 /* SAMKeychainAccessControl.m in Sources */, 21632D831C92599100C40D7D /* SAMKeychain.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -548,6 +571,7 @@ buildActionMask = 2147483647; files = ( 21632D921C9259C100C40D7D /* SAMKeychainQuery.m in Sources */, + C598E19B2086415500B79528 /* SAMKeychainAccessControl.m in Sources */, 21632D901C9259C100C40D7D /* SAMKeychain.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/SAMKeychainAccessControl.h b/Sources/SAMKeychainAccessControl.h new file mode 100644 index 0000000..2b2bdff --- /dev/null +++ b/Sources/SAMKeychainAccessControl.h @@ -0,0 +1,94 @@ +// +// SAMKeychainAccessControl.h +// SAMKeychain +// +// Created by Liam Nichols on 01/09/2014. +// Copyright (c) 2014 Sam Soffes. All rights reserved. +// + +@import Foundation; +@import Security; + +/** kSecAttrAccessible */ +typedef NS_ENUM(NSUInteger, SAMKeychainAccessibility) { + /** kSecAttrAccessibleWhenUnlocked */ + SAMKeychainAccessibilityWhenUnlocked = 1, + + /** kSecAttrAccessibleAfterFirstUnlock */ + SAMKeychainAccessibilityAfterFirstUnlock, + + /** kSecAttrAccessibleAlways */ + SAMKeychainAccessibilityAlways, + + /** kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly */ + SAMKeychainAccessibilityWhenPasscodeSetThisDeviceOnly, + + /** kSecAttrAccessibleWhenUnlockedThisDeviceOnly */ + SAMKeychainAccessibilityWhenUnlockedThisDeviceOnly, + + /** kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly */ + SAMKeychainAccessibilityAfterFirstUnlockThisDeviceOnly, + + /** kSecAttrAccessibleAlwaysThisDeviceOnly */ + SAMKeychainAccessibilityAlwaysThisDeviceOnly +}; + +/** SecAccessControlCreateFlags */ +typedef NS_OPTIONS(NSUInteger, SAMKeychainCreateFlags) { + /** kSecAccessControlUserPresence + User presence policy using biometry or Passcode. Biometry does not have to be available or enrolled. Item is still + accessible by Touch ID even if fingers are added or removed. Item is still accessible by Face ID if user is re-enrolled. + */ + SAMKeychainCreateFlagUserPresence = 1u << 0, + + /** kSecAccessControlBiometryAny + Constraint: Touch ID (any finger) or Face ID. Touch ID or Face ID must be available. With Touch ID + at least one finger must be enrolled. With Face ID user has to be enrolled. Item is still accessible by Touch ID even + if fingers are added or removed. Item is still accessible by Face ID if user is re-enrolled. + */ + SAMKeychainCreateFlagBiometryAny CF_ENUM_AVAILABLE(10_12_1, 9_0) = 1u << 1, + + /** kSecAccessControlBiometryCurrentSet + Constraint: Touch ID from the set of currently enrolled fingers. Touch ID must be available and at least one finger must + be enrolled. When fingers are added or removed, the item is invalidated. When Face ID is re-enrolled this item is invalidated. + */ + SAMKeychainCreateFlagBiometryCurrentSet CF_ENUM_AVAILABLE(10_12_1, 9_0) = 1u << 3, + + /** kSecAccessControlDevicePasscode + Constraint: Device passcode + */ + SAMKeychainCreateFlagDevicePasscode CF_ENUM_AVAILABLE(10_11, 9_0) = 1u << 4, + + /** kSecAccessControlOr + Constraint logic operation: when using more than one constraint, at least one of them must be satisfied. + */ + SAMKeychainCreateFlagOr CF_ENUM_AVAILABLE(10_12_1, 9_0) = 1u << 14, + + /** kSecAccessControlAnd + Constraint logic operation: when using more than one constraint, all must be satisfied. + */ + SAMKeychainCreateFlagAnd CF_ENUM_AVAILABLE(10_12_1, 9_0) = 1u << 15, + + /** kSecAccessControlPrivateKeyUsage + Create access control for private key operations (i.e. sign operation) + */ + SAMKeychainCreateFlagPrivateKeyUsage CF_ENUM_AVAILABLE(10_12_1, 9_0) = 1u << 30, + + /** kSecAccessControlApplicationPassword + Security: Application provided password for data encryption key generation. This is not a constraint but additional item + encryption mechanism. + */ + SAMKeychainCreateFlagApplicationPassword CF_ENUM_AVAILABLE(10_12_1, 9_0) = 1u << 31, +} __OSX_AVAILABLE_STARTING(__MAC_10_10, __IPHONE_8_0); + +extern CFTypeRef getSecAttrAccessibility(SAMKeychainAccessibility ssAttr); + +@interface SAMKeychainAccessControl : NSObject + ++ (instancetype)accessControlWithAccessibility:(SAMKeychainAccessibility)accesibility flags:(SAMKeychainCreateFlags)flags; + +@property (nonatomic, assign) SAMKeychainAccessibility accessibility; + +@property (nonatomic, assign) SAMKeychainCreateFlags flags; + +@end diff --git a/Sources/SAMKeychainAccessControl.m b/Sources/SAMKeychainAccessControl.m new file mode 100644 index 0000000..b894ab9 --- /dev/null +++ b/Sources/SAMKeychainAccessControl.m @@ -0,0 +1,33 @@ +// +// SAMKeychainAccessControl.m +// SAMKeychain +// +// Created by Liam Nichols on 01/09/2014. +// Copyright (c) 2014 Sam Soffes. All rights reserved. +// + +#import "SAMKeychainAccessControl.h" + +@implementation SAMKeychainAccessControl + ++ (instancetype)accessControlWithAccessibility:(SAMKeychainAccessibility)accesibility flags:(SAMKeychainCreateFlags)flags { + SAMKeychainAccessControl *accessControl = [self new]; + accessControl.accessibility = accesibility; + accessControl.flags = flags; + return accessControl; +} + +@end + +CFTypeRef getSecAttrAccessibility(SAMKeychainAccessibility ssAttr) { + switch (ssAttr) { + case SAMKeychainAccessibilityAlways: return kSecAttrAccessibleAlways; + case SAMKeychainAccessibilityWhenUnlocked: return kSecAttrAccessibleWhenUnlocked; + case SAMKeychainAccessibilityAfterFirstUnlock: return kSecAttrAccessibleAfterFirstUnlock; + case SAMKeychainAccessibilityAlwaysThisDeviceOnly: return kSecAttrAccessibleAlwaysThisDeviceOnly; + case SAMKeychainAccessibilityWhenUnlockedThisDeviceOnly: return kSecAttrAccessibleWhenUnlockedThisDeviceOnly; + case SAMKeychainAccessibilityWhenPasscodeSetThisDeviceOnly: return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly; + case SAMKeychainAccessibilityAfterFirstUnlockThisDeviceOnly: return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly; + default: return NULL; + } +} diff --git a/Sources/SAMKeychainQuery.h b/Sources/SAMKeychainQuery.h index 84658e2..9bf2b39 100644 --- a/Sources/SAMKeychainQuery.h +++ b/Sources/SAMKeychainQuery.h @@ -14,6 +14,14 @@ #import #endif +#if __IPHONE_8_0 || __MAC_10_10 + #define SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE 1 +#endif + +#ifdef SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE + #import +#endif + NS_ASSUME_NONNULL_BEGIN #if __IPHONE_7_0 || __MAC_10_9 @@ -58,6 +66,23 @@ typedef NS_ENUM(NSUInteger, SAMKeychainQuerySynchronizationMode) { @property (nonatomic) SAMKeychainQuerySynchronizationMode synchronizationMode; #endif +#if __IPHONE_8_0 && TARGET_OS_IPHONE +/** kSecUseOperationPrompt */ +@property (nonatomic, copy) NSString *useOperationPrompt; + +/** kSecUseNoAuthenticationUI */ +@property (nonatomic, assign) NSNumber *useNoAuthenticationUI DEPRECATED_MSG_ATTRIBUTE("Use -useAuthenticationUI instead."); + +/** kSecUseAuthenticationUI */ +@property (nonatomic, assign) NSNumber *useAuthenticationUI; +#endif + +#if SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE +/** kSecAttrAccessControl */ +@property (nonatomic, strong) SAMKeychainAccessControl *accessControl; +#endif + + /** Root storage for password information */ @property (nonatomic, copy, nullable) NSData *passwordData; @@ -75,7 +100,7 @@ typedef NS_ENUM(NSUInteger, SAMKeychainQuerySynchronizationMode) { ///------------------------ -/// @name Saving & Deleting +/// @name Saving, Updating & Deleting ///------------------------ /** @@ -88,6 +113,15 @@ typedef NS_ENUM(NSUInteger, SAMKeychainQuerySynchronizationMode) { */ - (BOOL)save:(NSError **)error; +/** + Updates the receiver's attributes. + + @param error Populated should an error occur. + + @return `YES` if saving was successful, `NO` otherwise. + */ +- (BOOL)update:(NSError **)error; + /** Delete keychain items that match the given account, service, and access group. @@ -133,7 +167,7 @@ typedef NS_ENUM(NSUInteger, SAMKeychainQuerySynchronizationMode) { #ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE /** - Returns a boolean indicating if keychain synchronization is available on the device at runtime. The #define + Returns a boolean indicating if keychain synchronization is available on the device at runtime. The #define SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE is only for compile time. If you are checking for the presence of synchronization, you should use this method. @@ -142,6 +176,38 @@ typedef NS_ENUM(NSUInteger, SAMKeychainQuerySynchronizationMode) { + (BOOL)isSynchronizationAvailable; #endif + +///----------------------------- +/// @name Access Control Status +///----------------------------- + +#ifdef SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE +/** + Returns a boolean indicating if keychain access control is available on the device at runtime. The #define + SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE is only for compile time. If you are checking for the presence of access control, + you should use this method. + + @return A value indicating if keychain access control is available + */ ++ (BOOL)isAccessControlAvailable; + +#if TARGET_OS_IPHONE +/** + Returns a boolean indicating if biometry is configured on the device. + + @return A value indicating if authentication by biometry is configured + */ ++ (BOOL)isBiometryAvailable; + +/** + Returns a boolean indicating if device passcode or biometry is configured on the device. + + @return A value indicating if device passcode or biometry is configured + */ ++ (BOOL)isPasscodeOrBiometryAvailable; +#endif +#endif + @end NS_ASSUME_NONNULL_END diff --git a/Sources/SAMKeychainQuery.m b/Sources/SAMKeychainQuery.m index 00ecb80..b48220e 100644 --- a/Sources/SAMKeychainQuery.m +++ b/Sources/SAMKeychainQuery.m @@ -8,6 +8,7 @@ #import "SAMKeychainQuery.h" #import "SAMKeychain.h" +#import @implementation SAMKeychainQuery @@ -20,6 +21,10 @@ @implementation SAMKeychainQuery @synthesize accessGroup = _accessGroup; #endif +#ifdef SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE +@synthesize accessControl = _accessControl; +#endif + #ifdef SAMKEYCHAIN_SYNCHRONIZATION_AVAILABLE @synthesize synchronizationMode = _synchronizationMode; #endif @@ -42,9 +47,15 @@ - (BOOL)save:(NSError *__autoreleasing *)error { [query setObject:self.passwordData forKey:(__bridge id)kSecValueData]; #if __IPHONE_4_0 && TARGET_OS_IPHONE CFTypeRef accessibilityType = [SAMKeychain accessibilityType]; +#if SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE + if (accessibilityType && !self.accessControl) { // accessibilityType and accessControl are mutually exclusive + [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; + } +#else if (accessibilityType) { [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; } +#endif #endif status = SecItemUpdate((__bridge CFDictionaryRef)(searchQuery), (__bridge CFDictionaryRef)(query)); }else if(status == errSecItemNotFound){//item not found, create it! @@ -55,17 +66,94 @@ - (BOOL)save:(NSError *__autoreleasing *)error { [query setObject:self.passwordData forKey:(__bridge id)kSecValueData]; #if __IPHONE_4_0 && TARGET_OS_IPHONE CFTypeRef accessibilityType = [SAMKeychain accessibilityType]; +#if SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE + if (accessibilityType && !self.accessControl) { // accessibilityType and accessControl are mutually exclusive + [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; + } +#else if (accessibilityType) { [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; } #endif +#endif + +#if __IPHONE_8_0 && TARGET_OS_IPHONE +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (self.useNoAuthenticationUI) { + self.useAuthenticationUI = @(!self.useNoAuthenticationUI.boolValue); + } +#pragma clang diagnostic pop + if (self.useAuthenticationUI) { + [query setObject:self.useAuthenticationUI forKey:(__bridge id)kSecUseAuthenticationUI]; + } +#endif +#if SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE + if (self.accessControl) { + CFErrorRef sacError = NULL; + SecAccessControlRef sacObject; + + sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, + getSecAttrAccessibility(self.accessControl.accessibility), + (CFIndex)self.accessControl.flags, + &sacError); + + if (sacObject == NULL || sacError != NULL) { + if (error) { + *error = (__bridge NSError *)sacError; + } + return NO; + } + + [query setObject:(__bridge id)sacObject forKey:(__bridge id)kSecAttrAccessControl]; + } +#endif + status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); } if (status != errSecSuccess && error != NULL) { *error = [[self class] errorWithCode:status]; } - return (status == errSecSuccess);} + return (status == errSecSuccess); +} + +- (BOOL)update:(NSError *__autoreleasing *)error { + OSStatus status = SAMKeychainErrorBadArguments; + if (!self.service || !self.account || !self.passwordData) { + if (error) { + *error = [[self class] errorWithCode:status]; + } + return NO; + } + + NSMutableDictionary *query = [self query]; + NSMutableDictionary *changes = [NSMutableDictionary dictionary]; + + [changes setObject:self.passwordData forKey:(__bridge id)kSecValueData]; + if (self.label) { + [changes setObject:self.label forKey:(__bridge id)kSecAttrLabel]; + } +#if __IPHONE_4_0 && TARGET_OS_IPHONE + CFTypeRef accessibilityType = [SAMKeychain accessibilityType]; + if (accessibilityType) { + [changes setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; + } +#endif + +#if __IPHONE_8_0 && TARGET_OS_IPHONE + if (self.useOperationPrompt) { + [query setObject:self.useOperationPrompt forKey:(__bridge id)kSecUseOperationPrompt]; + } +#endif + + status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes); + + if (status != errSecSuccess && error != NULL) { + *error = [[self class] errorWithCode:status]; + } + return (status == errSecSuccess); +} - (BOOL)deleteItem:(NSError *__autoreleasing *)error { OSStatus status = SAMKeychainErrorBadArguments; @@ -117,6 +205,12 @@ - (nullable NSArray *)fetchAll:(NSError *__autoreleasing *)error { } #endif +#if __IPHONE_8_0 && TARGET_OS_IPHONE + if (self.useOperationPrompt) { + [query setObject:self.useOperationPrompt forKey:(__bridge id)kSecUseOperationPrompt]; + } +#endif + CFTypeRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status != errSecSuccess && error != NULL) { @@ -141,6 +235,13 @@ - (BOOL)fetch:(NSError *__autoreleasing *)error { NSMutableDictionary *query = [self query]; [query setObject:@YES forKey:(__bridge id)kSecReturnData]; [query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; + +#if __IPHONE_8_0 && TARGET_OS_IPHONE + if (self.useOperationPrompt) { + [query setObject:self.useOperationPrompt forKey:(__bridge id)kSecUseOperationPrompt]; + } +#endif + status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status != errSecSuccess) { @@ -197,6 +298,29 @@ + (BOOL)isSynchronizationAvailable { } #endif +#pragma mark - Access Control Status + +#ifdef SAMKEYCHAIN_ACCESS_CONTROL_AVAILABLE ++ (BOOL)isAccessControlAvailable { +#if TARGET_OS_IPHONE + // Apple suggested way to check for 8.0 at runtime + // https://developer.apple.com/library/ios/documentation/userexperience/conceptual/transitionguide/SupportingEarlieriOS.html + return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1; +#else + return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_9_2; +#endif +} + +#if TARGET_OS_IPHONE ++ (BOOL)isBiometryAvailable { + return [[LAContext new] canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]; +} + ++ (BOOL)isPasscodeOrBiometryAvailable { + return [[LAContext new] canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:nil]; +} +#endif +#endif #pragma mark - Private diff --git a/Tests/KeychainTests.swift b/Tests/KeychainTests.swift index f51c480..3a3ee08 100644 --- a/Tests/KeychainTests.swift +++ b/Tests/KeychainTests.swift @@ -195,7 +195,7 @@ class KeychainTests: XCTestCase { private func accounts(accounts: [[String: AnyObject]], containsAccountWithName name: String) -> Bool { for account in accounts { - if let acct = account["acct"] as? String where acct == name { + if let acct = account["acct"] as? String, acct == name { return true } }