EMKeychainItem.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. /*Copyright (c) 2009 Extendmac, LLC. <support@extendmac.com>
  2. Permission is hereby granted, free of charge, to any person
  3. obtaining a copy of this software and associated documentation
  4. files (the "Software"), to deal in the Software without
  5. restriction, including without limitation the rights to use,
  6. copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. copies of the Software, and to permit persons to whom the
  8. Software is furnished to do so, subject to the following
  9. conditions:
  10. The above copyright notice and this permission notice shall be
  11. included in all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  13. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  14. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  15. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  16. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  17. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  18. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  19. OTHER DEALINGS IN THE SOFTWARE.
  20. */
  21. #import "EMKeychainItem.h"
  22. @interface EMKeychainItem (Private)
  23. /*!
  24. @abstract Modifies the given attribute to be newValue.
  25. @param attributeTag The attribute's tag.
  26. @param newValue A pointer to the new value.
  27. @param newLength The length of the new value.
  28. */
  29. - (void)_modifyAttributeWithTag:(SecItemAttr)attributeTag toBeValue:(void *)newValue ofLength:(UInt32)newLength;
  30. @end
  31. @implementation EMKeychainItem
  32. static BOOL _logsErrors;
  33. + (void)lockKeychain
  34. {
  35. SecKeychainLock(NULL);
  36. }
  37. + (void)unlockKeychain
  38. {
  39. SecKeychainUnlock(NULL, 0, NULL, NO);
  40. }
  41. + (BOOL)logsErrors
  42. {
  43. @synchronized (self)
  44. {
  45. return _logsErrors;
  46. }
  47. return NO;
  48. }
  49. + (void)setLogsErrors:(BOOL)logsErrors
  50. {
  51. @synchronized (self)
  52. {
  53. if (_logsErrors == logsErrors)
  54. return;
  55. _logsErrors = logsErrors;
  56. }
  57. }
  58. #pragma mark -
  59. - (id)_initWithCoreKeychainItem:(SecKeychainItemRef)item
  60. username:(NSString *)username
  61. password:(NSString *)password
  62. {
  63. if ((self = [super init]))
  64. {
  65. mCoreKeychainItem = item;
  66. mUsername = [username copy];
  67. mPassword = [password copy];
  68. return self;
  69. }
  70. return nil;
  71. }
  72. - (void)_modifyAttributeWithTag:(SecItemAttr)attributeTag toBeValue:(void *)newValue ofLength:(UInt32)newLength
  73. {
  74. NSAssert(mCoreKeychainItem, @"Core keychain item is nil. You cannot modify a keychain item that is not in the keychain.");
  75. SecKeychainAttribute attributes[1];
  76. attributes[0].tag = attributeTag;
  77. attributes[0].length = newLength;
  78. attributes[0].data = newValue;
  79. SecKeychainAttributeList attributeList;
  80. attributeList.count = 1;
  81. attributeList.attr = attributes;
  82. SecKeychainItemModifyAttributesAndData(mCoreKeychainItem, &attributeList, 0, NULL);
  83. }
  84. - (void)dealloc
  85. {
  86. /*
  87. [mUsername release];
  88. [mPassword release];
  89. [mLabel release];
  90. */
  91. if (mCoreKeychainItem)
  92. CFRelease(mCoreKeychainItem);
  93. //[super dealloc];
  94. }
  95. #pragma mark -
  96. #pragma mark General Properties
  97. @dynamic password;
  98. - (NSString *)password
  99. {
  100. @synchronized (self)
  101. {
  102. return mPassword;
  103. }
  104. }
  105. - (void)setPassword:(NSString *)newPassword
  106. {
  107. @synchronized (self)
  108. {
  109. if (mPassword == newPassword)
  110. return;
  111. mPassword = newPassword;
  112. const char *newPasswordCString = [newPassword UTF8String];
  113. SecKeychainItemModifyAttributesAndData(mCoreKeychainItem, NULL, strlen(newPasswordCString), (void *)newPasswordCString);
  114. }
  115. }
  116. #pragma mark -
  117. @dynamic username;
  118. - (NSString *)username
  119. {
  120. @synchronized (self)
  121. {
  122. return mUsername;
  123. }
  124. }
  125. - (void)setUsername:(NSString *)newUsername
  126. {
  127. @synchronized (self)
  128. {
  129. if (mUsername == newUsername)
  130. return;
  131. // [mUsername release];
  132. mUsername = newUsername;
  133. const char *newUsernameCString = [newUsername UTF8String];
  134. [self _modifyAttributeWithTag:kSecAccountItemAttr toBeValue:(void *)newUsernameCString ofLength:strlen(newUsernameCString)];
  135. }
  136. }
  137. #pragma mark -
  138. @dynamic label;
  139. - (NSString *)label
  140. {
  141. @synchronized (self)
  142. {
  143. return mLabel;
  144. }
  145. }
  146. - (void)setLabel:(NSString *)newLabel
  147. {
  148. @synchronized (self)
  149. {
  150. if (mLabel == newLabel)
  151. return;
  152. //[mLabel release];
  153. mLabel = newLabel;
  154. const char *newLabelCString = [newLabel UTF8String];
  155. [self _modifyAttributeWithTag:kSecLabelItemAttr toBeValue:(void *)newLabelCString ofLength:strlen(newLabelCString)];
  156. }
  157. }
  158. #pragma mark -
  159. #pragma mark Actions
  160. - (void)removeFromKeychain
  161. {
  162. NSAssert(mCoreKeychainItem, @"Core keychain item is nil. You cannot remove a keychain item that is not in the keychain already.");
  163. if (mCoreKeychainItem)
  164. {
  165. OSStatus resultStatus = SecKeychainItemDelete(mCoreKeychainItem);
  166. if (resultStatus == noErr)
  167. {
  168. CFRelease(mCoreKeychainItem);
  169. mCoreKeychainItem = nil;
  170. }
  171. }
  172. }
  173. @end
  174. #pragma mark -
  175. @implementation EMGenericKeychainItem
  176. - (id)_initWithCoreKeychainItem:(SecKeychainItemRef)item
  177. serviceName:(NSString *)serviceName
  178. username:(NSString *)username
  179. password:(NSString *)password
  180. {
  181. if ((self = [super _initWithCoreKeychainItem:item username:username password:password]))
  182. {
  183. mServiceName = [serviceName copy];
  184. return self;
  185. }
  186. return nil;
  187. }
  188. + (id)_genericKeychainItemWithCoreKeychainItem:(SecKeychainItemRef)coreKeychainItem
  189. forServiceName:(NSString *)serviceName
  190. username:(NSString *)username
  191. password:(NSString *)password
  192. {
  193. return [[EMGenericKeychainItem alloc] _initWithCoreKeychainItem:coreKeychainItem
  194. serviceName:serviceName
  195. username:username
  196. password:password];
  197. }
  198. - (void)dealloc
  199. {
  200. //[mServiceName release];
  201. //[super dealloc];
  202. }
  203. #pragma mark -
  204. + (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceName
  205. withUsername:(NSString *)username
  206. {
  207. if (!serviceName || !username)
  208. return nil;
  209. const char *serviceNameCString = [serviceName UTF8String];
  210. const char *usernameCString = [username UTF8String];
  211. UInt32 passwordLength = 0;
  212. char *password = nil;
  213. SecKeychainItemRef item = nil;
  214. OSStatus returnStatus = SecKeychainFindGenericPassword(NULL, strlen(serviceNameCString), serviceNameCString, strlen(usernameCString), usernameCString, &passwordLength, (void **)&password, &item);
  215. if (returnStatus != noErr || !item)
  216. {
  217. if (_logsErrors)
  218. NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus));
  219. return nil;
  220. }
  221. NSString *passwordString = [[NSString alloc] initWithData:[NSData dataWithBytes:password length:passwordLength] encoding:NSUTF8StringEncoding];
  222. SecKeychainItemFreeContent(NULL, password);
  223. return [EMGenericKeychainItem _genericKeychainItemWithCoreKeychainItem:item forServiceName:serviceName username:username password:passwordString];
  224. }
  225. + (EMGenericKeychainItem *)addGenericKeychainItemForService:(NSString *)serviceName
  226. withUsername:(NSString *)username
  227. password:(NSString *)password
  228. {
  229. if (!serviceName || !username || !password)
  230. return nil;
  231. const char *serviceNameCString = [serviceName UTF8String];
  232. const char *usernameCString = [username UTF8String];
  233. const char *passwordCString = [password UTF8String];
  234. SecKeychainItemRef item = nil;
  235. OSStatus returnStatus = SecKeychainAddGenericPassword(NULL, strlen(serviceNameCString), serviceNameCString, strlen(usernameCString), usernameCString, strlen(passwordCString), (void *)passwordCString, &item);
  236. if (returnStatus != noErr || !item)
  237. {
  238. if (_logsErrors)
  239. NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus));
  240. return nil;
  241. }
  242. return [EMGenericKeychainItem _genericKeychainItemWithCoreKeychainItem:item forServiceName:serviceName username:username password:password];
  243. }
  244. #pragma mark -
  245. #pragma mark Generic Properties
  246. @dynamic serviceName;
  247. - (NSString *)serviceName
  248. {
  249. @synchronized (self)
  250. {
  251. return mServiceName;
  252. }
  253. }
  254. - (void)setServiceName:(NSString *)newServiceName
  255. {
  256. @synchronized (self)
  257. {
  258. if (mServiceName == newServiceName)
  259. return;
  260. mServiceName = newServiceName;
  261. const char *newServiceNameCString = [newServiceName UTF8String];
  262. [self _modifyAttributeWithTag:kSecServiceItemAttr toBeValue:(void *)newServiceNameCString ofLength:strlen(newServiceNameCString)];
  263. }
  264. }
  265. @end
  266. #pragma mark -
  267. @implementation EMInternetKeychainItem
  268. - (id)_initWithCoreKeychainItem:(SecKeychainItemRef)item
  269. server:(NSString *)server
  270. username:(NSString *)username
  271. password:(NSString *)password
  272. path:(NSString *)path
  273. port:(NSInteger)port
  274. protocol:(SecProtocolType)protocol
  275. {
  276. if ((self = [super _initWithCoreKeychainItem:item username:username password:password]))
  277. {
  278. mServer = [server copy];
  279. mPath = [path copy];
  280. mPort = port;
  281. mProtocol = protocol;
  282. return self;
  283. }
  284. return nil;
  285. }
  286. - (void)dealloc
  287. {
  288. //[mServer release];
  289. //[mPath release];
  290. //[super dealloc];
  291. }
  292. + (id)_internetKeychainItemWithCoreKeychainItem:(SecKeychainItemRef)coreKeychainItem
  293. forServer:(NSString *)server
  294. username:(NSString *)username
  295. password:(NSString *)password
  296. path:(NSString *)path
  297. port:(NSInteger)port
  298. protocol:(SecProtocolType)protocol
  299. {
  300. return [[EMInternetKeychainItem alloc] _initWithCoreKeychainItem:coreKeychainItem
  301. server:server
  302. username:username
  303. password:password
  304. path:path
  305. port:port
  306. protocol:protocol];
  307. }
  308. #pragma mark -
  309. + (EMInternetKeychainItem *)internetKeychainItemForServer:(NSString *)server
  310. withUsername:(NSString *)username
  311. path:(NSString *)path
  312. port:(NSInteger)port
  313. protocol:(SecProtocolType)protocol
  314. {
  315. if (!server || !username)
  316. return nil;
  317. const char *serverCString = [server UTF8String];
  318. const char *usernameCString = [username UTF8String];
  319. const char *pathCString = [path UTF8String];
  320. if (!path || [path length] == 0)
  321. pathCString = "";
  322. UInt32 passwordLength = 0;
  323. char *password = nil;
  324. SecKeychainItemRef item = nil;
  325. //0 is kSecAuthenticationTypeAny
  326. OSStatus returnStatus = SecKeychainFindInternetPassword(NULL, strlen(serverCString), serverCString, 0, NULL, strlen(usernameCString), usernameCString, strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item);
  327. if (returnStatus != noErr && protocol == kSecProtocolTypeFTP)
  328. {
  329. //Some clients (like Transmit) still save passwords with kSecProtocolTypeFTPAccount, which was deprecated. Let's check for that.
  330. protocol = kSecProtocolTypeFTPAccount;
  331. returnStatus = SecKeychainFindInternetPassword(NULL, strlen(serverCString), serverCString, 0, NULL, strlen(usernameCString), usernameCString, strlen(pathCString), pathCString, port, protocol, 0, &passwordLength, (void **)&password, &item);
  332. }
  333. if (returnStatus != noErr || !item)
  334. {
  335. if (_logsErrors)
  336. NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus));
  337. return nil;
  338. }
  339. NSString *passwordString = [[NSString alloc] initWithData:[NSData dataWithBytes:password length:passwordLength] encoding:NSUTF8StringEncoding];
  340. SecKeychainItemFreeContent(NULL, password);
  341. return [EMInternetKeychainItem _internetKeychainItemWithCoreKeychainItem:item forServer:server username:username password:passwordString path:path port:port protocol:protocol];
  342. }
  343. + (EMInternetKeychainItem *)addInternetKeychainItemForServer:(NSString *)server
  344. withUsername:(NSString *)username
  345. password:(NSString *)password
  346. path:(NSString *)path
  347. port:(NSInteger)port
  348. protocol:(SecProtocolType)protocol
  349. {
  350. if (!username || !server || !password)
  351. return nil;
  352. const char *serverCString = [server UTF8String];
  353. const char *usernameCString = [username UTF8String];
  354. const char *passwordCString = [password UTF8String];
  355. const char *pathCString = [path UTF8String];
  356. if (!path || [path length] == 0)
  357. pathCString = "";
  358. SecKeychainItemRef item = nil;
  359. OSStatus returnStatus = SecKeychainAddInternetPassword(NULL, strlen(serverCString), serverCString, 0, NULL, strlen(usernameCString), usernameCString, strlen(pathCString), pathCString, port, protocol, kSecAuthenticationTypeDefault, strlen(passwordCString), (void *)passwordCString, &item);
  360. if (returnStatus != noErr || !item)
  361. {
  362. if (_logsErrors)
  363. NSLog(@"Error (%@) - %s", NSStringFromSelector(_cmd), GetMacOSStatusErrorString(returnStatus));
  364. return nil;
  365. }
  366. return [EMInternetKeychainItem _internetKeychainItemWithCoreKeychainItem:item forServer:server username:username password:password path:path port:port protocol:protocol];
  367. }
  368. #pragma mark -
  369. #pragma mark Internet Properties
  370. @dynamic server;
  371. - (NSString *)server
  372. {
  373. @synchronized (self)
  374. {
  375. return mServer;
  376. }
  377. }
  378. - (void)setServer:(NSString *)newServer
  379. {
  380. @synchronized (self)
  381. {
  382. if (mServer == newServer)
  383. return;
  384. mServer = newServer;
  385. const char *newServerCString = [newServer UTF8String];
  386. [self _modifyAttributeWithTag:kSecServerItemAttr toBeValue:(void *)newServerCString ofLength:strlen(newServerCString)];
  387. }
  388. }
  389. #pragma mark -
  390. @dynamic path;
  391. - (NSString *)path
  392. {
  393. @synchronized (self)
  394. {
  395. return mPath;
  396. }
  397. }
  398. - (void)setPath:(NSString *)newPath
  399. {
  400. if (mPath == newPath)
  401. return;
  402. //[mPath release];
  403. mPath = newPath;
  404. const char *newPathCString = [newPath UTF8String];
  405. [self _modifyAttributeWithTag:kSecPathItemAttr toBeValue:(void *)newPathCString ofLength:strlen(newPathCString)];
  406. }
  407. #pragma mark -
  408. @dynamic port;
  409. - (NSInteger)port
  410. {
  411. @synchronized (self)
  412. {
  413. return mPort;
  414. }
  415. }
  416. - (void)setPort:(NSInteger)newPort
  417. {
  418. @synchronized (self)
  419. {
  420. if (mPort == newPort)
  421. return;
  422. mPort = newPort;
  423. UInt32 newPortValue = newPort;
  424. [self _modifyAttributeWithTag:kSecPortItemAttr toBeValue:&newPortValue ofLength:sizeof(newPortValue)];
  425. }
  426. }
  427. #pragma mark -
  428. @dynamic protocol;
  429. - (SecProtocolType)protocol
  430. {
  431. @synchronized (self)
  432. {
  433. return mProtocol;
  434. }
  435. }
  436. - (void)setProtocol:(SecProtocolType)newProtocol
  437. {
  438. @synchronized (self)
  439. {
  440. if (mProtocol == newProtocol)
  441. return;
  442. mProtocol = newProtocol;
  443. [self _modifyAttributeWithTag:kSecProtocolItemAttr toBeValue:&newProtocol ofLength:sizeof(newProtocol)];
  444. }
  445. }
  446. @end