diff --git a/Podfile b/Podfile index 59d3020d3..40b9f4a76 100644 --- a/Podfile +++ b/Podfile @@ -71,6 +71,7 @@ target 'SessionMessagingKit' do pod 'Reachability', :inhibit_warnings => true pod 'SAMKeychain', :inhibit_warnings => true pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :inhibit_warnings => true + pod 'Sodium', :inhibit_warnings => true pod 'SwiftProtobuf', '~> 1.5.0', :inhibit_warnings => true pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true end diff --git a/Podfile.lock b/Podfile.lock index e843a6e1c..caf962654 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -230,6 +230,6 @@ SPEC CHECKSUMS: YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665 ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 7699c2a380fc803ef7f51157f1f75da756aa3b45 +PODFILE CHECKSUM: 3263ab95f60e220882ca53cca4c6bdc2e7a80381 COCOAPODS: 1.10.0.rc.1 diff --git a/Session/Meta/Images.xcassets/Loki V2/Shield.imageset/Contents.json b/Session/Meta/Images.xcassets/Loki V2/Shield.imageset/Contents.json new file mode 100644 index 000000000..c6de4ea4e --- /dev/null +++ b/Session/Meta/Images.xcassets/Loki V2/Shield.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Shield.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Session/Meta/Images.xcassets/Loki V2/Shield.imageset/Shield.pdf b/Session/Meta/Images.xcassets/Loki V2/Shield.imageset/Shield.pdf new file mode 100644 index 000000000..f03001d0a Binary files /dev/null and b/Session/Meta/Images.xcassets/Loki V2/Shield.imageset/Shield.pdf differ diff --git a/Session/Signal/AppDelegate.m b/Session/Signal/AppDelegate.m index f02985166..ed8040a59 100644 --- a/Session/Signal/AppDelegate.m +++ b/Session/Signal/AppDelegate.m @@ -633,7 +633,7 @@ static NSTimeInterval launchStartedAt; if (isUsingFullAPNs) { __unused AnyPromise *promise = [LKPushNotificationAPI registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber isForcedUpdate:NO]; } else { - __unused AnyPromise *promise = [LKPushNotificationAPI unregisterWithToken:deviceToken isForcedUpdate:NO]; + __unused AnyPromise *promise = [LKPushNotificationAPI unregisterToken:deviceToken]; } } @@ -817,7 +817,7 @@ static NSTimeInterval launchStartedAt; NSString *hexEncodedDeviceToken = [userDefaults stringForKey:@"deviceToken"]; if (isUsingFullAPNs && hexEncodedDeviceToken != nil) { NSData *deviceToken = [NSData dataFromHexString:hexEncodedDeviceToken]; - [[LKPushNotificationAPI unregisterWithToken:deviceToken isForcedUpdate:YES] retainUntilComplete]; + [[LKPushNotificationAPI unregisterToken:deviceToken] retainUntilComplete]; } [ThreadUtil deleteAllContent]; [SSKEnvironment.shared.identityManager clearIdentityKey]; diff --git a/Session/Utilities/Storage+Resetting.swift b/Session/Utilities/Storage+Resetting.swift new file mode 100644 index 000000000..ed132c393 --- /dev/null +++ b/Session/Utilities/Storage+Resetting.swift @@ -0,0 +1,23 @@ + +extension Storage { + + static func reset() { + let userDefaults = UserDefaults.standard + if userDefaults[.isUsingFullAPNs], let hexEncodedToken = userDefaults[.deviceToken] { + let token = Data(hex: hexEncodedToken) + PushNotificationAPI.unregister(token).retainUntilComplete() // TODO: Wait for this to complete? + } + + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.stopPoller() + appDelegate.stopClosedGroupPoller() + appDelegate.stopOpenGroupPollers() + + OWSStorage.resetAllStorage() + OWSUserProfile.resetProfileStorage() + Environment.shared.preferences.clear() + AppEnvironment.shared.notificationPresenter.clearAllNotifications() + + exit(0) + } +} diff --git a/Session/View Controllers/HomeVC.swift b/Session/View Controllers/HomeVC.swift index 940766973..8664d5f16 100644 --- a/Session/View Controllers/HomeVC.swift +++ b/Session/View Controllers/HomeVC.swift @@ -161,6 +161,20 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol super.viewDidAppear(animated) isViewVisible = true UserDefaults.standard[.hasLaunchedOnce] = true + showKeyPairMigrationNudgeIfNeeded() + } + + private func showKeyPairMigrationNudgeIfNeeded() { + guard !KeyPairUtilities.hasV2KeyPair() else { return } + let lastNudge = UserDefaults.standard[.lastKeyPairMigrationNudge] + let nudgeInterval: Double = 3 * 24 * 60 * 60 // 3 days + let nudge = given(lastNudge) { Date().timeIntervalSince($0) > nudgeInterval } ?? true + guard nudge else { return } + let sheet = KeyPairMigrationSheet() + sheet.modalPresentationStyle = .overFullScreen + sheet.modalTransitionStyle = .crossDissolve + present(sheet, animated: true, completion: nil) + UserDefaults.standard[.lastKeyPairMigrationNudge] = Date() } override func viewWillDisappear(_ animated: Bool) { diff --git a/Session/View Controllers/KeyPairMigrationSheet.swift b/Session/View Controllers/KeyPairMigrationSheet.swift new file mode 100644 index 000000000..c5ebecaae --- /dev/null +++ b/Session/View Controllers/KeyPairMigrationSheet.swift @@ -0,0 +1,74 @@ + +final class KeyPairMigrationSheet : Sheet { + + override func populateContentView() { + // Image view + let imageView = UIImageView(image: #imageLiteral(resourceName: "Shield").withTint(Colors.text)) + imageView.set(.width, to: 64) + imageView.set(.height, to: 64) + imageView.contentMode = .scaleAspectFit + // Title label + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) + titleLabel.text = "Session IDs Just Got Better" + titleLabel.numberOfLines = 0 + titleLabel.lineBreakMode = .byWordWrapping + // Top stack view + let topStackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ]) + topStackView.axis = .vertical + topStackView.spacing = Values.largeSpacing + topStackView.alignment = .center + // Explanation label + let explanationLabel = UILabel() + explanationLabel.textColor = Colors.text + explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) + explanationLabel.textAlignment = .center + explanationLabel.text = """ + We’ve upgraded Session IDs to make them even more private and secure. We recommend upgrading to a new Session ID now. + + You will lose existing contacts and conversations, but you’ll gain even more privacy and security. You will need to upgrade your Session ID eventually, but you can choose to delay the upgrade if you need to save contacts or conversations. + """ + explanationLabel.numberOfLines = 0 + explanationLabel.lineBreakMode = .byWordWrapping + // Upgrade now button + let upgradeNowButton = Button(style: .prominentOutline, size: .large) + upgradeNowButton.set(.width, to: 240) + upgradeNowButton.setTitle(NSLocalizedString("Upgrade Now", comment: ""), for: UIControl.State.normal) + upgradeNowButton.addTarget(self, action: #selector(upgradeNow), for: UIControl.Event.touchUpInside) + // Upgrade later button + let upgradeLaterButton = Button(style: .prominentOutline, size: .large) + upgradeLaterButton.set(.width, to: 240) + upgradeLaterButton.setTitle(NSLocalizedString("Upgrade Later", comment: ""), for: UIControl.State.normal) + upgradeLaterButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Button stack view + let buttonStackView = UIStackView(arrangedSubviews: [ upgradeNowButton, upgradeLaterButton ]) + buttonStackView.axis = .vertical + buttonStackView.spacing = Values.mediumSpacing + buttonStackView.alignment = .center + // Main stack view + let stackView = UIStackView(arrangedSubviews: [ topStackView, explanationLabel, buttonStackView ]) + stackView.axis = .vertical + stackView.spacing = Values.veryLargeSpacing + stackView.alignment = .center + // Constraints + contentView.addSubview(stackView) + stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.veryLargeSpacing) + stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.veryLargeSpacing) + contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.veryLargeSpacing + overshoot) + } + + @objc private func upgradeNow() { + guard let presentingVC = presentingViewController else { return } + let message = "You’re upgrading to a new Session ID. This will give you improved privacy and security, but it will clear ALL app data. Contacts and conversations will be lost. Proceed?" + let alert = UIAlertController(title: "Upgrade Session ID?", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in + Storage.reset() + }) + alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil)) + presentingVC.dismiss(animated: true) { // Dismiss self first + presentingVC.present(alert, animated: true, completion: nil) + } + } +} diff --git a/SessionMessagingKit/Database/Storage+Shared.swift b/SessionMessagingKit/Database/Storage+Shared.swift index 4ceec0b9a..19ee7a1a6 100644 --- a/SessionMessagingKit/Database/Storage+Shared.swift +++ b/SessionMessagingKit/Database/Storage+Shared.swift @@ -1,4 +1,5 @@ import PromiseKit +import Sodium extension Storage { @@ -23,6 +24,16 @@ extension Storage { public func getUserKeyPair() -> ECKeyPair? { return OWSIdentityManager.shared().identityKeyPair() } + + public func getUserED25519KeyPair() -> Box.KeyPair? { + let dbConnection = OWSIdentityManager.shared().dbConnection + let collection = OWSPrimaryStorageIdentityKeyStoreCollection + guard let hexEncodedPublicKey = dbConnection.object(forKey: LKED25519PublicKey, inCollection: collection) as? String, + let hexEncodedSecretKey = dbConnection.object(forKey: LKED25519SecretKey, inCollection: collection) as? String else { return nil } + let publicKey = Box.KeyPair.PublicKey(hex: hexEncodedPublicKey) + let secretKey = Box.KeyPair.SecretKey(hex: hexEncodedSecretKey) + return Box.KeyPair(publicKey: publicKey, secretKey: secretKey) + } public func getUserDisplayName() -> String? { return SSKEnvironment.shared.profileManager.localProfileName() diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index b6d4fd744..a20313452 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -1,6 +1,7 @@ import CryptoSwift import SessionProtocolKit import SessionUtilitiesKit +import Sodium internal extension MessageReceiver { @@ -8,7 +9,7 @@ internal extension MessageReceiver { let storage = SNMessagingKitConfiguration.shared.signalStorage let certificateValidator = SNMessagingKitConfiguration.shared.certificateValidator guard let data = envelope.content else { throw Error.noData } - guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else { throw Error.noUserPublicKey } + guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else { throw Error.noUserX25519KeyPair } let cipher = try SMKSecretSessionCipher(sessionResetImplementation: SNMessagingKitConfiguration.shared.sessionRestorationImplementation, sessionStore: storage, preKeyStore: storage, signedPreKeyStore: storage, identityStore: SNMessagingKitConfiguration.shared.identityKeyStore) let result = try cipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator, cipherTextData: data, @@ -16,6 +17,43 @@ internal extension MessageReceiver { return (result.paddedPayload, result.senderRecipientId) } + static func decryptWithSessionProtocol(envelope: SNProtoEnvelope) throws -> (plaintext: Data, senderX25519PublicKey: String) { + guard let ciphertext = envelope.content else { throw Error.noData } + let recipientX25519PrivateKey: Data + let recipientX25519PublicKey: Data + switch envelope.type { + case .unidentifiedSender: + guard let userX25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { throw Error.noUserX25519KeyPair } + recipientX25519PrivateKey = userX25519KeyPair.privateKey + recipientX25519PublicKey = Data(hex: userX25519KeyPair.hexEncodedPublicKey.removing05PrefixIfNeeded()) + case .closedGroupCiphertext: + guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey } + guard let hexEncodedGroupPrivateKey = SNMessagingKitConfiguration.shared.storage.getClosedGroupPrivateKey(for: hexEncodedGroupPublicKey) else { throw Error.noGroupPrivateKey } + recipientX25519PrivateKey = Data(hex: hexEncodedGroupPrivateKey) + recipientX25519PublicKey = Data(hex: hexEncodedGroupPublicKey.removing05PrefixIfNeeded()) + default: preconditionFailure() + } + let sodium = Sodium() + let signatureSize = sodium.sign.Bytes + let ed25519PublicKeySize = sodium.sign.PublicKeyBytes + + // 1. ) Decrypt the message + guard let plaintextWithMetadata = sodium.box.open(anonymousCipherText: Bytes(ciphertext), recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)), + recipientSecretKey: Bytes(recipientX25519PrivateKey)), plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize) else { throw Error.decryptionFailed } + // 2. ) Get the message parts + let signature = Bytes(plaintextWithMetadata[plaintextWithMetadata.count - signatureSize ..< plaintextWithMetadata.count]) + let senderED25519PublicKey = Bytes(plaintextWithMetadata[plaintextWithMetadata.count - (signatureSize + ed25519PublicKeySize) ..< plaintextWithMetadata.count - signatureSize]) + let plaintext = Bytes(plaintextWithMetadata[0.. (plaintext: Data, senderPublicKey: String) { // 1. ) Check preconditions guard let groupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(groupPublicKey) else { @@ -36,8 +74,8 @@ internal extension MessageReceiver { guard let ephemeralSharedSecret = try? Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey, privateKey: groupPrivateKey) else { throw Error.sharedSecretGenerationFailed } - let salt = "LOKI" - let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) + let salt = "LOKI".data(using: String.Encoding.utf8, allowLossyConversion: true)!.bytes + let symmetricKey = try HMAC(key: salt, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) let closedGroupCiphertextMessageAsData = try AESGCM.decrypt(ivAndCiphertext, with: Data(symmetricKey)) // 4. ) Parse the closed group ciphertext message let closedGroupCiphertextMessage = ClosedGroupCiphertextMessage(_throws_with: closedGroupCiphertextMessageAsData) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 780383c75..d46d9ae67 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -7,11 +7,14 @@ public enum MessageReceiver { case invalidMessage case unknownMessage case unknownEnvelopeType - case noUserPublicKey + case noUserX25519KeyPair + case noUserED25519KeyPair + case invalidSignature case noData case senderBlocked case noThread case selfSend + case decryptionFailed // Shared sender keys case invalidGroupPublicKey case noGroupPrivateKey @@ -19,7 +22,7 @@ public enum MessageReceiver { public var isRetryable: Bool { switch self { - case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .noData, .senderBlocked, .selfSend: return false + case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .selfSend, .decryptionFailed: return false default: return true } } @@ -30,15 +33,18 @@ public enum MessageReceiver { case .invalidMessage: return "Invalid message." case .unknownMessage: return "Unknown message type." case .unknownEnvelopeType: return "Unknown envelope type." - case .noUserPublicKey: return "Couldn't find user key pair." + case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair." + case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair." + case .invalidSignature: return "Invalid message signature." case .noData: return "Received an empty envelope." case .senderBlocked: return "Received a message from a blocked user." case .noThread: return "Couldn't find thread for message." + case .selfSend: return "Message addressed at self." + case .decryptionFailed: return "Decryption failed." // Shared sender keys case .invalidGroupPublicKey: return "Invalid group public key." case .noGroupPrivateKey: return "Missing group private key." case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." - case .selfSend: return "Message addressed at self." } } } @@ -59,9 +65,20 @@ public enum MessageReceiver { (plaintext, sender) = (envelope.content!, envelope.source!) } else { switch envelope.type { - case .unidentifiedSender: (plaintext, sender) = try decryptWithSignalProtocol(envelope: envelope, using: transaction) + case .unidentifiedSender: + do { + (plaintext, sender) = try decryptWithSessionProtocol(envelope: envelope) + } catch { + // Migration + (plaintext, sender) = try decryptWithSignalProtocol(envelope: envelope, using: transaction) + } case .closedGroupCiphertext: - (plaintext, sender) = try decryptWithSharedSenderKeys(envelope: envelope, using: transaction) + do { + (plaintext, sender) = try decryptWithSessionProtocol(envelope: envelope) + } catch { + // Migration + (plaintext, sender) = try decryptWithSharedSenderKeys(envelope: envelope, using: transaction) + } groupPublicKey = envelope.source default: throw Error.unknownEnvelopeType } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift index ce582d24a..ff11b4e39 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift @@ -1,5 +1,6 @@ import SessionProtocolKit import SessionUtilitiesKit +import Sodium internal extension MessageSender { @@ -11,12 +12,25 @@ internal extension MessageSender { return try cipher.throwswrapped_encryptMessage(recipientPublicKey: publicKey, deviceID: 1, paddedPlaintext: (plaintext as NSData).paddedMessageBody(), senderCertificate: certificate, protocolContext: transaction, useFallbackSessionCipher: true) } + + static func encryptWithSessionProtocol(_ plaintext: Data, for recipientHexEncodedX25519PublicKey: String) throws -> Data { + guard let userED25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserED25519KeyPair() else { throw Error.noUserED25519KeyPair } + let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded()) + let sodium = Sodium() + + let verificationData = plaintext + Data(userED25519KeyPair.publicKey) + recipientX25519PublicKey + guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { throw Error.signingFailed } + let plaintextWithMetadata = plaintext + Data(userED25519KeyPair.publicKey) + Data(signature) + guard let ciphertext = sodium.box.seal(message: Bytes(plaintextWithMetadata), recipientPublicKey: Bytes(recipientX25519PublicKey)) else { throw Error.encryptionFailed } + + return Data(ciphertext) + } static func encryptWithSharedSenderKeys(_ plaintext: Data, for groupPublicKey: String, using transaction: Any) throws -> Data { // 1. ) Encrypt the data with the user's sender key guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else { SNLog("Couldn't find user key pair.") - throw Error.noUserPublicKey + throw Error.noUserX25519KeyPair } let (ivAndCiphertext, keyIndex) = try SharedSenderKeys.encrypt((plaintext as NSData).paddedMessageBody(), for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) let encryptedMessage = ClosedGroupCiphertextMessage(_throws_withIVAndCiphertext: ivAndCiphertext, senderPublicKey: Data(hex: userPublicKey), keyIndex: UInt32(keyIndex)) diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 30aa77af3..998d7693c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -10,7 +10,10 @@ public final class MessageSender : NSObject { case invalidMessage case protoConversionFailed case proofOfWorkCalculationFailed - case noUserPublicKey + case noUserX25519KeyPair + case noUserED25519KeyPair + case signingFailed + case encryptionFailed // Closed groups case noThread case noPrivateKey @@ -18,7 +21,7 @@ public final class MessageSender : NSObject { internal var isRetryable: Bool { switch self { - case .invalidMessage, .protoConversionFailed, .proofOfWorkCalculationFailed, .invalidClosedGroupUpdate: return false + case .invalidMessage, .protoConversionFailed, .proofOfWorkCalculationFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false default: return true } } @@ -28,7 +31,10 @@ public final class MessageSender : NSObject { case .invalidMessage: return "Invalid message." case .protoConversionFailed: return "Couldn't convert message to proto." case .proofOfWorkCalculationFailed: return "Proof of work calculation failed." - case .noUserPublicKey: return "Couldn't find user key pair." + case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair." + case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair." + case .signingFailed: return "Couldn't sign message." + case .encryptionFailed: return "Couldn't encrypt message." // Closed groups case .noThread: return "Couldn't find a thread associated with the given group public key." case .noPrivateKey: return "Couldn't find a private key associated with the given group public key." diff --git a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift index a7076ad8c..e4783fc94 100644 --- a/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift +++ b/SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift @@ -19,7 +19,7 @@ public final class PushNotificationAPI : NSObject { private override init() { } // MARK: Registration - static func unregister(with token: Data, isForcedUpdate: Bool) -> Promise { + public static func unregister(_ token: Data) -> Promise { let hexEncodedToken = token.toHexString() let parameters = [ "token" : hexEncodedToken ] let url = URL(string: "\(server)/unregister")! @@ -45,12 +45,12 @@ public final class PushNotificationAPI : NSObject { return promise } - @objc(unregisterWithToken:isForcedUpdate:) - public static func objc_unregister(with token: Data, isForcedUpdate: Bool) -> AnyPromise { - return AnyPromise.from(unregister(with: token, isForcedUpdate: isForcedUpdate)) + @objc(unregisterToken:) + public static func objc_unregister(token: Data) -> AnyPromise { + return AnyPromise.from(unregister(token)) } - static func register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> Promise { + public static func register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> Promise { let hexEncodedToken = token.toHexString() let userDefaults = UserDefaults.standard let oldToken = userDefaults[.deviceToken] diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 0a0ad7b2d..590f66092 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -1,5 +1,6 @@ import SessionProtocolKit import PromiseKit +import Sodium public protocol SessionMessagingKitStorageProtocol { @@ -15,6 +16,7 @@ public protocol SessionMessagingKitStorageProtocol { func getUserPublicKey() -> String? func getUserKeyPair() -> ECKeyPair? + func getUserED25519KeyPair() -> Box.KeyPair? func getUserDisplayName() -> String? func getUserProfileKey() -> Data? func getUserProfilePictureURL() -> String? diff --git a/SessionMessagingKit/To Do/TSAccountManager.m b/SessionMessagingKit/To Do/TSAccountManager.m index 2ee6e4b8c..03c1cfcc4 100644 --- a/SessionMessagingKit/To Do/TSAccountManager.m +++ b/SessionMessagingKit/To Do/TSAccountManager.m @@ -277,7 +277,7 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; NSData *pushTokenAsData = [NSData dataFromHexString:pushToken]; AnyPromise *promise = isUsingFullAPNs ? [LKPushNotificationAPI registerWithToken:pushTokenAsData hexEncodedPublicKey:self.localNumber isForcedUpdate:isForcedUpdate] - : [LKPushNotificationAPI unregisterWithToken:pushTokenAsData isForcedUpdate:isForcedUpdate]; + : [LKPushNotificationAPI unregisterToken:pushTokenAsData]; promise .then(^() { successHandler(); diff --git a/Session/Utilities/Sodium+Conversion.swift b/SessionMessagingKit/Utilities/Sodium+Conversion.swift similarity index 100% rename from Session/Utilities/Sodium+Conversion.swift rename to SessionMessagingKit/Utilities/Sodium+Conversion.swift diff --git a/SessionUtilitiesKit/LKUserDefaults.swift b/SessionUtilitiesKit/LKUserDefaults.swift index 077c7c2a9..1286814ab 100644 --- a/SessionUtilitiesKit/LKUserDefaults.swift +++ b/SessionUtilitiesKit/LKUserDefaults.swift @@ -11,6 +11,7 @@ public enum LKUserDefaults { public enum Date : Swift.String { case lastProfilePictureUpload + case lastKeyPairMigrationNudge } public enum Double : Swift.String { diff --git a/SessionUtilitiesKit/String+SSK.swift b/SessionUtilitiesKit/String+SSK.swift index 3d13c9180..ae50abb28 100644 --- a/SessionUtilitiesKit/String+SSK.swift +++ b/SessionUtilitiesKit/String+SSK.swift @@ -5,6 +5,7 @@ import Foundation public extension String { + var digitsOnly: String { return (self as NSString).digitsOnly() } diff --git a/SessionUtilitiesKit/UIView+OWS.m b/SessionUtilitiesKit/UIView+OWS.m index d4d8ae493..aca47ba45 100644 --- a/SessionUtilitiesKit/UIView+OWS.m +++ b/SessionUtilitiesKit/UIView+OWS.m @@ -500,7 +500,6 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) - (UIView *)addBorderViewWithColor:(UIColor *)color strokeWidth:(CGFloat)strokeWidth cornerRadius:(CGFloat)cornerRadius { - UIView *borderView = [UIView new]; borderView.userInteractionEnabled = NO; borderView.backgroundColor = UIColor.clearColor; diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 5b02e70fe..639462ac6 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -236,6 +236,7 @@ B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */; }; B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408F239DD75000A248E7 /* RestoreVC.swift */; }; B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B4093239DF15900A248E7 /* ConversationTitleView.swift */; }; + B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */; }; B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */; }; B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; }; B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */; }; @@ -244,6 +245,8 @@ B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */; }; B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2D1255B6DAF007E1867 /* OWSUserProfile.m */; }; B8566C7D256F62030045A0B9 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2D3255B6DAF007E1867 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B85A68B12587141A008CC492 /* Storage+Resetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B85A68B02587141A008CC492 /* Storage+Resetting.swift */; }; + B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; }; @@ -914,7 +917,6 @@ C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; - C3E7134F251C867C009649BB /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; @@ -1362,6 +1364,7 @@ B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = ""; }; B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = ""; }; B82B4093239DF15900A248E7 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = ""; }; + B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSheet.swift; sourceTree = ""; }; B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = ""; }; B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = ""; }; B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSOutgoingMessage+Conversion.swift"; sourceTree = ""; }; @@ -1373,6 +1376,7 @@ B8544E3023D16CA500299F14 /* DeviceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUtilities.swift; sourceTree = ""; }; B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilities.swift; sourceTree = ""; }; B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OWSLinkPreview+Conversion.swift"; sourceTree = ""; }; + B85A68B02587141A008CC492 /* Storage+Resetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Resetting.swift"; sourceTree = ""; }; B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = ""; }; B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = ""; }; B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = ""; }; @@ -2603,7 +2607,7 @@ C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */, - C3E7134E251C867C009649BB /* Sodium+Conversion.swift */, + B85A68B02587141A008CC492 /* Storage+Resetting.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, C31A6C59247F214E001123EF /* UIView+Glow.swift */, @@ -2621,6 +2625,7 @@ B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */, B8BB82A4238F627000BA5194 /* HomeVC.swift */, B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */, + B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */, B82B40872399EB0E00A248E7 /* LandingVC.swift */, C329FEEB24F7277900B1C64C /* LightModeSheet.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, @@ -3372,6 +3377,7 @@ C33FDB91255A581200E217F9 /* ProtoUtils.h */, C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, + C3E7134E251C867C009649BB /* Sodium+Conversion.swift */, C33FDB31255A580A00E217F9 /* SSKEnvironment.h */, C33FDAF4255A580600E217F9 /* SSKEnvironment.m */, C33FDB32255A580A00E217F9 /* SSKIncrementingIdFinder.swift */, @@ -5253,6 +5259,7 @@ C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */, C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */, C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, + B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, @@ -5428,6 +5435,7 @@ C396DAF42518408B00FF6DC5 /* Parser.swift in Sources */, 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */, 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */, + B85A68B12587141A008CC492 /* Storage+Resetting.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, 45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */, 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */, @@ -5522,7 +5530,6 @@ 34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */, - C3E7134F251C867C009649BB /* Sodium+Conversion.swift in Sources */, 340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */, C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, @@ -5532,6 +5539,7 @@ 340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */, B88847BC23E10BC6009836D2 /* GroupMembersVC.swift in Sources */, B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, + B83786802586D296003CE78E /* KeyPairMigrationSheet.swift in Sources */, C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */, 340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */, 340FC8B0204DAC8D007AEB0F /* AddToBlockListViewController.m in Sources */, diff --git a/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift b/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift index bb7279306..ee7742dd9 100644 --- a/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift +++ b/SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift @@ -3,6 +3,7 @@ import PromiseKit extension MessageSender { + // MARK: Durable @objc(send:withAttachments:inThread:usingTransaction:) public static func send(_ message: VisibleMessage, with attachments: [SignalAttachment], in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) { prep(attachments, for: message, using: transaction) @@ -15,8 +16,16 @@ extension MessageSender { let destination = Message.Destination.from(thread) let job = MessageSendJob(message: message, destination: destination) JobQueue.shared.add(job, using: transaction) + guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else { return } + if case .contact(let recipientPublicKey) = destination, message is VisibleMessage, recipientPublicKey != userPublicKey { + DispatchQueue.main.async { + // Not strictly true, but nicer from a UX perspective + NotificationCenter.default.post(name: .encryptingMessage, object: NSNumber(value: message.sentTimestamp!)) + } + } } + // MARK: Non-Durable @objc(sendNonDurably:withAttachments:inThread:usingTransaction:) public static func objc_sendNonDurably(_ message: VisibleMessage, with attachments: [SignalAttachment], in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) -> AnyPromise { return AnyPromise.from(sendNonDurably(message, with: attachments, in: thread, using: transaction))