-
Notifications
You must be signed in to change notification settings - Fork 13
/
PDKeychainBindingsController.m
242 lines (207 loc) · 8.9 KB
/
PDKeychainBindingsController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//
// PDKeychainBindingsController.m
// PDKeychainBindingsController
//
// Created by Carl Brown on 7/10/11.
// Copyright 2011 PDAgent, LLC. Released under MIT License.
//
// There's (understandably) a lot of controversy about how (and whether)
// to use the Singleton pattern for Cocoa. I am here because I'm
// trying to emulate existing Singleton (NSUserDefaults) behavior
//
// and I'm using the singleton methodology from
// http://www.duckrowing.com/2010/05/21/using-the-singleton-pattern-in-objective-c/
// because it seemed reasonable
#import "PDKeychainBindingsController.h"
#import <Security/Security.h>
static PDKeychainBindingsController *sharedInstance = nil;
@implementation PDKeychainBindingsController
#pragma mark -
#pragma mark Keychain Access
- (NSString*)serviceName {
return [[NSBundle mainBundle] bundleIdentifier];
}
- (NSString*)stringForKey:(NSString*)key {
OSStatus status;
#if TARGET_OS_IPHONE
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:(id)kCFBooleanTrue, kSecReturnData,
kSecClassGenericPassword, kSecClass,
key, kSecAttrAccount,
[self serviceName], kSecAttrService,
nil];
CFDataRef stringData = NULL;
status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)&stringData);
#else //OSX
//SecKeychainItemRef item = NULL;
UInt32 stringLength;
void *stringBuffer;
status = SecKeychainFindGenericPassword(NULL, (uint) [[self serviceName] lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [[self serviceName] UTF8String],
(uint) [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [key UTF8String],
&stringLength, &stringBuffer, NULL);
#endif
if(status) return nil;
#if TARGET_OS_IPHONE
NSString *string = [[[NSString alloc] initWithData:(id)stringData encoding:NSUTF8StringEncoding] autorelease];
CFRelease(stringData);
#else //OSX
NSString *string = [[[NSString alloc] initWithBytes:stringBuffer length:stringLength encoding:NSUTF8StringEncoding] autorelease];
SecKeychainItemFreeAttributesAndData(NULL, stringBuffer);
#endif
return string;
}
- (BOOL)storeString:(NSString*)string forKey:(NSString*)key {
if (!string) {
//Need to delete the Key
#if TARGET_OS_IPHONE
NSDictionary *spec = [NSDictionary dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword, kSecClass,
key, kSecAttrAccount,[self serviceName], kSecAttrService, nil];
return !SecItemDelete((CFDictionaryRef)spec);
#else //OSX
SecKeychainItemRef item = NULL;
OSStatus status = SecKeychainFindGenericPassword(NULL, (uint) [[self serviceName] lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [[self serviceName] UTF8String],
(uint) [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [key UTF8String],
NULL, NULL, &item);
if(status) return YES;
if(!item) return YES;
return !SecKeychainItemDelete(item);
#endif
} else {
#if TARGET_OS_IPHONE
NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *spec = [NSDictionary dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword, kSecClass,
key, kSecAttrAccount,[self serviceName], kSecAttrService, nil];
if(!string) {
return !SecItemDelete((CFDictionaryRef)spec);
}else if([self stringForKey:key]) {
NSDictionary *update = [NSDictionary dictionaryWithObject:stringData forKey:(id)kSecValueData];
return !SecItemUpdate((CFDictionaryRef)spec, (CFDictionaryRef)update);
}else{
NSMutableDictionary *data = [NSMutableDictionary dictionaryWithDictionary:spec];
[data setObject:stringData forKey:(id)kSecValueData];
return !SecItemAdd((CFDictionaryRef)data, NULL);
}
#else //OSX
SecKeychainItemRef item = NULL;
OSStatus status = SecKeychainFindGenericPassword(NULL, (uint) [[self serviceName] lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [[self serviceName] UTF8String],
(uint) [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [key UTF8String],
NULL, NULL, &item);
if(status) {
//NO such item. Need to add it
return !SecKeychainAddGenericPassword(NULL, (uint) [[self serviceName] lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [[self serviceName] UTF8String],
(uint) [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [key UTF8String],
(uint) [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding],[string UTF8String],
NULL);
}
if(item)
return !SecKeychainItemModifyAttributesAndData(item, NULL, (uint) [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [string UTF8String]);
else
return !SecKeychainAddGenericPassword(NULL, (uint) [[self serviceName] lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [[self serviceName] UTF8String],
(uint) [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [key UTF8String],
(uint) [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding],[string UTF8String],
NULL);
#endif
}
}
#pragma mark -
#pragma mark Singleton Stuff
+ (PDKeychainBindingsController *)sharedKeychainBindingsController
{
@synchronized (self) {
if (sharedInstance == nil) {
__unused id unused = [[self alloc] init]; // assignment not done here, see allocWithZone
}
}
return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone
{
@synchronized(self) {
if (sharedInstance == nil) {
sharedInstance = [super allocWithZone:zone];
return sharedInstance; // assignment and return on first allocation
}
}
return nil; //on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (oneway void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
- (NSUInteger)retainCount
{
return NSUIntegerMax; // This is sooo not zero
}
- (id)init
{
@synchronized(self) {
[super init];
return self;
}
}
#pragma mark -
#pragma mark Business Logic
- (PDKeychainBindings *) keychainBindings {
if (_keychainBindings == nil) {
_keychainBindings = [[PDKeychainBindings alloc] init];
}
if (_valueBuffer==nil) {
_valueBuffer = [[NSMutableDictionary alloc] init];
}
return _keychainBindings;
}
-(id) values {
if (_valueBuffer==nil) {
_valueBuffer = [[NSMutableDictionary alloc] init];
}
return _valueBuffer;
}
- (id)valueForKeyPath:(NSString *)keyPath {
NSRange firstSeven=NSMakeRange(0, 7);
if (NSEqualRanges([keyPath rangeOfString:@"values."],firstSeven)) {
//This is a values keyPath, so we need to check the keychain
NSString *subKeyPath = [keyPath stringByReplacingCharactersInRange:firstSeven withString:@""];
NSString *retrievedString = [self stringForKey:subKeyPath];
if (retrievedString) {
if (![_valueBuffer objectForKey:subKeyPath] || ![[_valueBuffer objectForKey:subKeyPath] isEqualToString:retrievedString]) {
//buffer has wrong value, need to update it
[_valueBuffer setValue:retrievedString forKey:subKeyPath];
}
}
}
return [super valueForKeyPath:keyPath];
}
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath {
NSRange firstSeven=NSMakeRange(0, 7);
if (NSEqualRanges([keyPath rangeOfString:@"values."],firstSeven)) {
//This is a values keyPath, so we need to check the keychain
NSString *subKeyPath = [keyPath stringByReplacingCharactersInRange:firstSeven withString:@""];
NSString *retrievedString = [self stringForKey:subKeyPath];
if (retrievedString) {
if (![value isEqualToString:retrievedString]) {
[self storeString:value forKey:subKeyPath];
}
if (![_valueBuffer objectForKey:subKeyPath] || ![[_valueBuffer objectForKey:subKeyPath] isEqualToString:value]) {
//buffer has wrong value, need to update it
[_valueBuffer setValue:value forKey:subKeyPath];
}
} else {
//First time to set it
[self storeString:value forKey:subKeyPath];
[_valueBuffer setValue:value forKey:subKeyPath];
}
}
[super setValue:value forKeyPath:keyPath];
}
@end