snej@0
|
1 |
//
|
snej@0
|
2 |
// MYKeychainItem.m
|
snej@0
|
3 |
// MYCrypto
|
snej@0
|
4 |
//
|
snej@0
|
5 |
// Created by Jens Alfke on 3/26/09.
|
snej@0
|
6 |
// Copyright 2009 Jens Alfke. All rights reserved.
|
snej@0
|
7 |
//
|
snej@0
|
8 |
|
snej@0
|
9 |
#import "MYKeychainItem.h"
|
snej@0
|
10 |
#import "MYCrypto_Private.h"
|
jens@26
|
11 |
#import "MYBERParser.h"
|
snej@0
|
12 |
#import "MYErrorUtils.h"
|
snej@0
|
13 |
|
snej@0
|
14 |
|
snej@0
|
15 |
NSString* const MYCSSMErrorDomain = @"CSSMErrorDomain";
|
snej@0
|
16 |
|
snej@0
|
17 |
|
snej@0
|
18 |
@implementation MYKeychainItem
|
snej@0
|
19 |
|
snej@0
|
20 |
|
jens@23
|
21 |
- (id) initWithKeychainItemRef: (MYKeychainItemRef)itemRef
|
snej@0
|
22 |
{
|
snej@0
|
23 |
Assert(itemRef!=NULL);
|
snej@0
|
24 |
self = [super init];
|
snej@0
|
25 |
if (self != nil) {
|
snej@0
|
26 |
_itemRef = itemRef;
|
snej@0
|
27 |
CFRetain(_itemRef);
|
jens@23
|
28 |
LogTo(INIT,@"%@, _itemRef=%@", [self class], itemRef);
|
jens@26
|
29 |
#if MYCRYPTO_USE_IPHONE_API
|
jens@26
|
30 |
_isPersistent = YES;
|
jens@26
|
31 |
#endif
|
snej@0
|
32 |
}
|
snej@0
|
33 |
return self;
|
snej@0
|
34 |
}
|
snej@0
|
35 |
|
snej@0
|
36 |
|
snej@0
|
37 |
@synthesize keychainItemRef=_itemRef;
|
snej@0
|
38 |
|
jens@26
|
39 |
#if MYCRYPTO_USE_IPHONE_API
|
jens@26
|
40 |
@synthesize isPersistent = _isPersistent;
|
jens@26
|
41 |
#endif
|
jens@26
|
42 |
|
snej@0
|
43 |
- (void) dealloc
|
snej@0
|
44 |
{
|
snej@0
|
45 |
if (_itemRef) CFRelease(_itemRef);
|
snej@0
|
46 |
[super dealloc];
|
snej@0
|
47 |
}
|
snej@0
|
48 |
|
snej@2
|
49 |
- (void) finalize
|
snej@2
|
50 |
{
|
snej@2
|
51 |
if (_itemRef) CFRelease(_itemRef);
|
snej@2
|
52 |
[super finalize];
|
snej@2
|
53 |
}
|
snej@2
|
54 |
|
snej@0
|
55 |
- (id) copyWithZone: (NSZone*)zone {
|
snej@0
|
56 |
// As keys are immutable, it's not necessary to make copies of them. This makes it more efficient
|
snej@0
|
57 |
// to use instances as NSDictionary keys or store them in NSSets.
|
snej@0
|
58 |
return [self retain];
|
snej@0
|
59 |
}
|
snej@0
|
60 |
|
snej@0
|
61 |
- (BOOL) isEqual: (id)obj {
|
snej@3
|
62 |
return (obj == self) ||
|
snej@3
|
63 |
([obj isKindOfClass: [MYKeychainItem class]] && CFEqual(_itemRef, [obj keychainItemRef]));
|
snej@0
|
64 |
}
|
snej@0
|
65 |
|
snej@0
|
66 |
- (NSUInteger) hash {
|
snej@0
|
67 |
return CFHash(_itemRef);
|
snej@0
|
68 |
}
|
snej@0
|
69 |
|
snej@1
|
70 |
- (NSString*) description {
|
snej@1
|
71 |
return $sprintf(@"%@[%p]", [self class], _itemRef); //FIX: Can we do anything better?
|
snej@1
|
72 |
}
|
snej@1
|
73 |
|
snej@1
|
74 |
|
snej@0
|
75 |
- (NSArray*) _itemList {
|
snej@0
|
76 |
return $array((id)_itemRef);
|
snej@0
|
77 |
}
|
snej@0
|
78 |
|
snej@0
|
79 |
|
snej@0
|
80 |
- (MYKeychain*) keychain {
|
snej@2
|
81 |
#if MYCRYPTO_USE_IPHONE_API
|
jens@26
|
82 |
return _isPersistent ? [MYKeychain defaultKeychain] : nil;
|
snej@0
|
83 |
#else
|
snej@0
|
84 |
MYKeychain *keychain = nil;
|
snej@0
|
85 |
SecKeychainRef keychainRef = NULL;
|
snej@0
|
86 |
if (check(SecKeychainItemCopyKeychain((SecKeychainItemRef)_itemRef, &keychainRef), @"SecKeychainItemCopyKeychain")) {
|
snej@0
|
87 |
if (keychainRef) {
|
snej@0
|
88 |
keychain = [[[MYKeychain alloc] initWithKeychainRef: keychainRef] autorelease];
|
snej@0
|
89 |
CFRelease(keychainRef);
|
snej@0
|
90 |
}
|
snej@0
|
91 |
}
|
snej@0
|
92 |
return keychain;
|
snej@0
|
93 |
#endif
|
snej@0
|
94 |
}
|
snej@0
|
95 |
|
snej@0
|
96 |
- (BOOL) removeFromKeychain {
|
snej@12
|
97 |
OSStatus err;
|
snej@2
|
98 |
#if MYCRYPTO_USE_IPHONE_API
|
jens@24
|
99 |
err = SecItemDelete((CFDictionaryRef) $dict( {(id)kSecValueRef, (id)_itemRef} ));
|
jens@26
|
100 |
if (!err)
|
jens@26
|
101 |
_isPersistent = NO;
|
snej@0
|
102 |
#else
|
snej@12
|
103 |
err = SecKeychainItemDelete((SecKeychainItemRef)_itemRef);
|
snej@14
|
104 |
if (err==errSecInvalidItemRef)
|
snej@14
|
105 |
return YES; // result for an item that's not in a keychain
|
snej@0
|
106 |
#endif
|
snej@14
|
107 |
return err==errSecItemNotFound || check(err, @"SecKeychainItemDelete");
|
snej@0
|
108 |
}
|
snej@0
|
109 |
|
snej@0
|
110 |
|
jens@26
|
111 |
/* (Not useful yet, as only password items have dates.)
|
jens@26
|
112 |
- (NSDate*) dateValueOfAttribute: (SecKeychainAttrType)attr {
|
jens@26
|
113 |
NSString *dateStr = [self stringValueOfAttribute: attr];
|
jens@26
|
114 |
if (dateStr.length == 0)
|
jens@26
|
115 |
return nil;
|
jens@26
|
116 |
NSDate *date = [MYBERGeneralizedTimeFormatter() dateFromString: dateStr];
|
jens@26
|
117 |
if (!date)
|
jens@26
|
118 |
Warn(@"MYKeychainItem: unable to parse date '%@'", dateStr);
|
jens@26
|
119 |
return date;
|
jens@26
|
120 |
}
|
jens@26
|
121 |
|
jens@26
|
122 |
- (void) setDateValue: (NSDate*)date ofAttribute: (SecKeychainAttrType)attr {
|
jens@26
|
123 |
NSString *timeStr = nil;
|
jens@26
|
124 |
if (date)
|
jens@26
|
125 |
timeStr = [MYBERGeneralizedTimeFormatter() stringFromDate: date];
|
jens@26
|
126 |
[self setValue: timeStr ofAttribute: attr];
|
jens@26
|
127 |
}
|
jens@26
|
128 |
|
jens@26
|
129 |
|
jens@26
|
130 |
- (NSDate*) creationDate {
|
jens@26
|
131 |
return [self dateValueOfAttribute: kSecCreationDateItemAttr];
|
jens@26
|
132 |
}
|
jens@26
|
133 |
|
jens@26
|
134 |
- (void) setCreationDate: (NSDate*)date {
|
jens@26
|
135 |
[self setDateValue: date ofAttribute: kSecCreationDateItemAttr];
|
jens@26
|
136 |
}
|
jens@26
|
137 |
|
jens@26
|
138 |
- (NSDate*) modificationDate {
|
jens@26
|
139 |
return [self dateValueOfAttribute: kSecModDateItemAttr];
|
jens@26
|
140 |
}
|
jens@26
|
141 |
|
jens@26
|
142 |
- (void) setModificationDate: (NSDate*)date {
|
jens@26
|
143 |
[self setDateValue: date ofAttribute: kSecModDateItemAttr];
|
jens@26
|
144 |
}
|
jens@26
|
145 |
*/
|
jens@26
|
146 |
|
jens@26
|
147 |
|
snej@0
|
148 |
#pragma mark -
|
snej@0
|
149 |
#pragma mark DATA / METADATA ACCESSORS:
|
snej@0
|
150 |
|
snej@0
|
151 |
|
snej@0
|
152 |
- (NSData*) _getContents: (OSStatus*)outError {
|
snej@0
|
153 |
NSData *contents = nil;
|
snej@2
|
154 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@0
|
155 |
#else
|
snej@0
|
156 |
UInt32 length = 0;
|
snej@0
|
157 |
void *bytes = NULL;
|
snej@0
|
158 |
*outError = SecKeychainItemCopyAttributesAndData(_itemRef, NULL, NULL, NULL, &length, &bytes);
|
snej@0
|
159 |
if (!*outError && bytes) {
|
snej@0
|
160 |
contents = [NSData dataWithBytes: bytes length: length];
|
snej@0
|
161 |
SecKeychainItemFreeAttributesAndData(NULL, bytes);
|
snej@0
|
162 |
}
|
snej@0
|
163 |
#endif
|
snej@0
|
164 |
return contents;
|
snej@0
|
165 |
}
|
snej@0
|
166 |
|
snej@0
|
167 |
+ (NSData*) _getAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
|
snej@0
|
168 |
NSData *value = nil;
|
snej@2
|
169 |
#if MYCRYPTO_USE_IPHONE_API
|
jens@24
|
170 |
NSDictionary *info = $dict( {(id)kSecValueRef, (id)item},
|
snej@0
|
171 |
{(id)kSecReturnAttributes, $true} );
|
snej@0
|
172 |
CFDictionaryRef attrs;
|
snej@0
|
173 |
if (!check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs), @"SecItemCopyMatching"))
|
snej@0
|
174 |
return nil;
|
snej@0
|
175 |
CFTypeRef rawValue = CFDictionaryGetValue(attrs,attr);
|
snej@0
|
176 |
value = rawValue ?[[(id)CFMakeCollectable(rawValue) retain] autorelease] :nil;
|
snej@0
|
177 |
CFRelease(attrs);
|
snej@0
|
178 |
|
snej@0
|
179 |
#else
|
snej@0
|
180 |
UInt32 format = kSecFormatUnknown;
|
snej@0
|
181 |
SecKeychainAttributeInfo info = {.count=1, .tag=(UInt32*)&attr, .format=&format};
|
snej@0
|
182 |
SecKeychainAttributeList *list = NULL;
|
snej@0
|
183 |
|
snej@0
|
184 |
if (check(SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)item, &info,
|
snej@0
|
185 |
NULL, &list, NULL, NULL),
|
snej@0
|
186 |
@"SecKeychainItemCopyAttributesAndData")) {
|
snej@0
|
187 |
if (list) {
|
snej@0
|
188 |
if (list->count == 1)
|
snej@0
|
189 |
value = [NSData dataWithBytes: list->attr->data
|
snej@0
|
190 |
length: list->attr->length];
|
snej@0
|
191 |
else if (list->count > 1)
|
snej@0
|
192 |
Warn(@"Multiple values for keychain item attribute");
|
snej@0
|
193 |
SecKeychainItemFreeAttributesAndData(list, NULL);
|
snej@0
|
194 |
}
|
snej@0
|
195 |
}
|
snej@0
|
196 |
#endif
|
snej@0
|
197 |
return value;
|
snej@0
|
198 |
}
|
snej@0
|
199 |
|
snej@0
|
200 |
+ (NSString*) _getStringAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
|
snej@0
|
201 |
NSData *value = [self _getAttribute: attr ofItem: item];
|
snej@0
|
202 |
if (!value) return nil;
|
snej@0
|
203 |
const char *bytes = value.bytes;
|
snej@0
|
204 |
size_t length = value.length;
|
snej@0
|
205 |
if (length>0 && bytes[length-1] == 0)
|
snej@0
|
206 |
length--; // Some values are null-terminated!?
|
snej@0
|
207 |
NSString *str = [[NSString alloc] initWithBytes: bytes length: length
|
snej@0
|
208 |
encoding: NSUTF8StringEncoding];
|
snej@0
|
209 |
if (!str)
|
snej@0
|
210 |
Warn(@"MYKeychainItem: Couldn't decode attr value as string");
|
snej@0
|
211 |
return [str autorelease];
|
snej@0
|
212 |
}
|
snej@0
|
213 |
|
snej@0
|
214 |
- (NSString*) stringValueOfAttribute: (SecKeychainAttrType)attr {
|
jens@26
|
215 |
#if MYCRYPTO_USE_IPHONE_API
|
jens@26
|
216 |
if (!self.isPersistent)
|
jens@26
|
217 |
return nil;
|
jens@26
|
218 |
#endif
|
snej@0
|
219 |
return [[self class] _getStringAttribute: attr ofItem: _itemRef];
|
snej@0
|
220 |
}
|
snej@0
|
221 |
|
snej@0
|
222 |
|
snej@0
|
223 |
+ (BOOL) _setAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item
|
snej@0
|
224 |
stringValue: (NSString*)stringValue
|
snej@0
|
225 |
{
|
snej@2
|
226 |
#if MYCRYPTO_USE_IPHONE_API
|
snej@0
|
227 |
id value = stringValue ?(id)stringValue :(id)[NSNull null];
|
jens@24
|
228 |
NSDictionary *query = $dict({(id)kSecValueRef, (id)item});
|
snej@0
|
229 |
NSDictionary *attrs = $dict({(id)attr, value});
|
snej@0
|
230 |
return check(SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attrs), @"SecItemUpdate");
|
snej@0
|
231 |
|
snej@0
|
232 |
#else
|
snej@0
|
233 |
NSData *data = [stringValue dataUsingEncoding: NSUTF8StringEncoding];
|
snej@0
|
234 |
SecKeychainAttribute attribute = {.tag=attr, .length=data.length, .data=(void*)data.bytes};
|
snej@0
|
235 |
SecKeychainAttributeList list = {.count=1, .attr=&attribute};
|
snej@0
|
236 |
return check(SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)item, &list, 0, NULL),
|
snej@0
|
237 |
@"SecKeychainItemModifyAttributesAndData");
|
snej@0
|
238 |
#endif
|
snej@0
|
239 |
}
|
snej@0
|
240 |
|
snej@0
|
241 |
- (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
|
snej@0
|
242 |
return [[self class] _setAttribute: attr ofItem: _itemRef stringValue: valueStr];
|
snej@0
|
243 |
}
|
snej@0
|
244 |
|
snej@0
|
245 |
|
snej@0
|
246 |
@end
|
snej@0
|
247 |
|
snej@0
|
248 |
|
snej@0
|
249 |
|
snej@0
|
250 |
|
snej@0
|
251 |
BOOL check(OSStatus err, NSString *what) {
|
snej@0
|
252 |
if (err) {
|
snej@2
|
253 |
#if !MYCRYPTO_USE_IPHONE_API
|
snej@0
|
254 |
if (err < -2000000000)
|
snej@0
|
255 |
return checkcssm(err,what);
|
snej@0
|
256 |
#endif
|
snej@0
|
257 |
Warn(@"MYCrypto error, %@: %@", what, MYErrorName(NSOSStatusErrorDomain,err));
|
jens@23
|
258 |
if (err==-50)
|
jens@23
|
259 |
[NSException raise: NSGenericException format: @"%@ failed with paramErr (-50)",what];
|
snej@0
|
260 |
return NO;
|
snej@0
|
261 |
} else
|
snej@0
|
262 |
return YES;
|
snej@0
|
263 |
}
|
snej@0
|
264 |
|
snej@2
|
265 |
#if !MYCRYPTO_USE_IPHONE_API
|
snej@0
|
266 |
BOOL checkcssm(CSSM_RETURN err, NSString *what) {
|
snej@0
|
267 |
if (err != CSSM_OK) {
|
snej@0
|
268 |
Warn(@"MYCrypto error, %@: %@", what, MYErrorName(MYCSSMErrorDomain,err));
|
snej@0
|
269 |
return NO;
|
snej@0
|
270 |
} else
|
snej@0
|
271 |
return YES;
|
snej@0
|
272 |
}
|
snej@0
|
273 |
#endif
|
snej@14
|
274 |
|
snej@14
|
275 |
|
snej@14
|
276 |
|
snej@14
|
277 |
/*
|
snej@14
|
278 |
Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
|
snej@14
|
279 |
|
snej@14
|
280 |
Redistribution and use in source and binary forms, with or without modification, are permitted
|
snej@14
|
281 |
provided that the following conditions are met:
|
snej@14
|
282 |
|
snej@14
|
283 |
* Redistributions of source code must retain the above copyright notice, this list of conditions
|
snej@14
|
284 |
and the following disclaimer.
|
snej@14
|
285 |
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
snej@14
|
286 |
and the following disclaimer in the documentation and/or other materials provided with the
|
snej@14
|
287 |
distribution.
|
snej@14
|
288 |
|
snej@14
|
289 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
snej@14
|
290 |
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
snej@14
|
291 |
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
|
snej@14
|
292 |
BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
snej@14
|
293 |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
snej@14
|
294 |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
snej@14
|
295 |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
snej@14
|
296 |
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
snej@14
|
297 |
*/
|