在键盘目标中使用钥匙串
Using keychain in a keyboard target
我在我的键盘中使用了一个框架,它使用钥匙串来存储 UUID。不幸的是,我的键盘在尝试在此处写入该值时崩溃了:
2015-09-10 12:34:26.232 Keyboard[15504:3505665] *** Assertion failure in -[LUIKeychain writeToKeychain], /Projects/iOS/LUIFramework/LUIFramework/LUIKeychain.m:249
2015-09-10 12:34:26.233 Keyboard[15504:3505665] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Couldn't add the Keychain Item.'
该框架是我的,应该可以在所有苹果平台上运行,所以好消息是我可以修复它...有什么想法从哪里开始吗?
编辑: 请求的源代码:
我在这里用它来存储 UUID:
#import "LUIUUID.h"
#import "LUIKeychain.h"
#define kLUIUUIDKey @"LUIUUIDKey"
#define kLUISessionUUIDKey @"LUISessionUUIDKey"
@implementation LUIUUID
+ (NSString *)UUID {
LUIKeychain *keychain = [[LUIKeychain alloc] initWithIdentifier:kLUIUUIDKey accessGroup:nil];
NSString *uuid = [keychain objectForKey:(__bridge id)kSecAttrAccount];
if (!uuid || uuid.length == 0) {
uuid = [[NSUUID UUID] UUIDString];
[keychain setObject:uuid forKey:(__bridge id)(kSecAttrAccount)];
}
return uuid;
}
@end
这是图书馆:
#import "LUIKeychain.h"
#import <Security/Security.h>
/*
These are the default constants and their respective types,
available for the kSecClassGenericPassword Keychain Item class:
kSecAttrAccessGroup - CFStringRef
kSecAttrCreationDate - CFDateRef
kSecAttrModificationDate - CFDateRef
kSecAttrDescription - CFStringRef
kSecAttrComment - CFStringRef
kSecAttrCreator - CFNumberRef
kSecAttrType - CFNumberRef
kSecAttrLabel - CFStringRef
kSecAttrIsInvisible - CFBooleanRef
kSecAttrIsNegative - CFBooleanRef
kSecAttrAccount - CFStringRef
kSecAttrService - CFStringRef
kSecAttrGeneric - CFDataRef
See the header file Security/SecItem.h for more details.
*/
@interface LUIKeychain ()
@property (nonatomic, strong) NSMutableDictionary *genericPasswordQuery;
@property (nonatomic, strong) NSMutableDictionary *keychainItemData;
/*
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
to encapsulate the transition between what the detail view controller was expecting (NSString *)and what the
Keychain API expects as a validly constructed container class.
*/
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
// Updates the item in the keychain, or adds it if it doesn't exist.
- (void)writeToKeychain;
@end
@implementation LUIKeychain
- (instancetype)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *)accessGroup {
if (self = [super init]) {
// Begin Keychain search setup. The _genericPasswordQuery leverages the special user
// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
// items which may be included by the same application.
_genericPasswordQuery = [[NSMutableDictionary alloc] init];
[_genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[_genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
if (accessGroup != nil) {
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[_genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[_genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[_genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:_genericPasswordQuery];
NSMutableDictionary *dictionary = nil;
CFTypeRef outDictionary = (__bridge CFTypeRef)dictionary;
if (! SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, &outDictionary) == noErr) {
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];
// Add the generic attribute and the keychain access group.
[_keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
if (accessGroup != nil) {
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[_keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
}
}
else {
// load the saved data from Keychain.
_keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)(outDictionary)];
}
//[outDictionary release];
}
return self;
}
- (void)setObject:(id)inObject forKey:(id)key {
if (inObject == nil) return;
id currentObject = [_keychainItemData objectForKey:key];
if (![currentObject isEqual:inObject]) {
[_keychainItemData setObject:inObject forKey:key];
[self writeToKeychain];
}
}
- (id)objectForKey:(id)key {
return [_keychainItemData objectForKey:key];
}
- (void)resetKeychainItem {
OSStatus junk = noErr;
if (!_keychainItemData) {
_keychainItemData = [[NSMutableDictionary alloc] init];
}
else if (_keychainItemData) {
NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:_keychainItemData];
junk = SecItemDelete((__bridge CFDictionaryRef)tempDictionary);
NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
}
// Default attributes for keychain item.
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrAccount];
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrLabel];
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrDescription];
// Default data for keychain item.
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData];
}
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert {
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for a SecItem.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the Generic Password keychain item class attribute.
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
// This is where to store sensitive data that should be encrypted.
NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
[returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
return returnDictionary;
}
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert {
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for the UI element.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the proper search key and class attribute.
[returnDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Acquire the password data from the attributes.
NSData *pData = NULL;
CFTypeRef passwordData = (__bridge CFTypeRef)pData;
OSStatus val = SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, &passwordData);
if (val == noErr) {
// Remove the search, class, and identifier key/value, we don't need them anymore.
[returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
NSData *resultData = CFBridgingRelease(passwordData);
// Add the password to the dictionary, converting from NSData to NSString.
//NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]
//encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
[returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
}
else {
// Don't do anything if nothing is found.
// NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
}
//[passwordData release];
return returnDictionary;
}
- (void)writeToKeychain {
NSDictionary *attr = NULL;
CFTypeRef attributes = (__bridge CFTypeRef)attr;
NSMutableDictionary *updateItem = NULL;
OSStatus result;
if (SecItemCopyMatching((__bridge CFDictionaryRef)_genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) {
// First we need the attributes from the Keychain.
updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)(attributes)];
// Second we need to add the appropriate search key/values.
[updateItem setObject:[_genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
// Lastly, we need to set up the updated attribute list being careful to remove the class.
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:_keychainItemData];
[tempCheck removeObjectForKey:(__bridge id)kSecClass];
#if TARGET_IPHONE_SIMULATOR
// Remove the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
//
// The access group attribute will be included in items returned by SecItemCopyMatching,
// which is why we need to remove it before updating the item.
[tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup];
#endif
// An implicit assumption is that you can only update a single item at a time.
result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
}
else {
// No previous item found; add the new one.
result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:_keychainItemData], NULL);
NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
}
}
@end
好的,我已经在键盘上禁用了完全访问权限。
我已经通过在写入钥匙串方法上放置 try/catch 并使用:
解决了这个问题
- (BOOL)isFullAccessGranted {
return !![UIPasteboard generalPasteboard];
}
检查键盘是否具有完全访问权限。如果没有,我会显示一条消息。
我在我的键盘中使用了一个框架,它使用钥匙串来存储 UUID。不幸的是,我的键盘在尝试在此处写入该值时崩溃了:
2015-09-10 12:34:26.232 Keyboard[15504:3505665] *** Assertion failure in -[LUIKeychain writeToKeychain], /Projects/iOS/LUIFramework/LUIFramework/LUIKeychain.m:249
2015-09-10 12:34:26.233 Keyboard[15504:3505665] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Couldn't add the Keychain Item.'
该框架是我的,应该可以在所有苹果平台上运行,所以好消息是我可以修复它...有什么想法从哪里开始吗?
编辑: 请求的源代码:
我在这里用它来存储 UUID:
#import "LUIUUID.h"
#import "LUIKeychain.h"
#define kLUIUUIDKey @"LUIUUIDKey"
#define kLUISessionUUIDKey @"LUISessionUUIDKey"
@implementation LUIUUID
+ (NSString *)UUID {
LUIKeychain *keychain = [[LUIKeychain alloc] initWithIdentifier:kLUIUUIDKey accessGroup:nil];
NSString *uuid = [keychain objectForKey:(__bridge id)kSecAttrAccount];
if (!uuid || uuid.length == 0) {
uuid = [[NSUUID UUID] UUIDString];
[keychain setObject:uuid forKey:(__bridge id)(kSecAttrAccount)];
}
return uuid;
}
@end
这是图书馆:
#import "LUIKeychain.h"
#import <Security/Security.h>
/*
These are the default constants and their respective types,
available for the kSecClassGenericPassword Keychain Item class:
kSecAttrAccessGroup - CFStringRef
kSecAttrCreationDate - CFDateRef
kSecAttrModificationDate - CFDateRef
kSecAttrDescription - CFStringRef
kSecAttrComment - CFStringRef
kSecAttrCreator - CFNumberRef
kSecAttrType - CFNumberRef
kSecAttrLabel - CFStringRef
kSecAttrIsInvisible - CFBooleanRef
kSecAttrIsNegative - CFBooleanRef
kSecAttrAccount - CFStringRef
kSecAttrService - CFStringRef
kSecAttrGeneric - CFDataRef
See the header file Security/SecItem.h for more details.
*/
@interface LUIKeychain ()
@property (nonatomic, strong) NSMutableDictionary *genericPasswordQuery;
@property (nonatomic, strong) NSMutableDictionary *keychainItemData;
/*
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
to encapsulate the transition between what the detail view controller was expecting (NSString *)and what the
Keychain API expects as a validly constructed container class.
*/
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
// Updates the item in the keychain, or adds it if it doesn't exist.
- (void)writeToKeychain;
@end
@implementation LUIKeychain
- (instancetype)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *)accessGroup {
if (self = [super init]) {
// Begin Keychain search setup. The _genericPasswordQuery leverages the special user
// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
// items which may be included by the same application.
_genericPasswordQuery = [[NSMutableDictionary alloc] init];
[_genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[_genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
if (accessGroup != nil) {
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[_genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[_genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[_genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:_genericPasswordQuery];
NSMutableDictionary *dictionary = nil;
CFTypeRef outDictionary = (__bridge CFTypeRef)dictionary;
if (! SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, &outDictionary) == noErr) {
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];
// Add the generic attribute and the keychain access group.
[_keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
if (accessGroup != nil) {
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[_keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
}
}
else {
// load the saved data from Keychain.
_keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)(outDictionary)];
}
//[outDictionary release];
}
return self;
}
- (void)setObject:(id)inObject forKey:(id)key {
if (inObject == nil) return;
id currentObject = [_keychainItemData objectForKey:key];
if (![currentObject isEqual:inObject]) {
[_keychainItemData setObject:inObject forKey:key];
[self writeToKeychain];
}
}
- (id)objectForKey:(id)key {
return [_keychainItemData objectForKey:key];
}
- (void)resetKeychainItem {
OSStatus junk = noErr;
if (!_keychainItemData) {
_keychainItemData = [[NSMutableDictionary alloc] init];
}
else if (_keychainItemData) {
NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:_keychainItemData];
junk = SecItemDelete((__bridge CFDictionaryRef)tempDictionary);
NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
}
// Default attributes for keychain item.
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrAccount];
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrLabel];
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrDescription];
// Default data for keychain item.
[_keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData];
}
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert {
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for a SecItem.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the Generic Password keychain item class attribute.
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
// This is where to store sensitive data that should be encrypted.
NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
[returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
return returnDictionary;
}
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert {
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for the UI element.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the proper search key and class attribute.
[returnDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Acquire the password data from the attributes.
NSData *pData = NULL;
CFTypeRef passwordData = (__bridge CFTypeRef)pData;
OSStatus val = SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, &passwordData);
if (val == noErr) {
// Remove the search, class, and identifier key/value, we don't need them anymore.
[returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
NSData *resultData = CFBridgingRelease(passwordData);
// Add the password to the dictionary, converting from NSData to NSString.
//NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]
//encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
[returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
}
else {
// Don't do anything if nothing is found.
// NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
}
//[passwordData release];
return returnDictionary;
}
- (void)writeToKeychain {
NSDictionary *attr = NULL;
CFTypeRef attributes = (__bridge CFTypeRef)attr;
NSMutableDictionary *updateItem = NULL;
OSStatus result;
if (SecItemCopyMatching((__bridge CFDictionaryRef)_genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) {
// First we need the attributes from the Keychain.
updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)(attributes)];
// Second we need to add the appropriate search key/values.
[updateItem setObject:[_genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
// Lastly, we need to set up the updated attribute list being careful to remove the class.
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:_keychainItemData];
[tempCheck removeObjectForKey:(__bridge id)kSecClass];
#if TARGET_IPHONE_SIMULATOR
// Remove the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
//
// The access group attribute will be included in items returned by SecItemCopyMatching,
// which is why we need to remove it before updating the item.
[tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup];
#endif
// An implicit assumption is that you can only update a single item at a time.
result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
}
else {
// No previous item found; add the new one.
result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:_keychainItemData], NULL);
NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
}
}
@end
好的,我已经在键盘上禁用了完全访问权限。
我已经通过在写入钥匙串方法上放置 try/catch 并使用:
解决了这个问题- (BOOL)isFullAccessGranted {
return !![UIPasteboard generalPasteboard];
}
检查键盘是否具有完全访问权限。如果没有,我会显示一条消息。