diff --git a/Signal/src/Loki/Onboarding/SeedVC.swift b/Signal/src/Loki/Onboarding/SeedVC.swift index b9b30cec1..443eea814 100644 --- a/Signal/src/Loki/Onboarding/SeedVC.swift +++ b/Signal/src/Loki/Onboarding/SeedVC.swift @@ -341,8 +341,8 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate { deviceLinkingModal.modalPresentationStyle = .overFullScreen present(deviceLinkingModal, animated: true, completion: nil) let masterHexEncodedPublicKey = masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces) - let thread = TSContactThread.getOrCreateThread(contactId: masterHexEncodedPublicKey) - ThreadUtil.enqueueDeviceLinkingMessage(in: thread) + let linkingRequestMessage = DeviceLinkingUtilities.getLinkingRequestMessage(for: masterHexEncodedPublicKey) + ThreadUtil.enqueue(linkingRequestMessage) } else { onboardingController.pushDisplayNameVC(from: self) } diff --git a/Signal/src/Loki/Settings/DeviceLinkingModal.swift b/Signal/src/Loki/Settings/DeviceLinkingModal.swift index 4631e1589..67d4546a6 100644 --- a/Signal/src/Loki/Settings/DeviceLinkingModal.swift +++ b/Signal/src/Loki/Settings/DeviceLinkingModal.swift @@ -61,7 +61,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate { super.init(nibName: nil, bundle: nil) } - @objc convenience init(modeAsString: String, delegate: DeviceLinkingModalDelegate?) { + @objc(initWithMode:delegate:) + convenience init(modeAsString: String, delegate: DeviceLinkingModalDelegate?) { guard let mode = Mode(rawValue: modeAsString) else { preconditionFailure("Invalid mode: \(modeAsString).") } self.init(mode: mode, delegate: delegate) } @@ -110,10 +111,6 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate { contentView.pin(.bottom, to: .bottom, of: stackView, withInset: 16) } - deinit { - NotificationCenter.default.removeObserver(self) - } - // MARK: Device Linking func requestUserAuthorization(for deviceLink: DeviceLink) { self.deviceLink = deviceLink @@ -128,9 +125,10 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate { @objc private func authorizeDeviceLink() { let deviceLink = self.deviceLink! + let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink) + ThreadUtil.enqueue(linkingAuthorizationMessage) let session = DeviceLinkingSession.current! session.stopListeningForLinkingRequests() - // TODO: Send device link authorized message dismiss(animated: true, completion: nil) } diff --git a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m index e8b8b8ed5..ecc163777 100644 --- a/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AppSettingsViewController.m @@ -515,7 +515,7 @@ - (void)linkDevice { - LKDeviceLinkingModal *deviceLinkingModal = [[LKDeviceLinkingModal alloc] initWithModeAsString:@"master" delegate:nil]; + LKDeviceLinkingModal *deviceLinkingModal = [[LKDeviceLinkingModal alloc] initWithMode:@"master" delegate:nil]; deviceLinkingModal.modalPresentationStyle = UIModalPresentationOverFullScreen; [self presentViewController:deviceLinkingModal animated:YES completion:nil]; } diff --git a/SignalMessaging/utils/ThreadUtil.h b/SignalMessaging/utils/ThreadUtil.h index 1f0e92c3a..22344ed03 100644 --- a/SignalMessaging/utils/ThreadUtil.h +++ b/SignalMessaging/utils/ThreadUtil.h @@ -4,6 +4,7 @@ NS_ASSUME_NONNULL_BEGIN +@class LKDeviceLinkingMessage; @class OWSBlockingManager; @class OWSContact; @class OWSContactsManager; @@ -45,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Durable Message Enqueue + (TSOutgoingMessage *)enqueueFriendRequestAcceptanceMessageInThread:(TSThread *)thread; -+ (TSOutgoingMessage *)enqueueDeviceLinkingMessageInThread:(TSThread *)thread; ++ (void)enqueueDeviceLinkingMessage:(LKDeviceLinkingMessage *)message; + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText inThread:(TSThread *)thread diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m index 5daaee1a4..04e5aa55c 100644 --- a/SignalMessaging/utils/ThreadUtil.m +++ b/SignalMessaging/utils/ThreadUtil.m @@ -94,13 +94,11 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess return message; } -+ (LKDeviceLinkingMessage *)enqueueDeviceLinkingMessageInThread:(TSThread *)thread ++ (void)enqueueDeviceLinkingMessage:(LKDeviceLinkingMessage *)message { - LKDeviceLinkingMessage *message = [[LKDeviceLinkingMessage alloc] initInThread:thread]; [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.messageSenderJobQueue addMessage:message transaction:transaction]; }]; - return message; } + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText diff --git a/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingUtilities.swift b/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingUtilities.swift new file mode 100644 index 000000000..1b80d7bc8 --- /dev/null +++ b/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingUtilities.swift @@ -0,0 +1,24 @@ + +public enum DeviceLinkingUtilities { + + // When requesting a device link, the slave device signs the master device's public key. When authorizing + // a device link, the master device signs the slave device's public key. + + public static func getLinkingRequestMessage(for masterHexEncodedPublicKey: String) -> DeviceLinkingMessage { + let slaveKeyPair = OWSIdentityManager.shared().identityKeyPair()! + let slaveHexEncodedPublicKey = slaveKeyPair.hexEncodedPublicKey + let slaveSignature = try! Ed25519.sign(Data(hex: masterHexEncodedPublicKey), with: slaveKeyPair) + let thread = TSContactThread.getOrCreateThread(contactId: masterHexEncodedPublicKey) + return DeviceLinkingMessage(in: thread, masterHexEncodedPublicKey: masterHexEncodedPublicKey, slaveHexEncodedPublicKey: slaveHexEncodedPublicKey, masterSignature: nil, slaveSignature: slaveSignature) + } + + public static func getLinkingAuthorizationMessage(for deviceLink: DeviceLink) -> DeviceLinkingMessage { + let masterKeyPair = OWSIdentityManager.shared().identityKeyPair()! + let masterHexEncodedPublicKey = masterKeyPair.hexEncodedPublicKey + let slaveHexEncodedPublicKey = deviceLink.slave.hexEncodedPublicKey + let masterSignature = try! Ed25519.sign(Data(hex: slaveHexEncodedPublicKey), with: masterKeyPair) + let slaveSignature = deviceLink.slave.signature! + let thread = TSContactThread.getOrCreateThread(contactId: slaveHexEncodedPublicKey) + return DeviceLinkingMessage(in: thread, masterHexEncodedPublicKey: masterHexEncodedPublicKey, slaveHexEncodedPublicKey: slaveHexEncodedPublicKey, masterSignature: masterSignature, slaveSignature: slaveSignature) + } +} diff --git a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h index edb82900c..339f7bc64 100644 --- a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h +++ b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h @@ -3,6 +3,11 @@ NS_SWIFT_NAME(DeviceLinkingMessage) @interface LKDeviceLinkingMessage : TSOutgoingMessage -- (instancetype)initInThread:(TSThread *)thread; +@property (nonatomic, readonly) NSString *masterHexEncodedPublicKey; +@property (nonatomic, readonly) NSString *slaveHexEncodedPublicKey; +@property (nonatomic, readonly) NSData *masterSignature; // nil for device linking requests +@property (nonatomic, readonly) NSData *slaveSignature; + +- (instancetype)initInThread:(TSThread *)thread masterHexEncodedPublicKey:(NSString *)masterHexEncodedPublicKey slaveHexEncodedPublicKey:(NSString *)slaveHexEncodedPublicKey masterSignature:(NSData *)masterSignature slaveSignature:(NSData *)slaveSignature; @end diff --git a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m index 0c61ee7fa..b36468e4d 100644 --- a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m +++ b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m @@ -7,29 +7,30 @@ @implementation LKDeviceLinkingMessage -- (instancetype)initInThread:(nullable TSThread *)thread { - return [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] +- (instancetype)initInThread:(TSThread *)thread masterHexEncodedPublicKey:(NSString *)masterHexEncodedPublicKey slaveHexEncodedPublicKey:(NSString *)slaveHexEncodedPublicKey masterSignature:(NSData *)masterSignature slaveSignature:(NSData *)slaveSignature { + self = [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray new] expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil]; + if (self) { + _masterHexEncodedPublicKey = masterHexEncodedPublicKey; + _slaveHexEncodedPublicKey = slaveHexEncodedPublicKey; + _masterSignature = masterSignature; + _slaveSignature = slaveSignature; + } + return self; } - (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient]; - // When authorizing a device link, the master device signs the slave device's public key. When requesting - // a device link, the slave device signs the master device's public key. SSKProtoLokiDeviceLinkingMessageBuilder *deviceLinkingMessageBuilder = [SSKProtoLokiDeviceLinkingMessage builder]; - NSString *masterHexEncodedPublicKey = recipient.recipientId; - NSData *masterPublicKey = [NSData dataFromHexString:masterHexEncodedPublicKey]; - [deviceLinkingMessageBuilder setMasterHexEncodedPublicKey:masterHexEncodedPublicKey]; - ECKeyPair *slaveKeyPair = OWSIdentityManager.sharedManager.identityKeyPair; - NSString *slaveHexEncodedPublicKey = slaveKeyPair.hexEncodedPublicKey; - [deviceLinkingMessageBuilder setSlaveHexEncodedPublicKey:slaveHexEncodedPublicKey]; - NSData *slaveSignature = [Ed25519 sign:masterPublicKey withKeyPair:slaveKeyPair error:nil]; - [deviceLinkingMessageBuilder setSlaveSignature:slaveSignature]; + [deviceLinkingMessageBuilder setMasterHexEncodedPublicKey:self.masterHexEncodedPublicKey]; + [deviceLinkingMessageBuilder setSlaveHexEncodedPublicKey:self.slaveHexEncodedPublicKey]; + if (self.masterSignature != nil) { [deviceLinkingMessageBuilder setMasterSignature:self.masterSignature]; } + [deviceLinkingMessageBuilder setSlaveSignature:self.slaveSignature]; NSError *error; SSKProtoLokiDeviceLinkingMessage *deviceLinkingMessage = [deviceLinkingMessageBuilder buildAndReturnError:&error]; if (error || deviceLinkingMessage == nil) { - OWSFailDebug(@"Failed to build device linking message for: %@ due to error: %@", masterHexEncodedPublicKey, error); + OWSFailDebug(@"Failed to build device linking message for: %@ due to error: %@", self.masterHexEncodedPublicKey, error); } + SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient]; [contentBuilder setLokiDeviceLinkingMessage:deviceLinkingMessage]; return contentBuilder; } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 1ec9851cc..2a1d10928 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -429,14 +429,27 @@ NS_ASSUME_NONNULL_BEGIN OWSLogInfo(@"handling content: ", [self descriptionForContent:contentProto]); // Loki: Handle device linking message - if (contentProto.lokiDeviceLinkingMessage != nil && contentProto.lokiDeviceLinkingMessage.type == SSKProtoLokiDeviceLinkingMessageTypeRequest) { - OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); + if (contentProto.lokiDeviceLinkingMessage != nil) { + NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkingMessage.masterHexEncodedPublicKey; + NSString *slaveHexEncodedPublicKey = contentProto.lokiDeviceLinkingMessage.slaveHexEncodedPublicKey; NSData *slaveSignature = contentProto.lokiDeviceLinkingMessage.slaveSignature; - if (slaveSignature == nil) { - OWSFailDebug(@"Received a device linking request without an attached slave signature."); + switch (contentProto.lokiDeviceLinkingMessage.type) { + case SSKProtoLokiDeviceLinkingMessageTypeRequest: { + OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source); // Not slaveHexEncodedPublicKey + if (slaveSignature == nil) { OWSFailDebug(@"Received a device linking request from: %@ without a slave signature.", envelope.source); } + [LKDeviceLinkingSession.current processLinkingRequestFrom:slaveHexEncodedPublicKey to:masterHexEncodedPublicKey with:slaveSignature]; + break; + } + case SSKProtoLokiDeviceLinkingMessageTypeGrant: { + OWSLogInfo(@"[Loki] Received a device linking authorization from: %@", envelope.source); // Not slaveHexEncodedPublicKey + NSData *masterSignature = contentProto.lokiDeviceLinkingMessage.masterSignature; + if (masterSignature == nil) { OWSFailDebug(@"Received a device linking authorization from: %@ without a master signature.", envelope.source); } + if (slaveSignature == nil) { OWSFailDebug(@"Received a device linking authorization from: %@ without a slave signature.", envelope.source); } + [LKDeviceLinkingSession.current processLinkingAuthorizationFrom:masterHexEncodedPublicKey for:slaveHexEncodedPublicKey masterSignature:masterSignature slaveSignature:slaveSignature]; + break; + } + default: break; } - NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkingMessage.masterHexEncodedPublicKey; - [LKDeviceLinkingSession.current processLinkingRequestFrom:envelope.source to:masterHexEncodedPublicKey with:slaveSignature]; } // Loki: Handle pre key bundle message