MyIdentityStore.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2012-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import <CommonCrypto/CommonDigest.h>
  21. #import <CommonCrypto/CommonKeyDerivation.h>
  22. #import "MyIdentityStore.h"
  23. #import "ProtocolDefines.h"
  24. #import "NaClCrypto.h"
  25. #import "NSData+Base32.h"
  26. #import "CryptoUtils.h"
  27. #import "NaClCrypto.h"
  28. #import "ThreemaError.h"
  29. #import "AppGroup.h"
  30. #import "UserSettings.h"
  31. #import "ValidationLogger.h"
  32. #ifdef DEBUG
  33. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  34. #else
  35. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  36. #endif
  37. static const NSString *keychainLabel = @"Threema identity 1";
  38. @implementation MyIdentityStore {
  39. NSData *secretKey;
  40. BOOL secretKeyInKeychain;
  41. BOOL keychainLocked;
  42. }
  43. @synthesize identity;
  44. @synthesize serverGroup;
  45. @synthesize publicKey;
  46. static MyIdentityStore *instance;
  47. + (MyIdentityStore*)sharedMyIdentityStore {
  48. @synchronized (self) {
  49. if (!instance)
  50. instance = [[MyIdentityStore alloc] init];
  51. }
  52. return instance;
  53. }
  54. + (void)resetSharedInstance {
  55. instance = nil;
  56. }
  57. - (id)init
  58. {
  59. self = [super init];
  60. if (self) {
  61. OSStatus status = [self loadFromKeychain];
  62. if (identity == nil) {
  63. if (status == errSecInteractionNotAllowed) {
  64. keychainLocked = YES;
  65. } else {
  66. /* This can happen when a backup is restored to a new phone, and we have our NSUserDefaults
  67. but no keychain item. Make sure the identity-specific defaults are wiped to avoid confusion
  68. later on */
  69. DDLogVerbose(@"No identity - clearing identity-specific user defaults");
  70. [self removeIdentityUserDefaults];
  71. }
  72. }
  73. [self migrateProfilePicture];
  74. }
  75. return self;
  76. }
  77. - (BOOL)isProvisioned {
  78. /* check prerequisites */
  79. if ([self isInvalidIdentity]) {
  80. return false;
  81. }
  82. if (self.pendingCreateID) {
  83. return false;
  84. }
  85. return true;
  86. }
  87. - (BOOL)isKeychainLocked {
  88. return keychainLocked;
  89. }
  90. - (BOOL)isInvalidIdentity {
  91. return identity == nil || publicKey == nil || serverGroup == nil || (secretKey == nil && !secretKeyInKeychain);
  92. }
  93. - (void)generateKeyPairWithSeed:(NSData*)seed {
  94. NSData *newPublicKey, *newSecretKey;
  95. DDLogInfo(@"Generating key pair");
  96. [[NaClCrypto sharedCrypto] generateKeyPairPublicKey:&newPublicKey secretKey:&newSecretKey withSeed:seed];
  97. identity = nil;
  98. serverGroup = nil;
  99. publicKey = newPublicKey;
  100. secretKey = newSecretKey;
  101. secretKeyInKeychain = NO;
  102. }
  103. - (OSStatus)loadFromKeychain {
  104. NSMutableDictionary *keychainDict = [NSMutableDictionary dictionary];
  105. [keychainDict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  106. [keychainDict setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
  107. [keychainDict setObject:keychainLabel forKey:(__bridge id)kSecAttrLabel];
  108. CFDictionaryRef resultRef;
  109. OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)(keychainDict), (CFTypeRef *)&resultRef);
  110. if (status == noErr) {
  111. NSDictionary *result = (__bridge_transfer NSDictionary *)resultRef;
  112. /* sanity check on identity and public key; backup/restore to a new iPhone can produce
  113. strange results */
  114. NSString *tmpIdentity = [result objectForKey:(__bridge id)(kSecAttrAccount)];
  115. NSData *tmpPublicKey = [result objectForKey:(__bridge id)(kSecAttrGeneric)];
  116. if (tmpIdentity.length != kIdentityLen || tmpPublicKey.length != kNaClCryptoPubKeySize) {
  117. DDLogError(@"Got bad identity or key from keychain; ignoring");
  118. return status;
  119. }
  120. identity = tmpIdentity;
  121. publicKey = tmpPublicKey;
  122. serverGroup = [result objectForKey:(__bridge id)(kSecAttrService)];
  123. secretKey = nil;
  124. secretKeyInKeychain = YES;
  125. DDLogInfo(@"Loaded identity %@ from keychain", identity);
  126. } else {
  127. if (status != errSecItemNotFound) {
  128. [[ValidationLogger sharedValidationLogger] logString:[NSString stringWithFormat:@"Keychain: Error accessing keychain, status: %i", (int)status]];
  129. DDLogError(@"Error accessing keychain, status: %i", (int)status);
  130. }
  131. }
  132. return status;
  133. }
  134. - (void)storeInKeychain {
  135. if (identity == nil || publicKey == nil || secretKey == nil || serverGroup == nil) {
  136. DDLogError(@"Not enough data to store in keychain");
  137. return;
  138. }
  139. NSMutableDictionary *queryDict = [NSMutableDictionary dictionary];
  140. [queryDict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  141. [queryDict setObject:keychainLabel forKey:(__bridge id)kSecAttrLabel];
  142. /* check if we already have a keychain item and need to update */
  143. CFDictionaryRef resultRef;
  144. if (SecItemCopyMatching((__bridge CFDictionaryRef)(queryDict), (CFTypeRef *)&resultRef) == noErr) {
  145. if (SecItemDelete((__bridge CFDictionaryRef)(queryDict)) != noErr) {
  146. DDLogError(@"Couldn't delete keychain item");
  147. }
  148. }
  149. /* add new item */
  150. NSMutableDictionary *addDict = [NSMutableDictionary dictionary];
  151. [addDict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  152. [addDict setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
  153. [addDict setObject:publicKey forKey:(__bridge id)kSecAttrGeneric];
  154. [addDict setObject:identity forKey:(__bridge id)kSecAttrAccount];
  155. [addDict setObject:serverGroup forKey:(__bridge id)kSecAttrService];
  156. [addDict setObject:keychainLabel forKey:(__bridge id)kSecAttrLabel];
  157. [addDict setObject:secretKey forKey:(__bridge id)kSecValueData];
  158. if (SecItemAdd((__bridge CFDictionaryRef)(addDict), NULL) != noErr) {
  159. DDLogError(@"Couldn't add keychain item");
  160. } else {
  161. secretKey = nil;
  162. secretKeyInKeychain = YES;
  163. }
  164. self.privateIdentityInfoLastUpdate = [NSDate date];
  165. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationCreatedIdentity object:nil];
  166. }
  167. - (void)updateConnectionRights {
  168. OSStatus status = [self loadFromKeychain];
  169. if (status != errSecInteractionNotAllowed) {
  170. secretKey = [self _obtainSecretKey];
  171. if (secretKey != nil) {
  172. [self deleteFromKeychain];
  173. [self storeInKeychain];
  174. }
  175. }
  176. }
  177. - (void)deleteFromKeychain {
  178. NSMutableDictionary *queryDict = [NSMutableDictionary dictionary];
  179. [queryDict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  180. [queryDict setObject:keychainLabel forKey:(__bridge id)kSecAttrLabel];
  181. if (SecItemDelete((__bridge CFDictionaryRef)(queryDict)) != noErr) {
  182. DDLogError(@"Couldn't delete keychain item");
  183. }
  184. secretKeyInKeychain = NO;
  185. }
  186. - (void)destroy {
  187. [self deleteFromKeychain];
  188. [self removeIdentityUserDefaults];
  189. [[UserSettings sharedUserSettings] setPushDecrypt:NO];
  190. [[UserSettings sharedUserSettings] setAskedForPushDecryption:NO];
  191. [[UserSettings sharedUserSettings] setSafeConfig:nil];
  192. self.tempSafePassword = nil;
  193. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDestroyedIdentity object:nil];
  194. }
  195. - (void)removeIdentityUserDefaults {
  196. [[AppGroup userDefaults] removeObjectForKey:@"PushFromName"];
  197. NSFileManager *fileManager = [NSFileManager defaultManager];
  198. if ([fileManager fileExistsAtPath:[self profilePicturePath]]) {
  199. NSError *error;
  200. [fileManager removeItemAtPath:[self profilePicturePath] error:&error];
  201. if (error) {
  202. }
  203. }
  204. [[AppGroup userDefaults] removeObjectForKey:@"ProfilePicture"];
  205. [[AppGroup userDefaults] removeObjectForKey:@"LinkedEmail"];
  206. [[AppGroup userDefaults] removeObjectForKey:@"LinkEmailPending"];
  207. [[AppGroup userDefaults] removeObjectForKey:@"LinkedMobileNo"];
  208. [[AppGroup userDefaults] removeObjectForKey:@"LinkMobileNoPending"];
  209. [[AppGroup userDefaults] removeObjectForKey:@"LinkMobileNoVerificationId"];
  210. [[AppGroup userDefaults] removeObjectForKey:@"LinkMobileNoStartDate"];
  211. [[AppGroup userDefaults] removeObjectForKey:@"PrivateIdentityInfoLastUpdate"];
  212. [[AppGroup userDefaults] removeObjectForKey:@"LastSentFeatureMask"];
  213. [[AppGroup userDefaults] removeObjectForKey:@"RevocationPasswordSetDate"];
  214. [[AppGroup userDefaults] removeObjectForKey:@"RevocationPasswordLastCheck"];
  215. [[AppGroup userDefaults] removeObjectForKey:@"PendingCreateID"];
  216. [[AppGroup userDefaults] removeObjectForKey:@"CreateIDEmail"];
  217. [[AppGroup userDefaults] removeObjectForKey:@"CreateIDPhone"];
  218. [[AppGroup userDefaults] removeObjectForKey:@"FirstName"];
  219. [[AppGroup userDefaults] removeObjectForKey:@"LastName"];
  220. [[AppGroup userDefaults] removeObjectForKey:@"CSI"];
  221. [[AppGroup userDefaults] removeObjectForKey:@"Category"];
  222. [[AppGroup userDefaults] removeObjectForKey:@"CompanyName"];
  223. [[AppGroup userDefaults] removeObjectForKey:@"DirectoryCategories"];
  224. [[AppGroup userDefaults] removeObjectForKey:@"LastWorkUpdateRequest"];
  225. [[AppGroup userDefaults] removeObjectForKey:@"LastWorkUpdateDate"];
  226. [[AppGroup userDefaults] removeObjectForKey:@"MessageDrafts"];
  227. [[AppGroup userDefaults] removeObjectForKey:@"PushNotificationEncryptionKey"];
  228. [[AppGroup userDefaults] removeObjectForKey:@"MatchToken"];
  229. [[AppGroup userDefaults] synchronize];
  230. }
  231. - (NSString *)pushFromName {
  232. return [[AppGroup userDefaults] objectForKey:@"PushFromName"];
  233. }
  234. - (void)setPushFromName:(NSString *)pushFromName {
  235. [[AppGroup userDefaults] setObject:pushFromName forKey:@"PushFromName"];
  236. [[AppGroup userDefaults] synchronize];
  237. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationProfileNicknameChanged object:nil];
  238. }
  239. - (NSMutableDictionary *)profilePicture {
  240. return [NSMutableDictionary dictionaryWithContentsOfFile:[self profilePicturePath]];
  241. }
  242. - (void)setProfilePicture:(NSMutableDictionary *)profilePicture {
  243. if (!profilePicture) {
  244. [[AppGroup userDefaults] removeObjectForKey:@"ProfilePicture"];
  245. NSFileManager *fileManager = [NSFileManager defaultManager];
  246. if ([fileManager fileExistsAtPath:[self profilePicturePath]]) {
  247. NSError *error;
  248. [fileManager removeItemAtPath:[self profilePicturePath] error:&error];
  249. if (error) {
  250. }
  251. }
  252. } else {
  253. [profilePicture writeToFile:[self profilePicturePath] atomically:YES];
  254. }
  255. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationProfilePictureChanged object:nil];
  256. }
  257. - (NSString *)linkedEmail {
  258. return [[AppGroup userDefaults] objectForKey:@"LinkedEmail"];
  259. }
  260. - (void)setLinkedEmail:(NSString *)linkedEmail {
  261. [[AppGroup userDefaults] setObject:linkedEmail forKey:@"LinkedEmail"];
  262. [[AppGroup userDefaults] synchronize];
  263. }
  264. - (BOOL)linkEmailPending {
  265. return [[AppGroup userDefaults] boolForKey:@"LinkEmailPending"];
  266. }
  267. - (void)setLinkEmailPending:(BOOL)linkEmailPending {
  268. [[AppGroup userDefaults] setBool:linkEmailPending forKey:@"LinkEmailPending"];
  269. [[AppGroup userDefaults] synchronize];
  270. }
  271. - (NSString *)linkedMobileNo {
  272. return [[AppGroup userDefaults] objectForKey:@"LinkedMobileNo"];
  273. }
  274. - (void)setLinkedMobileNo:(NSString *)linkedMobileNo {
  275. [[AppGroup userDefaults] setObject:linkedMobileNo forKey:@"LinkedMobileNo"];
  276. [[AppGroup userDefaults] synchronize];
  277. }
  278. - (BOOL)linkMobileNoPending {
  279. return [[AppGroup userDefaults] boolForKey:@"LinkMobileNoPending"];
  280. }
  281. - (void)setLinkMobileNoPending:(BOOL)linkMobileNoPending {
  282. [[AppGroup userDefaults] setBool:linkMobileNoPending forKey:@"LinkMobileNoPending"];
  283. [[AppGroup userDefaults] synchronize];
  284. }
  285. - (NSString *)linkMobileNoVerificationId {
  286. return [[AppGroup userDefaults] stringForKey:@"LinkMobileNoVerificationId"];
  287. }
  288. - (void)setLinkMobileNoVerificationId:(NSString *)linkMobileNoVerificationId {
  289. [[AppGroup userDefaults] setObject:linkMobileNoVerificationId forKey:@"LinkMobileNoVerificationId"];
  290. [[AppGroup userDefaults] synchronize];
  291. }
  292. - (NSDate *)linkMobileNoStartDate {
  293. return [[AppGroup userDefaults] objectForKey:@"LinkMobileNoStartDate"];
  294. }
  295. - (void)setLinkMobileNoStartDate:(NSDate *)linkMobileNoStartDate {
  296. [[AppGroup userDefaults] setObject:linkMobileNoStartDate forKey:@"LinkMobileNoStartDate"];
  297. [[AppGroup userDefaults] synchronize];
  298. }
  299. - (NSDate *)privateIdentityInfoLastUpdate {
  300. return [[AppGroup userDefaults] objectForKey:@"PrivateIdentityInfoLastUpdate"];
  301. }
  302. - (void)setPrivateIdentityInfoLastUpdate:(NSDate *)privateIdentityInfoLastUpdate {
  303. [[AppGroup userDefaults] setObject:privateIdentityInfoLastUpdate forKey:@"PrivateIdentityInfoLastUpdate"];
  304. [[AppGroup userDefaults] synchronize];
  305. }
  306. - (NSInteger)lastSentFeatureMask {
  307. return [[AppGroup userDefaults] integerForKey:@"LastSentFeatureMask"];
  308. }
  309. - (void)setLastSentFeatureMask:(NSInteger)lastSentFeatureMask {
  310. [[AppGroup userDefaults] setInteger:lastSentFeatureMask forKey:@"LastSentFeatureMask"];
  311. [[AppGroup userDefaults] synchronize];
  312. }
  313. - (NSDate *)revocationPasswordSetDate {
  314. return [[AppGroup userDefaults] objectForKey:@"RevocationPasswordSetDate"];
  315. }
  316. - (void)setRevocationPasswordSetDate:(NSDate *)revocationPasswordSetDate {
  317. [[AppGroup userDefaults] setObject:revocationPasswordSetDate forKey:@"RevocationPasswordSetDate"];
  318. [[AppGroup userDefaults] synchronize];
  319. }
  320. - (NSDate *)revocationPasswordLastCheck {
  321. return [[AppGroup userDefaults] objectForKey:@"RevocationPasswordLastCheck"];
  322. }
  323. - (void)setLicenseLastCheck:(NSDate *)licenseLastCheck {
  324. [[AppGroup userDefaults] setObject:licenseLastCheck forKey:@"LicenseLastCheck"];
  325. [[AppGroup userDefaults] synchronize];
  326. }
  327. - (NSDate *)licenseLastCheck {
  328. return [[AppGroup userDefaults] objectForKey:@"LicenseLastCheck"];
  329. }
  330. - (void)setLicenseLogoLightUrl:(NSString *)licenseLogoLightUrl {
  331. [[AppGroup userDefaults] setObject:licenseLogoLightUrl forKey:@"LicenseLogoLightUrl"];
  332. [[AppGroup userDefaults] synchronize];
  333. }
  334. - (NSString *)licenseLogoLightUrl {
  335. return [[AppGroup userDefaults] objectForKey:@"LicenseLogoLightUrl"];
  336. }
  337. - (void)setLicenseLogoDarkUrl:(NSString *)licenseLogoDarkUrl {
  338. [[AppGroup userDefaults] setObject:licenseLogoDarkUrl forKey:@"LicenseLogoDarkUrl"];
  339. [[AppGroup userDefaults] synchronize];
  340. }
  341. - (NSString *)licenseLogoDarkUrl {
  342. return [[AppGroup userDefaults] objectForKey:@"LicenseLogoDarkUrl"];
  343. }
  344. - (void)setLicenseSupportUrl:(NSString *)licenseSupportUrl {
  345. [[AppGroup userDefaults] setObject:licenseSupportUrl forKey:@"LicenseSupportUrl"];
  346. [[AppGroup userDefaults] synchronize];
  347. }
  348. - (NSString *)licenseSupportUrl {
  349. return [[AppGroup userDefaults] objectForKey:@"LicenseSupportUrl"];
  350. }
  351. - (void)setRevocationPasswordLastCheck:(NSDate *)revocationPasswordLastCheck {
  352. [[AppGroup userDefaults] setObject:revocationPasswordLastCheck forKey:@"RevocationPasswordLastCheck"];
  353. [[AppGroup userDefaults] synchronize];
  354. }
  355. - (BOOL)pendingCreateID {
  356. return [[AppGroup userDefaults] boolForKey:@"PendingCreateID"];
  357. }
  358. - (void)setPendingCreateID:(BOOL)pendingCreateID {
  359. [[AppGroup userDefaults] setBool:pendingCreateID forKey:@"PendingCreateID"];
  360. [[AppGroup userDefaults] synchronize];
  361. }
  362. - (NSString*)createIDEmail {
  363. return [[AppGroup userDefaults] stringForKey:@"CreateIDEmail"];
  364. }
  365. - (void)setCreateIDEmail:(NSString *)createIDEmail {
  366. [[AppGroup userDefaults] setObject:createIDEmail forKey:@"CreateIDEmail"];
  367. [[AppGroup userDefaults] synchronize];
  368. }
  369. - (NSString*)createIDPhone {
  370. return [[AppGroup userDefaults] stringForKey:@"CreateIDPhone"];
  371. }
  372. - (void)setCreateIDPhone:(NSString *)createIDPhone {
  373. [[AppGroup userDefaults] setObject:createIDPhone forKey:@"CreateIDPhone"];
  374. [[AppGroup userDefaults] synchronize];
  375. }
  376. - (NSString *)firstName {
  377. return [[AppGroup userDefaults] stringForKey:@"FirstName"];
  378. }
  379. - (void)setFirstName:(NSString *)firstName {
  380. [[AppGroup userDefaults] setObject:firstName forKey:@"FirstName"];
  381. [[AppGroup userDefaults] synchronize];
  382. }
  383. - (NSString *)lastName {
  384. return [[AppGroup userDefaults] stringForKey:@"LastName"];
  385. }
  386. - (void)setLastName:(NSString *)lastName {
  387. [[AppGroup userDefaults] setObject:lastName forKey:@"LastName"];
  388. [[AppGroup userDefaults] synchronize];
  389. }
  390. - (NSString *)csi {
  391. return [[AppGroup userDefaults] stringForKey:@"CSI"];
  392. }
  393. - (void)setCsi:(NSString *)csi {
  394. [[AppGroup userDefaults] setObject:csi forKey:@"CSI"];
  395. [[AppGroup userDefaults] synchronize];
  396. }
  397. - (NSString *)category {
  398. return [[AppGroup userDefaults] stringForKey:@"Category"];
  399. }
  400. - (void)setCategory:(NSString *)category {
  401. [[AppGroup userDefaults] setObject:category forKey:@"Category"];
  402. [[AppGroup userDefaults] synchronize];
  403. }
  404. - (NSString *)companyName {
  405. return [[AppGroup userDefaults] stringForKey:@"CompanyName"];
  406. }
  407. - (void)setCompanyName:(NSString *)companyName {
  408. [[AppGroup userDefaults] setObject:companyName forKey:@"CompanyName"];
  409. [[AppGroup userDefaults] synchronize];
  410. }
  411. - (NSMutableDictionary *)directoryCategories {
  412. return [[AppGroup userDefaults] objectForKey:@"DirectoryCategories"];
  413. }
  414. - (void)setDirectoryCategories:(NSMutableDictionary *)directoryCategories {
  415. [[AppGroup userDefaults] setObject:directoryCategories forKey:@"DirectoryCategories"];
  416. [[AppGroup userDefaults] synchronize];
  417. }
  418. - (NSArray *)directoryCategoryIdsSortedByName {
  419. NSDictionary *allCategories = [self directoryCategories];
  420. NSArray *keys = [allCategories allKeys];
  421. NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
  422. NSString *first = [allCategories objectForKey:a];
  423. NSString *second = [allCategories objectForKey:b];
  424. return [first caseInsensitiveCompare:second];
  425. }];
  426. return sortedKeys;
  427. }
  428. - (NSDictionary *)lastWorkUpdateRequest {
  429. return [[AppGroup userDefaults] objectForKey:@"LastWorkUpdateRequest"];
  430. }
  431. - (void)setLastWorkUpdateRequest:(NSDictionary *)lastWorkUpdateRequest {
  432. [[AppGroup userDefaults] setObject:lastWorkUpdateRequest forKey:@"LastWorkUpdateRequest"];
  433. [[AppGroup userDefaults] synchronize];
  434. }
  435. - (NSDate *)lastWorkUpdateDate {
  436. return [[AppGroup userDefaults] objectForKey:@"LastWorkUpdateDate"];
  437. }
  438. - (void)setLastWorkUpdateDate:(NSDate *)lastWorkUpdateDate {
  439. [[AppGroup userDefaults] setObject:lastWorkUpdateDate forKey:@"LastWorkUpdateDate"];
  440. [[AppGroup userDefaults] synchronize];
  441. }
  442. - (NSString*)keyFingerprint {
  443. if (publicKey == nil)
  444. return nil;
  445. return [CryptoUtils fingerprintForPublicKey:publicKey];
  446. }
  447. - (NSData*)keySecret {
  448. NSData *mySecretKey = [self _obtainSecretKey];
  449. return mySecretKey;
  450. }
  451. - (NSData*)encryptData:(NSData*)data withNonce:(NSData*)nonce publicKey:(NSData*)_publicKey {
  452. NSData *mySecretKey = [self _obtainSecretKey];
  453. if (mySecretKey == nil) {
  454. DDLogError(@"Cannot encrypt: no secret key");
  455. return nil;
  456. }
  457. @try {
  458. return [[NaClCrypto sharedCrypto] encryptData:data withPublicKey:_publicKey signKey:mySecretKey nonce:nonce];
  459. }
  460. @catch (NSException *exception) {
  461. DDLogError(@"Cannot encrypt: %@", [exception reason]);
  462. return nil;
  463. }
  464. }
  465. - (NSData*)decryptData:(NSData*)data withNonce:(NSData*)nonce publicKey:(NSData*)_publicKey {
  466. NSData *mySecretKey = [self _obtainSecretKey];
  467. if (mySecretKey == nil) {
  468. DDLogError(@"Cannot decrypt: no secret key");
  469. return nil;
  470. }
  471. @try {
  472. return [[NaClCrypto sharedCrypto] decryptData:data withSecretKey:mySecretKey signKey:_publicKey nonce:nonce];
  473. }
  474. @catch (NSException *exception) {
  475. DDLogError(@"Cannot decrypt: %@", [exception reason]);
  476. return nil;
  477. }
  478. }
  479. - (NSData*)_obtainSecretKey {
  480. NSData *mySecretKey = secretKey;
  481. if (mySecretKey == nil && secretKeyInKeychain) {
  482. NSMutableDictionary *queryDict = [NSMutableDictionary dictionary];
  483. [queryDict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  484. [queryDict setObject:keychainLabel forKey:(__bridge id)kSecAttrLabel];
  485. [queryDict setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
  486. CFDataRef resultRef;
  487. if (SecItemCopyMatching((__bridge CFDictionaryRef)(queryDict), (CFTypeRef *)&resultRef) == noErr) {
  488. mySecretKey = (__bridge_transfer NSData*)resultRef;
  489. }
  490. }
  491. return mySecretKey;
  492. }
  493. - (NSString*)backupIdentityWithPassword:(NSString*)password {
  494. if ([self isInvalidIdentity]) {
  495. return nil;
  496. }
  497. /* Identity backup: derive an encryption key from the given password
  498. using PBKDF2 with 100000 iterations and a 64 bit salt.
  499. Then use it to encrypt a binary string of the following format
  500. using crypto_stream (zero nonce):
  501. <identity><private key><hash>
  502. hash = first two bytes of SHA256(<identity><private key>)
  503. Prepend the salt to the encrypted string and Base32-encode the result.
  504. Finally, split into groups of 4 and separate with dashes.
  505. The result will look like:
  506. XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-
  507. XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX
  508. */
  509. NSData *salt = [[NaClCrypto sharedCrypto] randomBytes:8];
  510. NSData *keyData = [self deriveBackupKeyFromPassword:password salt:salt];
  511. if (keyData == nil)
  512. return nil;
  513. NSData *mySecretKey = [self _obtainSecretKey];
  514. NSMutableData *idData = [NSMutableData dataWithCapacity:(mySecretKey.length + kIdentityLen + 2)];
  515. [idData appendData:[identity dataUsingEncoding:NSASCIIStringEncoding]];
  516. [idData appendData:mySecretKey];
  517. unsigned char digest[CC_SHA256_DIGEST_LENGTH];
  518. CC_SHA256(idData.bytes, (CC_LONG)idData.length, digest);
  519. [idData appendBytes:digest length:2];
  520. NSData *nonceData = [[NaClCrypto sharedCrypto] zeroBytes:kNaClCryptoStreamNonceSize];
  521. NSData *idDataEncrypted = [[NaClCrypto sharedCrypto] streamXorData:idData secretKey:keyData nonce:nonceData];
  522. /* Concatenate salt + encrypted data. The result should be 50 bytes. */
  523. NSMutableData *idDataWithSalt = [NSMutableData dataWithData:salt];
  524. [idDataWithSalt appendData:idDataEncrypted];
  525. NSString *base32 = [idDataWithSalt base32String];
  526. NSString *grouped = [self addBackupGroupDashes:base32];
  527. return grouped;
  528. }
  529. - (NSString*)addBackupGroupDashes:(NSString*)backup {
  530. NSString *myBackup = [backup stringByReplacingOccurrencesOfString:@"-" withString:@""];
  531. NSMutableString *grouped = [NSMutableString string];
  532. for (int i = 0; i < myBackup.length; i += 4) {
  533. NSUInteger len = 4;
  534. if ((i + len) > myBackup.length)
  535. len = myBackup.length - i;
  536. if (grouped.length > 0)
  537. [grouped appendString:@"-"];
  538. [grouped appendString:[myBackup substringWithRange:NSMakeRange(i, len)]];
  539. }
  540. return grouped;
  541. }
  542. - (void)restoreFromBackup:(NSString*)backup withPassword:(NSString*)password onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  543. /* decode Base32 data */
  544. NSData *backupDecoded = [NSData dataWithBase32String:backup];
  545. if (backupDecoded.length != 50) {
  546. DDLogError(@"Invalid decoded backup length: %lu", (unsigned long)backupDecoded.length);
  547. onError([ThreemaError threemaError:NSLocalizedString(@"bad_identity_backup", nil)]);
  548. return;
  549. }
  550. /* Extract salt and derive key */
  551. NSData *salt = [NSData dataWithBytes:backupDecoded.bytes length:8];
  552. NSData *keyData = [self deriveBackupKeyFromPassword:password salt:salt];
  553. if (keyData == nil) {
  554. DDLogError(@"Invalid password");
  555. onError([ThreemaError threemaError:NSLocalizedString(@"bad_identity_backup", nil)]);
  556. return;
  557. }
  558. /* Decrypt backup data */
  559. NSData *backupData = [NSData dataWithBytes:(backupDecoded.bytes + 8) length:42];
  560. NSData *nonceData = [[NaClCrypto sharedCrypto] zeroBytes:kNaClCryptoStreamNonceSize];
  561. NSData *backupDataDecrypted = [[NaClCrypto sharedCrypto] streamXorData:backupData secretKey:keyData nonce:nonceData];
  562. if (backupDataDecrypted == nil) {
  563. /* should never happen, even if the data is invalid */
  564. onError([ThreemaError threemaError:@"Backup decryption failed"]);
  565. return;
  566. }
  567. /* Calculate digest and verify */
  568. unsigned char digest[CC_SHA256_DIGEST_LENGTH];
  569. CC_SHA256(backupDataDecrypted.bytes, 40, digest);
  570. if (memcmp(digest, backupDataDecrypted.bytes + 40, 2) != 0) {
  571. DDLogWarn(@"Digest mismatch in decrypted identity backup");
  572. onError([ThreemaError threemaError:NSLocalizedString(@"bad_identity_backup", nil)]);
  573. return;
  574. }
  575. identity = [[NSString alloc] initWithData:[NSData dataWithBytes:backupDataDecrypted.bytes length:kIdentityLen] encoding:NSASCIIStringEncoding];
  576. secretKey = [NSData dataWithBytes:(backupDataDecrypted.bytes + 8) length:kNaClCryptoSecKeySize];
  577. [self restoreFromBackup:identity withSecretKey:secretKey onCompletion:onCompletion onError:onError];
  578. }
  579. - (void)restoreFromBackup:(NSString*)myIdentity withSecretKey:(NSData*)mySecretKey onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  580. identity = [[NSString alloc] initWithString:myIdentity];
  581. secretKey = [NSData dataWithBytes:(mySecretKey.bytes) length:kNaClCryptoSecKeySize];
  582. /* derive public key and store everything */
  583. publicKey = [[NaClCrypto sharedCrypto] derivePublicKeyFromSecretKey:secretKey];
  584. if (publicKey == nil) {
  585. /* should never happen, even if the data is invalid */
  586. onError([ThreemaError threemaError:@"Public key derivation failed"]);
  587. return;
  588. }
  589. secretKeyInKeychain = NO;
  590. serverGroup = nil;
  591. DDLogInfo(@"Restored identity %@ from backup", identity);
  592. onCompletion();
  593. }
  594. - (BOOL)isValidBackupFormat:(NSString *)backup {
  595. if (backup == nil)
  596. return NO;
  597. NSData *backupDecoded = [NSData dataWithBase32String:backup];
  598. return (backupDecoded.length == 50);
  599. }
  600. - (NSData*)deriveBackupKeyFromPassword:(NSString*)password salt:(NSData*)salt {
  601. NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
  602. uint8_t key[kNaClCryptoStreamKeySize];
  603. if (CCKeyDerivationPBKDF(kCCPBKDF2, passwordData.bytes, passwordData.length,
  604. salt.bytes, salt.length, kCCPRFHmacAlgSHA256, 100000,
  605. key, kNaClCryptoStreamKeySize) != 0) {
  606. DDLogError(@"PBKDF key derivation failed");
  607. return nil;
  608. }
  609. return [NSData dataWithBytes:key length:kNaClCryptoStreamKeySize];
  610. }
  611. - (void)migrateProfilePicture {
  612. NSMutableDictionary *profile = [[AppGroup userDefaults] objectForKey:@"ProfilePicture"];
  613. if (profile != nil) {
  614. [profile writeToFile:[self profilePicturePath] atomically:YES];
  615. [[AppGroup userDefaults] removeObjectForKey:@"ProfilePicture"];
  616. }
  617. }
  618. - (NSString *)profilePicturePath {
  619. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  620. return [[paths objectAtIndex:0] stringByAppendingPathComponent:@"ProfilePicture.out"];
  621. }
  622. - (BOOL)sendUpdateWorkInfoStatus {
  623. NSDate *dateLastCheck = [self lastWorkUpdateDate];
  624. if (dateLastCheck == nil) {
  625. return true;
  626. }
  627. return ![[NSCalendar currentCalendar] isDate:dateLastCheck inSameDayAsDate:[NSDate date]];
  628. }
  629. @end