Browse Source

Enable iCloud Keychain

- improved README with iCloud keychain info and code-coloring
- enabled iCloud Keychain (user must be signed into iCloud and have
iCloud Keychain turned on
- added error code meanings
- removed pre-iOS 3 code
Jamie Perkins 11 năm trước cách đây
mục cha
commit
945b2430de
3 tập tin đã thay đổi với 314 bổ sung426 xóa
  1. 56 45
      README.md
  2. 1 1
      plugin.xml
  3. 257 380
      src/ios/SFHFKeychainUtils/SFHFKeychainUtils.m

+ 56 - 45
README.md

@@ -14,55 +14,66 @@ The plugin's JavaScript functions are called after creating the plugin object th
  
         var kc = new Keychain();
         kc.getForKey(win, fail, "some_key", "some_servicename");
+
+### iCloud keychain enabled
+
+iCloud keychain synchonizing is enabled, so the keychain will be mirrored across all devices *if* the user is signed in to iCloud (Settings > iCloud) and has iCloud keychain turned on (Settings > iCloud > Keychain)
+
+### Usage
         
 **Important:**
 
-        If you are saving a JSON string value in setForKey, for example after applying JSON.stringify on an object, you must escape the characters in that string, if not you cannot retrieve it using getForKey.        
-        
-        var obj = { foo: 'bar' };
-        var value = JSON.stringify(obj);
-        value = value 
-              .replace(/[\\]/g, '\\\\')
-              .replace(/[\"]/g, '\\\"')
-              .replace(/[\/]/g, '\\/')
-              .replace(/[\b]/g, '\\b')
-              .replace(/[\f]/g, '\\f')
-              .replace(/[\n]/g, '\\n')
-              .replace(/[\r]/g, '\\r')
-              .replace(/[\t]/g, '\\t');
+```js
+If you are saving a JSON string value in setForKey, for example after applying JSON.stringify on an object, you must escape the characters in that string, if not you cannot retrieve it using getForKey.        
+
+var obj = { foo: 'bar' };
+var value = JSON.stringify(obj);
+value = value 
+      .replace(/[\\]/g, '\\\\')
+      .replace(/[\"]/g, '\\\"')
+      .replace(/[\/]/g, '\\/')
+      .replace(/[\b]/g, '\\b')
+      .replace(/[\f]/g, '\\f')
+      .replace(/[\n]/g, '\\n')
+      .replace(/[\r]/g, '\\r')
+      .replace(/[\t]/g, '\\t');
+```
+
               
 See the **example** folder for example usage.
 
-        // Get a reference to the plugin first
-        var kc = new Keychain();
+```js
+// Get a reference to the plugin first
+var kc = new Keychain();
 
-        /*
-         Retrieves a value for a key and servicename.
-         
-         @param successCallback returns the value as the argument to the callback when successful
-         @param failureCallback returns the error string as the argument to the callback, for a failure
-         @param key the key to retrieve
-         @param servicename the servicename to use
-         */
-        kc.getForKey(successCallback, failureCallback, 'key', 'servicename');
-        
-        /*
-         Sets a value for a key and servicename.
-         
-         @param successCallback returns when successful
-         @param failureCallback returns the error string as the argument to the callback, for a failure
-         @param key the key to set
-         @param servicename the servicename to use
-         @param value the value to set
-         */
-        kc.setForKey(successCallback, failureCallback, 'key', 'servicename', 'value');
-        
-        /*
-         Removes a value for a key and servicename.
-         
-         @param successCallback returns when successful
-         @param failureCallback returns the error string as the argument to the callback
-         @param key the key to remove
-         @param servicename the servicename to use
-         */
-        kc.removeForKey(successCallback, failureCallback, 'key', 'servicename');
+/*
+ Retrieves a value for a key and servicename.
+ 
+ @param successCallback returns the value as the argument to the callback when successful
+ @param failureCallback returns the error string as the argument to the callback, for a failure
+ @param key the key to retrieve
+ @param servicename the servicename to use
+ */
+kc.getForKey(successCallback, failureCallback, 'key', 'servicename');
+
+/*
+ Sets a value for a key and servicename.
+ 
+ @param successCallback returns when successful
+ @param failureCallback returns the error string as the argument to the callback, for a failure
+ @param key the key to set
+ @param servicename the servicename to use
+ @param value the value to set
+ */
+kc.setForKey(successCallback, failureCallback, 'key', 'servicename', 'value');
+
+/*
+ Removes a value for a key and servicename.
+ 
+ @param successCallback returns when successful
+ @param failureCallback returns the error string as the argument to the callback
+ @param key the key to remove
+ @param servicename the servicename to use
+ */
+kc.removeForKey(successCallback, failureCallback, 'key', 'servicename');
+```

+ 1 - 1
plugin.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
     id="com.shazron.cordova.plugin.keychainutil"
-    version="1.0.0">
+    version="2.0.0">
 
     <name>KeyChain Plugin for Cordova iOS</name>
     <description>This plugin allows your app access to the iOS KeyChain from Cordova. See: https://developer.apple.com/library/mac/documentation/security/conceptual/keychainServConcepts/iPhoneTasks/iPhoneTasks.html</description>

+ 257 - 380
src/ios/SFHFKeychainUtils/SFHFKeychainUtils.m

@@ -27,408 +27,285 @@
 //  OTHER DEALINGS IN THE SOFTWARE.
 //
 
+/* 
+
+ Modified to take advantage of iCloud Keychain by Jamie Perkins
+ 09/24/2014
+ 
+ Error codes:
+ 
+ 0 - No error.
+ -4 - Function or operation not implemented.
+ -50 - One or more parameters passed to the function were not valid.
+ -108 - Failed to allocate memory.
+ -2000 - username or servicename nil
+ –25291 - No trust results are available.
+ –25293 - Authorization/Authentication failed.
+ –25299 - The item already exists.
+ –25300 - The item cannot be found.
+ –25308 - Interaction with the Security Server is not allowed.
+ -26275 - Unable to decode the provided data.
+ */
+
 #import "SFHFKeychainUtils.h"
 #import <Security/Security.h>
 
 static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
-@interface SFHFKeychainUtils (PrivateMethods)
-+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
-@end
-#endif
-
 @implementation SFHFKeychainUtils
 
-#if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
-
-+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
-	if (!username || !serviceName) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-		return nil;
-	}
-	
-	SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
-	
-	if (*error || !item) {
-		return nil;
-	}
-	
-	// from Advanced Mac OS X Programming, ch. 16
-  UInt32 length;
-  char *password;
-  SecKeychainAttribute attributes[8];
-  SecKeychainAttributeList list;
-	
-  attributes[0].tag = kSecAccountItemAttr;
-  attributes[1].tag = kSecDescriptionItemAttr;
-  attributes[2].tag = kSecLabelItemAttr;
-  attributes[3].tag = kSecModDateItemAttr;
-  
-  list.count = 4;
-  list.attr = attributes;
-  
-  OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
-	
-	if (status != noErr) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-		return nil;
-  }
-  
-	NSString *passwordString = nil;
-	
-	if (password != NULL) {
-		char passwordBuffer[1024];
-		
-		if (length > 1023) {
-			length = 1023;
-		}
-		strncpy(passwordBuffer, password, length);
-		
-		passwordBuffer[length] = '\0';
-		passwordString = [NSString stringWithCString:passwordBuffer];
-	}
-	
-	SecKeychainItemFreeContent(&list, password);
-  
-  CFRelease(item);
-  
-  return passwordString;
-}
-
-+ (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {	
-	if (!username || !password || !serviceName) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-		return;
-	}
-	
-	OSStatus status = noErr;
-	
-	SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
-	
-	if (*error && [*error code] != noErr) {
-		return;
-	}
-	
-	*error = nil;
-	
-	if (item) {
-		status = SecKeychainItemModifyAttributesAndData(item,
-                                                    NULL,
-                                                    strlen([password UTF8String]),
-                                                    [password UTF8String]);
-		
-		CFRelease(item);
-	}
-	else {
-		status = SecKeychainAddGenericPassword(NULL,                                     
-                                           strlen([serviceName UTF8String]), 
-                                           [serviceName UTF8String],
-                                           strlen([username UTF8String]),                        
-                                           [username UTF8String],
-                                           strlen([password UTF8String]),
-                                           [password UTF8String],
-                                           NULL);
-	}
-	
-	if (status != noErr) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-	}
-}
-
-+ (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
-	if (!username || !serviceName) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
-		return;
-	}
-	
-	*error = nil;
-	
-	SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
-	
-	if (*error && [*error code] != noErr) {
-		return;
-	}
-	
-	OSStatus status;
-	
-	if (item) {
-		status = SecKeychainItemDelete(item);
-		
-		CFRelease(item);
-	}
-	
-	if (status != noErr) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-	}
-}
-
-+ (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
-	if (!username || !serviceName) {
-		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-		return nil;
-	}
-	
-	*error = nil;
-  
-	SecKeychainItemRef item;
-	
-	OSStatus status = SecKeychainFindGenericPassword(NULL,
-                                                   strlen([serviceName UTF8String]),
-                                                   [serviceName UTF8String],
-                                                   strlen([username UTF8String]),
-                                                   [username UTF8String],
-                                                   NULL,
-                                                   NULL,
-                                                   &item);
-	
-	if (status != noErr) {
-		if (status != errSecItemNotFound) {
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-		}
-		
-		return nil;		
-	}
-	
-	return item;
-}
-
-#else
 
 + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
