Преглед на файлове

Ported Keychain plugin to Cordova.

shazron преди 13 години
ревизия
115abe864c
променени са 6 файла, в които са добавени 985 реда и са изтрити 0 реда
  1. 54 0
      README.md
  2. 62 0
      SAiOSKeychainPlugin.h
  3. 126 0
      SAiOSKeychainPlugin.js
  4. 521 0
      SAiOSKeychainPlugin.m
  5. 125 0
      index.html
  6. 97 0
      master.css

+ 54 - 0
README.md

@@ -0,0 +1,54 @@
+# Cordova Keychain Plugin #
+by Shazron Abdullah
+
+## Adding the Plugin to your project ##
+
+Using this plugin requires [iOS Cordova](http://github.com/apache/incubator-cordova-ios) and Xcode 4.
+
+1. Make sure your Cordova Xcode project has been [updated for Cordova 1.6.0](https://github.com/apache/incubator-cordova-ios/blob/master/guides/Cordova%20Plugin%20Upgrade%20Guide.md)
+2. Add the .h and .m files to your Plugins folder in your project (as a Group "yellow folder" not a Reference "blue folder")
+3. Add the .js files to your "www" folder on disk, and add reference(s) to the .js files as <script> tags in your html file(s)
+4. In **Cordova.plist** (1.5.0 or greater) or **PhoneGap.plist** (1.4.1 or lesser), under the **Plugins** section, add an idential key and value of **"SAiOSKeychainPlugin"**
+5. Add the **"Security.framework"** to your project's Target, in the **Build Phase** tab - **Link Binary with Libraries**
+
+
+## RELEASE NOTES ##
+
+### 20120709 ###
+
+* Updated for Cordova
+
+### 20101105 ###
+* Initial release
+* See the .js file for API docs, and the KeychainPlugin-Host/www/index.html for sample code
+
+## BUGS AND CONTRIBUTIONS ##
+
+Patches welcome! Send a pull request. Since this is not a part of Cordova Core (which requires an Apache iCLA), this should be easier.
+
+Post issues in the [PhoneGap Google Groups](http://groups.google.com/group/phonegap), include in the subject heading - "KeychainPlugin" or on [Github](http://github.com/phonegap/phonegap-plugins/issues)
+(preferred)
+
+## LICENSE ##
+
+SFHFKeychainUtils code by:
+  Created by Buzz Andersen on 10/20/08.
+  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
+  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
+
+The rest:
+
+Copyright 2012 Shazron Abdullah
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+

+ 62 - 0
SAiOSKeychainPlugin.h

@@ -0,0 +1,62 @@
+//
+//  SAiOSPaypalPlugin.h
+//  Keychain Plugin for Cordova
+//
+//  Created by shazron on 10-11-05.
+//  Copyright 2012 Shazron Abdullah. All rights reserved.
+
+#import <UIKit/UIKit.h>
+#import <Foundation/Foundation.h>
+#ifdef CORDOVA_FRAMEWORK
+#import <Cordova/CDVPlugin.h>
+#else
+#import "CDVlugin.h"
+#endif
+
+@interface SAiOSKeychainPlugin : CDVPlugin {
+}
+
+- (void) getForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) setForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+- (void) removeForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
+
+@end
+
+//
+//  SFHFKeychainUtils.h
+//
+//  Created by Buzz Andersen on 10/20/08.
+//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
+//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+@interface SFHFKeychainUtils : NSObject {
+
+}
+
++ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
++ (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
++ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
+
+@end

+ 126 - 0
SAiOSKeychainPlugin.js

@@ -0,0 +1,126 @@
+// //////////////////////////////////////
+// Keychain PhoneGap Plugin
+// by Shazron Abdullah
+// Nov 5th 2010
+// 
+
+
+// ///////////////////
+(function(){
+// ///////////////////
+
+// get local ref to global PhoneGap/Cordova/cordova object for exec function
+var cordovaRef = window.PhoneGap || window.Cordova || window.cordova; // old to new fallbacks
+
+/**
+ * Constructor
+ */
+function SAiOSKeychainPlugin()
+{
+	this._getCallbacks = {};
+	this._setCallbacks = {};
+	this._removeCallbacks = {};
+}
+
+//MARK: Get
+
+SAiOSKeychainPlugin.prototype._onGetCallbackSuccess = function(key, value)
+{
+	if (this._getCallbacks[key] && this._getCallbacks[key].onSuccess) {
+		this._getCallbacks[key].onSuccess(key, value);
+	}
+	delete this._getCallbacks[key];
+}
+
+SAiOSKeychainPlugin.prototype._onGetCallbackFail = function(key, error)
+{
+	if (this._getCallbacks[key] && this._getCallbacks[key].onFail) {
+		this._getCallbacks[key].onFail(key, error);
+	}
+	delete this._getCallbacks[key];
+}
+
+
+SAiOSKeychainPlugin.prototype.getForKey = function(key, servicename, onSuccess, onFail)
+{
+	this._getCallbacks[key] = { onSuccess:onSuccess, onFail:onFail };
+	
+	cordovaRef.exec("SAiOSKeychainPlugin.getForKey", key, servicename);
+}
+
+//MARK: Set
+
+SAiOSKeychainPlugin.prototype._onSetCallbackSuccess = function(key)
+{
+	if (this._setCallbacks[key] && this._setCallbacks[key].onSuccess) {
+		this._setCallbacks[key].onSuccess(key);
+	}
+	delete this._setCallbacks[key];
+}
+
+SAiOSKeychainPlugin.prototype._onSetCallbackFail = function(key, error)
+{
+	if (this._setCallbacks[key] && this._setCallbacks[key].onFail) {
+		this._setCallbacks[key].onFail(key, error);
+	}
+	delete this._setCallbacks[key];
+}
+
+SAiOSKeychainPlugin.prototype.setForKey = function(key, value, servicename, onSuccess, onFail)
+{
+	this._setCallbacks[key] = { onSuccess:onSuccess, onFail:onFail };
+	
+	cordovaRef.exec("SAiOSKeychainPlugin.setForKey", key, value, servicename);
+}
+
+//MARK: Remove
+
+SAiOSKeychainPlugin.prototype._onRemoveCallbackSuccess = function(key)
+{
+	if (this._removeCallbacks[key] && this._removeCallbacks[key].onSuccess) {
+		this._removeCallbacks[key].onSuccess(key);
+	}
+	delete this._removeCallbacks[key];
+}
+
+SAiOSKeychainPlugin.prototype._onRemoveCallbackFail = function(key, error)
+{
+	if (this._removeCallbacks[key] && this._removeCallbacks[key].onFail) {
+		this._removeCallbacks[key].onFail(key, error);
+	}
+	delete this._removeCallbacks[key];
+}
+
+SAiOSKeychainPlugin.prototype.removeForKey = function(key, servicename, onSuccess, onFail)
+{
+	this._removeCallbacks[key] = { onSuccess:onSuccess, onFail:onFail };
+	
+	cordovaRef.exec("SAiOSKeychainPlugin.removeForKey", key, servicename);
+}
+
+//MARK: Install
+
+SAiOSKeychainPlugin.install = function()
+{
+	if ( !window.plugins ) {
+		window.plugins = {};
+	} 
+	if ( !window.plugins.keychain ) {
+		window.plugins.keychain = new SAiOSKeychainPlugin();
+	}
+}
+
+/**
+ * Add to Cordova constructor
+ */
+if (cordovaRef && cordovaRef.addConstructor) {
+	cordovaRef.addConstructor(SAiOSKeychainPlugin.install);
+} else {
+	console.log("Keychain Cordova Plugin could not be installed.");
+	return null;
+}
+
+// ///////////////////
+})();
+// ///////////////////
+

+ 521 - 0
SAiOSKeychainPlugin.m

@@ -0,0 +1,521 @@
+//
+//  SAiOSPaypalPlugin.m
+//  Cordova Plugin for Cordova
+//
+//  Created by shazron on 10-11-05.
+//  Copyright 2010 Shazron Abdullah. All rights reserved.
+
+#import "SAiOSKeychainPlugin.h"
+
+@implementation SAiOSKeychainPlugin
+
+-(CDVPlugin*) initWithWebView:(UIWebView*)theWebView
+{
+    self = (SAiOSKeychainPlugin*)[super initWithWebView:(UIWebView*)theWebView];
+    if (self) {
+		// initialization here
+    }
+    return self;
+}
+
+- (void) getForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	int argc = [arguments count];
+	if (argc < 2) {
+		return;
+	}
+	
+	NSString* key = [arguments objectAtIndex:0];
+	NSString* serviceName = [arguments objectAtIndex:1];
+	NSError* error = nil;
+	
+	NSString* value = [SFHFKeychainUtils getPasswordForUsername:key andServiceName:serviceName error:&error];
+	if (error == nil && value != nil) {
+		NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onGetCallbackSuccess(\"%@\", \"%@\");", key, value];
+		[super writeJavascript:jsCallback];
+	} else {
+		NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onGetCallbackFail(\"%@\", \"%@\");", key, [error localizedDescription]];
+		[super writeJavascript:jsCallback];
+	}
+}
+
+- (void) setForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{
+	int argc = [arguments count];
+	if (argc < 3) {
+		return;
+	}
+	
+	NSString* key = [arguments objectAtIndex:0];
+	NSString* value = [arguments objectAtIndex:1];
+	NSString* serviceName = [arguments objectAtIndex:2];
+	NSError* error = nil;
+	
+	BOOL stored = [SFHFKeychainUtils storeUsername:key andPassword:value forServiceName:serviceName updateExisting:YES error:&error];
+	if (stored) {
+		NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onSetCallbackSuccess(\"%@\");", key];
+		[super writeJavascript:jsCallback];
+	} else {
+		NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onSetCallbackFail(\"%@\", \"%@\");", key, [error localizedDescription]];
+		[super writeJavascript:jsCallback];
+	}
+}
+
+- (void) removeForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
+{	
+	int argc = [arguments count];
+	if (argc < 2) {
+		return;
+	}
+	
+	NSString* key = [arguments objectAtIndex:0];
+	NSString* serviceName = [arguments objectAtIndex:1];
+	NSError* error = nil;
+	
+	BOOL deleted = [SFHFKeychainUtils deleteItemForUsername:key andServiceName:serviceName error:&error];
+	if (deleted) {
+		NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onRemoveCallbackSuccess(\"%@\");", key];
+		[super writeJavascript:jsCallback];
+	} else {
+		NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onRemoveCallbackFail(\"%@\", \"%@\");", key, [error localizedDescription]];
+		[super writeJavascript:jsCallback];
+	}
+}
+
+
+@end
+
+
+//
+//  SFHFKeychainUtils.m
+//
+//  Created by Buzz Andersen on 10/20/08.
+//  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
+//  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person
+//  obtaining a copy of this software and associated documentation
+//  files (the "Software"), to deal in the Software without
+//  restriction, including without limitation the rights to use,
+//  copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the
+//  Software is furnished to do so, subject to the following
+//  conditions:
+//
+//  The above copyright notice and this permission notice shall be
+//  included in all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+//  OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#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: (NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil] autorelease];
+	NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, username, serviceName, nil] autorelease];
+	
+	NSMutableDictionary *query = [[[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
+	
+	// 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:(id) kSecReturnAttributes];
+	OSStatus status = SecItemCopyMatching((CFDictionaryRef) attributeQuery, (CFTypeRef *) &attributeResult);
+	
+	[attributeResult release];
+	[attributeQuery release];
+	
+	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: (id) kSecReturnData];
+  
+	status = SecItemCopyMatching((CFDictionaryRef) passwordQuery, (CFTypeRef *) &resultData);
+	
+	[resultData autorelease];
+	[passwordQuery release];
+	
+	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 autorelease];
+}
+
++ (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.
+    
+		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: (NSString *) kSecClass, 
+                        kSecAttrService, 
+                        kSecAttrLabel, 
+                        kSecAttrAccount, 
+                        nil] autorelease];
+			
+			NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, 
+                           serviceName,
+                           serviceName,
+                           username,
+                           nil] autorelease];
+			
+			NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];			
+			
+			status = SecItemUpdate((CFDictionaryRef) query, (CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (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: (NSString *) kSecClass, 
+                      kSecAttrService, 
+                      kSecAttrLabel, 
+                      kSecAttrAccount, 
+                      kSecValueData, 
+                      nil] autorelease];
+		
+		NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, 
+                         serviceName,
+                         serviceName,
+                         username,
+                         [password dataUsingEncoding: NSUTF8StringEncoding],
+                         nil] autorelease];
+		
+		NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];			
+    
+		status = SecItemAdd((CFDictionaryRef) query, NULL);
+	}
+	
+	if (error != nil && status != noErr) 
+  {
+		// Something went wrong with adding the new item. Return the Keychain error code.
+		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
+    
+    return NO;
+	}
+  
+  return YES;
+}
+
++ (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error 
+{
+	if (!username || !serviceName) 
+  {
+		if (error != nil) 
+    {
+			*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
+		}
+		return NO;
+	}
+	
+	if (error != nil) 
+  {
+		*error = nil;
+	}
+  
+	NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil] autorelease];
+	NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil] autorelease];
+	
+	NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
+	
+	OSStatus status = SecItemDelete((CFDictionaryRef) query);
+	
+	if (error != nil && status != noErr) 
+  {
+		*error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];		
+    
+    return NO;
+	}
+  
+  return YES;
+}
+
+#endif
+
+@end

