diff --git a/SSKeychain.xcodeproj/project.pbxproj b/SSKeychain.xcodeproj/project.pbxproj index 5677bf2..211a496 100644 --- a/SSKeychain.xcodeproj/project.pbxproj +++ b/SSKeychain.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 4412CE1C1A9A372F00C35DE1 /* SSKeychainAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4412CE1A1A9A372F00C35DE1 /* SSKeychainAccessControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4412CE1D1A9A372F00C35DE1 /* SSKeychainAccessControl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4412CE1A1A9A372F00C35DE1 /* SSKeychainAccessControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4412CE1E1A9A372F00C35DE1 /* SSKeychainAccessControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 4412CE1B1A9A372F00C35DE1 /* SSKeychainAccessControl.m */; }; + 4412CE1F1A9A372F00C35DE1 /* SSKeychainAccessControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 4412CE1B1A9A372F00C35DE1 /* SSKeychainAccessControl.m */; }; E8A6665B1A844D3A00287CA3 /* SSKeychain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8A666341A844CC400287CA3 /* SSKeychain.framework */; }; E8A666641A844DB700287CA3 /* SSKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 21CC42EF17DB878300201DDC /* SSKeychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; E8A666651A844DB700287CA3 /* SSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 21CC42F017DB878300201DDC /* SSKeychain.m */; }; @@ -52,12 +56,14 @@ 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; }; 21CC42DB17DB877900201DDC /* SSKeychainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychainTests.m; sourceTree = ""; }; - 21CC42EE17DB878300201DDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SSKeychain.strings; sourceTree = ""; }; + 21CC42EE17DB878300201DDC /* en */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/SSKeychain.strings; sourceTree = ""; }; 21CC42EF17DB878300201DDC /* SSKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychain.h; sourceTree = ""; }; 21CC42F017DB878300201DDC /* SSKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychain.m; sourceTree = ""; }; 21CC42F117DB878300201DDC /* SSKeychainQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychainQuery.h; sourceTree = ""; }; 21CC42F217DB878300201DDC /* SSKeychainQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychainQuery.m; sourceTree = ""; }; 21CC42F917DB87C300201DDC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 4412CE1A1A9A372F00C35DE1 /* SSKeychainAccessControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychainAccessControl.h; sourceTree = ""; }; + 4412CE1B1A9A372F00C35DE1 /* SSKeychainAccessControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychainAccessControl.m; sourceTree = ""; }; E8A666341A844CC400287CA3 /* SSKeychain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SSKeychain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E8A6664F1A844CF100287CA3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E8A666551A844D3A00287CA3 /* SSKeychain iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SSKeychain iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -166,6 +172,8 @@ 21CC42F017DB878300201DDC /* SSKeychain.m */, 21CC42F117DB878300201DDC /* SSKeychainQuery.h */, 21CC42F217DB878300201DDC /* SSKeychainQuery.m */, + 4412CE1A1A9A372F00C35DE1 /* SSKeychainAccessControl.h */, + 4412CE1B1A9A372F00C35DE1 /* SSKeychainAccessControl.m */, ); path = SSKeychain; sourceTree = ""; @@ -177,6 +185,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 4412CE1C1A9A372F00C35DE1 /* SSKeychainAccessControl.h in Headers */, E8A666641A844DB700287CA3 /* SSKeychain.h in Headers */, E8A666661A844DB700287CA3 /* SSKeychainQuery.h in Headers */, ); @@ -186,6 +195,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 4412CE1D1A9A372F00C35DE1 /* SSKeychainAccessControl.h in Headers */, E8A6668A1A844E5400287CA3 /* SSKeychain.h in Headers */, E8A6668C1A844E5400287CA3 /* SSKeychainQuery.h in Headers */, ); @@ -341,6 +351,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4412CE1E1A9A372F00C35DE1 /* SSKeychainAccessControl.m in Sources */, E8A666671A844DB700287CA3 /* SSKeychainQuery.m in Sources */, E8A666651A844DB700287CA3 /* SSKeychain.m in Sources */, ); @@ -358,6 +369,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4412CE1F1A9A372F00C35DE1 /* SSKeychainAccessControl.m in Sources */, E8A6668D1A844E5400287CA3 /* SSKeychainQuery.m in Sources */, E8A6668B1A844E5400287CA3 /* SSKeychain.m in Sources */, ); diff --git a/SSKeychain/SSKeychainAccessControl.h b/SSKeychain/SSKeychainAccessControl.h new file mode 100644 index 0000000..a34eaf3 --- /dev/null +++ b/SSKeychain/SSKeychainAccessControl.h @@ -0,0 +1,52 @@ +// +// SSKeychainAccessControl.h +// SSKeychain +// +// 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, SSKeychainAccessibility) { + /** kSecAttrAccessibleWhenUnlocked */ + SSKeychainAccessibilityWhenUnlocked = 1, + + /** kSecAttrAccessibleAfterFirstUnlock */ + SSKeychainAccessibilityAfterFirstUnlock, + + /** kSecAttrAccessibleAlways */ + SSKeychainAccessibilityAlways, + + /** kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly */ + SSKeychainAccessibilityWhenPasscodeSetThisDeviceOnly, + + /** kSecAttrAccessibleWhenUnlockedThisDeviceOnly */ + SSKeychainAccessibilityWhenUnlockedThisDeviceOnly, + + /** kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly */ + SSKeychainAccessibilityAfterFisrtUnlockThisDeviceOnly, + + /** kSecAttrAccessibleAlwaysThisDeviceOnly */ + SSKeychainAccessibilityAlwaysThisDeviceOnly +}; + +/** SecAccessControlCreateFlags */ +typedef NS_OPTIONS(NSUInteger, SSKeychainCreateFlags) { + /** kSecAccessControlUserPresence */ + SSKeychainCreateFlagUserPresence = 1UL << 0 +}; + +extern CFTypeRef getSecAttrAccessibility(SSKeychainAccessibility ssAttr); + +@interface SSKeychainAccessControl : NSObject + ++ (instancetype)accessControlWithAccessibility:(SSKeychainAccessibility)accesibility flags:(SSKeychainCreateFlags)flags; + +@property (nonatomic, assign) SSKeychainAccessibility accessibility; + +@property (nonatomic, assign) SSKeychainCreateFlags flags; + +@end diff --git a/SSKeychain/SSKeychainAccessControl.m b/SSKeychain/SSKeychainAccessControl.m new file mode 100644 index 0000000..f845f31 --- /dev/null +++ b/SSKeychain/SSKeychainAccessControl.m @@ -0,0 +1,50 @@ +// +// SSKeychainAccessControl.m +// SSKeychain +// +// Created by Liam Nichols on 01/09/2014. +// Copyright (c) 2014 Sam Soffes. All rights reserved. +// + +#import "SSKeychainAccessControl.h" + +@implementation SSKeychainAccessControl + ++ (instancetype)accessControlWithAccessibility:(SSKeychainAccessibility)accesibility flags:(SSKeychainCreateFlags)flags +{ + SSKeychainAccessControl *accessControl = [self new]; + accessControl.accessibility = accesibility; + accessControl.flags = flags; + return accessControl; +} + +@end + +CFTypeRef getSecAttrAccessibility(SSKeychainAccessibility ssAttr) +{ + switch (ssAttr) { + case SSKeychainAccessibilityAlways: + return kSecAttrAccessibleAlways; + + case SSKeychainAccessibilityWhenUnlocked: + return kSecAttrAccessibleWhenUnlocked; + + case SSKeychainAccessibilityAfterFirstUnlock: + return kSecAttrAccessibleAfterFirstUnlock; + + case SSKeychainAccessibilityAlwaysThisDeviceOnly: + return kSecAttrAccessibleAlwaysThisDeviceOnly; + + case SSKeychainAccessibilityWhenUnlockedThisDeviceOnly: + return kSecAttrAccessibleWhenUnlockedThisDeviceOnly; + + case SSKeychainAccessibilityWhenPasscodeSetThisDeviceOnly: + return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly; + + case SSKeychainAccessibilityAfterFisrtUnlockThisDeviceOnly: + return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly; + + default: + return NULL; + } +} diff --git a/SSKeychain/SSKeychainQuery.h b/SSKeychain/SSKeychainQuery.h index f0579e1..63608bc 100644 --- a/SSKeychain/SSKeychainQuery.h +++ b/SSKeychain/SSKeychainQuery.h @@ -9,6 +9,14 @@ @import Foundation; @import Security; +#if __IPHONE_8_0 || __MAC_10_10 + #define SSKEYCHAIN_ACCESS_CONTROL_AVAILABLE 1 +#endif + +#ifdef SSKEYCHAIN_ACCESS_CONTROL_AVAILABLE + #import +#endif + #if __IPHONE_7_0 || __MAC_10_9 // Keychain synchronization available at compile time #define SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE 1 @@ -46,6 +54,19 @@ typedef NS_ENUM(NSUInteger, SSKeychainQuerySynchronizationMode) { @property (nonatomic) SSKeychainQuerySynchronizationMode synchronizationMode; #endif +#if __IPHONE_8_0 && TARGET_OS_IPHONE +/** kSecUseOperationPrompt */ +@property (nonatomic, copy) NSString *useOperationPrompt; + +/** kSecUseNoAuthenticationUI */ +@property (nonatomic, assign) NSNumber *useNoAuthenticationUI; +#endif + +#if SSKEYCHAIN_ACCESS_CONTROL_AVAILABLE +/** kSecAttrAccessControl */ +@property (nonatomic, strong) SSKeychainAccessControl *accessControl; +#endif + /** Root storage for password information */ @property (nonatomic, copy) NSData *passwordData; @@ -63,7 +84,7 @@ typedef NS_ENUM(NSUInteger, SSKeychainQuerySynchronizationMode) { ///------------------------ -/// @name Saving & Deleting +/// @name Saving, Updating & Deleting ///------------------------ /** @@ -76,6 +97,15 @@ typedef NS_ENUM(NSUInteger, SSKeychainQuerySynchronizationMode) { */ - (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. @@ -121,7 +151,7 @@ typedef NS_ENUM(NSUInteger, SSKeychainQuerySynchronizationMode) { #ifdef SSKEYCHAIN_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 SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE is only for compile time. If you are checking for the presence of synchronization, you should use this method. @@ -130,4 +160,20 @@ typedef NS_ENUM(NSUInteger, SSKeychainQuerySynchronizationMode) { + (BOOL)isSynchronizationAvailable; #endif + +///----------------------------- +/// @name Access Control Status +///----------------------------- + +#ifdef SSKEYCHAIN_ACCESS_CONTROL_AVAILABLE +/** + Returns a boolean indicating if keychain access control is available on the device at runtime. The #define + SSKEYCHAIN_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; +#endif + @end diff --git a/SSKeychain/SSKeychainQuery.m b/SSKeychain/SSKeychainQuery.m index 70ee516..80c38b4 100644 --- a/SSKeychain/SSKeychainQuery.m +++ b/SSKeychain/SSKeychainQuery.m @@ -20,6 +20,10 @@ @implementation SSKeychainQuery @synthesize accessGroup = _accessGroup; #endif +#ifdef SSKEYCHAIN_ACCESS_CONTROL_AVAILABLE +@synthesize accessControl = _accessControl; +#endif + #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE @synthesize synchronizationMode = _synchronizationMode; #endif @@ -48,6 +52,33 @@ - (BOOL)save:(NSError *__autoreleasing *)error { [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; } #endif + +#if __IPHONE_8_0 && TARGET_OS_IPHONE + if (self.useNoAuthenticationUI) { + [query setObject:self.useNoAuthenticationUI forKey:(__bridge id)kSecUseNoAuthenticationUI]; + } +#endif +#if SSKEYCHAIN_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) { @@ -57,6 +88,44 @@ - (BOOL)save:(NSError *__autoreleasing *)error { return (status == errSecSuccess); } +- (BOOL)update:(NSError *__autoreleasing *)error +{ + OSStatus status = SSKeychainErrorBadArguments; + 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 = [SSKeychain 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 = SSKeychainErrorBadArguments; @@ -94,6 +163,12 @@ - (NSArray *)fetchAll:(NSError *__autoreleasing *)error { [query setObject:@YES forKey:(__bridge id)kSecReturnAttributes]; [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit]; +#if __IPHONE_8_0 && TARGET_OS_IPHONE + if (self.useOperationPrompt) { + [query setObject:self.useOperationPrompt forKey:(__bridge id)kSecUseOperationPrompt]; + } +#endif + CFTypeRef result = NULL; status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status != errSecSuccess && error != NULL) { @@ -118,6 +193,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) { @@ -175,6 +257,21 @@ + (BOOL)isSynchronizationAvailable { #endif +#pragma mark - Access Control Status + +#ifdef SSKEYCHAIN_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 +} +#endif + + #pragma mark - Private - (NSMutableDictionary *)query {