-	if (!username || !serviceName) {
-		if (error != nil) {
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-		}
-		return nil;
-	}
-	
-	if (error != nil) {
-		*error = nil;
-	}
-  
-	// Set up a query dictionary with the base query attributes: item type (generic), username, and service
-	
-	NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];
-	NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClassGenericPassword, username, serviceName, nil];
-	
-	NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
-	
-	// First do a query for attributes, in case we already have a Keychain item with no password data set.
-	// One likely way such an incorrect item could have come about is due to the previous (incorrect)
-	// version of this code (which set the password as a generic attribute instead of password data).
-	
-	NSDictionary *attributeResult = NULL;
-	NSMutableDictionary *attributeQuery = [query mutableCopy];
-	[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge id) kSecReturnAttributes];
-	OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery, (void*) &attributeResult);
-	
-	
-	if (status != noErr) {
-		// No existing item found--simply return nil for the password
-		if (error != nil && status != errSecItemNotFound) {
-			//Only return an error if a real exception happened--not simply for "not found."
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-		}
-		
-		return nil;
-	}
-	
-	// We have an existing item, now query for the password data associated with it.
-	
-	NSData *resultData = nil;
-	NSMutableDictionary *passwordQuery = [query mutableCopy];
-	[passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge id) kSecReturnData];
-  
-	status = SecItemCopyMatching((__bridge CFDictionaryRef) passwordQuery, (void*) &resultData);
-	
-	
-	if (status != noErr) {
-		if (status == errSecItemNotFound) {
-			// We found attributes for the item previously, but no password now, so return a special error.
-			// Users of this API will probably want to detect this error and prompt the user to
-			// re-enter their credentials.  When you attempt to store the re-entered credentials
-			// using storeUsername:andPassword:forServiceName:updateExisting:error
-			// the old, incorrect entry will be deleted and a new one with a properly encrypted
-			// password will be added.
-			if (error != nil) {
-				*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
-			}
-		}
-		else {
-			// Something else went wrong. Simply return the normal Keychain API error code.
-			if (error != nil) {
-				*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-			}
-		}
-		
-		return nil;
-	}
-  
-	NSString *password = nil;	
-  
-	if (resultData) {
-		password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
-	}
-	else {
-		// There is an existing item, but we weren't able to get password data for it for some reason,
-		// Possibly as a result of an item being incorrectly entered by the previous code.
-		// Set the -1999 error so the code above us can prompt the user again.
-		if (error != nil) {
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
-		}
-	}
-  
-	return password;
+    if (!username || !serviceName) {
+        if (error != nil) {
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
+        }
+        return nil;
+    }
+    
+    if (error != nil) {
+        *error = nil;
+    }
+    
+    // Set up a query dictionary with the base query attributes: item type (generic), username, and service
+    NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *)
+                     kSecClass,
+                     kSecAttrAccount,
+                     kSecAttrService,
+                     kSecAttrSynchronizable,
+                     nil];
+    
+    NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *)
+                        kSecClassGenericPassword,
+                        username,
+                        serviceName,
+                        kCFBooleanTrue,
+                        nil];
+    
+    NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];
+    
+    // First do a query for attributes, in case we already have a Keychain item with no password data set.
+    // One likely way such an incorrect item could have come about is due to the previous (incorrect)
+    // version of this code (which set the password as a generic attribute instead of password data).
+    
+    NSDictionary *attributeResult = NULL;
+    NSMutableDictionary *attributeQuery = [query mutableCopy];
+    [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge id) kSecReturnAttributes];
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery, (void*) &attributeResult);
+    
+    
+    if (status != noErr) {
+        // No existing item found--simply return nil for the password
+        if (error != nil && status != errSecItemNotFound) {
+            //Only return an error if a real exception happened--not simply for "not found."
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
+        }
+        
+        return nil;
+    }
+    
+    // We have an existing item, now query for the password data associated with it.
+    
+    NSData *resultData = nil;
+    NSMutableDictionary *passwordQuery = [query mutableCopy];
+    [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge id) kSecReturnData];
+    
+    status = SecItemCopyMatching((__bridge CFDictionaryRef) passwordQuery, (void*) &resultData);
+    
+    
+    if (status != noErr) {
+        if (status == errSecItemNotFound) {
+            // We found attributes for the item previously, but no password now, so return a special error.
+            // Users of this API will probably want to detect this error and prompt the user to
+            // re-enter their credentials.  When you attempt to store the re-entered credentials
+            // using storeUsername:andPassword:forServiceName:updateExisting:error
+            // the old, incorrect entry will be deleted and a new one with a properly encrypted
+            // password will be added.
+            if (error != nil) {
+                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
+            }
+        }
+        else {
+            // Something else went wrong. Simply return the normal Keychain API error code.
+            if (error != nil) {
+                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
+            }
+        }
+        
+        return nil;
+    }
+    
+    NSString *password = nil;
+    
+    if (resultData) {
+        password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
+    }
+    else {
+        // There is an existing item, but we weren't able to get password data for it for some reason,
+        // Possibly as a result of an item being incorrectly entered by the previous code.
+        // Set the -1999 error so the code above us can prompt the user again.
+        if (error != nil) {
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
+        }
+    }
+    
+    return password;
 }
 
