Further build out device link authorization
This commit is contained in:
parent
96872c523b
commit
5b04b5ed3b
|
@ -570,12 +570,12 @@
|
|||
B86BD08123399883000F5AE3 /* QRCodeModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08023399883000F5AE3 /* QRCodeModal.swift */; };
|
||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
||||
B86BD08C2339AE43000F5AE3 /* DeviceLinkingModalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08B2339AE43000F5AE3 /* DeviceLinkingModalDelegate.swift */; };
|
||||
B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; };
|
||||
B885D5F62334A32100EE0D8E /* UIView+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */; };
|
||||
B891105C2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
|
||||
B891105E2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
|
||||
B891105F2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
|
||||
B894D0712339D6F300B4D94D /* DeviceLinkingModalDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */; };
|
||||
B89841E322B7579F00B1BDC6 /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */; };
|
||||
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
|
||||
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
|
||||
|
@ -1378,10 +1378,10 @@
|
|||
B86BD08023399883000F5AE3 /* QRCodeModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeModal.swift; sourceTree = "<group>"; };
|
||||
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
|
||||
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
|
||||
B86BD08B2339AE43000F5AE3 /* DeviceLinkingModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModalDelegate.swift; sourceTree = "<group>"; };
|
||||
B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = "<group>"; };
|
||||
B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraint.swift"; sourceTree = "<group>"; };
|
||||
B891105B2320872800F15FCC /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModalDelegate.swift; sourceTree = "<group>"; };
|
||||
B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = "<group>"; };
|
||||
B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
|
||||
B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = "<group>"; };
|
||||
|
@ -2668,7 +2668,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */,
|
||||
B86BD08B2339AE43000F5AE3 /* DeviceLinkingModalDelegate.swift */,
|
||||
B894D0702339D6F300B4D94D /* DeviceLinkingModalDelegate.swift */,
|
||||
B86BD08023399883000F5AE3 /* QRCodeModal.swift */,
|
||||
B86BD08523399CEF000F5AE3 /* SeedModal.swift */,
|
||||
);
|
||||
|
@ -3913,7 +3913,7 @@
|
|||
45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */,
|
||||
34D1F0B01F867BFC0066283D /* OWSSystemMessageCell.m in Sources */,
|
||||
45A663C51F92EC760027B59E /* GroupTableViewCell.swift in Sources */,
|
||||
B86BD08C2339AE43000F5AE3 /* DeviceLinkingModalDelegate.swift in Sources */,
|
||||
B894D0712339D6F300B4D94D /* DeviceLinkingModalDelegate.swift in Sources */,
|
||||
34CA631B2097806F00E526A0 /* OWSContactShareView.m in Sources */,
|
||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */,
|
||||
34D1F0861F8678AA0066283D /* ConversationViewController.m in Sources */,
|
||||
|
|
|
@ -51,32 +51,32 @@ public final class GroupChatPoller : NSObject {
|
|||
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
|
||||
let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
|
||||
let id = group.id.data(using: String.Encoding.utf8)!
|
||||
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
|
||||
x1.setName(group.displayName)
|
||||
let x2 = SSKProtoDataMessage.builder()
|
||||
x2.setTimestamp(message.timestamp)
|
||||
x2.setGroup(try! x1.build())
|
||||
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
|
||||
groupContext.setName(group.displayName)
|
||||
let dataMessage = SSKProtoDataMessage.builder()
|
||||
dataMessage.setTimestamp(message.timestamp)
|
||||
dataMessage.setGroup(try! groupContext.build())
|
||||
if let quote = message.quote {
|
||||
let x5 = SSKProtoDataMessageQuote.builder(id: quote.quotedMessageTimestamp, author: quote.quoteeHexEncodedPublicKey)
|
||||
x5.setText(quote.quotedMessageBody)
|
||||
x2.setQuote(try! x5.build())
|
||||
let signalQuote = SSKProtoDataMessageQuote.builder(id: quote.quotedMessageTimestamp, author: quote.quoteeHexEncodedPublicKey)
|
||||
signalQuote.setText(quote.quotedMessageBody)
|
||||
dataMessage.setQuote(try! signalQuote.build())
|
||||
}
|
||||
x2.setBody(message.body)
|
||||
dataMessage.setBody(message.body)
|
||||
if let messageServerID = message.serverID {
|
||||
let publicChatInfo = SSKProtoPublicChatInfo.builder()
|
||||
publicChatInfo.setServerID(messageServerID)
|
||||
x2.setPublicChatInfo(try! publicChatInfo.build())
|
||||
dataMessage.setPublicChatInfo(try! publicChatInfo.build())
|
||||
}
|
||||
let x3 = SSKProtoContent.builder()
|
||||
x3.setDataMessage(try! x2.build())
|
||||
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
|
||||
x4.setSource(senderHexEncodedPublicKey)
|
||||
x4.setSourceDevice(OWSDevicePrimaryDeviceId)
|
||||
x4.setContent(try! x3.build().serializedData())
|
||||
let content = SSKProtoContent.builder()
|
||||
content.setDataMessage(try! dataMessage.build())
|
||||
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
|
||||
envelope.setSource(senderHexEncodedPublicKey)
|
||||
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
|
||||
envelope.setContent(try! content.build().serializedData())
|
||||
let storage = OWSPrimaryStorage.shared()
|
||||
storage.dbReadWriteConnection.readWrite { transaction in
|
||||
transaction.setObject(senderDisplayName, forKey: senderHexEncodedPublicKey, inCollection: group.id)
|
||||
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
|
||||
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
|
||||
}
|
||||
}
|
||||
// Processing logic for outgoing messages
|
||||
|
|
|
@ -49,20 +49,20 @@ public final class RSSFeedPoller : NSObject {
|
|||
let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ]
|
||||
guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return }
|
||||
let id = feed.id.data(using: String.Encoding.utf8)!
|
||||
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
|
||||
x1.setName(feed.displayName)
|
||||
let x2 = SSKProtoDataMessage.builder()
|
||||
x2.setTimestamp(timestamp)
|
||||
x2.setGroup(try! x1.build())
|
||||
x2.setBody(body)
|
||||
let x3 = SSKProtoContent.builder()
|
||||
x3.setDataMessage(try! x2.build())
|
||||
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
|
||||
x4.setSource(NSLocalizedString("Loki", comment: ""))
|
||||
x4.setSourceDevice(OWSDevicePrimaryDeviceId)
|
||||
x4.setContent(try! x3.build().serializedData())
|
||||
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
|
||||
groupContext.setName(feed.displayName)
|
||||
let dataMessage = SSKProtoDataMessage.builder()
|
||||
dataMessage.setTimestamp(timestamp)
|
||||
dataMessage.setGroup(try! groupContext.build())
|
||||
dataMessage.setBody(body)
|
||||
let content = SSKProtoContent.builder()
|
||||
content.setDataMessage(try! dataMessage.build())
|
||||
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
|
||||
envelope.setSource(NSLocalizedString("Loki", comment: ""))
|
||||
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
|
||||
envelope.setContent(try! content.build().serializedData())
|
||||
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
|
||||
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
|
||||
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -337,10 +337,10 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate {
|
|||
case .link: Analytics.shared.track("Device Linked")
|
||||
}
|
||||
if mode == .link {
|
||||
let masterHexEncodedPublicKey = masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces)
|
||||
let deviceLinkingModal = DeviceLinkingModal(mode: .slave, delegate: self)
|
||||
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)
|
||||
} else {
|
||||
|
@ -348,7 +348,7 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func handleDeviceLinkingRequestAuthorized() {
|
||||
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
|
||||
TSAccountManager.sharedInstance().didRegister()
|
||||
UserDefaults.standard.set(true, forKey: "didUpdateForMainnet")
|
||||
onboardingController.verificationDidComplete(fromView: self)
|
||||
|
|
|
@ -71,7 +71,10 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
let _ = DeviceLinkingSession.startListeningForLinkingRequests(with: self)
|
||||
switch mode {
|
||||
case .master: let _ = DeviceLinkingSession.startListeningForLinkingRequests(with: self)
|
||||
case .slave: let _ = DeviceLinkingSession.startListeningForLinkingAuthorization(with: self)
|
||||
}
|
||||
}
|
||||
|
||||
override func populateContentView() {
|
||||
|
@ -107,6 +110,10 @@ 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
|
||||
|
@ -126,4 +133,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
|
|||
// TODO: Send device link authorized message
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) {
|
||||
delegate?.handleDeviceLinkAuthorized(deviceLink)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
|
||||
@objc(LKDeviceLinkingModalDelegate)
|
||||
protocol DeviceLinkingModalDelegate {
|
||||
@objc protocol DeviceLinkingModalDelegate {
|
||||
|
||||
/// Provides an opportunity for the slave device to update its UI after the master device has authorized the device linking request.
|
||||
func handleDeviceLinkingRequestAuthorized()
|
||||
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink)
|
||||
}
|
||||
|
||||
extension DeviceLinkingModalDelegate {
|
||||
|
||||
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) { /* Do nothing */ }
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import PromiseKit
|
|||
public final class DeviceLinkingSession : NSObject {
|
||||
private let delegate: DeviceLinkingSessionDelegate
|
||||
@objc public var isListeningForLinkingRequests = false
|
||||
@objc public var isListeningForLinkingAuthorization = false
|
||||
|
||||
// MARK: Lifecycle
|
||||
@objc public static var current: DeviceLinkingSession?
|
||||
|
@ -21,28 +22,57 @@ public final class DeviceLinkingSession : NSObject {
|
|||
return session
|
||||
}
|
||||
|
||||
@objc public func processLinkingRequest(from slaveHexEncodedPublicKey: String, with slaveSignature: Data) {
|
||||
guard isListeningForLinkingRequests else { return }
|
||||
stopListeningForLinkingRequests()
|
||||
let master = DeviceLink.Device(hexEncodedPublicKey: OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey)
|
||||
public static func startListeningForLinkingAuthorization(with delegate: DeviceLinkingSessionDelegate) -> DeviceLinkingSession {
|
||||
let session = DeviceLinkingSession(delegate: delegate)
|
||||
session.isListeningForLinkingAuthorization = true
|
||||
DeviceLinkingSession.current = session
|
||||
return session
|
||||
}
|
||||
|
||||
@objc public func processLinkingRequest(from slaveHexEncodedPublicKey: String, to masterHexEncodedPublicKey: String, with slaveSignature: Data) {
|
||||
guard isListeningForLinkingRequests, masterHexEncodedPublicKey == OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey else { return }
|
||||
let master = DeviceLink.Device(hexEncodedPublicKey: masterHexEncodedPublicKey)
|
||||
let slave = DeviceLink.Device(hexEncodedPublicKey: slaveHexEncodedPublicKey, signature: slaveSignature)
|
||||
let deviceLink = DeviceLink(between: master, and: slave)
|
||||
guard isValidLinkingRequest(deviceLink) else { return }
|
||||
guard hasValidSlaveSignature(deviceLink) else { return }
|
||||
delegate.requestUserAuthorization(for: deviceLink)
|
||||
}
|
||||
|
||||
@objc public func processLinkingAuthorization(from masterHexEncodedPublicKey: String, for slaveHexEncodedPublicKey: String, masterSignature: Data, slaveSignature: Data) {
|
||||
guard isListeningForLinkingAuthorization, slaveHexEncodedPublicKey == OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey else { return }
|
||||
let master = DeviceLink.Device(hexEncodedPublicKey: masterHexEncodedPublicKey, signature: masterSignature)
|
||||
let slave = DeviceLink.Device(hexEncodedPublicKey: slaveHexEncodedPublicKey, signature: slaveSignature)
|
||||
let deviceLink = DeviceLink(between: master, and: slave)
|
||||
guard hasValidSlaveSignature(deviceLink) && hasValidMasterSignature(deviceLink) else { return }
|
||||
delegate.handleDeviceLinkAuthorized(deviceLink)
|
||||
}
|
||||
|
||||
public func stopListeningForLinkingRequests() {
|
||||
DeviceLinkingSession.current = nil
|
||||
isListeningForLinkingRequests = false
|
||||
}
|
||||
|
||||
public func stopListeningForLinkingAuthorization() {
|
||||
DeviceLinkingSession.current = nil
|
||||
isListeningForLinkingAuthorization = false
|
||||
}
|
||||
|
||||
// MARK: Private API
|
||||
private func isValidLinkingRequest(_ deviceLink: DeviceLink) -> Bool {
|
||||
// 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.
|
||||
let slaveSignature = deviceLink.slave.signature!
|
||||
|
||||
// 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.
|
||||
|
||||
private func hasValidSlaveSignature(_ deviceLink: DeviceLink) -> Bool {
|
||||
guard let slaveSignature = deviceLink.slave.signature else { return false }
|
||||
let slavePublicKey = Data(hex: deviceLink.slave.hexEncodedPublicKey)
|
||||
let masterPublicKey = Data(hex: deviceLink.master.hexEncodedPublicKey)
|
||||
return (try? Ed25519.verifySignature(slaveSignature, publicKey: slavePublicKey, data: masterPublicKey)) ?? false
|
||||
}
|
||||
|
||||
private func hasValidMasterSignature(_ deviceLink: DeviceLink) -> Bool {
|
||||
guard let masterSignature = deviceLink.master.signature else { return false }
|
||||
let slavePublicKey = Data(hex: deviceLink.slave.hexEncodedPublicKey)
|
||||
let masterPublicKey = Data(hex: deviceLink.master.hexEncodedPublicKey)
|
||||
return (try? Ed25519.verifySignature(masterSignature, publicKey: masterPublicKey, data: slavePublicKey)) ?? false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
@objc(LKDeviceLinkingSessionDelegate)
|
||||
public protocol DeviceLinkingSessionDelegate {
|
||||
|
||||
@objc func requestUserAuthorization(for deviceLink: DeviceLink)
|
||||
func requestUserAuthorization(for deviceLink: DeviceLink)
|
||||
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
public extension Notification.Name {
|
||||
public static let linkingRequestAuthorized = Notification.Name("linkingRequestAuthorized")
|
||||
}
|
|
@ -431,11 +431,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// Loki: Handle device linking message
|
||||
if (contentProto.lokiDeviceLinkingMessage != nil && contentProto.lokiDeviceLinkingMessage.type == SSKProtoLokiDeviceLinkingMessageTypeRequest) {
|
||||
OWSLogInfo(@"[Loki] Received a device linking request from: %@", envelope.source);
|
||||
NSData *signature = contentProto.lokiDeviceLinkingMessage.slaveSignature;
|
||||
if (signature == nil) {
|
||||
NSData *slaveSignature = contentProto.lokiDeviceLinkingMessage.slaveSignature;
|
||||
if (slaveSignature == nil) {
|
||||
OWSFailDebug(@"Received a device linking request without an attached slave signature.");
|
||||
}
|
||||
[LKDeviceLinkingSession.current processLinkingRequestFrom:envelope.source with:signature];
|
||||
NSString *masterHexEncodedPublicKey = contentProto.lokiDeviceLinkingMessage.masterHexEncodedPublicKey;
|
||||
[LKDeviceLinkingSession.current processLinkingRequestFrom:envelope.source to:masterHexEncodedPublicKey with:slaveSignature];
|
||||
}
|
||||
|
||||
// Loki: Handle pre key bundle message
|
||||
|
|
Loading…
Reference in New Issue