+ 125 - 0
index.html

@@ -0,0 +1,125 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+	<!-- Change this if you want to allow scaling -->
+    <meta name="viewport" content="width=default-width; user-scalable=no" />
+
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+
+    <title>paypal-plugin-host</title>
+	
+	<!-- iPad/iPhone specific css below, add after your main css >
+	<link rel="stylesheet" media="only screen and (max-device-width: 1024px)" href="ipad.css" type="text/css" />		
+	<link rel="stylesheet" media="only screen and (max-device-width: 480px)" href="iphone.css" type="text/css" />		
+	-->
+	<script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
+	<script type="text/javascript" charset="utf-8" src="SAiOSKeychainPlugin.js"></script>
+    <script type="text/javascript" charset="utf-8">
+
+
+	// If you want to prevent dragging, uncomment this section
+	/*
+	function preventBehavior(e) 
+	{ 
+      e.preventDefault(); 
+    };
+	document.addEventListener("touchmove", preventBehavior, false);
+	*/
+	
+	function onBodyLoad()
+	{
+		document.addEventListener("deviceready", onDeviceReady,false);
+	}
+	
+	/* When this function is called, PhoneGap has been initialized and is ready to roll */
+	function onDeviceReady()
+	{
+		try {
+		
+			// do your thing!
+		
+		} catch (e) {
+			debug.error(e);
+		}
+	}
+	
+	function onGet()
+	{
+	   var key = document.getElementById("keytoget").value;
+	   var servicename = document.getElementById("servicename").value
+	   
+	   var win = function(key, value) {
+			alert("GET SUCCESS - Key: " + key + " Value: " + value);
+	   };
+	   var fail = function(key, error) {
+			alert("GET FAIL - Key: " + key + " Error: " + error);
+	   };
+	   
+	   window.plugins.keychain.getForKey(key, servicename, win, fail);
+	}
+
+	function onSet()
+	{
+	   var key = document.getElementById("keytoset").value;
+	   var value = document.getElementById("valuetoset").value;
+	   var servicename = document.getElementById("servicename").value;
+	   
+	   var win = function(key) {
+			alert("SET SUCCESS - Key: " + key);
+	   };
+	   var fail = function(key, error) {
+			alert("SET FAIL - Key: " + key + " Error: " + error);
+	   };
+	   
+	   window.plugins.keychain.setForKey(key, value, servicename, win, fail);
+	}
+
+	function onRemove()
+	{
+	   var key = document.getElementById("keytoremove").value;
+	   var servicename = document.getElementById("servicename").value
+	   
+	   var win = function(key) {
+			alert("REMOVE SUCCESS - Key: " + key);
+	   };
+	   var fail = function(key, error) {
+			alert("REMOVE FAIL - Key: " + key + " Error: " + error);
+	   };
+	   
+	   window.plugins.keychain.removeForKey(key, servicename, win, fail);
+	}
+    
+    </script>
+  </head>
+  <body onload="onBodyLoad()">
+  
+  <div style="color:red">(using servicename <input type="text" value="GOLDILOCKS" id="servicename" />)</div>
+
+  <hr>
+  <br />
+
+  <div> GET FROM KEYCHAIN </div>
+  <br />
+  <label for="keytoget">Key to Get&nbsp;&nbsp;&nbsp;&nbsp;<input type="text" id="keytoget" value="ILLUMINATI" /></label>
+  <button onclick="onGet();">GET</button>
+
+  <br />
+  <hr />
+  <br />
+
+  <div> SET TO KEYCHAIN </div>  <br />
+
+  <label for="keytoset">Key to Set&nbsp;&nbsp;&nbsp;&nbsp;<input type="text" id="keytoset" value="ILLUMINATI"/></label>  <br />
+  <label for="valuetoset">Value to Set <input type="text" id="valuetoset" value="SEKRIT" /></label>
+  <button onclick="onSet();">SET</button>
+
+  <br />
+  <hr />
+  <br />
+  <div> REMOVE FROM KEYCHAIN </div>  <br />
+
+  <label for="keytoremove">Key to Remove<input type="text" id="keytoremove" value="ILLUMINATI"/></label> 
+  <button onclick="onRemove();">DEL</button>
+	
+  </body>
+</html>

