// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSMessageDecrypter.h" #import "NSData+messagePadding.h" #import "NotificationsProtocol.h" #import "OWSAnalytics.h" #import "OWSBlockingManager.h" #import "OWSError.h" #import "OWSIdentityManager.h" #import "OWSPrimaryStorage+PreKeyStore.h" #import "OWSPrimaryStorage+SessionStore.h" #import "OWSPrimaryStorage+SignedPreKeyStore.h" #import "OWSPrimaryStorage.h" #import "SSKEnvironment.h" #import "SignalRecipient.h" #import "TSAccountManager.h" #import "TSContactThread.h" #import "TSErrorMessage.h" #import "TSPreKeyManager.h" #import #import #import NS_ASSUME_NONNULL_BEGIN @interface OWSMessageDecrypter () @property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; @property (nonatomic, readonly) OWSBlockingManager *blockingManager; @property (nonatomic, readonly) OWSIdentityManager *identityManager; @end #pragma mark - @implementation OWSMessageDecrypter + (instancetype)sharedManager { static OWSMessageDecrypter *sharedMyManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedMyManager = [[self alloc] initDefault]; }); return sharedMyManager; } - (instancetype)initDefault { OWSPrimaryStorage *primaryStorage = [OWSPrimaryStorage sharedManager]; OWSIdentityManager *identityManager = [OWSIdentityManager sharedManager]; OWSBlockingManager *blockingManager = [OWSBlockingManager sharedManager]; return [self initWithPrimaryStorage:primaryStorage identityManager:identityManager blockingManager:blockingManager]; } - (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage identityManager:(OWSIdentityManager *)identityManager blockingManager:(OWSBlockingManager *)blockingManager { self = [super init]; if (!self) { return self; } _primaryStorage = primaryStorage; _identityManager = identityManager; _blockingManager = blockingManager; _dbConnection = primaryStorage.newDatabaseConnection; OWSSingletonAssert(); return self; } #pragma mark - Blocking - (BOOL)isEnvelopeBlocked:(SSKProtoEnvelope *)envelope { OWSAssertDebug(envelope); return [_blockingManager.blockedPhoneNumbers containsObject:envelope.source]; } #pragma mark - Decryption - (void)decryptEnvelope:(SSKProtoEnvelope *)envelope successBlock:(DecryptSuccessBlock)successBlockParameter failureBlock:(DecryptFailureBlock)failureBlockParameter { OWSAssertDebug(envelope); OWSAssertDebug(successBlockParameter); OWSAssertDebug(failureBlockParameter); OWSAssertDebug([TSAccountManager isRegistered]); // successBlock is called synchronously so that we can avail ourselves of // the transaction. // // Ensure that failureBlock is called on a worker queue. DecryptFailureBlock failureBlock = ^() { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ failureBlockParameter(); }); }; DecryptSuccessBlock successBlock = ^(NSData *_Nullable plaintextData, YapDatabaseReadWriteTransaction *transaction) { [SignalRecipient markRecipientAsRegistered:envelope.source deviceId:envelope.sourceDevice transaction:transaction]; successBlockParameter(plaintextData, transaction); }; @try { OWSLogInfo(@"decrypting envelope: %@", [self descriptionForEnvelope:envelope]); OWSAssertDebug(envelope.source.length > 0); if ([self isEnvelopeBlocked:envelope]) { OWSLogInfo(@"ignoring blocked envelope: %@", envelope.source); failureBlock(); return; } switch (envelope.type) { case SSKProtoEnvelopeTypeCiphertext: { [self decryptSecureMessage:envelope successBlock:^(NSData *_Nullable plaintextData, YapDatabaseReadWriteTransaction *transaction) { OWSLogDebug(@"decrypted secure message."); successBlock(plaintextData, transaction); } failureBlock:^(NSError *_Nullable error) { OWSLogError(@"decrypting secure message from address: %@ failed with error: %@", envelopeAddress(envelope), error); OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleSecureMessage]); failureBlock(); }]; // Return to avoid double-acknowledging. return; } case SSKProtoEnvelopeTypePrekeyBundle: { [self decryptPreKeyBundle:envelope successBlock:^(NSData *_Nullable plaintextData, YapDatabaseReadWriteTransaction *transaction) { OWSLogDebug(@"decrypted pre-key whisper message"); successBlock(plaintextData, transaction); } failureBlock:^(NSError *_Nullable error) { OWSLogError(@"decrypting pre-key whisper message from address: %@ failed " @"with error: %@", envelopeAddress(envelope), error); OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandlePrekeyBundle]); failureBlock(); }]; // Return to avoid double-acknowledging. return; } // These message types don't have a payload to decrypt. case SSKProtoEnvelopeTypeReceipt: case SSKProtoEnvelopeTypeKeyExchange: case SSKProtoEnvelopeTypeUnknown: { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { successBlock(nil, transaction); }]; // Return to avoid double-acknowledging. return; } default: OWSLogWarn(@"Received unhandled envelope type: %d", (int)envelope.type); break; } } @catch (NSException *exception) { OWSFailDebug(@"Received an invalid envelope: %@", exception.debugDescription); OWSProdFail([OWSAnalyticsEvents messageManagerErrorInvalidProtocolMessage]); [[OWSPrimaryStorage.sharedManager newDatabaseConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; [[SSKEnvironment sharedEnv].notificationsManager notifyUserForThreadlessErrorMessage:errorMessage transaction:transaction]; }]; } failureBlock(); } - (void)decryptSecureMessage:(SSKProtoEnvelope *)envelope successBlock:(DecryptSuccessBlock)successBlock failureBlock:(void (^)(NSError *_Nullable error))failureBlock { OWSAssertDebug(envelope); OWSAssertDebug(successBlock); OWSAssertDebug(failureBlock); [self decryptEnvelope:envelope cipherTypeName:@"Secure Message" cipherMessageBlock:^(NSData *encryptedData) { return [[WhisperMessage alloc] initWithData:encryptedData]; } successBlock:successBlock failureBlock:failureBlock]; } - (void)decryptPreKeyBundle:(SSKProtoEnvelope *)envelope successBlock:(DecryptSuccessBlock)successBlock failureBlock:(void (^)(NSError *_Nullable error))failureBlock { OWSAssertDebug(envelope); OWSAssertDebug(successBlock); OWSAssertDebug(failureBlock); // Check whether we need to refresh our PreKeys every time we receive a PreKeyWhisperMessage. [TSPreKeyManager checkPreKeys]; [self decryptEnvelope:envelope cipherTypeName:@"PreKey Bundle" cipherMessageBlock:^(NSData *encryptedData) { return [[PreKeyWhisperMessage alloc] initWithData:encryptedData]; } successBlock:successBlock failureBlock:failureBlock]; } - (void)decryptEnvelope:(SSKProtoEnvelope *)envelope cipherTypeName:(NSString *)cipherTypeName cipherMessageBlock:(id (^_Nonnull)(NSData *))cipherMessageBlock successBlock:(DecryptSuccessBlock)successBlock failureBlock:(void (^)(NSError *_Nullable error))failureBlock { OWSAssertDebug(envelope); OWSAssertDebug(cipherTypeName.length > 0); OWSAssertDebug(cipherMessageBlock); OWSAssertDebug(successBlock); OWSAssertDebug(failureBlock); OWSPrimaryStorage *primaryStorage = self.primaryStorage; NSString *recipientId = envelope.source; int deviceId = envelope.sourceDevice; // DEPRECATED - Remove `legacyMessage` after all clients have been upgraded. NSData *encryptedData = envelope.content ?: envelope.legacyMessage; if (!encryptedData) { OWSProdFail([OWSAnalyticsEvents messageManagerErrorMessageEnvelopeHasNoContent]); failureBlock(nil); return; } [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { @try { id cipherMessage = cipherMessageBlock(encryptedData); SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:primaryStorage preKeyStore:primaryStorage signedPreKeyStore:primaryStorage identityKeyStore:self.identityManager recipientId:recipientId deviceId:deviceId]; NSData *plaintextData = [[cipher decrypt:cipherMessage protocolContext:transaction] removePadding]; successBlock(plaintextData, transaction); } @catch (NSException *exception) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self processException:exception envelope:envelope]; NSString *errorDescription = [NSString stringWithFormat:@"Exception while decrypting %@: %@", cipherTypeName, exception.description]; NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription); failureBlock(error); }); } }]; } - (void)processException:(NSException *)exception envelope:(SSKProtoEnvelope *)envelope { OWSLogError( @"Got exception: %@ of type: %@ with reason: %@", exception.description, exception.name, exception.reason); [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { TSErrorMessage *errorMessage; if ([exception.name isEqualToString:NoSessionException]) { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorNoSession], envelope); errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction]; } else if ([exception.name isEqualToString:InvalidKeyException]) { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKey], envelope); errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction]; } else if ([exception.name isEqualToString:InvalidKeyIdException]) { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKeyId], envelope); errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction]; } else if ([exception.name isEqualToString:DuplicateMessageException]) { // Duplicate messages are dismissed return; } else if ([exception.name isEqualToString:InvalidVersionException]) { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidMessageVersion], envelope); errorMessage = [TSErrorMessage invalidVersionWithEnvelope:envelope withTransaction:transaction]; } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { // Should no longer get here, since we now record the new identity for incoming messages. OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorUntrustedIdentityKeyException], envelope); OWSFailDebug(@"Failed to trust identity on incoming message from: %@", envelopeAddress(envelope)); return; } else { OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorCorruptMessage], envelope); errorMessage = [TSErrorMessage corruptedMessageWithEnvelope:envelope withTransaction:transaction]; } OWSAssertDebug(errorMessage); if (errorMessage != nil) { [errorMessage saveWithTransaction:transaction]; [self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction]; } }]; } - (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage envelope:(SSKProtoEnvelope *)envelope transaction:(YapDatabaseReadWriteTransaction *)transaction { TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; [[SSKEnvironment sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage thread:contactThread transaction:transaction]; } @end NS_ASSUME_NONNULL_END