-+ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error 
-{		
-	if (!username || !password || !serviceName) 
-  {
-		if (error != nil) 
-    {
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-		}
-		return NO;
-	}
-	
-	// See if we already have a password entered for these credentials.
-	NSError *getError = nil;
-	NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
-  
-	if ([getError code] == -1999) 
-  {
-		// There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
-		// Delete the existing item before moving on entering a correct one.
++ (BOOL) storeUsername: (NSString *)username andPassword: (NSString *)password forServiceName: (NSString *)serviceName updateExisting: (BOOL)updateExisting error: (NSError **)error {
     
-		getError = nil;
-		
-		[self deleteItemForUsername: username andServiceName: serviceName error: &getError];
+    if (!username || !password || !serviceName) {
+        if (error != nil) {
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
+        }
+        return NO;
+    }
     
-		if ([getError code] != noErr) 
-    {
-			if (error != nil) 
-      {
-				*error = getError;
-			}
-			return NO;
-		}
-	}
-	else if ([getError code] != noErr) 
-  {
-		if (error != nil) 
-    {
-			*error = getError;
-		}
-		return NO;
-	}
-	
-	if (error != nil) 
-  {
-		*error = nil;
-	}
-	
-	OSStatus status = noErr;
-  
-	if (existingPassword) 
-  {
-		// We have an existing, properly entered item with a password.
-		// Update the existing item.
-		
-		if (![existingPassword isEqualToString:password] && updateExisting) 
-    {
-			//Only update if we're allowed to update existing.  If not, simply do nothing.
-			
-			NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClass,
-                        kSecAttrService, 
-                        kSecAttrLabel, 
-                        kSecAttrAccount, 
-                        nil];
-			
-			NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClassGenericPassword,
-                           serviceName,
-                           serviceName,
-                           username,
-                           nil];
-			
-			NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
-			
-			status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge NSString *) kSecValueData]);
-		}
-	}
-	else 
-  {
-		// No existing entry (or an existing, improperly entered, and therefore now
-		// deleted, entry).  Create a new entry.
-		
-		NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClass,
-                      kSecAttrService, 
-                      kSecAttrLabel, 
-                      kSecAttrAccount, 
-                      kSecValueData, 
-                      nil];
-		
-		NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClassGenericPassword,
-                         serviceName,
-                         serviceName,
-                         username,
-                         [password dataUsingEncoding: NSUTF8StringEncoding],
+    // See if we already have a password entered for these credentials.
+    NSError *getError = nil;
+    NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
+    
+    if ([getError code] == -1999)  {
+        // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
+        // Delete the existing item before moving on entering a correct one.
+        
+        getError = nil;
+        
+        [self deleteItemForUsername: username andServiceName: serviceName error: &getError];
+        
+        if ([getError code] != noErr) {
+            if (error != nil) {
+                *error = getError;
+            }
+            return NO;
+        }
+    }
+    else if ([getError code] != noErr) {
+        if (error != nil) {
+            *error = getError;
+        }
+        return NO;
+    }
+    
+    if (error != nil) {
+        *error = nil;
+    }
+    
+    OSStatus status = noErr;
+    
+    if (existingPassword) {
+        // We have an existing, properly entered item with a password.
+        // Update the existing item.
+        
+        if (![existingPassword isEqualToString:password] && updateExisting) {
+            //Only update if we're allowed to update existing.  If not, simply do nothing.
+            
+            NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClass,
+                             kSecAttrService,
+                             kSecAttrLabel,
+                             kSecAttrAccount,
+                             kSecAttrSynchronizable,
+                             nil];
+            
+            NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClassGenericPassword,
+                                serviceName,
+                                serviceName,
+                                username,
+                                kCFBooleanTrue,
+                                nil];
+            
+            NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
+            
+            status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge NSString *) kSecValueData]);
+        }
+    }
+    else {
+        // No existing entry (or an existing, improperly entered, and therefore now
+        // deleted, entry).  Create a new entry.
+        
+        NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *)
+                         kSecClass,
+                         kSecAttrService,
+                         kSecAttrLabel,
+                         kSecAttrAccount,
+                         kSecValueData,
+                         kSecAttrSynchronizable,
                          nil];
