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
This commit is contained in:
Morgan Pretty 2022-02-21 10:01:53 +11:00
parent ef09d4d5aa
commit 1edd500dab
20 changed files with 496 additions and 279 deletions

View file

@ -776,7 +776,6 @@
FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; }; FD5D201127AA331F00FEA984 /* ConfigurationMessage+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */; };
FD5D201E27B0D87C00FEA984 /* IdPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5D201D27B0D87C00FEA984 /* IdPrefix.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 */; }; 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 */; }; FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */; };
FD705A8C278CDB5600F16121 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.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 */; }; 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 */; }; FD705A92278D051200F16121 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A91278D051200F16121 /* ReusableView.swift */; };
FD705A94278D052B00F16121 /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD705A93278D052B00F16121 /* UITableView+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 */; }; 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 */; }; FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */; };
FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; }; FD88BADB27A750F200BBC442 /* MessageRequestsMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */; };
FDC4380927B31D4E00C60D73 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* Error.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 = "<group>"; }; FD5D201027AA331F00FEA984 /* ConfigurationMessage+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfigurationMessage+Convenience.swift"; sourceTree = "<group>"; };
FD5D201D27B0D87C00FEA984 /* IdPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdPrefix.swift; sourceTree = "<group>"; }; FD5D201D27B0D87C00FEA984 /* IdPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdPrefix.swift; sourceTree = "<group>"; };
FD5D201F27B0E67800FEA984 /* String+Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Encoding.swift"; sourceTree = "<group>"; }; FD5D201F27B0E67800FEA984 /* String+Encoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Encoding.swift"; sourceTree = "<group>"; };
FD5D202127B1D74F00FEA984 /* ECKeyPair+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ECKeyPair+Conversion.swift"; sourceTree = "<group>"; };
FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; }; FD659ABF27A7649600F12C02 /* MessageRequestsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewController.swift; sourceTree = "<group>"; };
FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = "<group>"; }; FD705A8B278CDB5600F16121 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = "<group>"; };
FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; }; FD705A8D278CE29800F16121 /* String+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
@ -1920,6 +1924,14 @@
FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = "<group>"; }; FD705A91278D051200F16121 /* ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = "<group>"; };
FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; }; FD705A93278D052B00F16121 /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; }; FD705A97278E9F4D00F16121 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
FD859EF327C2F49200510D0C /* TestSodium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSodium.swift; sourceTree = "<group>"; };
FD859EF527C2F52C00510D0C /* TestSign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSign.swift; sourceTree = "<group>"; };
FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAeadXChaCha20Poly1305Ietf.swift; sourceTree = "<group>"; };
FD859EF927C2F5C500510D0C /* TestGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGenericHash.swift; sourceTree = "<group>"; };
FD859EFB27C2F60700510D0C /* TestEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestEd25519.swift; sourceTree = "<group>"; };
FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; }; FD88BAD827A7439C00BBC442 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; }; FD88BADA27A750F200BBC442 /* MessageRequestsMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestsMigration.swift; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
@ -3384,6 +3396,7 @@
C33FDB01255A580700E217F9 /* AppReadiness.h */, C33FDB01255A580700E217F9 /* AppReadiness.h */,
C33FDB75255A581000E217F9 /* AppReadiness.m */, C33FDB75255A581000E217F9 /* AppReadiness.m */,
FDC4383D27B4708600C60D73 /* Atomic.swift */, FDC4383D27B4708600C60D73 /* Atomic.swift */,
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */,
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */, C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
C37F53E8255BA9BB002AEA92 /* Environment.h */, C37F53E8255BA9BB002AEA92 /* Environment.h */,
C37F5402255BA9ED002AEA92 /* Environment.m */, C37F5402255BA9ED002AEA92 /* Environment.m */,
@ -3423,7 +3436,6 @@
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
C3E7134E251C867C009649BB /* Sodium+Utilities.swift */, C3E7134E251C867C009649BB /* Sodium+Utilities.swift */,
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */, FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
FD5D202127B1D74F00FEA984 /* ECKeyPair+Conversion.swift */,
FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */, FDC4387327B5BB9B00C60D73 /* Promise+Utilities.swift */,
C33FDB31255A580A00E217F9 /* SSKEnvironment.h */, C33FDB31255A580A00E217F9 /* SSKEnvironment.h */,
C33FDAF4255A580600E217F9 /* SSKEnvironment.m */, C33FDAF4255A580600E217F9 /* SSKEnvironment.m */,
@ -3539,6 +3551,8 @@
C3C2A7802553AA6300C340D1 /* Protos */ = { C3C2A7802553AA6300C340D1 /* Protos */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FD859EEF27BF207700510D0C /* SessionProtos.proto */,
FD859EF027BF207C00510D0C /* WebSocketResources.proto */,
C3C2A7812553AA9000C340D1 /* Generated */, C3C2A7812553AA9000C340D1 /* Generated */,
); );
path = Protos; path = Protos;
@ -3938,6 +3952,11 @@
children = ( children = (
FDC438BC27BB2AB400C60D73 /* Mockable.swift */, FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
FDC4389C27BA01F000C60D73 /* TestStorage.swift */, FDC4389C27BA01F000C60D73 /* TestStorage.swift */,
FD859EF327C2F49200510D0C /* TestSodium.swift */,
FD859EF527C2F52C00510D0C /* TestSign.swift */,
FD859EF727C2F58900510D0C /* TestAeadXChaCha20Poly1305Ietf.swift */,
FD859EF927C2F5C500510D0C /* TestGenericHash.swift */,
FD859EFB27C2F60700510D0C /* TestEd25519.swift */,
); );
path = _TestUtilities; path = _TestUtilities;
sourceTree = "<group>"; sourceTree = "<group>";
@ -5110,6 +5129,7 @@
C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */, C32C5A13256DB7A5003C73A2 /* PushNotificationAPI.swift in Sources */,
FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */, FDC4385F27B4C4A200C60D73 /* PinnedMessage.swift in Sources */,
C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */, C32A026325A801AA000ED5D4 /* NSData+messagePadding.m in Sources */,
FD859EF227BF6BA200510D0C /* Data+Utilities.swift in Sources */,
FDC4384927B47F4D00C60D73 /* LegacyCompactPollResponse.swift in Sources */, FDC4384927B47F4D00C60D73 /* LegacyCompactPollResponse.swift in Sources */,
C352A3932557883D00338F3E /* JobDelegate.swift in Sources */, C352A3932557883D00338F3E /* JobDelegate.swift in Sources */,
C32C5B84256DC54F003C73A2 /* SSKEnvironment.m in Sources */, C32C5B84256DC54F003C73A2 /* SSKEnvironment.m in Sources */,
@ -5168,7 +5188,6 @@
FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */, FDC4382827B37FD300C60D73 /* LegacyModeratorsResponse.swift in Sources */,
C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */, C32C5B3F256DC1DF003C73A2 /* TSQuotedMessage+Conversion.swift in Sources */,
B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */, B8EB20EE2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift in Sources */,
FD5D202227B1D74F00FEA984 /* ECKeyPair+Conversion.swift in Sources */,
FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */, FDC4381C27B354AC00C60D73 /* LegacyPublicKeyBody.swift in Sources */,
FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */, FDC4382F27B383AF00C60D73 /* UnregisterResponse.swift in Sources */,
FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */, FDC4386327B4D94E00C60D73 /* OGMessage.swift in Sources */,
@ -5463,8 +5482,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( 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 */, FDC4389A27BA002500C60D73 /* OpenGroupAPIV2Tests.swift in Sources */,
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */, FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
FD859EF627C2F52C00510D0C /* TestSign.swift in Sources */,
FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */, FDC4389D27BA01F000C60D73 /* TestStorage.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View file

@ -62,13 +62,13 @@ extension OpenGroupAPI {
// MARK: - Convenience // MARK: - Convenience
public extension Decodable { public extension Decodable {
static func decoded(from data: Data) throws -> Self { static func decoded(from data: Data, customError: Error, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) throws -> Self {
return try JSONDecoder().decode(Self.self, from: data) return try data.decoded(as: Self.self, customError: customError, using: dependencies)
} }
} }
extension Promise where T == (OnionRequestResponseInfoType, Data?) { extension Promise where T == (OnionRequestResponseInfoType, Data?) {
func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error) -> Promise<OpenGroupAPI.BatchResponse> { func decoded(as types: OpenGroupAPI.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<OpenGroupAPI.BatchResponse> {
self.map(on: queue) { responseInfo, maybeData -> OpenGroupAPI.BatchResponse in 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 // 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 } guard let data: Data = maybeData else { throw OpenGroupAPI.Error.parsingFailed }
@ -82,7 +82,7 @@ extension Promise where T == (OnionRequestResponseInfoType, Data?) {
do { do {
return try zip(dataArray, types) 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) } .map { data in (responseInfo, data) }
} }
catch _ { catch _ {

View file

@ -47,14 +47,30 @@ extension OpenGroupAPI.Message {
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else { guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
throw OpenGroupAPI.Error.parsingFailed throw OpenGroupAPI.Error.parsingFailed
} }
guard let dependencies: OpenGroupAPI.Dependencies = decoder.userInfo[OpenGroupAPI.Dependencies.userInfoKey] as? OpenGroupAPI.Dependencies else {
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 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( self = OpenGroupAPI.Message(

View file

@ -125,7 +125,7 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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 .map { result in
result.enumerated() result.enumerated()
.reduce(into: [:]) { prev, next in .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) // TODO: Handle a `412` response (ie. a required capability isn't supported)
return send(request, using: dependencies) 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 .map { result in
result.enumerated() result.enumerated()
.reduce(into: [:]) { prev, next in .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) // TODO: Handle a `412` response (ie. a required capability isn't supported)
return send(request, using: dependencies) 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 // MARK: - Room
@ -188,7 +188,7 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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)> { 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) 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)> { 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) 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 // MARK: - Messages
@ -221,13 +221,13 @@ public final class OpenGroupAPI: NSObject {
whisperMods: Bool, whisperMods: Bool,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Message)> { ) -> 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) return Promise(error: Error.signingFailed)
} }
let requestBody: SendMessageRequest = SendMessageRequest( let requestBody: SendMessageRequest = SendMessageRequest(
data: signedMessage.data, data: plaintext,
signature: signedMessage.signature, signature: Data(signResult.signature),
whisperTo: whisperTo, whisperTo: whisperTo,
whisperMods: whisperMods, whisperMods: whisperMods,
fileIds: nil // TODO: Add support for 'fileIds'. fileIds: nil // TODO: Add support for 'fileIds'.
@ -245,7 +245,7 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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)> { 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) 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( public static func messageUpdate(
@ -265,13 +265,13 @@ public final class OpenGroupAPI: NSObject {
on server: String, on server: String,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Data?)> { ) -> 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) return Promise(error: Error.signingFailed)
} }
let requestBody: UpdateMessageRequest = UpdateMessageRequest( let requestBody: UpdateMessageRequest = UpdateMessageRequest(
data: signedMessage.data, data: plaintext,
signature: signedMessage.signature signature: Data(signResult.signature)
) )
guard let body: Data = try? JSONEncoder().encode(requestBody) else { guard let body: Data = try? JSONEncoder().encode(requestBody) else {
@ -302,7 +302,7 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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()` /// 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) 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()` /// 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) 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 // MARK: - Pinning
@ -385,7 +385,7 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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 /// 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) 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)> { 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). // TODO: This endpoint is getting rewritten to return just data (properties would come through as headers).
return send(request, using: dependencies) 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) // MARK: - Inbox (Message Requests)
@ -436,7 +436,7 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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])> { 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) 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])> { 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) 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 // MARK: - Users
@ -581,79 +581,45 @@ public final class OpenGroupAPI: NSObject {
) )
return send(request, using: dependencies) 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 // MARK: - Authentication
/// Sign a message to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities) /// 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 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 // Check if the server supports blinded keys, if so then sign using the blinded key
// unblinded if we have none)
// TODO: Remove this (blinding will be required)
if server?.capabilities.capabilities.contains(.blinding) == true { if server?.capabilities.capabilities.contains(.blinding) == true {
// TODO: Validate this 'openGroupId' is correct for the 'getOpenGroup' call guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
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 {
return nil return nil
} }
targetKeyPair = blindedKeyPair guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else {
} return nil
else { }
guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else { return nil }
return (
targetKeyPair = userKeyPair publicKey: IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey),
signature: signatureResult
)
} }
guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else { // Otherwise fall back to sign using the unblinded key
SNLog("Failed to sign open group message.") guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
return nil return nil
} }
return (message, signature) return (
} publicKey: IdPrefix.unblinded.hexEncodedPublicKey(for: userEdKeyPair.publicKey),
signature: signatureResult
/// 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)
} }
/// Sign a request to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities) /// 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 method: String = (request.httpMethod ?? "GET")
let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970)) let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970))
let nonce: Data = Data(dependencies.nonceGenerator.nonce()) 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 serverPublicKeyData: Data = serverPublicKey.dataFromHex() else { return nil }
guard let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes 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 /// Get a hash of any body content
let bodyHash: Bytes? = { let bodyHash: Bytes? = {
@ -693,51 +655,24 @@ public final class OpenGroupAPI: NSObject {
/// `Method` /// `Method`
/// `Path` /// `Path`
/// `Body` is a Blake2b hash of the data (if there is a body) /// `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(nonce.bytes)
.appending(timestampBytes) .appending(timestampBytes)
.appending(method.bytes) .appending(method.bytes)
.appending(path.bytes) .appending(path.bytes)
.appending(bodyHash ?? []) .appending(bodyHash ?? [])
// Determine if we want to sign using standard or blinded keys based on the server capabilities (assume /// Sign the above message
// unblinded if we have none) guard let signResult: (publicKey: String, signature: Bytes) = sign(messageBytes, for: serverName, using: dependencies) else {
// TODO: Remove this (blinding will be required) return nil
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
} }
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 ?? [:]) updatedRequest.allHTTPHeaderFields = (request.allHTTPHeaderFields ?? [:])
.updated(with: [ .updated(with: [
Header.sogsPubKey.rawValue: userPublicKeyHex, Header.sogsPubKey.rawValue: signResult.publicKey,
Header.sogsTimestamp.rawValue: "\(timestamp)", Header.sogsTimestamp.rawValue: "\(timestamp)",
Header.sogsNonce.rawValue: nonce.base64EncodedString(), Header.sogsNonce.rawValue: nonce.base64EncodedString(),
Header.sogsSignature.rawValue: signatureBytes.toBase64() Header.sogsSignature.rawValue: signResult.signature.toBase64()
]) ])
return updatedRequest return updatedRequest
@ -756,7 +691,7 @@ public final class OpenGroupAPI: NSObject {
urlRequest.httpBody = request.body urlRequest.httpBody = request.body
if request.useOnionRouting { 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) return Promise(error: Error.noPublicKey)
} }

View file

@ -10,16 +10,21 @@ extension OpenGroupAPI {
let storage: SessionMessagingKitStorageProtocol let storage: SessionMessagingKitStorageProtocol
let sodium: SodiumType let sodium: SodiumType
let aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType let aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType
let sign: SignType
let genericHash: GenericHashType let genericHash: GenericHashType
let ed25519: Ed25519Type.Type
let nonceGenerator: NonceGenerator16ByteType let nonceGenerator: NonceGenerator16ByteType
let date: Date let date: Date
public init( public init(
api: OnionRequestAPIType.Type = OnionRequestAPI.self, api: OnionRequestAPIType.Type = OnionRequestAPI.self,
storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage, storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage,
// TODO: Shift the next 3 to be abstracted behind a single "signing" class?
sodium: SodiumType = Sodium(), sodium: SodiumType = Sodium(),
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil, aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
sign: SignType? = nil,
genericHash: GenericHashType? = nil, genericHash: GenericHashType? = nil,
ed25519: Ed25519Type.Type = Ed25519.self,
nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(), nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(),
date: Date = Date() date: Date = Date()
) { ) {
@ -27,7 +32,9 @@ extension OpenGroupAPI {
self.storage = storage self.storage = storage
self.sodium = sodium self.sodium = sodium
self.aeadXChaCha20Poly1305Ietf = (aeadXChaCha20Poly1305Ietf ?? sodium.getAeadXChaCha20Poly1305Ietf()) self.aeadXChaCha20Poly1305Ietf = (aeadXChaCha20Poly1305Ietf ?? sodium.getAeadXChaCha20Poly1305Ietf())
self.sign = (sign ?? sodium.getSign())
self.genericHash = (genericHash ?? sodium.getGenericHash()) self.genericHash = (genericHash ?? sodium.getGenericHash())
self.ed25519 = ed25519
self.nonceGenerator = nonceGenerator self.nonceGenerator = nonceGenerator
self.date = date self.date = date
} }
@ -39,7 +46,9 @@ extension OpenGroupAPI {
storage: SessionMessagingKitStorageProtocol? = nil, storage: SessionMessagingKitStorageProtocol? = nil,
sodium: SodiumType? = nil, sodium: SodiumType? = nil,
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil, aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
sign: SignType? = nil,
genericHash: GenericHashType? = nil, genericHash: GenericHashType? = nil,
ed25519: Ed25519Type.Type? = nil,
nonceGenerator: NonceGenerator16ByteType? = nil, nonceGenerator: NonceGenerator16ByteType? = nil,
date: Date? = nil date: Date? = nil
) -> Dependencies { ) -> Dependencies {
@ -48,7 +57,9 @@ extension OpenGroupAPI {
storage: (storage ?? self.storage), storage: (storage ?? self.storage),
sodium: (sodium ?? self.sodium), sodium: (sodium ?? self.sodium),
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self.aeadXChaCha20Poly1305Ietf), aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self.aeadXChaCha20Poly1305Ietf),
sign: (sign ?? self.sign),
genericHash: (genericHash ?? self.genericHash), genericHash: (genericHash ?? self.genericHash),
ed25519: (ed25519 ?? self.ed25519),
nonceGenerator: (nonceGenerator ?? self.nonceGenerator), nonceGenerator: (nonceGenerator ?? self.nonceGenerator),
date: (date ?? self.date) date: (date ?? self.date)
) )

View file

@ -2,20 +2,32 @@
import Foundation import Foundation
import Sodium import Sodium
import Curve25519Kit
public protocol SodiumType { public protocol SodiumType {
func getGenericHash() -> GenericHashType func getGenericHash() -> GenericHashType
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType
func getSign() -> SignType
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair?
func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes?
func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret?
func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes?
} }
public protocol AeadXChaCha20Poly1305IetfType { public protocol AeadXChaCha20Poly1305IetfType {
func encrypt(message: Bytes, secretKey: Aead.XChaCha20Poly1305Ietf.Key, additionalData: Bytes?) -> (authenticatedCipherText: Bytes, nonce: Aead.XChaCha20Poly1305Ietf.Nonce)? 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 { public protocol GenericHashType {
func hash(message: Bytes, key: Bytes?) -> Bytes? func hash(message: Bytes, key: Bytes?) -> Bytes?
func hash(message: Bytes, outputLength: Int) -> Bytes? func hash(message: Bytes, outputLength: Int) -> Bytes?
@ -42,6 +54,7 @@ extension GenericHashType {
extension Sodium: SodiumType { extension Sodium: SodiumType {
public func getGenericHash() -> GenericHashType { return genericHash } public func getGenericHash() -> GenericHashType { return genericHash }
public func getSign() -> SignType { return sign }
public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf } public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf }
public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair) -> Box.KeyPair? { public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair) -> Box.KeyPair? {
@ -50,4 +63,6 @@ extension Sodium: SodiumType {
} }
extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {} extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {}
extension Sign: SignType {}
extension GenericHash: GenericHashType {} extension GenericHash: GenericHashType {}
extension Ed25519: Ed25519Type {}

View file

@ -1,6 +1,7 @@
import PromiseKit import PromiseKit
import SessionSnodeKit import SessionSnodeKit
import SessionUtilitiesKit import SessionUtilitiesKit
import Sodium
@objc(SNMessageSender) @objc(SNMessageSender)
public final class MessageSender : NSObject { public final class MessageSender : NSObject {
@ -277,22 +278,34 @@ public final class MessageSender : NSObject {
// MARK: - Open Groups // MARK: - Open Groups
internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise<Void> { internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any, dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<Void> {
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
let storage = SNMessagingKitConfiguration.shared.storage
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
// Set the timestamp, sender and recipient // Set the timestamp, sender and recipient
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set 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() preconditionFailure()
} }
// TODO: Check if blinding is enabled on this server? guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { preconditionFailure() }
if let userDerivedKey: ECKeyPair = try? OWSIdentityManager.shared().identityKeyPair()?.convert(to: .blinded, with: openGroup.publicKey) {
message.sender = userDerivedKey.hexEncodedPublicKey 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 { switch destination {
@ -332,12 +345,12 @@ public final class MessageSender : NSObject {
} }
// Attach the user's profile // 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) handleFailure(with: Error.noUsername, using: transaction)
return promise 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) message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL)
} }
else { else {
@ -379,15 +392,15 @@ public final class MessageSender : NSObject {
.done(on: DispatchQueue.global(qos: .userInitiated)) { responseInfo, data in .done(on: DispatchQueue.global(qos: .userInitiated)) { responseInfo, data in
message.openGroupServerMessageID = given(data.seqNo) { UInt64($0) } 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) MessageSender.handleSuccessfulMessageSend(message, to: destination, serverTimestamp: UInt64(floor(data.posted)), using: transaction)
seal.fulfill(()) seal.fulfill(())
} }
} }
.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in .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) handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction)
}, completion: { }) }
} }
return promise return promise

View file

@ -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<T: Decodable>(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)
}
}
}

View file

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

View file

@ -5,21 +5,21 @@ import PromiseKit
import SessionSnodeKit import SessionSnodeKit
extension Promise where T == Data { extension Promise where T == Data {
func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil) -> Promise<R> { func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil, using dependencies: OpenGroupAPI.Dependencies = OpenGroupAPI.Dependencies()) -> Promise<R> {
self.map(on: queue) { data -> R in 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?) { extension Promise where T == (OnionRequestResponseInfoType, Data?) {
func decoded<R: Decodable>(as type: R.Type, on queue: DispatchQueue? = nil, error: Error? = nil) -> Promise<(OnionRequestResponseInfoType, R)> { func decoded<R: Decodable>(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 self.map(on: queue) { responseInfo, maybeData -> (OnionRequestResponseInfoType, R) in
guard let data: Data = maybeData else { guard let data: Data = maybeData else {
throw OpenGroupAPI.Error.parsingFailed throw OpenGroupAPI.Error.parsingFailed
} }
return (responseInfo, try data.decoded(as: type, customError: error)) return (responseInfo, try data.decoded(as: type, customError: error, using: dependencies))
} }
} }
} }

View file

@ -41,14 +41,13 @@ extension Sign {
} }
extension Sodium { extension Sodium {
public typealias SharedSecret = Data
private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32 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 noClampLength: Int = Int(crypto_scalarmult_ed25519_bytes()) // 32
private static let scalarMultLength: Int = Int(crypto_scalarmult_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 publicKeyLength: Int = Int(crypto_scalarmult_bytes()) // 32
private static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64 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? { public func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? {
guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else { guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else {
return nil 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) 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<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength) let sharedSecretPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength)
let result = secondKeyBytes.withUnsafeBytes { (secondKeyPtr: UnsafeRawBufferPointer) -> Int32 in let result = secondKeyBytes.withUnsafeBytes { (secondKeyPtr: UnsafeRawBufferPointer) -> Int32 in
return firstKeyBytes.withUnsafeBytes { (firstKeyPtr: UnsafeRawBufferPointer) -> Int32 in return firstKeyBytes.withUnsafeBytes { (firstKeyPtr: UnsafeRawBufferPointer) -> Int32 in
@ -188,33 +188,7 @@ extension Sodium {
guard result == 0 else { return nil } guard result == 0 else { return nil }
return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength) return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength).bytes
}
public func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> SharedSecret? {
guard firstKeyBytes.count == Sodium.publicKeyLength && secondKeyBytes.count == Sodium.publicKeyLength else {
return nil
}
let sharedSecretPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarMultLength)
let result = secondKeyBytes.withUnsafeBytes { (secondKeyPtr: UnsafeRawBufferPointer) -> Int32 in
return firstKeyBytes.withUnsafeBytes { (firstKeyPtr: UnsafeRawBufferPointer) -> Int32 in
guard let firstKeyBaseAddress: UnsafePointer<UInt8> = firstKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
}
guard let secondKeyBaseAddress: UnsafePointer<UInt8> = secondKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return -1
}
//crypto_sign_ed25519_publickeybytes
//crypto_scalarmult_curve25519(<#T##q: UnsafeMutablePointer<UInt8>##UnsafeMutablePointer<UInt8>#>, <#T##n: UnsafePointer<UInt8>##UnsafePointer<UInt8>#>, <#T##p: UnsafePointer<UInt8>##UnsafePointer<UInt8>#>)
return crypto_scalarmult(sharedSecretPtr, firstKeyBaseAddress, secondKeyBaseAddress)
}
}
guard result == 0 else { return nil }
return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength)
} }
} }

View file

@ -67,15 +67,28 @@ class OpenGroupAPITests: XCTestCase {
} }
var testStorage: TestStorage! var testStorage: TestStorage!
var testSodium: TestSodium!
var testAeadXChaCha20Poly1305Ietf: TestAeadXChaCha20Poly1305Ietf!
var testGenericHash: TestGenericHash!
var testSign: TestSign!
var dependencies: OpenGroupAPI.Dependencies! var dependencies: OpenGroupAPI.Dependencies!
// MARK: - Configuration // MARK: - Configuration
override func setUpWithError() throws { override func setUpWithError() throws {
testStorage = TestStorage() testStorage = TestStorage()
testSodium = TestSodium()
testAeadXChaCha20Poly1305Ietf = TestAeadXChaCha20Poly1305Ietf()
testGenericHash = TestGenericHash()
testSign = TestSign()
dependencies = OpenGroupAPI.Dependencies( dependencies = OpenGroupAPI.Dependencies(
api: TestApi.self, api: TestApi.self,
storage: testStorage, storage: testStorage,
sodium: testSodium,
aeadXChaCha20Poly1305Ietf: testAeadXChaCha20Poly1305Ietf,
sign: testSign,
genericHash: testGenericHash,
ed25519: TestEd25519.self,
nonceGenerator: TestNonceGenerator(), nonceGenerator: TestNonceGenerator(),
date: Date(timeIntervalSince1970: 1234567890) date: Date(timeIntervalSince1970: 1234567890)
) )
@ -100,6 +113,18 @@ class OpenGroupAPITests: XCTestCase {
publicKeyData: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!, publicKeyData: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!,
privateKeyData: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")! 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 { override func tearDownWithError() throws {
@ -415,12 +440,16 @@ class OpenGroupAPITests: XCTestCase {
// MARK: - Authentication // MARK: - Authentication
func testItSignsTheRequestCorrectly() throws { func testItSignsTheUnblindedRequestCorrectly() throws {
class LocalTestApi: TestApi { class LocalTestApi: TestApi {
override class var mockResponse: Data? { override class var mockResponse: Data? {
return try! JSONEncoder().encode([OpenGroupAPI.Room]()) 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) dependencies = dependencies.with(api: LocalTestApi.self)
var response: (OnionRequestResponseInfoType, [OpenGroupAPI.Room])? = nil var response: (OnionRequestResponseInfoType, [OpenGroupAPI.Room])? = nil
@ -445,14 +474,74 @@ class OpenGroupAPITests: XCTestCase {
expect(requestData?.server).to(equal("testServer")) expect(requestData?.server).to(equal("testServer"))
expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")) expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
expect(requestData?.headers).to(haveCount(4)) 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.sogsTimestamp.rawValue]).to(equal("1234567890"))
expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg==")) 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 { func testItFailsToSignIfTheServerPublicKeyIsInvalid() throws {
testStorage.mockData[.openGroupPublicKeys] = ["testServer": ""] testStorage.mockData[.openGroupPublicKeys] = [:]
var response: Any? = nil var response: Any? = nil
var error: Error? = nil var error: Error? = nil
@ -464,44 +553,32 @@ class OpenGroupAPITests: XCTestCase {
expect(error?.localizedDescription) expect(error?.localizedDescription)
.toEventually( .toEventually(
equal(OpenGroupAPI.Error.signingFailed.localizedDescription), equal(OpenGroupAPI.Error.noPublicKey.localizedDescription),
timeout: .milliseconds(100) timeout: .milliseconds(100)
) )
expect(response).to(beNil()) expect(response).to(beNil())
} }
func testItFailsToSignIfThereIsNoUserKeyPair() throws { func testItFailsToSignIfBlindedAndTheBlindedKeyDoesNotGetGenerated() 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 {
class InvalidSodium: SodiumType { class InvalidSodium: SodiumType {
func getGenericHash() -> GenericHashType { return Sodium().genericHash } 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 getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return Sodium().aead.xchacha20poly1305ietf }
func getSign() -> SignType { return Sodium().sign }
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? { func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? {
return nil 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()) dependencies = dependencies.with(sodium: InvalidSodium())
var response: Any? = nil var response: Any? = nil
@ -521,15 +598,29 @@ class OpenGroupAPITests: XCTestCase {
expect(response).to(beNil()) expect(response).to(beNil())
} }
func testItFailsToSignIfTheIntermediateHashDoesNotGetGenerated() throws { func testItFailsToSignIfBlindedAndTheSogsSignatureDoesNotGetGenerated() throws {
class InvalidGenericHash: GenericHashType { class InvalidSodium: SodiumType {
func hash(message: Bytes, key: Bytes?) -> Bytes? { return nil } func getGenericHash() -> GenericHashType { return Sodium().genericHash }
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? { 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 return nil
} }
func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { return nil }
} }
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
dependencies = dependencies.with(genericHash: InvalidGenericHash()) name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: [])
)
dependencies = dependencies.with(sodium: InvalidSodium())
var response: Any? = nil var response: Any? = nil
var error: Error? = nil var error: Error? = nil
@ -548,22 +639,16 @@ class OpenGroupAPITests: XCTestCase {
expect(response).to(beNil()) expect(response).to(beNil())
} }
func testItFailsToSignIfTheSecretHashDoesNotGetGenerated() throws { func testItFailsToSignIfUnblindedAndTheSignatureDoesNotGetGenerated() throws {
class InvalidSecondGenericHash: GenericHashType { class InvalidSign: SignType {
static var didSucceedOnce: Bool = false func signature(message: Bytes, secretKey: Bytes) -> Bytes? { return nil }
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool { return 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
}
} }
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
dependencies = dependencies.with(genericHash: InvalidSecondGenericHash()) name: "testServer",
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
)
dependencies = dependencies.with(sign: InvalidSign())
var response: Any? = nil var response: Any? = nil
var error: Error? = nil var error: Error? = nil

View file

@ -7,3 +7,9 @@ protocol Mockable {
var mockData: [Key: Any] { get } var mockData: [Key: Any] { get }
} }
protocol StaticMockable {
associatedtype Key: Hashable
static var mockData: [Key: Any] { get }
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
case allOpenGroups case allOpenGroups
case openGroupPublicKeys case openGroupPublicKeys
case userKeyPair case userKeyPair
case userEdKeyPair
case openGroup case openGroup
case openGroupServer case openGroupServer
case openGroupImage case openGroupImage
@ -43,7 +44,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
func getUserPublicKey() -> String? { return nil } func getUserPublicKey() -> String? { return nil }
func getUserKeyPair() -> ECKeyPair? { return (mockData[.userKeyPair] as? ECKeyPair) } 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 getUser() -> Contact? { return nil }
func getAllContacts() -> Set<Contact> { return Set() } func getAllContacts() -> Set<Contact> { return Set() }

View file

@ -33,16 +33,3 @@ public extension Data {
return result as NSData return result as NSData
} }
} }
// MARK: - Decoding
public extension Data {
func decoded<T: Decodable>(as type: T.Type, customError: Error? = nil) throws -> T {
do {
return try JSONDecoder().decode(type, from: self)
}
catch let error {
throw (customError ?? error)
}
}
}