-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHCKeychainManager.m
183 lines (137 loc) · 6.16 KB
/
HCKeychainManager.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
//
// HCKeychainManager.m
// HCKeychainManager
//
// Created by Árpád Goretity on 08/10/16.
// Copyright © 2016 SciApps.io. All rights reserved.
//
#import "HCKeychainManager.h"
#import <Security/Security.h>
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
@interface HCKeychainManager ()
+ (NSMutableDictionary *)queryDictionaryForKey:(NSString *)key;
+ (void)atomicallyInvokeBlock:(void (^)(void))block;
@end
NS_ASSUME_NONNULL_END
@implementation HCKeychainManager
#warning TODO(H2CO3): insert @dynamic for properties
// @dynamic foo;
// @dynamic bar;
// @dynamic qux;
#pragma mark - Initialization
+ (void)initialize {
// This class is not to be subclassed, but for defensive
// programming reasons, we still use the superclass checking idiom.
if (self != HCKeychainManager.class) {
return;
}
// Generate accessors for each declared property
Class metaClass = object_getClass(self);
NSString *getterType = [NSString stringWithFormat:@"%s%s", @encode(id), @encode(SEL)];
NSString *setterType = [NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(SEL), @encode(id)];
NSArray<NSString *> *propertyNames = @[
#warning TODO(H2CO3): fill in with declared property names
// @"foo",
// @"bar",
// @"qux",
];
for (NSString *propertyName in propertyNames) {
NSString *getterName = propertyName;
NSString *setterName = [NSString stringWithFormat:@"set%@%@:",
[propertyName substringToIndex:1].uppercaseString,
[propertyName substringFromIndex:1]];
IMP getter = imp_implementationWithBlock(^(id selfPtr) {
return selfPtr[propertyName];
});
IMP setter = imp_implementationWithBlock(^(id selfPtr, id newValue) {
selfPtr[propertyName] = newValue;
});
class_addMethod(metaClass, NSSelectorFromString(getterName), getter, getterType.UTF8String);
class_addMethod(metaClass, NSSelectorFromString(setterName), setter, setterType.UTF8String);
}
}
#pragma mark - Public methods
+ (void)atomicallyInvokeBlock:(void (^)(void))block {
NSParameterAssert(block);
static NSLock *lock;
static dispatch_once_t token;
dispatch_once(&token, ^{
lock = [NSLock new];
});
[lock lock];
block();
[lock unlock];
}
+ (void)setObject:(id _Nullable)newValue forKeyedSubscript:(NSString *)key {
NSParameterAssert(key);
NSString *oldValue = self.self[key];
if (newValue) {
if (oldValue == nil) {
// Does not yet exist, so add to keychain
NSMutableDictionary *update = [self queryDictionaryForKey:key];
update[(__bridge id) kSecClass] = (__bridge id) kSecClassGenericPassword;
update[(__bridge id) kSecValueData] = [NSPropertyListSerialization dataWithPropertyList:newValue
format:NSPropertyListBinaryFormat_v1_0
options:kNilOptions
error:NULL];
[self atomicallyInvokeBlock:^{
SecItemAdd((__bridge CFDictionaryRef) update, NULL);
}];
} else if ([oldValue isEqual:newValue] == NO) {
// Exists but new value differs from old value: update item
NSMutableDictionary *query = [self queryDictionaryForKey:key];
NSMutableDictionary *update = [self queryDictionaryForKey:key];
query[(__bridge id) kSecClass] = (__bridge id) kSecClassGenericPassword;
update[(__bridge id) kSecValueData] = [NSPropertyListSerialization dataWithPropertyList:newValue
format:NSPropertyListBinaryFormat_v1_0
options:kNilOptions
error:NULL];
[self atomicallyInvokeBlock:^{
SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update);
}];
}
// otherwise: old value exists and is equal to new value - no need to do anything
} else {
if (oldValue) {
// New value is nil, but old value exists, so remove it from the keychain
NSMutableDictionary *query = [self queryDictionaryForKey:key];
query[(__bridge id) kSecClass] = (__bridge id) kSecClassGenericPassword;
[self atomicallyInvokeBlock:^{
SecItemDelete((__bridge CFDictionaryRef) query);
}];
}
// otherwise: setting nonexistent to nil, which is a no-op
}
}
+ (id _Nullable)objectForKeyedSubscript:(NSString *)key {
NSParameterAssert(key);
__block CFTypeRef data = NULL;
NSMutableDictionary *query = [self queryDictionaryForKey:key];
query[(__bridge id) kSecReturnData] = (__bridge id) kCFBooleanTrue;
query[(__bridge id) kSecClass] = (__bridge id) kSecClassGenericPassword;
__block OSStatus status;
[self atomicallyInvokeBlock:^{
status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &data);
}];
if (status != noErr) {
return nil;
}
if (data == NULL) {
return nil;
}
return [NSPropertyListSerialization propertyListWithData:CFBridgingRelease(data)
options:kNilOptions
format:NULL
error:NULL];
}
#pragma mark - Private methods
+ (NSMutableDictionary *)queryDictionaryForKey:(NSString *)key {
NSParameterAssert(key);
NSDictionary *dictionary = @{
(__bridge id) kSecAttrAccount: [key dataUsingEncoding:NSUTF8StringEncoding],
(__bridge id) kSecAttrService: NSBundle.mainBundle.bundleIdentifier,
};
return [dictionary mutableCopy];
}
@end