/* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "MXKEncryptionInfoView.h" #import "NSBundle+MatrixKit.h" #import "MXKConstants.h" #import "MXKSwiftHeader.h" static NSAttributedString *verticalWhitespace = nil; @interface MXKEncryptionInfoView () { /** Current request in progress. */ MXHTTPOperation *mxCurrentOperation; } @end @implementation MXKEncryptionInfoView + (UINib *)nib { // Check whether a nib file is available NSBundle *mainBundle = [NSBundle mxk_bundleForClass:self.class]; NSString *path = [mainBundle pathForResource:NSStringFromClass([self class]) ofType:@"nib"]; if (path) { return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:mainBundle]; } return [UINib nibWithNibName:NSStringFromClass([MXKEncryptionInfoView class]) bundle:[NSBundle mxk_bundleForClass:[MXKEncryptionInfoView class]]]; } - (void)awakeFromNib { [super awakeFromNib]; // Localize string [_cancelButton setTitle:[MatrixKitL10n ok] forState:UIControlStateNormal]; [_cancelButton setTitle:[MatrixKitL10n ok] forState:UIControlStateHighlighted]; [_confirmVerifyButton setTitle:[MatrixKitL10n roomEventEncryptionVerifyOk] forState:UIControlStateNormal]; [_confirmVerifyButton setTitle:[MatrixKitL10n roomEventEncryptionVerifyOk] forState:UIControlStateHighlighted]; } - (void)layoutSubviews { [super layoutSubviews]; // Scroll to the top the text view content self.textView.contentOffset = CGPointZero; } - (void)removeFromSuperview { if (mxCurrentOperation) { [mxCurrentOperation cancel]; mxCurrentOperation = nil; } [super removeFromSuperview]; } #pragma mark - Override MXKView -(void)customizeViewRendering { [super customizeViewRendering]; _defaultTextColor = [UIColor blackColor]; } #pragma mark - - (instancetype)initWithEvent:(MXEvent*)event andMatrixSession:(MXSession*)session { self = [[[self class] nib] instantiateWithOwner:nil options:nil].firstObject; if (self) { _mxEvent = event; _mxSession = session; _mxDeviceInfo = nil; [self setTranslatesAutoresizingMaskIntoConstraints: NO]; [self updateTextViewText]; } return self; } - (instancetype)initWithDeviceInfo:(MXDeviceInfo*)deviceInfo andMatrixSession:(MXSession*)session { self = [[[self class] nib] instantiateWithOwner:nil options:nil].firstObject; if (self) { _mxEvent = nil; _mxDeviceInfo = deviceInfo; _mxSession = session; [self setTranslatesAutoresizingMaskIntoConstraints: NO]; [self updateTextViewText]; } return self; } - (void)dealloc { _mxEvent = nil; _mxSession = nil; _mxDeviceInfo = nil; } #pragma mark - - (void)updateTextViewText { // Prepare the text view content NSMutableAttributedString *textViewAttributedString = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoTitle] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]; if (_mxEvent) { NSString *senderId = _mxEvent.sender; if (_mxSession && _mxSession.crypto && !_mxDeviceInfo) { _mxDeviceInfo = [_mxSession.crypto eventDeviceInfo:_mxEvent]; if (!_mxDeviceInfo) { // Trigger a server request to get the device information for the event sender mxCurrentOperation = [_mxSession.crypto downloadKeys:@[senderId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { self->mxCurrentOperation = nil; // Sanity check: check whether some device information has been retrieved. self->_mxDeviceInfo = [self.mxSession.crypto eventDeviceInfo:self.mxEvent]; if (self.mxDeviceInfo) { [self updateTextViewText]; } } failure:^(NSError *error) { self->mxCurrentOperation = nil; MXLogDebug(@"[MXKEncryptionInfoView] Crypto failed to download device info for user: %@", self.mxEvent.sender); // Notify MatrixKit user NSString *myUserId = self.mxSession.myUser.userId; [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; }]; } } // Event information NSMutableAttributedString *eventInformationString = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEvent] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:15]}]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; NSString *senderKey = _mxEvent.senderKey; NSString *claimedKey = _mxEvent.keysClaimed[@"ed25519"]; NSString *algorithm = _mxEvent.wireContent[@"algorithm"]; NSString *sessionId = _mxEvent.wireContent[@"session_id"]; NSString *decryptionError; if (_mxEvent.decryptionError) { decryptionError = [NSString stringWithFormat:@"** %@ **", _mxEvent.decryptionError.localizedDescription]; } if (!senderKey.length) { senderKey = [MatrixKitL10n roomEventEncryptionInfoEventNone]; } if (!claimedKey.length) { claimedKey = [MatrixKitL10n roomEventEncryptionInfoEventNone]; } if (!algorithm.length) { algorithm = [MatrixKitL10n roomEventEncryptionInfoEventUnencrypted]; } if (!sessionId.length) { sessionId = [MatrixKitL10n roomEventEncryptionInfoEventNone]; } [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEventUserId] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:senderId attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEventIdentityKey] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:senderKey attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEventFingerprintKey] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:claimedKey attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEventAlgorithm] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:algorithm attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; if (decryptionError.length) { [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEventDecryptionError] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:decryptionError attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; } [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoEventSessionId] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:sessionId attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [eventInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [textViewAttributedString appendAttributedString:eventInformationString]; } // Device information NSMutableAttributedString *deviceInformationString = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDevice] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:15]}]; [deviceInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; if (_mxDeviceInfo) { NSString *name = _mxDeviceInfo.displayName; NSString *deviceId = _mxDeviceInfo.deviceId; NSMutableAttributedString *verification; NSString *fingerprint = _mxDeviceInfo.fingerprint; // Display here the Verify and Block buttons except if the device is the current one. _verifyButton.hidden = _blockButton.hidden = [_mxDeviceInfo.deviceId isEqualToString:_mxSession.matrixRestClient.credentials.deviceId]; switch (_mxDeviceInfo.trustLevel.localVerificationStatus) { case MXDeviceUnknown: case MXDeviceUnverified: { verification = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceNotVerified] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]; [_verifyButton setTitle:[MatrixKitL10n roomEventEncryptionInfoVerify] forState:UIControlStateNormal]; [_verifyButton setTitle:[MatrixKitL10n roomEventEncryptionInfoVerify] forState:UIControlStateHighlighted]; [_blockButton setTitle:[MatrixKitL10n roomEventEncryptionInfoBlock] forState:UIControlStateNormal]; [_blockButton setTitle:[MatrixKitL10n roomEventEncryptionInfoBlock] forState:UIControlStateHighlighted]; break; } case MXDeviceVerified: { verification = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceVerified] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]; [_verifyButton setTitle:[MatrixKitL10n roomEventEncryptionInfoUnverify] forState:UIControlStateNormal]; [_verifyButton setTitle:[MatrixKitL10n roomEventEncryptionInfoUnverify] forState:UIControlStateHighlighted]; [_blockButton setTitle:[MatrixKitL10n roomEventEncryptionInfoBlock] forState:UIControlStateNormal]; [_blockButton setTitle:[MatrixKitL10n roomEventEncryptionInfoBlock] forState:UIControlStateHighlighted]; break; } case MXDeviceBlocked: { verification = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceBlocked] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]; [_verifyButton setTitle:[MatrixKitL10n roomEventEncryptionInfoVerify] forState:UIControlStateNormal]; [_verifyButton setTitle:[MatrixKitL10n roomEventEncryptionInfoVerify] forState:UIControlStateHighlighted]; [_blockButton setTitle:[MatrixKitL10n roomEventEncryptionInfoUnblock] forState:UIControlStateNormal]; [_blockButton setTitle:[MatrixKitL10n roomEventEncryptionInfoUnblock] forState:UIControlStateHighlighted]; break; } default: break; } [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceName] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:(name.length ? name : @"") attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceId] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:deviceId attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceVerification] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:verification]; [deviceInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceFingerprint] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:fingerprint attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; [deviceInformationString appendAttributedString:[MXKEncryptionInfoView verticalWhitespace]]; } else { // Unknown device [deviceInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionInfoDeviceUnknown] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont italicSystemFontOfSize:14]}]]; } [textViewAttributedString appendAttributedString:deviceInformationString]; self.textView.attributedText = textViewAttributedString; } + (NSAttributedString *)verticalWhitespace { if (verticalWhitespace == nil) { verticalWhitespace = [[NSAttributedString alloc] initWithString:@"\n\n" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:4]}]; } return verticalWhitespace; } #pragma mark - Actions - (IBAction)onButtonPressed:(id)sender { if (sender == _cancelButton) { [self removeFromSuperview]; if ([_delegate respondsToSelector:@selector(encryptionInfoViewDidClose:)]) { [_delegate encryptionInfoViewDidClose:self]; } } // Note: Verify and Block buttons are hidden when the deviceInfo is not available else if (sender == _confirmVerifyButton && _mxDeviceInfo) { [_mxSession.crypto setDeviceVerification:MXDeviceVerified forDevice:_mxDeviceInfo.deviceId ofUser:_mxDeviceInfo.userId success:^{ // Refresh data _mxDeviceInfo = [self.mxSession.crypto eventDeviceInfo:self.mxEvent]; if (self->_delegate) { [self->_delegate encryptionInfoView:self didDeviceInfoVerifiedChange:self.mxDeviceInfo]; } [self removeFromSuperview]; } failure:^(NSError *error) { [self removeFromSuperview]; }]; } else if (_mxDeviceInfo) { MXDeviceVerification verificationStatus; if (sender == _verifyButton) { verificationStatus = ((_mxDeviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) ? MXDeviceUnverified : MXDeviceVerified); } else if (sender == _blockButton) { verificationStatus = ((_mxDeviceInfo.trustLevel.localVerificationStatus == MXDeviceBlocked) ? MXDeviceUnverified : MXDeviceBlocked); } else { // Unexpected case MXLogDebug(@"[MXKEncryptionInfoView] Invalid button pressed."); return; } if (verificationStatus == MXDeviceVerified) { // Prompt user NSMutableAttributedString *textViewAttributedString = [[NSMutableAttributedString alloc] initWithString:[MatrixKitL10n roomEventEncryptionVerifyTitle] attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]; NSString *message = [MatrixKitL10n roomEventEncryptionVerifyMessage:_mxDeviceInfo.displayName :_mxDeviceInfo.deviceId :_mxDeviceInfo.fingerprint]; [textViewAttributedString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:message attributes:@{NSForegroundColorAttributeName: _defaultTextColor, NSFontAttributeName: [UIFont systemFontOfSize:14]}]]; self.textView.attributedText = textViewAttributedString; [_cancelButton setTitle:[MatrixKitL10n cancel] forState:UIControlStateNormal]; [_cancelButton setTitle:[MatrixKitL10n cancel] forState:UIControlStateHighlighted]; _verifyButton.hidden = _blockButton.hidden = YES; _confirmVerifyButton.hidden = NO; } else { [_mxSession.crypto setDeviceVerification:verificationStatus forDevice:_mxDeviceInfo.deviceId ofUser:_mxDeviceInfo.userId success:^{ // Refresh data _mxDeviceInfo = [self.mxSession.crypto eventDeviceInfo:self.mxEvent]; if (self->_delegate) { [self->_delegate encryptionInfoView:self didDeviceInfoVerifiedChange:self.mxDeviceInfo]; } [self removeFromSuperview]; } failure:^(NSError *error) { [self removeFromSuperview]; }]; } } } @end