Further build out device link authorization

This commit is contained in:
Niels Andriesse 2019-09-24 15:05:59 +10:00
parent 96872c523b
commit 5b04b5ed3b
10 changed files with 104 additions and 55 deletions

View File

@ -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 */,

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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 */ }
}

View File

@ -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
}
}

View File

@ -1,6 +1,6 @@
@objc(LKDeviceLinkingSessionDelegate)
public protocol DeviceLinkingSessionDelegate {
@objc func requestUserAuthorization(for deviceLink: DeviceLink)
func requestUserAuthorization(for deviceLink: DeviceLink)
func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink)
}

View File

@ -0,0 +1,4 @@
public extension Notification.Name {
public static let linkingRequestAuthorized = Notification.Name("linkingRequestAuthorized")
}

View File

@ -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