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,13 +47,29 @@ extension OpenGroupAPI.Message { guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else { throw OpenGroupAPI.Error.parsingFailed } + 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()) - let isValid: Bool = ((try? Ed25519.verifySignature(signature, publicKey: publicKey, data: data)) ?? false) - guard isValid else { - SNLog("Ignoring message with invalid signature.") - throw OpenGroupAPI.Error.parsingFailed + 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 } } 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)" + guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else { + return nil + } - // 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 signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else { return nil } - - targetKeyPair = blindedKeyPair - } - else { - guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else { return nil } - - targetKeyPair = userKeyPair - } - - guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else { - SNLog("Failed to sign open group message.") - 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 + + return ( + publicKey: IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey), + signature: signatureResult + ) } - /// 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 { + // Otherwise fall back to sign using the unblinded key + guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) 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,54 @@ 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 testItFailsToSignIfTheServerPublicKeyIsInvalid() throws { - testStorage.mockData[.openGroupPublicKeys] = ["testServer": ""] + 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 @@ -471,8 +540,8 @@ class OpenGroupAPITests: XCTestCase { expect(response).to(beNil()) } - func testItFailsToSignIfThereIsNoUserKeyPair() throws { - testStorage.mockData[.userKeyPair] = nil + func testItFailsToSignIfTheServerPublicKeyIsInvalid() throws { + testStorage.mockData[.openGroupPublicKeys] = [:] var response: Any? = nil var error: Error? = nil @@ -484,24 +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 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) - } - } -}