Merge pull request #318 from loki-project/session-protocol

Session Protocol
This commit is contained in:
Niels Andriesse 2020-12-16 14:31:24 +11:00 committed by GitHub
commit ad3f04a8f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 256 additions and 26 deletions

View File

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

View File

@ -230,6 +230,6 @@ SPEC CHECKSUMS:
YYImage: 6db68da66f20d9f169ceb94dfb9947c3867b9665
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 7699c2a380fc803ef7f51157f1f75da756aa3b45
PODFILE CHECKSUM: 3263ab95f60e220882ca53cca4c6bdc2e7a80381
COCOAPODS: 1.10.0.rc.1

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Shield.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

View File

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

View File

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

View File

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

View File

@ -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 = """
Weve 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 youll 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 = "Youre 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)
}
}
}

View File

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

View File

@ -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..<plaintextWithMetadata.count - (signatureSize + ed25519PublicKeySize)])
// 3. ) Verify the signature
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
let isValid = sodium.sign.verify(message: verificationData, publicKey: senderED25519PublicKey, signature: signature)
guard isValid else { throw Error.invalidSignature }
// 4. ) Get the sender's X25519 public key
guard let senderX25519PublicKey = sodium.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else { throw Error.decryptionFailed }
return (Data(plaintext), "05" + senderX25519PublicKey.toHexString())
}
static func decryptWithSharedSenderKeys(envelope: SNProtoEnvelope, using transaction: Any) throws -> (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)

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ public final class PushNotificationAPI : NSObject {
private override init() { }
// MARK: Registration
static func unregister(with token: Data, isForcedUpdate: Bool) -> Promise<Void> {
public static func unregister(_ token: Data) -> Promise<Void> {
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<Void> {
public static func register(with token: Data, publicKey: String, isForcedUpdate: Bool) -> Promise<Void> {
let hexEncodedToken = token.toHexString()
let userDefaults = UserDefaults.standard
let oldToken = userDefaults[.deviceToken]

View File

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

View File

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

View File

@ -11,6 +11,7 @@ public enum LKUserDefaults {
public enum Date : Swift.String {
case lastProfilePictureUpload
case lastKeyPairMigrationNudge
}
public enum Double : Swift.String {

View File

@ -5,6 +5,7 @@
import Foundation
public extension String {
var digitsOnly: String {
return (self as NSString).digitsOnly()
}

View File

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

View File

@ -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 = "<group>"; };
B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = "<group>"; };
B82B4093239DF15900A248E7 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = "<group>"; };
B837867F2586D296003CE78E /* KeyPairMigrationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairMigrationSheet.swift; sourceTree = "<group>"; };
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = "<group>"; };
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = "<group>"; };
B84072952565E9F50037CB17 /* TSOutgoingMessage+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSOutgoingMessage+Conversion.swift"; sourceTree = "<group>"; };
@ -1373,6 +1376,7 @@
B8544E3023D16CA500299F14 /* DeviceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceUtilities.swift; sourceTree = "<group>"; };
B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilities.swift; sourceTree = "<group>"; };
B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OWSLinkPreview+Conversion.swift"; sourceTree = "<group>"; };
B85A68B02587141A008CC492 /* Storage+Resetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Resetting.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>"; };
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = "<group>"; };
@ -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 */,

View File

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