-		
-		NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
+        
+        NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *)
+                            kSecClassGenericPassword,
+                            serviceName,
+                            serviceName,
+                            username,
+                            [password dataUsingEncoding: NSUTF8StringEncoding],
+                            kCFBooleanTrue,
+                            nil];
+        
+        NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
+        
+        status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
+    }
+    NSLog(@"save keychain status: %d", (int)status);
     
-		status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
-	}
-	
-	if (status != noErr) 
-  {
-		// Something went wrong with adding the new item. Return the Keychain error code.
-		if (error != nil) {
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-		}
+    if (status != noErr) {
+        // Something went wrong with adding the new item. Return the Keychain error code.
+        if (error != nil) {
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
+        }
+        
+        return NO;
+    }
     
-    return NO;
-	}
-  
-  return YES;
+    return YES;
 }
 
 + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error 
 {
-	if (!username || !serviceName) 
-  {
-		if (error != nil) 
+    if (!username || !serviceName) 
+    {
+        if (error != nil) 
+        {
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -
+                      000 userInfo: nil];
+        }
+        return NO;
+    }
+    
+    if (error != nil) 
+    {
+        *error = nil;
+    }
+    
+    NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *)
+                     kSecClass,
+                     kSecAttrAccount,
+                     kSecAttrService,
+                     kSecReturnAttributes,
+                     kSecAttrSynchronizable,
+                     nil];
+    
+    NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *)
+                        kSecClassGenericPassword,
+                        username,
+                        serviceName,
+                        kCFBooleanTrue,
+                        kCFBooleanTrue,
+                        nil];
+    
+    NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
+    
+    OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);
+    
+    if (status != noErr) 
     {
-			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
-		}
-		return NO;
-	}
-	
-	if (error != nil) 
-  {
-		*error = nil;
-	}
-  
-	NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];
-	NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];
-	
-	NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];
-	
-	OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);
-	
-	if (status != noErr) 
-  {
-	  if (error != nil) {
-		  *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
-	  }
+        if (error != nil) {
+            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
+        }
+        
+        return NO;
+    }
     
-    return NO;
-	}
-  
-  return YES;
+    return YES;
 }
 
-#endif
-
 @end