Implement Session protocol

This commit is contained in:
Niels Andriesse 2020-12-10 16:12:22 +11:00
parent 4cb17b388d
commit 2a4977d269
12 changed files with 106 additions and 18 deletions

View File

@ -71,6 +71,7 @@ target 'SessionMessagingKit' do
pod 'Reachability', :inhibit_warnings => true pod 'Reachability', :inhibit_warnings => true
pod 'SAMKeychain', :inhibit_warnings => true pod 'SAMKeychain', :inhibit_warnings => true
pod 'SignalCoreKit', git: 'https://github.com/signalapp/SignalCoreKit.git', :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 '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 pod 'YapDatabase/SQLCipher', :git => 'https://github.com/loki-project/session-ios-yap-database.git', branch: 'signal-release', :inhibit_warnings => true
end end

View File

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

View File

@ -1,4 +1,5 @@
import PromiseKit import PromiseKit
import Sodium
extension Storage { extension Storage {
@ -23,6 +24,16 @@ extension Storage {
public func getUserKeyPair() -> ECKeyPair? { public func getUserKeyPair() -> ECKeyPair? {
return OWSIdentityManager.shared().identityKeyPair() 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? { public func getUserDisplayName() -> String? {
return SSKEnvironment.shared.profileManager.localProfileName() return SSKEnvironment.shared.profileManager.localProfileName()

View File

@ -1,6 +1,7 @@
import CryptoSwift import CryptoSwift
import SessionProtocolKit import SessionProtocolKit
import SessionUtilitiesKit import SessionUtilitiesKit
import Sodium
internal extension MessageReceiver { internal extension MessageReceiver {
@ -8,7 +9,7 @@ internal extension MessageReceiver {
let storage = SNMessagingKitConfiguration.shared.signalStorage let storage = SNMessagingKitConfiguration.shared.signalStorage
let certificateValidator = SNMessagingKitConfiguration.shared.certificateValidator let certificateValidator = SNMessagingKitConfiguration.shared.certificateValidator
guard let data = envelope.content else { throw Error.noData } 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, let cipher = try SMKSecretSessionCipher(sessionResetImplementation: SNMessagingKitConfiguration.shared.sessionRestorationImplementation,
sessionStore: storage, preKeyStore: storage, signedPreKeyStore: storage, identityStore: SNMessagingKitConfiguration.shared.identityKeyStore) sessionStore: storage, preKeyStore: storage, signedPreKeyStore: storage, identityStore: SNMessagingKitConfiguration.shared.identityKeyStore)
let result = try cipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator, cipherTextData: data, let result = try cipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator, cipherTextData: data,
@ -16,6 +17,42 @@ internal extension MessageReceiver {
return (result.paddedPayload, result.senderRecipientId) 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 isValid = sodium.sign.verify(message: plaintext + senderED25519PublicKey + recipientX25519PublicKey, 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) { static func decryptWithSharedSenderKeys(envelope: SNProtoEnvelope, using transaction: Any) throws -> (plaintext: Data, senderPublicKey: String) {
// 1. ) Check preconditions // 1. ) Check preconditions
guard let groupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(groupPublicKey) else { guard let groupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(groupPublicKey) else {
@ -36,8 +73,8 @@ internal extension MessageReceiver {
guard let ephemeralSharedSecret = try? Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey, privateKey: groupPrivateKey) else { guard let ephemeralSharedSecret = try? Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey, privateKey: groupPrivateKey) else {
throw Error.sharedSecretGenerationFailed throw Error.sharedSecretGenerationFailed
} }
let salt = "LOKI" let salt = "LOKI".data(using: String.Encoding.utf8, allowLossyConversion: true)!.bytes
let symmetricKey = try HMAC(key: salt.bytes, variant: .sha256).authenticate(ephemeralSharedSecret.bytes) let symmetricKey = try HMAC(key: salt, variant: .sha256).authenticate(ephemeralSharedSecret.bytes)
let closedGroupCiphertextMessageAsData = try AESGCM.decrypt(ivAndCiphertext, with: Data(symmetricKey)) let closedGroupCiphertextMessageAsData = try AESGCM.decrypt(ivAndCiphertext, with: Data(symmetricKey))
// 4. ) Parse the closed group ciphertext message // 4. ) Parse the closed group ciphertext message
let closedGroupCiphertextMessage = ClosedGroupCiphertextMessage(_throws_with: closedGroupCiphertextMessageAsData) let closedGroupCiphertextMessage = ClosedGroupCiphertextMessage(_throws_with: closedGroupCiphertextMessageAsData)

View File

@ -7,11 +7,14 @@ public enum MessageReceiver {
case invalidMessage case invalidMessage
case unknownMessage case unknownMessage
case unknownEnvelopeType case unknownEnvelopeType
case noUserPublicKey case noUserX25519KeyPair
case noUserED25519KeyPair
case invalidSignature
case noData case noData
case senderBlocked case senderBlocked
case noThread case noThread
case selfSend case selfSend
case decryptionFailed
// Shared sender keys // Shared sender keys
case invalidGroupPublicKey case invalidGroupPublicKey
case noGroupPrivateKey case noGroupPrivateKey
@ -19,7 +22,7 @@ public enum MessageReceiver {
public var isRetryable: Bool { public var isRetryable: Bool {
switch self { 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 default: return true
} }
} }
@ -30,15 +33,18 @@ public enum MessageReceiver {
case .invalidMessage: return "Invalid message." case .invalidMessage: return "Invalid message."
case .unknownMessage: return "Unknown message type." case .unknownMessage: return "Unknown message type."
case .unknownEnvelopeType: return "Unknown envelope 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 .noData: return "Received an empty envelope."
case .senderBlocked: return "Received a message from a blocked user." case .senderBlocked: return "Received a message from a blocked user."
case .noThread: return "Couldn't find thread for message." case .noThread: return "Couldn't find thread for message."
case .selfSend: return "Message addressed at self."
case .decryptionFailed: return "Decryption failed."
// Shared sender keys // Shared sender keys
case .invalidGroupPublicKey: return "Invalid group public key." case .invalidGroupPublicKey: return "Invalid group public key."
case .noGroupPrivateKey: return "Missing group private key." case .noGroupPrivateKey: return "Missing group private key."
case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." 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!) (plaintext, sender) = (envelope.content!, envelope.source!)
} else { } else {
switch envelope.type { 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: 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 groupPublicKey = envelope.source
default: throw Error.unknownEnvelopeType default: throw Error.unknownEnvelopeType
} }

View File

@ -1,5 +1,6 @@
import SessionProtocolKit import SessionProtocolKit
import SessionUtilitiesKit import SessionUtilitiesKit
import Sodium
internal extension MessageSender { internal extension MessageSender {
@ -11,12 +12,25 @@ internal extension MessageSender {
return try cipher.throwswrapped_encryptMessage(recipientPublicKey: publicKey, deviceID: 1, paddedPlaintext: (plaintext as NSData).paddedMessageBody(), return try cipher.throwswrapped_encryptMessage(recipientPublicKey: publicKey, deviceID: 1, paddedPlaintext: (plaintext as NSData).paddedMessageBody(),
senderCertificate: certificate, protocolContext: transaction, useFallbackSessionCipher: true) 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 data = plaintext + Data(userED25519KeyPair.publicKey) + recipientX25519PublicKey
guard let signature = sodium.sign.signature(message: Bytes(data), secretKey: userED25519KeyPair.secretKey) else { throw Error.signingFailed }
guard let ciphertext = sodium.box.seal(message: Bytes(plaintext + Data(userED25519KeyPair.publicKey)
+ Data(signature)), recipientPublicKey: Bytes(recipientX25519PublicKey)) else { throw Error.encryptionFailed }
return Data(ciphertext)
}
static func encryptWithSharedSenderKeys(_ plaintext: Data, for groupPublicKey: String, using transaction: Any) throws -> Data { static func encryptWithSharedSenderKeys(_ plaintext: Data, for groupPublicKey: String, using transaction: Any) throws -> Data {
// 1. ) Encrypt the data with the user's sender key // 1. ) Encrypt the data with the user's sender key
guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else { guard let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() else {
SNLog("Couldn't find user key pair.") 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 (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)) 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 invalidMessage
case protoConversionFailed case protoConversionFailed
case proofOfWorkCalculationFailed case proofOfWorkCalculationFailed
case noUserPublicKey case noUserX25519KeyPair
case noUserED25519KeyPair
case signingFailed
case encryptionFailed
// Closed groups // Closed groups
case noThread case noThread
case noPrivateKey case noPrivateKey
@ -18,7 +21,7 @@ public final class MessageSender : NSObject {
internal var isRetryable: Bool { internal var isRetryable: Bool {
switch self { switch self {
case .invalidMessage, .protoConversionFailed, .proofOfWorkCalculationFailed, .invalidClosedGroupUpdate: return false case .invalidMessage, .protoConversionFailed, .proofOfWorkCalculationFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false
default: return true default: return true
} }
} }
@ -28,7 +31,10 @@ public final class MessageSender : NSObject {
case .invalidMessage: return "Invalid message." case .invalidMessage: return "Invalid message."
case .protoConversionFailed: return "Couldn't convert message to proto." case .protoConversionFailed: return "Couldn't convert message to proto."
case .proofOfWorkCalculationFailed: return "Proof of work calculation failed." 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 // Closed groups
case .noThread: return "Couldn't find a thread associated with the given group public key." 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." case .noPrivateKey: return "Couldn't find a private key associated with the given group public key."

View File

@ -1,5 +1,6 @@
import SessionProtocolKit import SessionProtocolKit
import PromiseKit import PromiseKit
import Sodium
public protocol SessionMessagingKitStorageProtocol { public protocol SessionMessagingKitStorageProtocol {
@ -15,6 +16,7 @@ public protocol SessionMessagingKitStorageProtocol {
func getUserPublicKey() -> String? func getUserPublicKey() -> String?
func getUserKeyPair() -> ECKeyPair? func getUserKeyPair() -> ECKeyPair?
func getUserED25519KeyPair() -> Box.KeyPair?
func getUserDisplayName() -> String? func getUserDisplayName() -> String?
func getUserProfileKey() -> Data? func getUserProfileKey() -> Data?
func getUserProfilePictureURL() -> String? func getUserProfilePictureURL() -> String?

View File

@ -5,6 +5,7 @@
import Foundation import Foundation
public extension String { public extension String {
var digitsOnly: String { var digitsOnly: String {
return (self as NSString).digitsOnly() 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 *)addBorderViewWithColor:(UIColor *)color strokeWidth:(CGFloat)strokeWidth cornerRadius:(CGFloat)cornerRadius
{ {
UIView *borderView = [UIView new]; UIView *borderView = [UIView new];
borderView.userInteractionEnabled = NO; borderView.userInteractionEnabled = NO;
borderView.backgroundColor = UIColor.clearColor; borderView.backgroundColor = UIColor.clearColor;

View File

@ -244,6 +244,7 @@
B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */; }; B8566C63256F55930045A0B9 /* OWSLinkPreview+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8566C62256F55930045A0B9 /* OWSLinkPreview+Conversion.swift */; };
B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2D1255B6DAF007E1867 /* OWSUserProfile.m */; }; 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, ); }; }; B8566C7D256F62030045A0B9 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2D3255B6DAF007E1867 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; };
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.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 */; }; B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; };
@ -914,7 +915,6 @@
C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; };
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; };
C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.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 */; }; C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECBF7A257056B700EA7FCE /* Threading.swift */; };
C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; };
D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; };
@ -2603,7 +2603,6 @@
C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */, C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */,
B84664F4235022F30083A1CD /* MentionUtilities.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */,
B886B4A82398BA1500211ABE /* QRCode.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */,
C3E7134E251C867C009649BB /* Sodium+Conversion.swift */,
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */,
C31A6C59247F214E001123EF /* UIView+Glow.swift */, C31A6C59247F214E001123EF /* UIView+Glow.swift */,
@ -3372,6 +3371,7 @@
C33FDB91255A581200E217F9 /* ProtoUtils.h */, C33FDB91255A581200E217F9 /* ProtoUtils.h */,
C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */, C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */,
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
C3E7134E251C867C009649BB /* Sodium+Conversion.swift */,
C33FDB31255A580A00E217F9 /* SSKEnvironment.h */, C33FDB31255A580A00E217F9 /* SSKEnvironment.h */,
C33FDAF4255A580600E217F9 /* SSKEnvironment.m */, C33FDAF4255A580600E217F9 /* SSKEnvironment.m */,
C33FDB32255A580A00E217F9 /* SSKIncrementingIdFinder.swift */, C33FDB32255A580A00E217F9 /* SSKIncrementingIdFinder.swift */,
@ -5253,6 +5253,7 @@
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */, C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */, C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */,
C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */,
B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */,
C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */,
C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */,
C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */,
@ -5522,7 +5523,6 @@
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */, 34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */,
340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */,
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */, 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */,
C3E7134F251C867C009649BB /* Sodium+Conversion.swift in Sources */,
340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */, 340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */,
C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */,
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */,