ServerAPIConnector.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  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. #include <CommonCrypto/CommonDigest.h>
  21. #import "ServerAPIConnector.h"
  22. #import "ServerAPIRequest.h"
  23. #import "NSString+Hex.h"
  24. #import "NaClCrypto.h"
  25. #import "Utils.h"
  26. #import "ThreemaError.h"
  27. #import "FeatureMask.h"
  28. #import "UserSettings.h"
  29. #import "MyIdentityStore.h"
  30. #import "LicenseStore.h"
  31. #import "AppGroup.h"
  32. #ifdef DEBUG
  33. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  34. #else
  35. static const DDLogLevel ddLogLevel = DDLogLevelInfo;
  36. #endif
  37. @implementation ServerAPIConnector
  38. - (void)createIdentityWithStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(MyIdentityStore *store))onCompletion onError:(void(^)(NSError *error))onError {
  39. static NSString *apiPath = @"identity/create";
  40. NSMutableDictionary *request = [NSMutableDictionary dictionaryWithDictionary:@{
  41. @"publicKey": [identityStore.publicKey base64EncodedStringWithOptions:0],
  42. @"version": [Utils getClientVersion],
  43. @"deviceId": [[[UIDevice currentDevice] identifierForVendor] UUIDString]
  44. }];
  45. // add App Store receipt if available (but not for Work)
  46. NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
  47. if (receiptUrl && [[NSFileManager defaultManager] fileExistsAtPath:receiptUrl.path] && ![LicenseStore requiresLicenseKey]) {
  48. NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
  49. if (receiptData) {
  50. request[@"appStoreReceipt"] = [receiptData base64EncodedStringWithOptions:0];
  51. }
  52. }
  53. // add Work license if available
  54. NSString *licenseUsername = [[LicenseStore sharedLicenseStore] licenseUsername];
  55. NSString *licensePassword = [[LicenseStore sharedLicenseStore] licensePassword];
  56. if (licenseUsername && licensePassword) {
  57. request[@"licenseUsername"] = licenseUsername;
  58. request[@"licensePassword"] = licensePassword;
  59. }
  60. [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
  61. NSData *nonce = [@"createIdentity response." dataUsingEncoding:NSASCIIStringEncoding];
  62. [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response withNonce:nonce forStore:identityStore onCompletion:^(NSDictionary *response) {
  63. identityStore.identity = response[@"identity"];
  64. identityStore.serverGroup = response[@"serverGroup"];
  65. [identityStore storeInKeychain];
  66. [FeatureMask updateFeatureMask];
  67. onCompletion(identityStore);
  68. } onError:onError];
  69. } onError:onError];
  70. }
  71. - (void)fetchIdentityInfo:(NSString*)identity onCompletion:(void(^)(NSData *publicKey, NSNumber *state, NSNumber *type, NSNumber *featureMask))onCompletion onError:(void(^)(NSError *error))onError {
  72. NSString *apiPath = [NSString stringWithFormat:@"identity/%@", identity];
  73. [ServerAPIRequest loadJSONFromAPIPath:apiPath withCachePolicy:NSURLRequestUseProtocolCachePolicy onCompletion:^(id jsonObject) {
  74. NSData *publicKey = [[NSData alloc] initWithBase64EncodedString:jsonObject[@"publicKey"] options:0];
  75. NSNumber *state = [NSNumber numberWithInt:[jsonObject[@"state"] intValue]];
  76. NSNumber *type = [NSNumber numberWithInt:[jsonObject[@"type"] intValue]];
  77. NSNumber *featureMask = [NSNumber numberWithInt:[jsonObject[@"featureMask"] intValue]];
  78. onCompletion(publicKey, state, type, featureMask);
  79. } onError:^(NSError *error) {
  80. onError(error);
  81. }];
  82. }
  83. - (void)fetchBulkIdentityInfo:(NSArray*)identities onCompletion:(void(^)(NSArray *identities, NSArray *publicKeys, NSArray *featureMasks, NSArray *states, NSArray *types))onCompletion onError:(void(^)(NSError *error))onError {
  84. NSDictionary *req = [NSDictionary dictionaryWithObjectsAndKeys:identities, @"identities", nil];
  85. [ServerAPIRequest postJSONToAPIPath:@"identity/fetch_bulk" data:req onCompletion:^(id jsonObject) {
  86. DDLogVerbose(@"Bulk fetch of ID status success: %@", jsonObject);
  87. NSMutableArray *responseIdentities = [[NSMutableArray alloc] init];
  88. NSMutableArray *responsePublicKeys = [[NSMutableArray alloc] init];
  89. NSMutableArray *responseFeatureMasks = [[NSMutableArray alloc] init];
  90. NSMutableArray *responseStates = [[NSMutableArray alloc] init];
  91. NSMutableArray *responseTypes = [[NSMutableArray alloc] init];
  92. int index = 0;
  93. NSUInteger count = [jsonObject[@"identities"] count];
  94. while(index < count) {
  95. id item = jsonObject[@"identities"][index];
  96. [responseIdentities addObject:item[@"identity"]];
  97. [responsePublicKeys addObject:[[NSData alloc] initWithBase64EncodedString:item[@"publicKey"] options:0]];
  98. [responseFeatureMasks addObject:item[@"featureMask"]];
  99. [responseStates addObject:item[@"state"]];
  100. [responseTypes addObject:item[@"type"]];
  101. index++;
  102. }
  103. onCompletion(responseIdentities, responsePublicKeys, responseFeatureMasks, responseStates, responseTypes);
  104. } onError:^(NSError *error) {
  105. DDLogError(@"Bulk fetch of ID status failed: %@", error);
  106. onError(error);
  107. }];
  108. }
  109. - (void)fetchPrivateIdentityInfo:(MyIdentityStore*)identityStore onCompletion:(void(^)(NSString *serverGroup, NSString *email, NSString *mobileNo))onCompletion onError:(void(^)(NSError *error))onError {
  110. if (identityStore.identity == nil) {
  111. onError([ThreemaError threemaError:@"store has no valid identity"]);
  112. return;
  113. }
  114. NSDictionary *request = @{
  115. @"identity": identityStore.identity
  116. };
  117. [self sendSignedRequest:request toApiPath:@"identity/fetch_priv" forStore:identityStore onCompletion:^(id jsonObject) {
  118. onCompletion(jsonObject[@"serverGroup"], jsonObject[@"email"], jsonObject[@"mobileNo"]);
  119. } onError:onError];
  120. }
  121. - (void)updateMyIdentityStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError* error))onError {
  122. [self fetchPrivateIdentityInfo:identityStore onCompletion:^(NSString *serverGroup, NSString *email, NSString *mobileNo) {
  123. DDLogVerbose(@"Got identity info: serverGroup %@, email %@, mobileNo %@", serverGroup, email, mobileNo);
  124. identityStore.serverGroup = serverGroup;
  125. identityStore.linkedEmail = email;
  126. if (identityStore.linkedEmail != nil) {
  127. identityStore.linkEmailPending = NO;
  128. }
  129. identityStore.linkedMobileNo = mobileNo;
  130. if (identityStore.linkedMobileNo != nil) {
  131. identityStore.linkMobileNoPending = NO;
  132. }
  133. onCompletion();
  134. } onError:^(NSError *error) {
  135. onError(error);
  136. }];
  137. }
  138. - (void)linkEmailWithStore:(MyIdentityStore*)identityStore email:(NSString*)email onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
  139. static NSString *apiPath = @"identity/link_email";
  140. if (identityStore.identity == nil) {
  141. onError([ThreemaError threemaError:@"store has no valid identity"]);
  142. return;
  143. }
  144. if (email == nil) {
  145. onError([ThreemaError threemaError:@"link email with store: email missing"]);
  146. return;
  147. }
  148. NSDictionary *request = @{
  149. @"identity": identityStore.identity,
  150. @"email": email,
  151. @"lang": [self preferredLanguage]
  152. };
  153. [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
  154. if ([response[@"linked"] boolValue]) {
  155. /* Already linked - update address with user provided value, as it is possible that we currently
  156. only have ***@*** from the server after an ID restore */
  157. identityStore.linkEmailPending = NO;
  158. identityStore.linkedEmail = email;
  159. onCompletion(YES);
  160. return;
  161. }
  162. [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:^(NSDictionary *response) {
  163. if (email.length == 0) {
  164. /* unlink */
  165. identityStore.linkEmailPending = NO;
  166. identityStore.linkedEmail = nil;
  167. } else {
  168. identityStore.linkEmailPending = YES;
  169. identityStore.linkedEmail = email;
  170. }
  171. onCompletion(NO);
  172. } onError:onError];
  173. } onError:onError];
  174. }
  175. - (NSString *)preferredLanguage {
  176. NSString *lang = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];
  177. if (lang) {
  178. return lang;
  179. } else {
  180. return @"en_US";
  181. }
  182. }
  183. - (void)checkLinkEmailStatus:(MyIdentityStore*)identityStore email:(NSString*)email onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
  184. if (identityStore.identity == nil) {
  185. onError([ThreemaError threemaError:@"store has no valid identity"]);
  186. return;
  187. }
  188. if (email == nil) {
  189. onError([ThreemaError threemaError:@"check link email status: email missing"]);
  190. return;
  191. }
  192. NSDictionary *request = @{
  193. @"identity": identityStore.identity,
  194. @"email": email
  195. };
  196. [self sendSignedRequestPhase1:request toApiPath:@"identity/link_email" onCompletion:^(NSDictionary *response) {
  197. BOOL linked = [response[@"linked"] boolValue];
  198. if (linked)
  199. [MyIdentityStore sharedMyIdentityStore].linkEmailPending = NO;
  200. onCompletion(linked);
  201. } onError:onError];
  202. }
  203. - (void)linkMobileNoWithStore:(MyIdentityStore*)identityStore mobileNo:(NSString*)mobileNo onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
  204. if (identityStore.identity == nil) {
  205. onError([ThreemaError threemaError:@"store has no valid identity"]);
  206. return;
  207. }
  208. if (mobileNo == nil) {
  209. onError([ThreemaError threemaError:@"link mobile: mobileNo missing"]);
  210. return;
  211. }
  212. static NSString *apiPath = @"identity/link_mobileno";
  213. NSString *urlScheme = @"threema";
  214. if ([LicenseStore requiresLicenseKey]) {
  215. urlScheme = @"threemawork";
  216. }
  217. NSDictionary *request = @{
  218. @"identity": identityStore.identity,
  219. @"mobileNo": mobileNo,
  220. @"lang": [self preferredLanguage],
  221. @"urlScheme": urlScheme
  222. };
  223. [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
  224. if ([response[@"linked"] boolValue]) {
  225. /* already linked */
  226. identityStore.linkMobileNoPending = NO;
  227. identityStore.linkedMobileNo = mobileNo;
  228. identityStore.linkMobileNoVerificationId = nil;
  229. onCompletion(YES);
  230. return;
  231. }
  232. [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:^(NSDictionary *response) {
  233. if (mobileNo.length == 0) {
  234. /* unlink */
  235. identityStore.linkMobileNoPending = NO;
  236. identityStore.linkedMobileNo = nil;
  237. identityStore.linkMobileNoVerificationId = nil;
  238. } else {
  239. identityStore.linkMobileNoPending = YES;
  240. identityStore.linkedMobileNo = mobileNo;
  241. identityStore.linkMobileNoVerificationId = response[@"verificationId"];
  242. identityStore.linkMobileNoStartDate = [NSDate date];
  243. }
  244. onCompletion(NO);
  245. } onError:onError];
  246. } onError:onError];
  247. }
  248. - (void)linkMobileNoWithStore:(MyIdentityStore*)identityStore code:(NSString*)code onCompletion:(void(^)(BOOL linked))onCompletion onError:(void(^)(NSError *error))onError {
  249. if (!identityStore.linkMobileNoPending || identityStore.linkMobileNoVerificationId == nil) {
  250. DDLogWarn(@"No mobileNo verification pending");
  251. onError([ThreemaError threemaError:@"No verification pending"]);
  252. return;
  253. }
  254. NSDictionary *req = @{
  255. @"verificationId": identityStore.linkMobileNoVerificationId,
  256. @"code": code
  257. };
  258. [ServerAPIRequest postJSONToAPIPath:@"identity/link_mobileno" data:req onCompletion:^(id jsonObject) {
  259. DDLogVerbose(@"link mobileNo phase 3 request success: %@", jsonObject);
  260. identityStore.linkMobileNoPending = NO;
  261. identityStore.linkMobileNoVerificationId = nil;
  262. [[NSNotificationCenter defaultCenter] postNotificationName:@"ThreemaIdentityLinkedWithMobileNo" object:nil];
  263. onCompletion(YES);
  264. } onError:^(NSError *error) {
  265. DDLogError(@"link mobileNo phase 3 request failed: %@", error);
  266. onError(error);
  267. }];
  268. }
  269. - (void)linkMobileNoRequestCallWithStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  270. if (!identityStore.linkMobileNoPending || identityStore.linkMobileNoVerificationId == nil) {
  271. DDLogWarn(@"No mobileNo verification pending");
  272. onError([ThreemaError threemaError:@"No verification pending"]);
  273. return;
  274. }
  275. NSDictionary *req = @{
  276. @"verificationId": identityStore.linkMobileNoVerificationId
  277. };
  278. [ServerAPIRequest postJSONToAPIPath:@"identity/link_mobileno_call" data:req onCompletion:^(id jsonObject) {
  279. DDLogVerbose(@"link mobileNo call request success: %@", jsonObject);
  280. onCompletion();
  281. } onError:^(NSError *error) {
  282. DDLogError(@"link mobileNo call request failed: %@", error);
  283. onError(error);
  284. }];
  285. }
  286. - (void)obtainMatchTokenForIdentity:(MyIdentityStore*)identityStore forceRefresh:(BOOL)forceRefresh onCompletion:(void(^)(NSString *matchToken))onCompletion onError:(void(^)(NSError *error))onError {
  287. if (identityStore.identity == nil) {
  288. onCompletion(nil);
  289. return;
  290. }
  291. // Cached match token?
  292. NSUserDefaults *defaults = [AppGroup userDefaults];
  293. NSString *matchToken = [defaults objectForKey:@"MatchToken"];
  294. if (!forceRefresh && matchToken) {
  295. onCompletion(matchToken);
  296. return;
  297. }
  298. NSDictionary *request = @{
  299. @"identity": identityStore.identity
  300. };
  301. [self sendSignedRequest:request toApiPath:@"identity/match_token" forStore:identityStore onCompletion:^(id jsonObject) {
  302. if (jsonObject[@"matchToken"]) {
  303. [defaults setObject:jsonObject[@"matchToken"] forKey:@"MatchToken"];
  304. [defaults synchronize];
  305. onCompletion(jsonObject[@"matchToken"]);
  306. } else {
  307. onError([ThreemaError threemaError:jsonObject[@"error"]]);
  308. }
  309. } onError:onError];
  310. }
  311. - (void)matchIdentitiesWithEmailHashes:(NSArray*)emailHashes mobileNoHashes:(NSArray*)mobileNoHashes includeInactive:(BOOL)includeInactive onCompletion:(void(^)(NSArray *identities, int checkInterval))onCompletion onError:(void(^)(NSError *error))onError {
  312. [self obtainMatchTokenForIdentity:[MyIdentityStore sharedMyIdentityStore] forceRefresh:NO onCompletion:^(NSString *matchToken) {
  313. DDLogInfo(@"Match token: %@", matchToken);
  314. [self matchIdentitiesWithEmailHashes:emailHashes mobileNoHashes:mobileNoHashes includeInactive:includeInactive matchToken:matchToken onCompletion:onCompletion onError:^(NSError *error) {
  315. // Match token may be invalid/expired, refresh and try again
  316. [self obtainMatchTokenForIdentity:[MyIdentityStore sharedMyIdentityStore] forceRefresh:YES onCompletion:^(NSString *matchToken) {
  317. [self matchIdentitiesWithEmailHashes:emailHashes mobileNoHashes:mobileNoHashes includeInactive:includeInactive matchToken:matchToken onCompletion:onCompletion onError:onError];
  318. } onError:onError];
  319. }];
  320. } onError:^(NSError *error) {
  321. DDLogError(@"Cannot obtain match token: %@", error);
  322. onError(error);
  323. }];
  324. }
  325. - (void)matchIdentitiesWithEmailHashes:(NSArray*)emailHashes mobileNoHashes:(NSArray*)mobileNoHashes includeInactive:(BOOL)includeInactive matchToken:(NSString*)matchToken onCompletion:(void(^)(NSArray *identities, int checkInterval))onCompletion onError:(void(^)(NSError *error))onError {
  326. NSMutableDictionary *req = [NSMutableDictionary dictionary];
  327. req[@"emailHashes"] = emailHashes;
  328. req[@"mobileNoHashes"] = mobileNoHashes;
  329. if (matchToken != nil) {
  330. req[@"matchToken"] = matchToken;
  331. }
  332. if (includeInactive) {
  333. req[@"includeInactive"] = @YES;
  334. }
  335. [ServerAPIRequest postJSONToAPIPath:@"identity/match" data:req onCompletion:^(id jsonObject) {
  336. DDLogVerbose(@"Match identities request success: %@", jsonObject);
  337. onCompletion([jsonObject objectForKey:@"identities"], ((NSNumber*)jsonObject[@"checkInterval"]).intValue);
  338. } onError:^(NSError *error) {
  339. DDLogError(@"Match identities request failed: %@", error);
  340. onError(error);
  341. }];
  342. }
  343. - (void)setFeatureMask:(NSNumber *)featureMask forStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  344. if (identityStore.identity == nil) {
  345. onError([ThreemaError threemaError:@"store has no valid identity"]);
  346. return;
  347. }
  348. NSString *apiPath = @"identity/set_featuremask";
  349. NSDictionary *req = @{
  350. @"identity": identityStore.identity,
  351. @"featureMask": featureMask
  352. };
  353. [self sendSignedRequest:req toApiPath:apiPath forStore:identityStore onCompletion:^(NSDictionary *response) {
  354. if ([response[@"success"] boolValue])
  355. onCompletion();
  356. else
  357. onError([ThreemaError threemaError:response[@"error"]]);
  358. } onError:onError];
  359. }
  360. - (void)getFeatureMasksForIdentities:(NSArray*)identities onCompletion:(void(^)(NSArray* featureMasks))onCompletion onError:(void(^)(NSError *error))onError {
  361. NSDictionary *req = [NSDictionary dictionaryWithObjectsAndKeys:identities, @"identities", nil];
  362. [ServerAPIRequest postJSONToAPIPath:@"identity/check_featuremask" data:req onCompletion:^(id jsonObject) {
  363. DDLogVerbose(@"Check featureMask success: %@", jsonObject);
  364. NSArray *featureMaskStrings = jsonObject[@"featureMasks"];
  365. NSMutableArray *featureMaskNumbers = [NSMutableArray arrayWithCapacity: [featureMaskStrings count]];
  366. for (NSString *maskString in featureMaskStrings) {
  367. if ([maskString isEqual: [NSNull null]]) {
  368. NSNumber *number = [NSNumber numberWithInt: 0];
  369. [featureMaskNumbers addObject: number];
  370. } else {
  371. NSNumber *number = [NSNumber numberWithInt: maskString.intValue];
  372. [featureMaskNumbers addObject: number];
  373. }
  374. }
  375. onCompletion(featureMaskNumbers);
  376. } onError:^(NSError *error) {
  377. DDLogError(@"Check featureMask failed: %@", error);
  378. onError(error);
  379. }];
  380. }
  381. - (void)checkRevocationPasswordForStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(BOOL revocationPasswordSet, NSDate *lastChanged))onCompletion onError:(void(^)(NSError *error))onError {
  382. if (identityStore.identity == nil) {
  383. onError([ThreemaError threemaError:@"store has no valid identity"]);
  384. return;
  385. }
  386. NSDictionary *request = @{
  387. @"identity": identityStore.identity
  388. };
  389. [self sendSignedRequest:request toApiPath:@"identity/check_revocation_key" forStore:identityStore onCompletion:^(NSDictionary *response) {
  390. BOOL revocationPasswordSet = [response[@"revocationKeySet"] boolValue];
  391. NSDate *lastChanged = nil;
  392. if (revocationPasswordSet)
  393. lastChanged = [Utils parseISO8601DateString:response[@"lastChanged"]];
  394. onCompletion(revocationPasswordSet, lastChanged);
  395. } onError:onError];
  396. }
  397. - (void)setRevocationPassword:(NSString*)revocationPassword forStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  398. if (identityStore.identity == nil) {
  399. onError([ThreemaError threemaError:@"store has no valid identity"]);
  400. return;
  401. }
  402. /* key = first 32 bits of SHA-256 hash of password string */
  403. NSData *revocationPasswordData = [revocationPassword dataUsingEncoding:NSUTF8StringEncoding];
  404. unsigned char digest[CC_SHA256_DIGEST_LENGTH];
  405. CC_SHA256(revocationPasswordData.bytes, (CC_LONG)revocationPasswordData.length, digest);
  406. NSData *revocationKey = [NSData dataWithBytes:digest length:4];
  407. NSDictionary *request = @{
  408. @"identity": identityStore.identity,
  409. @"revocationKey": [revocationKey base64EncodedStringWithOptions:0]
  410. };
  411. [self sendSignedRequest:request toApiPath:@"identity/set_revocation_key" forStore:identityStore onCompletion:^(id jsonObject) {
  412. if ([jsonObject[@"success"] boolValue])
  413. onCompletion();
  414. else
  415. onError([ThreemaError threemaError:jsonObject[@"error"]]);
  416. } onError:onError];
  417. }
  418. - (void)checkStatusOfIdentities:(NSArray*)identities onCompletion:(void(^)(NSArray* states, NSArray* types, NSArray* featureMasks, int checkInterval))onCompletion onError:(void(^)(NSError *error))onError {
  419. NSDictionary *req = [NSDictionary dictionaryWithObjectsAndKeys:identities, @"identities", nil];
  420. [ServerAPIRequest postJSONToAPIPath:@"identity/check" data:req onCompletion:^(id jsonObject) {
  421. DDLogVerbose(@"Check ID status success: %@", jsonObject);
  422. onCompletion(jsonObject[@"states"], jsonObject[@"types"], jsonObject[@"featureMasks"], [jsonObject[@"checkInterval"] intValue]);
  423. } onError:^(NSError *error) {
  424. DDLogError(@"Check ID status failed: %@", error);
  425. onError(error);
  426. }];
  427. }
  428. - (void)revokeIdForStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  429. if (identityStore.identity == nil) {
  430. onError([ThreemaError threemaError:@"store has no valid identity"]);
  431. return;
  432. }
  433. NSDictionary *request = @{
  434. @"identity": identityStore.identity,
  435. @"lang": [self preferredLanguage]
  436. };
  437. [self sendSignedRequest:request toApiPath:@"identity/revoke" forStore:identityStore onCompletion:^(id jsonObject) {
  438. if ([jsonObject[@"success"] boolValue])
  439. onCompletion();
  440. else
  441. onError([ThreemaError threemaError:jsonObject[@"error"]]);
  442. } onError:onError];
  443. }
  444. - (void)validateLicenseUsername:(NSString*)licenseUsername password:(NSString*)licensePassword appId:(NSString*)appId version:(NSString*)version deviceId:(NSString*)deviceId onCompletion:(void(^)(BOOL success, NSDictionary *info))onCompletion onError:(void(^)(NSError *error))onError {
  445. static NSString *apiPath = @"check_license";
  446. NSDictionary *request = @{
  447. @"licenseUsername": licenseUsername,
  448. @"licensePassword": licensePassword,
  449. @"appId": appId,
  450. @"version": version,
  451. @"deviceId": deviceId
  452. };
  453. [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
  454. BOOL success = [response[@"success"] boolValue];
  455. onCompletion(success, response);
  456. } onError:onError];
  457. }
  458. - (void)updateWorkInfoForStore:(MyIdentityStore*)identityStore licenseUsername:(NSString*)licenseUsername password:(NSString*)licensePassword onCompletion:(void(^)(void))onCompletion onError:(void(^)(NSError *error))onError {
  459. if (identityStore.identity == nil) {
  460. onError([ThreemaError threemaError:@"store has no valid identity"]);
  461. return;
  462. }
  463. NSMutableDictionary *request = [@{
  464. @"identity": identityStore.identity,
  465. @"licenseUsername": licenseUsername,
  466. @"licensePassword": licensePassword,
  467. @"publicNickname": (identityStore.pushFromName != nil ? identityStore.pushFromName : identityStore.identity),
  468. @"version": [Utils getClientVersion]
  469. } mutableCopy];
  470. if (identityStore.firstName != nil)
  471. request[@"firstName"] = identityStore.firstName;
  472. if (identityStore.lastName != nil)
  473. request[@"lastName"] = identityStore.lastName;
  474. if (identityStore.csi != nil)
  475. request[@"csi"] = identityStore.csi;
  476. if (identityStore.category != nil)
  477. request[@"category"] = identityStore.category;
  478. if ([request isEqualToDictionary:identityStore.lastWorkUpdateRequest] && ![identityStore sendUpdateWorkInfoStatus]) {
  479. // request hasn't changed since last update and it's the same date
  480. onCompletion();
  481. return;
  482. }
  483. identityStore.lastWorkUpdateRequest = request;
  484. [self sendSignedRequest:request toApiPath:@"identity/update_work_info" forStore:identityStore onCompletion:^(id jsonObject) {
  485. if ([jsonObject[@"success"] boolValue]) {
  486. [identityStore setLastWorkUpdateDate:[NSDate date]];
  487. onCompletion();
  488. } else {
  489. onError([ThreemaError threemaError:jsonObject[@"error"]]);
  490. }
  491. } onError:onError];
  492. }
  493. - (void)searchInDirectory:(NSString *)searchString categories:(NSArray *)categories page:(int)page forLicenseStore:(LicenseStore *)licenseStore forMyIdentityStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(NSArray *contacts, NSDictionary *paging))onCompletion onError:(void(^)(NSError *error))onError {
  494. NSString *sortOrder = [[UserSettings sharedUserSettings] sortOrderFirstName] ? @"firstName" : @"lastName";
  495. NSMutableDictionary *req = [[NSMutableDictionary alloc] initWithDictionary:@{@"username": licenseStore.licenseUsername,
  496. @"password": licenseStore.licensePassword,
  497. @"identity": identityStore.identity,
  498. @"query": searchString,
  499. @"sort": @{@"by": sortOrder, @"asc": @true},
  500. @"page": [NSNumber numberWithInt:page]
  501. }];
  502. if (categories.count > 0) {
  503. [req setValue:categories forKey:@"categories"];
  504. }
  505. [ServerAPIRequest postJSONToWorkAPIPath:@"directory" data:req onCompletion:^(id jsonObject) {
  506. DDLogVerbose(@"Work directory search success: %@", jsonObject);
  507. NSArray *contactsArray = jsonObject[@"contacts"];
  508. NSDictionary *paging = jsonObject[@"paging"];
  509. onCompletion(contactsArray, paging);
  510. } onError:^(NSError *error) {
  511. DDLogError(@"Check featureMask failed: %@", error);
  512. onError(error);
  513. }];
  514. }
  515. - (void)obtainTurnServersWithStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
  516. if (identityStore.identity == nil) {
  517. onError([ThreemaError threemaError:@"No identity"]);
  518. return;
  519. }
  520. NSDictionary *request = @{
  521. @"identity": identityStore.identity,
  522. @"type": @"voip"
  523. };
  524. [self sendSignedRequest:request toApiPath:@"identity/turn_cred" forStore:identityStore onCompletion:^(id jsonObject) {
  525. if (jsonObject[@"turnUrls"]) {
  526. onCompletion(jsonObject);
  527. } else {
  528. onError([ThreemaError threemaError:jsonObject[@"error"]]);
  529. }
  530. } onError:onError];
  531. }
  532. - (void)sendSignedRequest:(NSDictionary*)request toApiPath:(NSString*)apiPath forStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
  533. [self sendSignedRequestPhase1:request toApiPath:apiPath onCompletion:^(NSDictionary *response) {
  534. [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:response forStore:identityStore onCompletion:onCompletion onError:onError];
  535. } onError:onError];
  536. }
  537. - (void)sendSignedRequestPhase1:(NSDictionary*)request toApiPath:(NSString*)apiPath onCompletion:(void(^)(NSDictionary* response))onCompletion onError:(void(^)(NSError *error))onError {
  538. [ServerAPIRequest postJSONToAPIPath:apiPath data:request onCompletion:^(id jsonObject) {
  539. DDLogVerbose(@"Send API request %@ phase 1 success: %@", apiPath, jsonObject);
  540. onCompletion((NSDictionary*)jsonObject);
  541. } onError:^(NSError *error) {
  542. DDLogVerbose(@"Send API request %@ phase 1 failed: %@", apiPath, error);
  543. onError(error);
  544. }];
  545. }
  546. - (void)sendSignedRequestPhase2:(NSDictionary*)request toApiPath:(NSString*)apiPath phase1Response:(id)phase1Response forStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
  547. NSData *nonce = [[NaClCrypto sharedCrypto] randomBytes:kNaClCryptoNonceSize];
  548. [self sendSignedRequestPhase2:request toApiPath:apiPath phase1Response:phase1Response withNonce:nonce forStore:identityStore onCompletion:onCompletion onError:onError];
  549. }
  550. - (void)sendSignedRequestPhase2:(NSDictionary*)request toApiPath:(NSString*)apiPath phase1Response:(id)phase1Response withNonce:(NSData*)nonce forStore:(MyIdentityStore*)identityStore onCompletion:(void(^)(NSDictionary *response))onCompletion onError:(void(^)(NSError *error))onError {
  551. NSDictionary *resp1 = (NSDictionary*)phase1Response;
  552. NSString *tokenStr = resp1[@"token"];
  553. NSData *token = [[NSData alloc] initWithBase64EncodedString:tokenStr options:0];
  554. NSData *tokenRespKeyPub = [[NSData alloc] initWithBase64EncodedString:resp1[@"tokenRespKeyPub"] options:0];
  555. /* sign token with our secret key */
  556. NSData *response = [identityStore encryptData:token withNonce:nonce publicKey:tokenRespKeyPub];
  557. if (response == nil) {
  558. NSError *error = [ThreemaError threemaError:@"could not encrypt response"];
  559. DDLogVerbose(@"Send API request %@ phase 2 failed: %@", apiPath, error);
  560. onError(error);
  561. return;
  562. }
  563. NSMutableDictionary *signedRequest = [NSMutableDictionary dictionaryWithDictionary:request];
  564. signedRequest[@"token"] = tokenStr;
  565. signedRequest[@"response"] = [response base64EncodedStringWithOptions:0];
  566. signedRequest[@"nonce"] = [nonce base64EncodedStringWithOptions:0];
  567. [ServerAPIRequest postJSONToAPIPath:apiPath data:signedRequest onCompletion:^(id jsonObject) {
  568. DDLogVerbose(@"Send API request %@ phase 2 success: %@", apiPath, jsonObject);
  569. onCompletion((NSDictionary*)jsonObject);
  570. } onError:^(NSError *error) {
  571. DDLogVerbose(@"Send API request %@ phase 2 failed: %@", apiPath, error);
  572. onError(error);
  573. }];
  574. }
  575. @end