From 1edd500dab829b4b4b03dd7c7e69629b90600b1b Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 21 Feb 2022 10:01:53 +1100 Subject: [PATCH] Updated to the latest blinding behaviour Added a couple more dependencies for unit testing injection Updated the MessageSender to set the sender of the message to the appropriate blinded/unblinded key Updated the OpenGroup Message to handle verification of both blinded and unblinded messages Updated the MessageSender to use dependency injection for it's sendToOpenGroupDestination method Updated the JSONDecoder to support getting dependencies (for signature verification) Fixed tests broken by updating the signing logic --- Session.xcodeproj/project.pbxproj | 32 ++- .../Open Groups/Models/BatchRequestInfo.swift | 8 +- .../Open Groups/Models/OGMessage.swift | 28 ++- .../Open Groups/OpenGroupAPI.swift | 173 +++++----------- .../Open Groups/Types/Dependencies.swift | 11 ++ .../Open Groups/Types/SodiumProtocols.swift | 19 +- .../Sending & Receiving/MessageSender.swift | 37 ++-- .../Utilities/Data+Utilities.swift | 23 +++ .../Utilities/ECKeyPair+Conversion.swift | 34 ---- .../Utilities/Promise+Utilities.swift | 8 +- .../Utilities/Sodium+Utilities.swift | 34 +--- .../Open Groups/OpenGroupAPIV2Tests.swift | 185 +++++++++++++----- .../_TestUtilities/Mockable.swift | 6 + .../TestAeadXChaCha20Poly1305Ietf.swift | 25 +++ .../_TestUtilities/TestEd25519.swift | 25 +++ .../_TestUtilities/TestGenericHash.swift | 35 ++++ .../_TestUtilities/TestSign.swift | 30 +++ .../_TestUtilities/TestSodium.swift | 46 +++++ .../_TestUtilities/TestStorage.swift | 3 +- .../General/Data+Utilities.swift | 13 -- 20 files changed, 496 insertions(+), 279 deletions(-) create mode 100644 SessionMessagingKit/Utilities/Data+Utilities.swift delete mode 100644 SessionMessagingKit/Utilities/ECKeyPair+Conversion.swift create mode 100644 SessionMessagingKitTests/_TestUtilities/TestAeadXChaCha20Poly1305Ietf.swift create mode 100644 SessionMessagingKitTests/_TestUtilities/TestEd25519.swift create mode 100644 SessionMessagingKitTests/_TestUtilities/TestGenericHash.swift create mode 100644 SessionMessagingKitTests/_TestUtilities/TestSign.swift create mode 100644 SessionMessagingKitTests/_TestUtilities/TestSodium.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index ad42a143f..e4cb5bf98 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -776,7 +776,6 @@ FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; }; FD5D201E27B0D87C00FEA984 /* IdPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201D27B0D87C00FEA984 /* IdPrefix.swift */; }; FD5D202027B0E67900FEA984 /* String+Encoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201F27B0E67800FEA984 /* String+Encoding.swift */; }; - FD5D202227B1D74F00FEA984 /* ECKeyPair+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D202127B1D74F00FEA984 /* ECKeyPair+Conversion.swift */; }; FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */; }; FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */; }; FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8D278CE29800F16121 /* String+Localization.swift */; }; @@ -784,6 +783,12 @@ FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; }; FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */; }; FD705A98278E9F4D00F16121 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */; }; + FD859EF227BF6BA200510D0C /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; }; + FD859EF427C2F49200510D0C /* TestSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* TestSodium.swift */; }; + FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* TestSign.swift */; }; + FD859EF827C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */; }; + FD859EFA27C2F5C500510D0C /* TestGenericHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF927C2F5C500510D0C /* TestGenericHash.swift */; }; + FD859EFC27C2F60700510D0C /* TestEd25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFB27C2F60700510D0C /* TestEd25519.swift */; }; FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; }; FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; }; FDC4380927B31D4E00C60D73 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* Error.swift */; }; @@ -1912,7 +1917,6 @@ FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = ""; }; FD5D201D27B0D87C00FEA984 /* IdPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdPrefix.swift; sourceTree = ""; }; FD5D201F27B0E67800FEA984 /* String+Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Encoding.swift"; sourceTree = ""; }; - FD5D202127B1D74F00FEA984 /* ECKeyPair+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ECKeyPair+Conversion.swift"; sourceTree = ""; }; FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = ""; }; FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = ""; }; FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = ""; }; @@ -1920,6 +1924,14 @@ FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; + FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = ""; }; + FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = ""; }; + FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = ""; }; + FD859EF327C2F49200510D0C /* TestSodium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSodium.swift; sourceTree = ""; }; + FD859EF527C2F52C00510D0C /* TestSign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSign.swift; sourceTree = ""; }; + FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAeadXChaCha20Poly1305Ietf.swift; sourceTree = ""; }; + FD859EF927C2F5C500510D0C /* TestGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGenericHash.swift; sourceTree = ""; }; + FD859EFB27C2F60700510D0C /* TestEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestEd25519.swift; sourceTree = ""; }; FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = ""; }; FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = ""; }; FD9039443F7CB729CF71350E /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-SessionNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; @@ -3384,6 +3396,7 @@ C33FDB01255A580700E217F9 /* AppReadiness.h */, C33FDB75255A581000E217F9 /* AppReadiness.m */, FDC4383D27B4708600C60D73 /* Atomic.swift */, + FD859EF127BF6BA200510D0C /* Data+Utilities.swift */, C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */, C37F53E8255BA9BB002AEA92 /* Environment.h */, C37F5402255BA9ED002AEA92 /* Environment.m */, @@ -3423,7 +3436,6 @@ C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, C3E7134E251C867C009649BB /* Sodium+Utilities.swift */, FDC4386827B4E6B700C60D73 /* String+Utlities.swift */, - FD5D202127B1D74F00FEA984 /* ECKeyPair+Conversion.swift */, FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */, C33FDB31255A580A00E217F9 /* SSKEnvironment.h */, C33FDAF4255A580600E217F9 /* SSKEnvironment.m */, @@ -3539,6 +3551,8 @@ C3C2A7802553AA6300C340D1 /* Protos */ = { isa = PBXGroup; children = ( + FD859EEF27BF207700510D0C /* SessionProtos.proto */, + FD859EF027BF207C00510D0C /* WebSocketResources.proto */, C3C2A7812553AA9000C340D1 /* Generated */, ); path = Protos; @@ -3938,6 +3952,11 @@ children = ( FDC438BC27BB2AB400C60D73 /* Mockable.swift */, FDC4389C27BA01F000C60D73 /* TestStorage.swift */, + FD859EF327C2F49200510D0C /* TestSodium.swift */, + FD859EF527C2F52C00510D0C /* TestSign.swift */, + FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */, + FD859EF927C2F5C500510D0C /* TestGenericHash.swift */, + FD859EFB27C2F60700510D0C /* TestEd25519.swift */, ); path = _TestUtilities; sourceTree = ""; @@ -5110,6 +5129,7 @@ C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */, + FD859EF227BF6BA200510D0C /* Data+Utilities.swift in Sources */, FDC4384927B47F4D00C60D73 /* LegacyCompactPollResponse.swift in Sources */, C352A3932557883D00338F3E /* JobDelegate.swift in Sources */, C32C5B84256DC54F003C73A2 /* SSKEnvironment.m in Sources */, @@ -5168,7 +5188,6 @@ FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */, C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, - FD5D202227B1D74F00FEA984 /* ECKeyPair+Conversion.swift in Sources */, FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */, FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */, FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */, @@ -5463,8 +5482,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD859EFA27C2F5C500510D0C /* TestGenericHash.swift in Sources */, + FD859EF827C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift in Sources */, + FD859EF427C2F49200510D0C /* TestSodium.swift in Sources */, + FD859EFC27C2F60700510D0C /* TestEd25519.swift in Sources */, FDC4389A27BA002500C60D73 /* OpenGroupAPIV2Tests.swift in Sources */, FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */, + FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */, FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift b/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift index 28525723d..89e35dc9c 100644 --- a/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift +++ b/SessionMessagingKit/Open Groups/Models/BatchRequestInfo.swift @@ -62,13 +62,13 @@ extension OpenGroupAPI { // MARK: - Convenience public extension Decodable { - static func decoded(from data: Data) throws -> Self { - return try JSONDecoder().decode(Self.self, from: data) + static func decoded(from data: Data, customError: Error, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> Self { + return try data.decoded(as: Self.self, customError: customError, using: dependencies) } } extension Promise where T == (OnionRequestResponseInfoType, Data?) { - func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error) -> Promise { + func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise { self.map(on: queue) { responseInfo, maybeData -> OpenGroupAPI.BatchResponse in // Need to split the data into an array of data so each item can be Decoded correctly guard let data: Data = maybeData else { throw OpenGroupAPI.Error.parsingFailed } @@ -82,7 +82,7 @@ extension Promise where T == (OnionRequestResponseInfoType, Data?) { do { return try zip(dataArray, types) - .map { data, type in try type.decoded(from: data) } + .map { data, type in try type.decoded(from: data, customError: error, using: dependencies) } .map { data in (responseInfo, data) } } catch _ { diff --git a/SessionMessagingKit/Open Groups/Models/OGMessage.swift b/SessionMessagingKit/Open Groups/Models/OGMessage.swift index 4101dca7c..15ce06931 100644 --- a/SessionMessagingKit/Open Groups/Models/OGMessage.swift +++ b/SessionMessagingKit/Open Groups/Models/OGMessage.swift @@ -47,14 +47,30 @@ extension OpenGroupAPI.Message { guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else { throw OpenGroupAPI.Error.parsingFailed } - - let publicKey: Data = Data(hex: sender.removingIdPrefixIfNeeded()) - let isValid: Bool = ((try? Ed25519.verifySignature(signature, publicKey: publicKey, data: data)) ?? false) - - guard isValid else { - SNLog("Ignoring message with invalid signature.") + guard let dependencies: OpenGroupAPI.Dependencies = decoder.userInfo[OpenGroupAPI.Dependencies.userInfoKey] as? OpenGroupAPI.Dependencies else { throw OpenGroupAPI.Error.parsingFailed } + + // Verify the signature based on the IdPrefix + let publicKey: Data = Data(hex: sender.removingIdPrefixIfNeeded()) + + switch IdPrefix(with: sender) { + case .blinded: + guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else { + SNLog("Ignoring message with invalid signature.") + throw OpenGroupAPI.Error.parsingFailed + } + + case .standard, .unblinded: + guard (try? dependencies.ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true else { + SNLog("Ignoring message with invalid signature.") + throw OpenGroupAPI.Error.parsingFailed + } + + case .none: + SNLog("Ignoring message with invalid sender.") + throw OpenGroupAPI.Error.parsingFailed + } } self = OpenGroupAPI.Message( diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift index 7009ae873..c369744a7 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPI.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPI.swift @@ -125,7 +125,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) .map { result in result.enumerated() .reduce(into: [:]) { prev, next in @@ -156,7 +156,7 @@ public final class OpenGroupAPI: NSObject { // TODO: Handle a `412` response (ie. a required capability isn't supported) return send(request, using: dependencies) - .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) .map { result in result.enumerated() .reduce(into: [:]) { prev, next in @@ -176,7 +176,7 @@ public final class OpenGroupAPI: NSObject { // TODO: Handle a `412` response (ie. a required capability isn't supported) return send(request, using: dependencies) - .decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } // MARK: - Room @@ -188,7 +188,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [Room].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [Room].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func room(for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Room)> { @@ -198,7 +198,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: Room.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: Room.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func roomPollInfo(lastUpdated: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> { @@ -208,7 +208,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } // MARK: - Messages @@ -221,13 +221,13 @@ public final class OpenGroupAPI: NSObject { whisperMods: Bool, using dependencies: Dependencies = Dependencies() ) -> Promise<(OnionRequestResponseInfoType, Message)> { - guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, to: roomToken, on: server, using: dependencies) else { + guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else { return Promise(error: Error.signingFailed) } let requestBody: SendMessageRequest = SendMessageRequest( - data: signedMessage.data, - signature: signedMessage.signature, + data: plaintext, + signature: Data(signResult.signature), whisperTo: whisperTo, whisperMods: whisperMods, fileIds: nil // TODO: Add support for 'fileIds'. @@ -245,7 +245,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func message(_ id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Message)> { @@ -255,7 +255,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func messageUpdate( @@ -265,13 +265,13 @@ public final class OpenGroupAPI: NSObject { on server: String, using dependencies: Dependencies = Dependencies() ) -> Promise<(OnionRequestResponseInfoType, Data?)> { - guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, to: roomToken, on: server, using: dependencies) else { + guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else { return Promise(error: Error.signingFailed) } let requestBody: UpdateMessageRequest = UpdateMessageRequest( - data: signedMessage.data, - signature: signedMessage.signature + data: plaintext, + signature: Data(signResult.signature) ) guard let body: Data = try? JSONEncoder().encode(requestBody) else { @@ -302,7 +302,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } /// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()` @@ -319,7 +319,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } /// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()` @@ -335,7 +335,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } // MARK: - Pinning @@ -385,7 +385,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } /// Warning: This approach is less efficient as it expects the data to be base64Encoded (with is 33% larger than binary), please use the binary approach @@ -400,7 +400,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func downloadFile(_ fileId: Int64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data)> { @@ -424,7 +424,7 @@ public final class OpenGroupAPI: NSObject { ) // TODO: This endpoint is getting rewritten to return just data (properties would come through as headers). return send(request, using: dependencies) - .decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } // MARK: - Inbox (Message Requests) @@ -436,7 +436,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func messageRequestsSince(id: Int64, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage])> { @@ -446,7 +446,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } public static func sendMessageRequest(_ plaintext: Data, to blindedSessionId: String, on server: String, with serverPublicKey: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage])> { @@ -471,7 +471,7 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } // MARK: - Users @@ -581,79 +581,45 @@ public final class OpenGroupAPI: NSObject { ) return send(request, using: dependencies) - .decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed) + .decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies) } // MARK: - Authentication /// Sign a message to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities) - public static func sign(message: Data, to roomToken: String, on serverName: String, using dependencies: Dependencies = Dependencies()) -> (data: Data, signature: Data)? { + public static func sign(_ messageBytes: Bytes, for serverName: String, using dependencies: Dependencies = Dependencies()) -> (publicKey: String, signature: Bytes)? { + guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil } + guard let serverPublicKey: String = dependencies.storage.getOpenGroupPublicKey(for: serverName) else { + return nil + } + let server: Server? = dependencies.storage.getOpenGroupServer(name: serverName) - let targetKeyPair: ECKeyPair - // Determine if we want to sign using standard or blinded keys based on the server capabilities (assume - // unblinded if we have none) - // TODO: Remove this (blinding will be required) + // Check if the server supports blinded keys, if so then sign using the blinded key if server?.capabilities.capabilities.contains(.blinding) == true { - // TODO: Validate this 'openGroupId' is correct for the 'getOpenGroup' call - let openGroupId: String = "\(serverName).\(roomToken)" - - // TODO: Validate this is the correct logic (Most likely not) - guard let openGroup: OpenGroup = Storage.shared.getOpenGroup(for: openGroupId) else { return nil } - guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil } - guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { + guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { return nil } - - targetKeyPair = blindedKeyPair - } - else { - guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else { return nil } - - targetKeyPair = userKeyPair + + guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else { + return nil + } + + return ( + publicKey: IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey), + signature: signatureResult + ) } - guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else { - SNLog("Failed to sign open group message.") + // Otherwise fall back to sign using the unblinded key + guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else { return nil } - return (message, signature) - } - - /// Sign a blinded message request to be sent to a users inbox via SOGS v4 - private static func sign(message: Data, to blindedSessionId: String, on serverName: String, with serverPublicKey: String, using dependencies: Dependencies = Dependencies()) -> Data? { - guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil } - guard let blindedKeyPair: BlindedECKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { - return nil - } - guard let blindedRecipientPublicKey: Data = String(blindedSessionId.suffix(from: blindedSessionId.index(blindedSessionId.startIndex, offsetBy: IdPrefix.blinded.rawValue.count))).dataFromHex() else { - return nil - } - - /// Generate the sharedSecret by "a kB || kA || kB" where - /// a, A are the users private and public keys respectively, - /// kA is the users blinded public key - /// kB is the recipients blinded public key - let maybeSharedSecret: Data? = dependencies.sodium - .sharedEdSecret(userEdKeyPair.secretKey, blindedRecipientPublicKey.bytes)? - .appending(blindedKeyPair.publicKey.bytes) - .appending(blindedRecipientPublicKey.bytes) - - guard let sharedSecret: Data = maybeSharedSecret else { return nil } - guard let intermediateHash: Bytes = dependencies.genericHash.hash(message: sharedSecret.bytes) else { return nil } - - /// Generate the inner message by "message || A" where - /// A is the sender's ed25519 master pubkey (**not** kA blinded pubkey) - let innerMessage: Bytes = (message.bytes + userEdKeyPair.publicKey) - guard let (ciphertext, nonce) = dependencies.aeadXChaCha20Poly1305Ietf.encrypt(message: innerMessage, secretKey: intermediateHash) else { - return nil - } - - /// Generate the final data by "b'\x00' + ciphertext + nonce" - let finalData: Bytes = [0] + ciphertext + nonce - - return Data(finalData) + return ( + publicKey: IdPrefix.unblinded.hexEncodedPublicKey(for: userEdKeyPair.publicKey), + signature: signatureResult + ) } /// Sign a request to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities) @@ -666,13 +632,9 @@ public final class OpenGroupAPI: NSObject { let method: String = (request.httpMethod ?? "GET") let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970)) let nonce: Data = Data(dependencies.nonceGenerator.nonce()) - let server: Server? = dependencies.storage.getOpenGroupServer(name: serverName) - let userPublicKeyHex: String - let signatureBytes: Bytes guard let serverPublicKeyData: Data = serverPublicKey.dataFromHex() else { return nil } guard let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes else { return nil } - guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil } /// Get a hash of any body content let bodyHash: Bytes? = { @@ -693,51 +655,24 @@ public final class OpenGroupAPI: NSObject { /// `Method` /// `Path` /// `Body` is a Blake2b hash of the data (if there is a body) - let signatureMessageBytes: Bytes = serverPublicKeyData.bytes + let messageBytes: Bytes = serverPublicKeyData.bytes .appending(nonce.bytes) .appending(timestampBytes) .appending(method.bytes) .appending(path.bytes) .appending(bodyHash ?? []) - // Determine if we want to sign using standard or blinded keys based on the server capabilities (assume - // unblinded if we have none) - // TODO: Remove this (blinding will be required) - if server?.capabilities.capabilities.contains(.blinding) == true { - // TODO: More testing of this blinded id signing (though it seems to be working!!!) - guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { - return nil - } - - userPublicKeyHex = IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey) - - guard let signatureResult: Bytes = Sodium().sogsSignature(message: signatureMessageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else { - return nil - } - - signatureBytes = signatureResult - } - else { - userPublicKeyHex = IdPrefix.unblinded.hexEncodedPublicKey(for: userEdKeyPair.publicKey) - - // TODO: shift this to dependencies - guard let signatureResult: Bytes = Sodium().sign.signature(message: signatureMessageBytes, secretKey: userEdKeyPair.secretKey) else { - return nil - } - - signatureBytes = signatureResult + /// Sign the above message + guard let signResult: (publicKey: String, signature: Bytes) = sign(messageBytes, for: serverName, using: dependencies) else { + return nil } - print("RAWR X-SOGS-Pubkey: \(userPublicKeyHex)") - print("RAWR X-SOGS-Timestamp: \(timestamp)") - print("RAWR X-SOGS-Nonce: \(nonce.base64EncodedString())") - print("RAWR X-SOGS-Signature: \(signatureBytes.toBase64())") updatedRequest.allHTTPHeaderFields = (request.allHTTPHeaderFields ?? [:]) .updated(with: [ - Header.sogsPubKey.rawValue: userPublicKeyHex, + Header.sogsPubKey.rawValue: signResult.publicKey, Header.sogsTimestamp.rawValue: "\(timestamp)", Header.sogsNonce.rawValue: nonce.base64EncodedString(), - Header.sogsSignature.rawValue: signatureBytes.toBase64() + Header.sogsSignature.rawValue: signResult.signature.toBase64() ]) return updatedRequest @@ -756,7 +691,7 @@ public final class OpenGroupAPI: NSObject { urlRequest.httpBody = request.body if request.useOnionRouting { - guard let publicKey = SNMessagingKitConfiguration.shared.storage.getOpenGroupPublicKey(for: request.server) else { + guard let publicKey = dependencies.storage.getOpenGroupPublicKey(for: request.server) else { return Promise(error: Error.noPublicKey) } diff --git a/SessionMessagingKit/Open Groups/Types/Dependencies.swift b/SessionMessagingKit/Open Groups/Types/Dependencies.swift index 8b02b68b9..d6872d7f1 100644 --- a/SessionMessagingKit/Open Groups/Types/Dependencies.swift +++ b/SessionMessagingKit/Open Groups/Types/Dependencies.swift @@ -10,16 +10,21 @@ extension OpenGroupAPI { let storage: SessionMessagingKitStorageProtocol let sodium: SodiumType let aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType + let sign: SignType let genericHash: GenericHashType + let ed25519: Ed25519Type.Type let nonceGenerator: NonceGenerator16ByteType let date: Date public init( api: OnionRequestAPIType.Type = OnionRequestAPI.self, storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage, + // TODO: Shift the next 3 to be abstracted behind a single "signing" class? sodium: SodiumType = Sodium(), aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil, + sign: SignType? = nil, genericHash: GenericHashType? = nil, + ed25519: Ed25519Type.Type = Ed25519.self, nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(), date: Date = Date() ) { @@ -27,7 +32,9 @@ extension OpenGroupAPI { self.storage = storage self.sodium = sodium self.aeadXChaCha20Poly1305Ietf = (aeadXChaCha20Poly1305Ietf ?? sodium.getAeadXChaCha20Poly1305Ietf()) + self.sign = (sign ?? sodium.getSign()) self.genericHash = (genericHash ?? sodium.getGenericHash()) + self.ed25519 = ed25519 self.nonceGenerator = nonceGenerator self.date = date } @@ -39,7 +46,9 @@ extension OpenGroupAPI { storage: SessionMessagingKitStorageProtocol? = nil, sodium: SodiumType? = nil, aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil, + sign: SignType? = nil, genericHash: GenericHashType? = nil, + ed25519: Ed25519Type.Type? = nil, nonceGenerator: NonceGenerator16ByteType? = nil, date: Date? = nil ) -> Dependencies { @@ -48,7 +57,9 @@ extension OpenGroupAPI { storage: (storage ?? self.storage), sodium: (sodium ?? self.sodium), aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self.aeadXChaCha20Poly1305Ietf), + sign: (sign ?? self.sign), genericHash: (genericHash ?? self.genericHash), + ed25519: (ed25519 ?? self.ed25519), nonceGenerator: (nonceGenerator ?? self.nonceGenerator), date: (date ?? self.date) ) diff --git a/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift b/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift index 9d525d26e..421b5cff9 100644 --- a/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift +++ b/SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift @@ -2,20 +2,32 @@ import Foundation import Sodium +import Curve25519Kit public protocol SodiumType { func getGenericHash() -> GenericHashType func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType + func getSign() -> SignType func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? - func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? - func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? + func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? + + func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? } public protocol AeadXChaCha20Poly1305IetfType { func encrypt(message: Bytes, secretKey: Aead.XChaCha20Poly1305Ietf.Key, additionalData: Bytes?) -> (authenticatedCipherText: Bytes, nonce: Aead.XChaCha20Poly1305Ietf.Nonce)? } +public protocol Ed25519Type { + static func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool +} + +public protocol SignType { + func signature(message: Bytes, secretKey: Bytes) -> Bytes? + func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool +} + public protocol GenericHashType { func hash(message: Bytes, key: Bytes?) -> Bytes? func hash(message: Bytes, outputLength: Int) -> Bytes? @@ -42,6 +54,7 @@ extension GenericHashType { extension Sodium: SodiumType { public func getGenericHash() -> GenericHashType { return genericHash } + public func getSign() -> SignType { return sign } public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf } public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair) -> Box.KeyPair? { @@ -50,4 +63,6 @@ extension Sodium: SodiumType { } extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {} +extension Sign: SignType {} extension GenericHash: GenericHashType {} +extension Ed25519: Ed25519Type {} diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index f7bfec355..5ca65cee4 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -1,6 +1,7 @@ import PromiseKit import SessionSnodeKit import SessionUtilitiesKit +import Sodium @objc(SNMessageSender) public final class MessageSender : NSObject { @@ -277,22 +278,34 @@ public final class MessageSender : NSObject { // MARK: - Open Groups - internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise { + internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any, dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise { let (promise, seal) = Promise.pending() - let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction // Set the timestamp, sender and recipient if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set - message.sentTimestamp = NSDate.millisecondTimestamp() + message.sentTimestamp = UInt64(dependencies.date.timeIntervalSince1970 * 1000) // Should be in ms } - guard let threadId: String = message.threadID, let openGroup = Storage.shared.getOpenGroup(for: threadId) else { + guard let threadId: String = message.threadID, let openGroup = dependencies.storage.getOpenGroup(for: threadId) else { preconditionFailure() } - // TODO: Check if blinding is enabled on this server? - if let userDerivedKey: ECKeyPair = try? OWSIdentityManager.shared().identityKeyPair()?.convert(to: .blinded, with: openGroup.publicKey) { - message.sender = userDerivedKey.hexEncodedPublicKey + guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { preconditionFailure() } + + let server: OpenGroupAPI.Server? = dependencies.storage.getOpenGroupServer(name: openGroup.server) + + if server?.capabilities.capabilities.contains(.blinding) == true { + guard let serverPublicKey = dependencies.storage.getOpenGroupPublicKey(for: openGroup.server) else { + preconditionFailure() + } + guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { + preconditionFailure() + } + + message.sender = IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey) + } + else { + message.sender = IdPrefix.unblinded.hexEncodedPublicKey(for: userEdKeyPair.publicKey) } switch destination { @@ -332,12 +345,12 @@ public final class MessageSender : NSObject { } // Attach the user's profile - guard let name = storage.getUser()?.name else { + guard let name = dependencies.storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction) return promise } - if let profileKey = storage.getUser()?.profileEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL { + if let profileKey = dependencies.storage.getUser()?.profileEncryptionKey?.keyData, let profilePictureURL = dependencies.storage.getUser()?.profilePictureURL { message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL) } else { @@ -379,15 +392,15 @@ public final class MessageSender : NSObject { .done(on: DispatchQueue.global(qos: .userInitiated)) { responseInfo, data in message.openGroupServerMessageID = given(data.seqNo) { UInt64($0) } - Storage.shared.write { transaction in + dependencies.storage.write { transaction in MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: UInt64(floor(data.posted)), using: transaction) seal.fulfill(()) } } .catch(on: DispatchQueue.global(qos: .userInitiated)) { error in - storage.write(with: { transaction in + dependencies.storage.write { transaction in handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction) - }, completion: { }) + } } return promise diff --git a/SessionMessagingKit/Utilities/Data+Utilities.swift b/SessionMessagingKit/Utilities/Data+Utilities.swift new file mode 100644 index 000000000..38fb839e0 --- /dev/null +++ b/SessionMessagingKit/Utilities/Data+Utilities.swift @@ -0,0 +1,23 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +// MARK: - Decoding + +extension OpenGroupAPI.Dependencies { + static let userInfoKey: CodingUserInfoKey = CodingUserInfoKey(rawValue: "io.oxen.dependencies.codingOptions")! +} + +public extension Data { + func decoded(as type: T.Type, customError: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> T { + do { + let decoder: JSONDecoder = JSONDecoder() + decoder.userInfo = [ OpenGroupAPI.Dependencies.userInfoKey: dependencies ] + + return try decoder.decode(type, from: self) + } + catch let error { + throw (customError ?? error) + } + } +} diff --git a/SessionMessagingKit/Utilities/ECKeyPair+Conversion.swift b/SessionMessagingKit/Utilities/ECKeyPair+Conversion.swift deleted file mode 100644 index f420b5164..000000000 --- a/SessionMessagingKit/Utilities/ECKeyPair+Conversion.swift +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation -import Curve25519Kit -import SessionUtilitiesKit -import Sodium - -public extension ECKeyPair { - func convert(to targetPrefix: IdPrefix, with otherKey: String, using sodium: Sodium = Sodium()) throws -> ECKeyPair? { - guard let publicKeyPrefix: IdPrefix = IdPrefix(with: hexEncodedPublicKey) else { return nil } - - switch (publicKeyPrefix, targetPrefix) { - case (.standard, .blinded): // Only support standard -> blinded conversions - // TODO: Figure out why this is broken... -// guard let otherPubKeyData: Data = otherKey.data(using: .utf8) else { return nil } - guard let otherPubKeyData: Data = otherKey.dataFromHex() else { return nil } - guard let otherPubKeyHashBytes: Bytes = sodium.genericHash.hash(message: [UInt8](otherPubKeyData)) else { - return nil - } - guard let blindedPublicKey: Sodium.SharedSecret = sodium.sharedSecret(otherPubKeyHashBytes, [UInt8](publicKey)) else { - return nil - } - guard let blindedPrivateKey: Sodium.SharedSecret = sodium.sharedSecret(otherPubKeyHashBytes, [UInt8](privateKey)) else { - return nil - } - - return try BlindedECKeyPair(publicKeyData: blindedPublicKey, privateKeyData: blindedPrivateKey) - - case (.standard, .standard): return self - case (.blinded, .blinded): return self - default: return nil - } - } -} diff --git a/SessionMessagingKit/Utilities/Promise+Utilities.swift b/SessionMessagingKit/Utilities/Promise+Utilities.swift index b59ebdbc2..91d25c937 100644 --- a/SessionMessagingKit/Utilities/Promise+Utilities.swift +++ b/SessionMessagingKit/Utilities/Promise+Utilities.swift @@ -5,21 +5,21 @@ import PromiseKit import SessionSnodeKit extension Promise where T == Data { - func decoded(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil) -> Promise { + func decoded(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise { self.map(on: queue) { data -> R in - try data.decoded(as: type, customError: error) + try data.decoded(as: type, customError: error, using: dependencies) } } } extension Promise where T == (OnionRequestResponseInfoType, Data?) { - func decoded(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil) -> Promise<(OnionRequestResponseInfoType, R)> { + func decoded(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<(OnionRequestResponseInfoType, R)> { self.map(on: queue) { responseInfo, maybeData -> (OnionRequestResponseInfoType, R) in guard let data: Data = maybeData else { throw OpenGroupAPI.Error.parsingFailed } - return (responseInfo, try data.decoded(as: type, customError: error)) + return (responseInfo, try data.decoded(as: type, customError: error, using: dependencies)) } } } diff --git a/SessionMessagingKit/Utilities/Sodium+Utilities.swift b/SessionMessagingKit/Utilities/Sodium+Utilities.swift index 244d66236..8a2c8f67c 100644 --- a/SessionMessagingKit/Utilities/Sodium+Utilities.swift +++ b/SessionMessagingKit/Utilities/Sodium+Utilities.swift @@ -41,14 +41,13 @@ extension Sign { } extension Sodium { - public typealias SharedSecret = Data - private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32 private static let noClampLength: Int = Int(crypto_scalarmult_ed25519_bytes()) // 32 private static let scalarMultLength: Int = Int(crypto_scalarmult_bytes()) // 32 private static let publicKeyLength: Int = Int(crypto_scalarmult_bytes()) // 32 private static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64 + /// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair` public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else { return nil @@ -171,7 +170,8 @@ extension Sodium { return (Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes + Data(bytes: sig_sPtr, count: Sodium.scalarLength).bytes) } - public func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> SharedSecret? { + // TODO: Determine if we still need this? (To generate the `kB` value for the `/inbox` API????) + public func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { let sharedSecretPtr: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: Sodium.noClampLength) let result = secondKeyBytes.withUnsafeBytes { (secondKeyPtr: UnsafeRawBufferPointer) -> Int32 in return firstKeyBytes.withUnsafeBytes { (firstKeyPtr: UnsafeRawBufferPointer) -> Int32 in @@ -188,33 +188,7 @@ extension Sodium { guard result == 0 else { return nil } - return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength) - } - - public func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> SharedSecret? { - guard firstKeyBytes.count == Sodium.publicKeyLength && secondKeyBytes.count == Sodium.publicKeyLength else { - return nil - } - - let sharedSecretPtr: UnsafeMutablePointer = UnsafeMutablePointer.allocate(capacity: Sodium.scalarMultLength) - let result = secondKeyBytes.withUnsafeBytes { (secondKeyPtr: UnsafeRawBufferPointer) -> Int32 in - return firstKeyBytes.withUnsafeBytes { (firstKeyPtr: UnsafeRawBufferPointer) -> Int32 in - guard let firstKeyBaseAddress: UnsafePointer = firstKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { - return -1 - } - guard let secondKeyBaseAddress: UnsafePointer = secondKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else { - return -1 - } - - //crypto_sign_ed25519_publickeybytes - //crypto_scalarmult_curve25519(<#T##q: UnsafeMutablePointer##UnsafeMutablePointer#>, <#T##n: UnsafePointer##UnsafePointer#>, <#T##p: UnsafePointer##UnsafePointer#>) - return crypto_scalarmult(sharedSecretPtr, firstKeyBaseAddress, secondKeyBaseAddress) - } - } - - guard result == 0 else { return nil } - - return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength) + return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength).bytes } } diff --git a/SessionMessagingKitTests/Open Groups/OpenGroupAPIV2Tests.swift b/SessionMessagingKitTests/Open Groups/OpenGroupAPIV2Tests.swift index 1e8254ec1..e8506c11f 100644 --- a/SessionMessagingKitTests/Open Groups/OpenGroupAPIV2Tests.swift +++ b/SessionMessagingKitTests/Open Groups/OpenGroupAPIV2Tests.swift @@ -67,15 +67,28 @@ class OpenGroupAPITests: XCTestCase { } var testStorage: TestStorage! + var testSodium: TestSodium! + var testAeadXChaCha20Poly1305Ietf: TestAeadXChaCha20Poly1305Ietf! + var testGenericHash: TestGenericHash! + var testSign: TestSign! var dependencies: OpenGroupAPI.Dependencies! // MARK: - Configuration override func setUpWithError() throws { testStorage = TestStorage() + testSodium = TestSodium() + testAeadXChaCha20Poly1305Ietf = TestAeadXChaCha20Poly1305Ietf() + testGenericHash = TestGenericHash() + testSign = TestSign() dependencies = OpenGroupAPI.Dependencies( api: TestApi.self, storage: testStorage, + sodium: testSodium, + aeadXChaCha20Poly1305Ietf: testAeadXChaCha20Poly1305Ietf, + sign: testSign, + genericHash: testGenericHash, + ed25519: TestEd25519.self, nonceGenerator: TestNonceGenerator(), date: Date(timeIntervalSince1970: 1234567890) ) @@ -100,6 +113,18 @@ class OpenGroupAPITests: XCTestCase { publicKeyData: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!, privateKeyData: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")! ) + testStorage.mockData[.userEdKeyPair] = Box.KeyPair( + publicKey: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!.bytes, + secretKey: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!.bytes + ) + + testGenericHash.mockData[.hashOutputLength] = [] + testSodium.mockData[.blindedKeyPair] = Box.KeyPair( + publicKey: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!.bytes, + secretKey: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!.bytes + ) + testSodium.mockData[.sogsSignature] = "TestSogsSignature".bytes + testSign.mockData[.signature] = "TestSignature".bytes } override func tearDownWithError() throws { @@ -415,12 +440,16 @@ class OpenGroupAPITests: XCTestCase { // MARK: - Authentication - func testItSignsTheRequestCorrectly() throws { + func testItSignsTheUnblindedRequestCorrectly() throws { class LocalTestApi: TestApi { override class var mockResponse: Data? { return try! JSONEncoder().encode([OpenGroupAPI.Room]()) } } + testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server( + name: "testServer", + capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: []) + ) dependencies = dependencies.with(api: LocalTestApi.self) var response: (OnionRequestResponseInfoType, [OpenGroupAPI.Room])? = nil @@ -445,14 +474,74 @@ class OpenGroupAPITests: XCTestCase { expect(requestData?.server).to(equal("testServer")) expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")) expect(requestData?.headers).to(haveCount(4)) - expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("057aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")) + expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("007aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")) expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890")) expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) - expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("fxqLy5ZDWCsLQpwLw0Dax+4xe7cG2vPRk1NlHORIm0DPd3o9UA24KLZY")) + expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("TestSignature".bytes.toBase64())) + } + + func testItSignsTheBlindedRequestCorrectly() throws { + class LocalTestApi: TestApi { + override class var mockResponse: Data? { + return try! JSONEncoder().encode([OpenGroupAPI.Room]()) + } + } + testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server( + name: "testServer", + capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: []) + ) + dependencies = dependencies.with(api: LocalTestApi.self) + + var response: (OnionRequestResponseInfoType, [OpenGroupAPI.Room])? = nil + var error: Error? = nil + + OpenGroupAPI.rooms(for: "testServer", using: dependencies) + .get { result in response = result } + .catch { requestError in error = requestError } + .retainUntilComplete() + + expect(response) + .toEventuallyNot( + beNil(), + timeout: .milliseconds(100) + ) + expect(error?.localizedDescription).to(beNil()) + + // Validate signature headers + let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData + expect(requestData?.urlString).to(equal("testServer/rooms")) + expect(requestData?.httpMethod).to(equal("GET")) + expect(requestData?.server).to(equal("testServer")) + expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")) + expect(requestData?.headers).to(haveCount(4)) + expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("157aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")) + expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890")) + expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) + expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("TestSogsSignature".bytes.toBase64())) + } + + func testItFailsToSignIfThereIsNoUserEdKeyPair() throws { + testStorage.mockData[.userEdKeyPair] = nil + + var response: Any? = nil + var error: Error? = nil + + OpenGroupAPI.rooms(for: "testServer", using: dependencies) + .get { result in response = result } + .catch { requestError in error = requestError } + .retainUntilComplete() + + expect(error?.localizedDescription) + .toEventually( + equal(OpenGroupAPI.Error.signingFailed.localizedDescription), + timeout: .milliseconds(100) + ) + + expect(response).to(beNil()) } func testItFailsToSignIfTheServerPublicKeyIsInvalid() throws { - testStorage.mockData[.openGroupPublicKeys] = ["testServer": ""] + testStorage.mockData[.openGroupPublicKeys] = [:] var response: Any? = nil var error: Error? = nil @@ -464,44 +553,32 @@ class OpenGroupAPITests: XCTestCase { expect(error?.localizedDescription) .toEventually( - equal(OpenGroupAPI.Error.signingFailed.localizedDescription), + equal(OpenGroupAPI.Error.noPublicKey.localizedDescription), timeout: .milliseconds(100) ) expect(response).to(beNil()) } - func testItFailsToSignIfThereIsNoUserKeyPair() throws { - testStorage.mockData[.userKeyPair] = nil - - var response: Any? = nil - var error: Error? = nil - - OpenGroupAPI.rooms(for: "testServer", using: dependencies) - .get { result in response = result } - .catch { requestError in error = requestError } - .retainUntilComplete() - - expect(error?.localizedDescription) - .toEventually( - equal(OpenGroupAPI.Error.signingFailed.localizedDescription), - timeout: .milliseconds(100) - ) - - expect(response).to(beNil()) - } - - func testItFailsToSignIfTheSharedSecretDoesNotGetGenerated() throws { + func testItFailsToSignIfBlindedAndTheBlindedKeyDoesNotGetGenerated() throws { class InvalidSodium: SodiumType { func getGenericHash() -> GenericHashType { return Sodium().genericHash } - func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? { return nil } - func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? { return nil } func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return Sodium().aead.xchacha20poly1305ietf } + func getSign() -> SignType { return Sodium().sign } + func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { return nil } + func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? { + return nil + } + + func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { return nil } } - + testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server( + name: "testServer", + capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: []) + ) dependencies = dependencies.with(sodium: InvalidSodium()) var response: Any? = nil @@ -521,15 +598,29 @@ class OpenGroupAPITests: XCTestCase { expect(response).to(beNil()) } - func testItFailsToSignIfTheIntermediateHashDoesNotGetGenerated() throws { - class InvalidGenericHash: GenericHashType { - func hash(message: Bytes, key: Bytes?) -> Bytes? { return nil } - func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? { + func testItFailsToSignIfBlindedAndTheSogsSignatureDoesNotGetGenerated() throws { + class InvalidSodium: SodiumType { + func getGenericHash() -> GenericHashType { return Sodium().genericHash } + func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return Sodium().aead.xchacha20poly1305ietf } + func getSign() -> SignType { return Sodium().sign } + + func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { + return Box.KeyPair( + publicKey: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!.bytes, + secretKey: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!.bytes + ) + } + func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? { return nil } + + func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { return nil } } - - dependencies = dependencies.with(genericHash: InvalidGenericHash()) + testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server( + name: "testServer", + capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: []) + ) + dependencies = dependencies.with(sodium: InvalidSodium()) var response: Any? = nil var error: Error? = nil @@ -548,22 +639,16 @@ class OpenGroupAPITests: XCTestCase { expect(response).to(beNil()) } - func testItFailsToSignIfTheSecretHashDoesNotGetGenerated() throws { - class InvalidSecondGenericHash: GenericHashType { - static var didSucceedOnce: Bool = false - - func hash(message: Bytes, key: Bytes?) -> Bytes? { return nil } - func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? { - if !InvalidSecondGenericHash.didSucceedOnce { - InvalidSecondGenericHash.didSucceedOnce = true - return Data().bytes - } - - return nil - } + func testItFailsToSignIfUnblindedAndTheSignatureDoesNotGetGenerated() throws { + class InvalidSign: SignType { + func signature(message: Bytes, secretKey: Bytes) -> Bytes? { return nil } + func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool { return false } } - - dependencies = dependencies.with(genericHash: InvalidSecondGenericHash()) + testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server( + name: "testServer", + capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: []) + ) + dependencies = dependencies.with(sign: InvalidSign()) var response: Any? = nil var error: Error? = nil diff --git a/SessionMessagingKitTests/_TestUtilities/Mockable.swift b/SessionMessagingKitTests/_TestUtilities/Mockable.swift index b903f0fa3..6c438d881 100644 --- a/SessionMessagingKitTests/_TestUtilities/Mockable.swift +++ b/SessionMessagingKitTests/_TestUtilities/Mockable.swift @@ -7,3 +7,9 @@ protocol Mockable { var mockData: [Key: Any] { get } } + +protocol StaticMockable { + associatedtype Key: Hashable + + static var mockData: [Key: Any] { get } +} diff --git a/SessionMessagingKitTests/_TestUtilities/TestAeadXChaCha20Poly1305Ietf.swift b/SessionMessagingKitTests/_TestUtilities/TestAeadXChaCha20Poly1305Ietf.swift new file mode 100644 index 000000000..0906b8e25 --- /dev/null +++ b/SessionMessagingKitTests/_TestUtilities/TestAeadXChaCha20Poly1305Ietf.swift @@ -0,0 +1,25 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit +import Sodium + +@testable import SessionMessagingKit + +class TestAeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType, Mockable { + // MARK: - Mockable + + enum DataKey: Hashable { + case encrypt + } + + typealias Key = DataKey + + var mockData: [DataKey: Any] = [:] + + // MARK: - SignType + + func encrypt(message: Bytes, secretKey: Aead.XChaCha20Poly1305Ietf.Key, additionalData: Bytes?) -> (authenticatedCipherText: Bytes, nonce: Aead.XChaCha20Poly1305Ietf.Nonce)? { + return (mockData[.encrypt] as? (authenticatedCipherText: Bytes, nonce: Aead.XChaCha20Poly1305Ietf.Nonce)) + } +} diff --git a/SessionMessagingKitTests/_TestUtilities/TestEd25519.swift b/SessionMessagingKitTests/_TestUtilities/TestEd25519.swift new file mode 100644 index 000000000..43989397b --- /dev/null +++ b/SessionMessagingKitTests/_TestUtilities/TestEd25519.swift @@ -0,0 +1,25 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit +import Sodium + +@testable import SessionMessagingKit + +class TestEd25519: Ed25519Type, StaticMockable { + // MARK: - Mockable + + enum DataKey: Hashable { + case verifySignature(signature: Data, publicKey: Data, data: Data) // TODO: Test the uniqueness of this + } + + typealias Key = DataKey + + static var mockData: [DataKey: Any] = [:] + + // MARK: - SignType + + static func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool { + return (mockData[.verifySignature(signature: signature, publicKey: publicKey, data: data)] as! Bool) + } +} diff --git a/SessionMessagingKitTests/_TestUtilities/TestGenericHash.swift b/SessionMessagingKitTests/_TestUtilities/TestGenericHash.swift new file mode 100644 index 000000000..f6b51cfcd --- /dev/null +++ b/SessionMessagingKitTests/_TestUtilities/TestGenericHash.swift @@ -0,0 +1,35 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit +import Sodium + +@testable import SessionMessagingKit + +class TestGenericHash: GenericHashType, Mockable { + // MARK: - Mockable + + enum DataKey: Hashable { + case hash + case hashOutputLength + case hashSaltPersonal + } + + typealias Key = DataKey + + var mockData: [DataKey: Any] = [:] + + // MARK: - SignType + + func hash(message: Bytes, key: Bytes?) -> Bytes? { + return (mockData[.hash] as? Bytes) + } + + func hash(message: Bytes, outputLength: Int) -> Bytes? { + return (mockData[.hashOutputLength] as? Bytes) + } + + func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? { + return (mockData[.hashSaltPersonal] as? Bytes) + } +} diff --git a/SessionMessagingKitTests/_TestUtilities/TestSign.swift b/SessionMessagingKitTests/_TestUtilities/TestSign.swift new file mode 100644 index 000000000..a193b2a5f --- /dev/null +++ b/SessionMessagingKitTests/_TestUtilities/TestSign.swift @@ -0,0 +1,30 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit +import Sodium + +@testable import SessionMessagingKit + +class TestSign: SignType, Mockable { + // MARK: - Mockable + + enum DataKey: Hashable { + case signature + case verify + } + + typealias Key = DataKey + + var mockData: [DataKey: Any] = [:] + + // MARK: - SignType + + func signature(message: Bytes, secretKey: Bytes) -> Bytes? { + return (mockData[.signature] as? Bytes) + } + + func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool { + return (mockData[.verify] as! Bool) + } +} diff --git a/SessionMessagingKitTests/_TestUtilities/TestSodium.swift b/SessionMessagingKitTests/_TestUtilities/TestSodium.swift new file mode 100644 index 000000000..862cbfc8d --- /dev/null +++ b/SessionMessagingKitTests/_TestUtilities/TestSodium.swift @@ -0,0 +1,46 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import PromiseKit +import Sodium + +@testable import SessionMessagingKit + +class TestSodium: SodiumType, Mockable { + // MARK: - Mockable + + enum DataKey: Hashable { + case genericHash + case aeadXChaCha20Poly1305Ietf + case sign + case blindedKeyPair + case sogsSignature + case sharedEdSecret + } + + typealias Key = DataKey + + var mockData: [DataKey: Any] = [:] + + // MARK: - SodiumType + + func getGenericHash() -> GenericHashType { return (mockData[.genericHash] as! GenericHashType) } + + func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { + return (mockData[.aeadXChaCha20Poly1305Ietf] as! AeadXChaCha20Poly1305IetfType) + } + + func getSign() -> SignType { return (mockData[.sign] as! SignType) } + + func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { + return (mockData[.blindedKeyPair] as? Box.KeyPair) + } + + func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? { + return (mockData[.sogsSignature] as? Bytes) + } + + func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { + return (mockData[.sharedEdSecret] as? Bytes) + } +} diff --git a/SessionMessagingKitTests/_TestUtilities/TestStorage.swift b/SessionMessagingKitTests/_TestUtilities/TestStorage.swift index 28efb7bc0..1128e0f9c 100644 --- a/SessionMessagingKitTests/_TestUtilities/TestStorage.swift +++ b/SessionMessagingKitTests/_TestUtilities/TestStorage.swift @@ -13,6 +13,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable { case allOpenGroups case openGroupPublicKeys case userKeyPair + case userEdKeyPair case openGroup case openGroupServer case openGroupImage @@ -43,7 +44,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable { func getUserPublicKey() -> String? { return nil } func getUserKeyPair() -> ECKeyPair? { return (mockData[.userKeyPair] as? ECKeyPair) } - func getUserED25519KeyPair() -> Box.KeyPair? { return nil } + func getUserED25519KeyPair() -> Box.KeyPair? { return (mockData[.userEdKeyPair] as? Box.KeyPair) } func getUser() -> Contact? { return nil } func getAllContacts() -> Set { return Set() } diff --git a/SessionUtilitiesKit/General/Data+Utilities.swift b/SessionUtilitiesKit/General/Data+Utilities.swift index e0fddf1a3..54502935a 100644 --- a/SessionUtilitiesKit/General/Data+Utilities.swift +++ b/SessionUtilitiesKit/General/Data+Utilities.swift @@ -33,16 +33,3 @@ public extension Data { return result as NSData } } - -// MARK: - Decoding - -public extension Data { - func decoded(as type: T.Type, customError: Error? = nil) throws -> T { - do { - return try JSONDecoder().decode(type, from: self) - } - catch let error { - throw (customError ?? error) - } - } -}