+ 97 - 0
master.css

@@ -0,0 +1,97 @@
+
+body {
+  background:#222 none repeat scroll 0 0;
+  color:#666;
+  font-family:Helvetica;
+  font-size:72%;
+  line-height:1.5em;
+  margin:0;
+  border-top:1px solid #393939;
+}
+
+#info{
+  background:#ffa;
+  border: 1px solid #ffd324;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  clear:both;
+  margin:15px 6px 0;
+  width:295px;
+  padding:4px 0px 2px 10px;
+}
+
+#info h4{
+  font-size:.95em;
+  margin:0;
+  padding:0;
+}
+
+#stage.theme{
+  padding-top:3px;
+}
+
+/* Definition List */
+#Page1 > dl{
+	padding-top:10px;
+	clear:both;
+	margin:0;
+	list-style-type:none;
+	padding-left:10px;
+	overflow:auto;
+}
+
+#Page1 > dl > dt{
+	font-weight:bold;
+	float:left;
+	margin-left:5px;
+}
+
+#Page1 > dl > dd{
+	width:45px;
+	float:left;
+	color:#a87;
+	font-weight:bold;
+}
+
+/* Content Styling */
+h1, h2, p{
+  margin:1em 0 .5em 13px;
+}
+
+h1{
+  color:#eee;
+  font-size:1.6em;
+  text-align:center;
+  margin:0;
+  margin-top:15px;
+  padding:0;
+}
+
+h2{
+	clear:both;
+  margin:0;
+  padding:3px;
+  font-size:1em;
+  text-align:center;
+}
+
+/* Stage Buttons */
+#stage.theme a.btn{
+	border: 1px solid #555;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+	text-align:center;
+	display:block;
+	float:left;
+	background:#444;
+	width:150px;
+	color:#9ab;
+	font-size:1.1em;
+	text-decoration:none;
+	padding:1.2em 0;
+	margin:3px 0px 3px 5px;
+}
+#stage.theme a.btn.large{
+	width:308px;
+	padding:1.2em 0;
+}