5555
5656@interface OTPAuthURL ()
5757
58- @property (strong , nonatomic , readwrite ) NSData *keychainItemRef;
5958@property (strong , nonatomic ) OTPGenerator *generator;
6059
6160// Initialize an OTPAuthURL with a dictionary of attributes from a keychain.
@@ -149,29 +148,12 @@ + (OTPAuthURL *)authURLWithURL:(NSURL *)url
149148 return authURL;
150149}
151150
152- + (OTPAuthURL *)authURLWithKeychainItemRef : (NSData *)data {
153- OTPAuthURL *authURL = nil ;
154- NSDictionary *query = @{
155- (__bridge id )kSecClass : (__bridge id )kSecClassGenericPassword ,
156- (__bridge id )kSecValuePersistentRef : data,
157- (__bridge id )kSecReturnAttributes : @YES ,
158- (__bridge id )kSecReturnData : @YES
159- };
160- CFDictionaryRef result = NULL ;
161- OSStatus status = SecItemCopyMatching ((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
162- if (status == noErr) {
163- authURL = [self authURLWithKeychainDictionary: (__bridge_transfer NSDictionary *)result];
164- [authURL setKeychainItemRef: data];
165- }
166- return authURL;
167- }
168-
169151+ (OTPAuthURL *)ease_authURLWithKeychainDictionary : (NSDictionary *)dict {
170152 OTPAuthURL *authURL = [self authURLWithKeychainDictionary: dict];
171153 if (authURL) {
172154 NSString *secAttrAccount = dict[(__bridge id )kSecAttrAccount ];
173- if (secAttrAccount. length > 0 ) {
174- authURL.ease_SecAttrAccount = secAttrAccount;
155+ if (![ secAttrAccount isEqualToString: authURL.name] ) {
156+ authURL.name = secAttrAccount;
175157 }
176158 }
177159 return authURL;
@@ -216,81 +198,63 @@ - (NSURL *)url {
216198 return nil ;
217199}
218200
219- - (BOOL )saveToKeychain {
220- NSString *urlString = [[self url ] absoluteString ];
221- NSData *urlData = [urlString dataUsingEncoding: NSUTF8StringEncoding];
222-
223- NSMutableDictionary *attributes =
224- [NSMutableDictionary dictionaryWithObject: urlData
225- forKey: (__bridge id )kSecAttrGeneric ];
226- OSStatus status = noErr;
227-
228- if ([self isInKeychain ] || self.ease_SecAttrAccount .length > 0 ) {
229- NSDictionary *query = @{(__bridge id )kSecClass : (__bridge id )kSecClassGenericPassword ,
230- (__bridge id )kSecValuePersistentRef : self.keychainItemRef };
231-
232- status = SecItemUpdate ((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
201+ - (NSDictionary *)keychainQuery {
202+ return @{(__bridge id )kSecClass : (__bridge id )kSecClassGenericPassword ,
203+ (__bridge id )kSecAttrAccount : self.name };
204+ }
233205
234- OTPDevLog (@" SecItemUpdate(%@ , %@ ) = %d " , query, attributes, (int )status);
235- } else {
206+ - (BOOL )saveToKeychain {
207+ NSString *urlString = [[self url ] absoluteString ];
208+ NSData *urlData = [urlString dataUsingEncoding: NSUTF8StringEncoding];
209+
210+ NSMutableDictionary *attributes = [[NSMutableDictionary alloc ] init ];
236211 attributes[(__bridge id )kSecClass ] = (__bridge id )kSecClassGenericPassword ;
237212 attributes[(__bridge id )kSecReturnPersistentRef ] = (id )kCFBooleanTrue ;
238- attributes[(__bridge id )kSecValueData ] = self.generator .secret ;
239213 attributes[(__bridge id )kSecAttrService ] = kOTPService ;
240- if (self.issuer .length > 0 ) {
241- attributes[(__bridge id )kSecAttrType ] = self.issuer ;
242- }
243214
244- CFDataRef ref = NULL ;
245-
246- // The name here has to be unique or else we will get a errSecDuplicateItem
247- // so if we have two items with the same name, we will just append a
248- // random number on the end until we get success. We will try at max of
249- // 1000 times so as to not hang in shut down.
250- // We do not display this name to the user, so anything will do.
251- NSString *name = self.name ;
252- for (int i = 0 ; i < 1000 ; i++) {
253- attributes[(__bridge id )kSecAttrAccount ] = name;
254- status = SecItemAdd ((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&ref);
255- if (status == errSecDuplicateItem) {
256- name = [NSString stringWithFormat: @" %@ .%ld " , self .name, random ()];
257- } else {
258- break ;
259- }
215+
216+ attributes[(__bridge id )kSecAttrGeneric ] = urlData;
217+ attributes[(__bridge id )kSecValueData ] = self.generator .secret ;
218+ if (self.issuer .length > 0 ) {
219+ attributes[(__bridge id )kSecAttrType ] = self.issuer ;
260220 }
261-
262- OTPDevLog (@" SecItemAdd(%@ , %@ ) = %d " , attributes, ref, (int )status);
263-
264- if (status == noErr) {
265- self.keychainItemRef = (__bridge_transfer NSData *)ref;
221+
222+ OSStatus status = noErr;
223+ CFDataRef ref = NULL ;
224+ status = SecItemAdd ((__bridge CFDictionaryRef)attributes, (CFTypeRef *)&ref);
225+ if (status == errSecDuplicateItem) {// 如果有同名的话,就直接覆盖
226+ [attributes removeObjectsForKeys: @[(__bridge id )kSecClass , (__bridge id )kSecReturnPersistentRef , (__bridge id )kSecAttrService ]];
227+ NSDictionary *query = [self keychainQuery ];
228+ status = SecItemUpdate ((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
266229 }
267- }
268-
269- return status == noErr;
230+ return status == noErr;
270231}
271232
272- - (BOOL )removeFromKeychain {
273- if (self.ease_SecAttrAccount .length > 0 ) {
274- return [SSKeychain deletePasswordForService: kOTPService account: self .ease_SecAttrAccount];
275- }
233+ - (BOOL )hotpUpdateToKeychain {
234+ NSString *urlString = [[self url ] absoluteString ];
235+ NSData *urlData = [urlString dataUsingEncoding: NSUTF8StringEncoding];
276236
277- if (![self isInKeychain ]) {
278- return NO ;
279- }
280- NSDictionary *query = @{(__bridge id )kSecClass : (__bridge id )kSecClassGenericPassword ,
281- (__bridge id )kSecValuePersistentRef : [self keychainItemRef ]};
282- OSStatus status = SecItemDelete ((__bridge CFDictionaryRef)query);
283-
284- OTPDevLog (@" SecItemDelete(%@ ) = %d " , query, (int )status);
285-
286- if (status == noErr) {
287- [self setKeychainItemRef: nil ];
288- }
289- return status == noErr;
237+ NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject: urlData forKey: (__bridge id )kSecAttrGeneric ];
238+ NSDictionary *query = [self keychainQuery ];
239+ OSStatus status = SecItemUpdate ((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributes);
240+
241+ OTPDevLog (@" SecItemUpdate(%@ , %@ ) = %d " , query, attributes, (int )status);
242+
243+ return status == noErr;
290244}
291245
292- - (BOOL )isInKeychain {
293- return self.keychainItemRef != nil ;
246+ - (BOOL )removeFromKeychain {
247+ if (self.name .length > 0 ) {
248+ NSDictionary *query = [self keychainQuery ];
249+ OSStatus status = SecItemDelete ((__bridge CFDictionaryRef)query);
250+ OTPDevLog (@" SecItemDelete(%@ ) = %d " , query, (int )status);
251+ if (status == noErr) {
252+ self.name = nil ;
253+ }
254+ return status == noErr;
255+ }else {
256+ return NO ;
257+ }
294258}
295259
296260- (void )generateNextOTPCode {
@@ -302,8 +266,8 @@ - (NSString*)checkCode {
302266}
303267
304268- (NSString *)description {
305- return [NSString stringWithFormat: @" <%@ %p > Name: %@ ref: %p checkCode: %@ " ,
306- [self class ], self , self .name, self .keychainItemRef, self . checkCode];
269+ return [NSString stringWithFormat: @" <%@ %p > Name: %@ checkCode: %@ " ,
270+ [self class ], self , self .name, self .checkCode];
307271}
308272
309273#pragma mark -
@@ -515,7 +479,7 @@ - (id)initWithName:(NSString *)name
515479
516480- (void )generateNextOTPCode {
517481 self.otpCode = [[self generator ] generateOTP ];
518- [self saveToKeychain ];
482+ [self hotpUpdateToKeychain ];
519483 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter ];
520484 [nc postNotificationName: OTPAuthURLDidGenerateNewOTPNotification object: self ];
521485}
0 commit comments