SAiOSKeychainPlugin.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. //
  2. // SAiOSPaypalPlugin.m
  3. // Cordova Plugin for Cordova
  4. //
  5. // Created by shazron on 10-11-05.
  6. // Copyright 2010 Shazron Abdullah. All rights reserved.
  7. #import "SAiOSKeychainPlugin.h"
  8. @implementation SAiOSKeychainPlugin
  9. -(CDVPlugin*) initWithWebView:(UIWebView*)theWebView
  10. {
  11. self = (SAiOSKeychainPlugin*)[super initWithWebView:(UIWebView*)theWebView];
  12. if (self) {
  13. // initialization here
  14. }
  15. return self;
  16. }
  17. - (void) getForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
  18. {
  19. int argc = [arguments count];
  20. if (argc < 2) {
  21. return;
  22. }
  23. NSString* key = [arguments objectAtIndex:0];
  24. NSString* serviceName = [arguments objectAtIndex:1];
  25. NSError* error = nil;
  26. NSString* value = [SFHFKeychainUtils getPasswordForUsername:key andServiceName:serviceName error:&error];
  27. if (error == nil && value != nil) {
  28. NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onGetCallbackSuccess(\"%@\", \"%@\");", key, value];
  29. [super writeJavascript:jsCallback];
  30. } else {
  31. NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onGetCallbackFail(\"%@\", \"%@\");", key, [error localizedDescription]];
  32. [super writeJavascript:jsCallback];
  33. }
  34. }
  35. - (void) setForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
  36. {
  37. int argc = [arguments count];
  38. if (argc < 3) {
  39. return;
  40. }
  41. NSString* key = [arguments objectAtIndex:0];
  42. NSString* value = [arguments objectAtIndex:1];
  43. NSString* serviceName = [arguments objectAtIndex:2];
  44. NSError* error = nil;
  45. BOOL stored = [SFHFKeychainUtils storeUsername:key andPassword:value forServiceName:serviceName updateExisting:YES error:&error];
  46. if (stored) {
  47. NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onSetCallbackSuccess(\"%@\");", key];
  48. [super writeJavascript:jsCallback];
  49. } else {
  50. NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onSetCallbackFail(\"%@\", \"%@\");", key, [error localizedDescription]];
  51. [super writeJavascript:jsCallback];
  52. }
  53. }
  54. - (void) removeForKey:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
  55. {
  56. int argc = [arguments count];
  57. if (argc < 2) {
  58. return;
  59. }
  60. NSString* key = [arguments objectAtIndex:0];
  61. NSString* serviceName = [arguments objectAtIndex:1];
  62. NSError* error = nil;
  63. BOOL deleted = [SFHFKeychainUtils deleteItemForUsername:key andServiceName:serviceName error:&error];
  64. if (deleted) {
  65. NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onRemoveCallbackSuccess(\"%@\");", key];
  66. [super writeJavascript:jsCallback];
  67. } else {
  68. NSString* jsCallback = [NSString stringWithFormat:@"window.plugins.keychain._onRemoveCallbackFail(\"%@\", \"%@\");", key, [error localizedDescription]];
  69. [super writeJavascript:jsCallback];
  70. }
  71. }
  72. @end
  73. //
  74. // SFHFKeychainUtils.m
  75. //
  76. // Created by Buzz Andersen on 10/20/08.
  77. // Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
  78. // Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
  79. //
  80. // Permission is hereby granted, free of charge, to any person
  81. // obtaining a copy of this software and associated documentation
  82. // files (the "Software"), to deal in the Software without
  83. // restriction, including without limitation the rights to use,
  84. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  85. // copies of the Software, and to permit persons to whom the
  86. // Software is furnished to do so, subject to the following
  87. // conditions:
  88. //
  89. // The above copyright notice and this permission notice shall be
  90. // included in all copies or substantial portions of the Software.
  91. //
  92. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  93. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  94. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  95. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  96. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  97. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  98. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  99. // OTHER DEALINGS IN THE SOFTWARE.
  100. //
  101. #import <Security/Security.h>
  102. static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
  103. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
  104. @interface SFHFKeychainUtils (PrivateMethods)
  105. + (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
  106. @end
  107. #endif
  108. @implementation SFHFKeychainUtils
  109. #if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
  110. + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
  111. if (!username || !serviceName) {
  112. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
  113. return nil;
  114. }
  115. SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
  116. if (*error || !item) {
  117. return nil;
  118. }
  119. // from Advanced Mac OS X Programming, ch. 16
  120. UInt32 length;
  121. char *password;
  122. SecKeychainAttribute attributes[8];
  123. SecKeychainAttributeList list;
  124. attributes[0].tag = kSecAccountItemAttr;
  125. attributes[1].tag = kSecDescriptionItemAttr;
  126. attributes[2].tag = kSecLabelItemAttr;
  127. attributes[3].tag = kSecModDateItemAttr;
  128. list.count = 4;
  129. list.attr = attributes;
  130. OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
  131. if (status != noErr) {
  132. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  133. return nil;
  134. }
  135. NSString *passwordString = nil;
  136. if (password != NULL) {
  137. char passwordBuffer[1024];
  138. if (length > 1023) {
  139. length = 1023;
  140. }
  141. strncpy(passwordBuffer, password, length);
  142. passwordBuffer[length] = '\0';
  143. passwordString = [NSString stringWithCString:passwordBuffer];
  144. }
  145. SecKeychainItemFreeContent(&list, password);
  146. CFRelease(item);
  147. return passwordString;
  148. }
  149. + (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {
  150. if (!username || !password || !serviceName) {
  151. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
  152. return;
  153. }
  154. OSStatus status = noErr;
  155. SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
  156. if (*error && [*error code] != noErr) {
  157. return;
  158. }
  159. *error = nil;
  160. if (item) {
  161. status = SecKeychainItemModifyAttributesAndData(item,
  162. NULL,
  163. strlen([password UTF8String]),
  164. [password UTF8String]);
  165. CFRelease(item);
  166. }
  167. else {
  168. status = SecKeychainAddGenericPassword(NULL,
  169. strlen([serviceName UTF8String]),
  170. [serviceName UTF8String],
  171. strlen([username UTF8String]),
  172. [username UTF8String],
  173. strlen([password UTF8String]),
  174. [password UTF8String],
  175. NULL);
  176. }
  177. if (status != noErr) {
  178. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  179. }
  180. }
  181. + (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
  182. if (!username || !serviceName) {
  183. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];
  184. return;
  185. }
  186. *error = nil;
  187. SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
  188. if (*error && [*error code] != noErr) {
  189. return;
  190. }
  191. OSStatus status;
  192. if (item) {
  193. status = SecKeychainItemDelete(item);
  194. CFRelease(item);
  195. }
  196. if (status != noErr) {
  197. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  198. }
  199. }
  200. + (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
  201. if (!username || !serviceName) {
  202. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
  203. return nil;
  204. }
  205. *error = nil;
  206. SecKeychainItemRef item;
  207. OSStatus status = SecKeychainFindGenericPassword(NULL,
  208. strlen([serviceName UTF8String]),
  209. [serviceName UTF8String],
  210. strlen([username UTF8String]),
  211. [username UTF8String],
  212. NULL,
  213. NULL,
  214. &item);
  215. if (status != noErr) {
  216. if (status != errSecItemNotFound) {
  217. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  218. }
  219. return nil;
  220. }
  221. return item;
  222. }
  223. #else
  224. + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
  225. if (!username || !serviceName) {
  226. if (error != nil) {
  227. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
  228. }
  229. return nil;
  230. }
  231. if (error != nil) {
  232. *error = nil;
  233. }
  234. // Set up a query dictionary with the base query attributes: item type (generic), username, and service
  235. NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil] autorelease];
  236. NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, username, serviceName, nil] autorelease];
  237. NSMutableDictionary *query = [[[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
  238. // First do a query for attributes, in case we already have a Keychain item with no password data set.
  239. // One likely way such an incorrect item could have come about is due to the previous (incorrect)
  240. // version of this code (which set the password as a generic attribute instead of password data).
  241. NSDictionary *attributeResult = NULL;
  242. NSMutableDictionary *attributeQuery = [query mutableCopy];
  243. [attributeQuery setObject: (id) kCFBooleanTrue forKey:(id) kSecReturnAttributes];
  244. OSStatus status = SecItemCopyMatching((CFDictionaryRef) attributeQuery, (CFTypeRef *) &attributeResult);
  245. [attributeResult release];
  246. [attributeQuery release];
  247. if (status != noErr) {
  248. // No existing item found--simply return nil for the password
  249. if (error != nil && status != errSecItemNotFound) {
  250. //Only return an error if a real exception happened--not simply for "not found."
  251. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  252. }
  253. return nil;
  254. }
  255. // We have an existing item, now query for the password data associated with it.
  256. NSData *resultData = nil;
  257. NSMutableDictionary *passwordQuery = [query mutableCopy];
  258. [passwordQuery setObject: (id) kCFBooleanTrue forKey: (id) kSecReturnData];
  259. status = SecItemCopyMatching((CFDictionaryRef) passwordQuery, (CFTypeRef *) &resultData);
  260. [resultData autorelease];
  261. [passwordQuery release];
  262. if (status != noErr) {
  263. if (status == errSecItemNotFound) {
  264. // We found attributes for the item previously, but no password now, so return a special error.
  265. // Users of this API will probably want to detect this error and prompt the user to
  266. // re-enter their credentials. When you attempt to store the re-entered credentials
  267. // using storeUsername:andPassword:forServiceName:updateExisting:error
  268. // the old, incorrect entry will be deleted and a new one with a properly encrypted
  269. // password will be added.
  270. if (error != nil) {
  271. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
  272. }
  273. }
  274. else {
  275. // Something else went wrong. Simply return the normal Keychain API error code.
  276. if (error != nil) {
  277. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  278. }
  279. }
  280. return nil;
  281. }
  282. NSString *password = nil;
  283. if (resultData) {
  284. password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
  285. }
  286. else {
  287. // There is an existing item, but we weren't able to get password data for it for some reason,
  288. // Possibly as a result of an item being incorrectly entered by the previous code.
  289. // Set the -1999 error so the code above us can prompt the user again.
  290. if (error != nil) {
  291. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];
  292. }
  293. }
  294. return [password autorelease];
  295. }
  296. + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error
  297. {
  298. if (!username || !password || !serviceName)
  299. {
  300. if (error != nil)
  301. {
  302. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
  303. }
  304. return NO;
  305. }
  306. // See if we already have a password entered for these credentials.
  307. NSError *getError = nil;
  308. NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];
  309. if ([getError code] == -1999)
  310. {
  311. // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
  312. // Delete the existing item before moving on entering a correct one.
  313. getError = nil;
  314. [self deleteItemForUsername: username andServiceName: serviceName error: &getError];
  315. if ([getError code] != noErr)
  316. {
  317. if (error != nil)
  318. {
  319. *error = getError;
  320. }
  321. return NO;
  322. }
  323. }
  324. else if ([getError code] != noErr)
  325. {
  326. if (error != nil)
  327. {
  328. *error = getError;
  329. }
  330. return NO;
  331. }
  332. if (error != nil)
  333. {
  334. *error = nil;
  335. }
  336. OSStatus status = noErr;
  337. if (existingPassword)
  338. {
  339. // We have an existing, properly entered item with a password.
  340. // Update the existing item.
  341. if (![existingPassword isEqualToString:password] && updateExisting)
  342. {
  343. //Only update if we're allowed to update existing. If not, simply do nothing.
  344. NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass,
  345. kSecAttrService,
  346. kSecAttrLabel,
  347. kSecAttrAccount,
  348. nil] autorelease];
  349. NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword,
  350. serviceName,
  351. serviceName,
  352. username,
  353. nil] autorelease];
  354. NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
  355. status = SecItemUpdate((CFDictionaryRef) query, (CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (NSString *) kSecValueData]);
  356. }
  357. }
  358. else
  359. {
  360. // No existing entry (or an existing, improperly entered, and therefore now
  361. // deleted, entry). Create a new entry.
  362. NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass,
  363. kSecAttrService,
  364. kSecAttrLabel,
  365. kSecAttrAccount,
  366. kSecValueData,
  367. nil] autorelease];
  368. NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword,
  369. serviceName,
  370. serviceName,
  371. username,
  372. [password dataUsingEncoding: NSUTF8StringEncoding],
  373. nil] autorelease];
  374. NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
  375. status = SecItemAdd((CFDictionaryRef) query, NULL);
  376. }
  377. if (error != nil && status != noErr)
  378. {
  379. // Something went wrong with adding the new item. Return the Keychain error code.
  380. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  381. return NO;
  382. }
  383. return YES;
  384. }
  385. + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error
  386. {
  387. if (!username || !serviceName)
  388. {
  389. if (error != nil)
  390. {
  391. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];
  392. }
  393. return NO;
  394. }
  395. if (error != nil)
  396. {
  397. *error = nil;
  398. }
  399. NSArray *keys = [[[NSArray alloc] initWithObjects: (NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil] autorelease];
  400. NSArray *objects = [[[NSArray alloc] initWithObjects: (NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil] autorelease];
  401. NSDictionary *query = [[[NSDictionary alloc] initWithObjects: objects forKeys: keys] autorelease];
  402. OSStatus status = SecItemDelete((CFDictionaryRef) query);
  403. if (error != nil && status != noErr)
  404. {
  405. *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];
  406. return NO;
  407. }
  408. return YES;
  409. }
  410. #endif
  411. @end