Working on export/import of symmetric keys, and passphrase entry. Not ready for release quite yet.
5 // Created by Jens Alfke on 3/26/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYKeychainItem.h"
10 #import "MYCrypto_Private.h"
11 #import "MYErrorUtils.h"
14 NSString* const MYCSSMErrorDomain = @"CSSMErrorDomain";
17 @implementation MYKeychainItem
20 - (id) initWithKeychainItemRef: (MYKeychainItemRef)itemRef;
22 Assert(itemRef!=NULL);
32 @synthesize keychainItemRef=_itemRef;
36 if (_itemRef) CFRelease(_itemRef);
42 if (_itemRef) CFRelease(_itemRef);
46 - (id) copyWithZone: (NSZone*)zone {
47 // As keys are immutable, it's not necessary to make copies of them. This makes it more efficient
48 // to use instances as NSDictionary keys or store them in NSSets.
52 - (BOOL) isEqual: (id)obj {
53 return (obj == self) ||
54 ([obj isKindOfClass: [MYKeychainItem class]] && CFEqual(_itemRef, [obj keychainItemRef]));
58 return CFHash(_itemRef);
61 - (NSString*) description {
62 return $sprintf(@"%@[%p]", [self class], _itemRef); //FIX: Can we do anything better?
66 - (NSArray*) _itemList {
67 return $array((id)_itemRef);
70 #if MYCRYPTO_USE_IPHONE_API
71 - (CFDictionaryRef) asQuery {
72 return (CFDictionaryRef) $dict( {(id)kSecClass, (id)kSecClassKey},//FIX
73 {(id)kSecMatchItemList, self._itemList} );
78 - (MYKeychain*) keychain {
79 #if MYCRYPTO_USE_IPHONE_API
80 return [MYKeychain defaultKeychain];
82 MYKeychain *keychain = nil;
83 SecKeychainRef keychainRef = NULL;
84 if (check(SecKeychainItemCopyKeychain((SecKeychainItemRef)_itemRef, &keychainRef), @"SecKeychainItemCopyKeychain")) {
86 keychain = [[[MYKeychain alloc] initWithKeychainRef: keychainRef] autorelease];
87 CFRelease(keychainRef);
94 - (BOOL) removeFromKeychain {
96 #if MYCRYPTO_USE_IPHONE_API
97 err = SecItemDelete(self.asQuery);
99 err = SecKeychainItemDelete((SecKeychainItemRef)_itemRef);
101 return err==errSecItemNotFound || check(err, @"SecKeychainItemDelete");
106 #pragma mark DATA / METADATA ACCESSORS:
109 - (NSData*) _getContents: (OSStatus*)outError {
110 NSData *contents = nil;
111 #if MYCRYPTO_USE_IPHONE_API
115 *outError = SecKeychainItemCopyAttributesAndData(_itemRef, NULL, NULL, NULL, &length, &bytes);
116 if (!*outError && bytes) {
117 contents = [NSData dataWithBytes: bytes length: length];
118 SecKeychainItemFreeAttributesAndData(NULL, bytes);
124 + (NSData*) _getAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
126 #if MYCRYPTO_USE_IPHONE_API
127 NSDictionary *info = $dict( {(id)kSecClass, (id)kSecClassKey},
128 {(id)kSecMatchItemList, $array((id)item)},
129 {(id)kSecReturnAttributes, $true} );
130 CFDictionaryRef attrs;
131 if (!check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs), @"SecItemCopyMatching"))
133 CFTypeRef rawValue = CFDictionaryGetValue(attrs,attr);
134 value = rawValue ?[[(id)CFMakeCollectable(rawValue) retain] autorelease] :nil;
138 UInt32 format = kSecFormatUnknown;
139 SecKeychainAttributeInfo info = {.count=1, .tag=(UInt32*)&attr, .format=&format};
140 SecKeychainAttributeList *list = NULL;
142 if (check(SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)item, &info,
143 NULL, &list, NULL, NULL),
144 @"SecKeychainItemCopyAttributesAndData")) {
146 if (list->count == 1)
147 value = [NSData dataWithBytes: list->attr->data
148 length: list->attr->length];
149 else if (list->count > 1)
150 Warn(@"Multiple values for keychain item attribute");
151 SecKeychainItemFreeAttributesAndData(list, NULL);
158 + (NSString*) _getStringAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
159 NSData *value = [self _getAttribute: attr ofItem: item];
160 if (!value) return nil;
161 const char *bytes = value.bytes;
162 size_t length = value.length;
163 if (length>0 && bytes[length-1] == 0)
164 length--; // Some values are null-terminated!?
165 NSString *str = [[NSString alloc] initWithBytes: bytes length: length
166 encoding: NSUTF8StringEncoding];
168 Warn(@"MYKeychainItem: Couldn't decode attr value as string");
169 return [str autorelease];
172 - (NSString*) stringValueOfAttribute: (SecKeychainAttrType)attr {
173 return [[self class] _getStringAttribute: attr ofItem: _itemRef];
177 + (BOOL) _setAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item
178 stringValue: (NSString*)stringValue
180 #if MYCRYPTO_USE_IPHONE_API
181 id value = stringValue ?(id)stringValue :(id)[NSNull null];
182 NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey},
183 {(id)kSecAttrKeyType, (id)attr},
184 {(id)kSecMatchItemList, $array((id)item)});
185 NSDictionary *attrs = $dict({(id)attr, value});
186 return check(SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attrs), @"SecItemUpdate");
189 NSData *data = [stringValue dataUsingEncoding: NSUTF8StringEncoding];
190 SecKeychainAttribute attribute = {.tag=attr, .length=data.length, .data=(void*)data.bytes};
191 SecKeychainAttributeList list = {.count=1, .attr=&attribute};
192 return check(SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)item, &list, 0, NULL),
193 @"SecKeychainItemModifyAttributesAndData");
197 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
198 return [[self class] _setAttribute: attr ofItem: _itemRef stringValue: valueStr];
207 BOOL check(OSStatus err, NSString *what) {
209 #if !MYCRYPTO_USE_IPHONE_API
210 if (err < -2000000000)
211 return checkcssm(err,what);
213 Warn(@"MYCrypto error, %@: %@", what, MYErrorName(NSOSStatusErrorDomain,err));
219 #if !MYCRYPTO_USE_IPHONE_API
220 BOOL checkcssm(CSSM_RETURN err, NSString *what) {
221 if (err != CSSM_OK) {
222 Warn(@"MYCrypto error, %@: %@", what, MYErrorName(MYCSSMErrorDomain,err));