From 2a4977d26953b1f62ba1e5e544388401dc7cb009 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 10 Dec 2020 16:12:22 +1100 Subject: [PATCH] Implement Session protocol --- Podfile | 1 + Podfile.lock | 2 +- .../Database/Storage+Shared.swift | 11 +++++ .../MessageReceiver+Decryption.swift | 43 +++++++++++++++++-- .../Sending & Receiving/MessageReceiver.swift | 29 ++++++++++--- .../MessageSender+Encryption.swift | 16 ++++++- .../Sending & Receiving/MessageSender.swift | 12 ++++-- SessionMessagingKit/Storage.swift | 2 + .../Utilities/Sodium+Conversion.swift | 0 SessionUtilitiesKit/String+SSK.swift | 1 + SessionUtilitiesKit/UIView+OWS.m | 1 - Signal.xcodeproj/project.pbxproj | 6 +-- 12 files changed, 106 insertions(+), 18 deletions(-) rename {Session => SessionMessagingKit}/Utilities/Sodium+Conversion.swift (100%) 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/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..ea502640b 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,42 @@ 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 +73,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..7138fa2e6 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 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 { // 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/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/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/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 7b2d9ad8c..dc7d26319 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -244,6 +244,7 @@ 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, ); }; }; + 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 +915,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 */; }; @@ -2603,7 +2603,6 @@ C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */, - C3E7134E251C867C009649BB /* Sodium+Conversion.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, C31A6C59247F214E001123EF /* UIView+Glow.swift */, @@ -3372,6 +3371,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 +5253,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 */, @@ -5522,7 +5523,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 */,