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:
parent
ef09d4d5aa
commit
1edd500dab
|
@ -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 = "<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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1920,6 +1924,14 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -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 = "<group>";
|
||||
|
@ -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;
|
||||
|
|
|
@ -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<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
|
||||
// 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 _ {
|
||||
|
|
|
@ -47,14 +47,30 @@ extension OpenGroupAPI.Message {
|
|||
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
let publicKey: Data = Data(hex: sender.removingIdPrefixIfNeeded())
|
||||
let isValid: Bool = ((try? Ed25519.verifySignature(signature, publicKey: publicKey, data: data)) ?? false)
|
||||
|
||||
guard isValid else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
guard let dependencies: OpenGroupAPI.Dependencies = decoder.userInfo[OpenGroupAPI.Dependencies.userInfoKey] as? OpenGroupAPI.Dependencies else {
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
// Verify the signature based on the IdPrefix
|
||||
let publicKey: Data = Data(hex: sender.removingIdPrefixIfNeeded())
|
||||
|
||||
switch IdPrefix(with: sender) {
|
||||
case .blinded:
|
||||
guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
case .standard, .unblinded:
|
||||
guard (try? dependencies.ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
|
||||
case .none:
|
||||
SNLog("Ignoring message with invalid sender.")
|
||||
throw OpenGroupAPI.Error.parsingFailed
|
||||
}
|
||||
}
|
||||
|
||||
self = OpenGroupAPI.Message(
|
||||
|
|
|
@ -125,7 +125,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.map { result in
|
||||
result.enumerated()
|
||||
.reduce(into: [:]) { prev, next in
|
||||
|
@ -156,7 +156,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// TODO: Handle a `412` response (ie. a required capability isn't supported)
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: responseTypes, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
.map { result in
|
||||
result.enumerated()
|
||||
.reduce(into: [:]) { prev, next in
|
||||
|
@ -176,7 +176,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
|
||||
// TODO: Handle a `412` response (ie. a required capability isn't supported)
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Room
|
||||
|
@ -188,7 +188,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Room].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [Room].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func room(for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Room)> {
|
||||
|
@ -198,7 +198,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Room.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: Room.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func roomPollInfo(lastUpdated: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> {
|
||||
|
@ -208,7 +208,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: RoomPollInfo.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Messages
|
||||
|
@ -221,13 +221,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
whisperMods: Bool,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, to: roomToken, on: server, using: dependencies) else {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
let requestBody: SendMessageRequest = SendMessageRequest(
|
||||
data: signedMessage.data,
|
||||
signature: signedMessage.signature,
|
||||
data: plaintext,
|
||||
signature: Data(signResult.signature),
|
||||
whisperTo: whisperTo,
|
||||
whisperMods: whisperMods,
|
||||
fileIds: nil // TODO: Add support for 'fileIds'.
|
||||
|
@ -245,7 +245,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func message(_ id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Message)> {
|
||||
|
@ -255,7 +255,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: Message.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func messageUpdate(
|
||||
|
@ -265,13 +265,13 @@ public final class OpenGroupAPI: NSObject {
|
|||
on server: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
|
||||
guard let signedMessage: (data: Data, signature: Data) = sign(message: plaintext, to: roomToken, on: server, using: dependencies) else {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(plaintext.bytes, for: server, using: dependencies) else {
|
||||
return Promise(error: Error.signingFailed)
|
||||
}
|
||||
|
||||
let requestBody: UpdateMessageRequest = UpdateMessageRequest(
|
||||
data: signedMessage.data,
|
||||
signature: signedMessage.signature
|
||||
data: plaintext,
|
||||
signature: Data(signResult.signature)
|
||||
)
|
||||
|
||||
guard let body: Data = try? JSONEncoder().encode(requestBody) else {
|
||||
|
@ -302,7 +302,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
|
||||
|
@ -319,7 +319,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
/// This is the direct request to retrieve recent messages from an Open Group so should be retrieved automatically from the `poll()`
|
||||
|
@ -335,7 +335,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [Message].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Pinning
|
||||
|
@ -385,7 +385,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
/// Warning: This approach is less efficient as it expects the data to be base64Encoded (with is 33% larger than binary), please use the binary approach
|
||||
|
@ -400,7 +400,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: FileUploadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func downloadFile(_ fileId: Int64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data)> {
|
||||
|
@ -424,7 +424,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
// TODO: This endpoint is getting rewritten to return just data (properties would come through as headers).
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: FileDownloadResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Inbox (Message Requests)
|
||||
|
@ -436,7 +436,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func messageRequestsSince(id: Int64, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage])> {
|
||||
|
@ -446,7 +446,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
public static func sendMessageRequest(_ plaintext: Data, to blindedSessionId: String, on server: String, with serverPublicKey: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [DirectMessage])> {
|
||||
|
@ -471,7 +471,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: [DirectMessage].self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Users
|
||||
|
@ -581,79 +581,45 @@ public final class OpenGroupAPI: NSObject {
|
|||
)
|
||||
|
||||
return send(request, using: dependencies)
|
||||
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed)
|
||||
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPI.workQueue, error: Error.parsingFailed, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Authentication
|
||||
|
||||
/// Sign a message to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities)
|
||||
public static func sign(message: Data, to roomToken: String, on serverName: String, using dependencies: Dependencies = Dependencies()) -> (data: Data, signature: Data)? {
|
||||
public static func sign(_ messageBytes: Bytes, for serverName: String, using dependencies: Dependencies = Dependencies()) -> (publicKey: String, signature: Bytes)? {
|
||||
guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil }
|
||||
guard let serverPublicKey: String = dependencies.storage.getOpenGroupPublicKey(for: serverName) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let server: Server? = dependencies.storage.getOpenGroupServer(name: serverName)
|
||||
let targetKeyPair: ECKeyPair
|
||||
|
||||
// Determine if we want to sign using standard or blinded keys based on the server capabilities (assume
|
||||
// unblinded if we have none)
|
||||
// TODO: Remove this (blinding will be required)
|
||||
// Check if the server supports blinded keys, if so then sign using the blinded key
|
||||
if server?.capabilities.capabilities.contains(.blinding) == true {
|
||||
// TODO: Validate this 'openGroupId' is correct for the 'getOpenGroup' call
|
||||
let openGroupId: String = "\(serverName).\(roomToken)"
|
||||
|
||||
// TODO: Validate this is the correct logic (Most likely not)
|
||||
guard let openGroup: OpenGroup = Storage.shared.getOpenGroup(for: openGroupId) else { return nil }
|
||||
guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil }
|
||||
guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
targetKeyPair = blindedKeyPair
|
||||
}
|
||||
else {
|
||||
guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else { return nil }
|
||||
|
||||
targetKeyPair = userKeyPair
|
||||
|
||||
guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (
|
||||
publicKey: IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey),
|
||||
signature: signatureResult
|
||||
)
|
||||
}
|
||||
|
||||
guard let signature = try? Ed25519.sign(message, with: targetKeyPair) else {
|
||||
SNLog("Failed to sign open group message.")
|
||||
// Otherwise fall back to sign using the unblinded key
|
||||
guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (message, signature)
|
||||
}
|
||||
|
||||
/// Sign a blinded message request to be sent to a users inbox via SOGS v4
|
||||
private static func sign(message: Data, to blindedSessionId: String, on serverName: String, with serverPublicKey: String, using dependencies: Dependencies = Dependencies()) -> Data? {
|
||||
guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil }
|
||||
guard let blindedKeyPair: BlindedECKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
return nil
|
||||
}
|
||||
guard let blindedRecipientPublicKey: Data = String(blindedSessionId.suffix(from: blindedSessionId.index(blindedSessionId.startIndex, offsetBy: IdPrefix.blinded.rawValue.count))).dataFromHex() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Generate the sharedSecret by "a kB || kA || kB" where
|
||||
/// a, A are the users private and public keys respectively,
|
||||
/// kA is the users blinded public key
|
||||
/// kB is the recipients blinded public key
|
||||
let maybeSharedSecret: Data? = dependencies.sodium
|
||||
.sharedEdSecret(userEdKeyPair.secretKey, blindedRecipientPublicKey.bytes)?
|
||||
.appending(blindedKeyPair.publicKey.bytes)
|
||||
.appending(blindedRecipientPublicKey.bytes)
|
||||
|
||||
guard let sharedSecret: Data = maybeSharedSecret else { return nil }
|
||||
guard let intermediateHash: Bytes = dependencies.genericHash.hash(message: sharedSecret.bytes) else { return nil }
|
||||
|
||||
/// Generate the inner message by "message || A" where
|
||||
/// A is the sender's ed25519 master pubkey (**not** kA blinded pubkey)
|
||||
let innerMessage: Bytes = (message.bytes + userEdKeyPair.publicKey)
|
||||
guard let (ciphertext, nonce) = dependencies.aeadXChaCha20Poly1305Ietf.encrypt(message: innerMessage, secretKey: intermediateHash) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Generate the final data by "b'\x00' + ciphertext + nonce"
|
||||
let finalData: Bytes = [0] + ciphertext + nonce
|
||||
|
||||
return Data(finalData)
|
||||
return (
|
||||
publicKey: IdPrefix.unblinded.hexEncodedPublicKey(for: userEdKeyPair.publicKey),
|
||||
signature: signatureResult
|
||||
)
|
||||
}
|
||||
|
||||
/// Sign a request to be sent to SOGS (handles both un-blinded and blinded signing based on the server capabilities)
|
||||
|
@ -666,13 +632,9 @@ public final class OpenGroupAPI: NSObject {
|
|||
let method: String = (request.httpMethod ?? "GET")
|
||||
let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970))
|
||||
let nonce: Data = Data(dependencies.nonceGenerator.nonce())
|
||||
let server: Server? = dependencies.storage.getOpenGroupServer(name: serverName)
|
||||
let userPublicKeyHex: String
|
||||
let signatureBytes: Bytes
|
||||
|
||||
guard let serverPublicKeyData: Data = serverPublicKey.dataFromHex() else { return nil }
|
||||
guard let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes else { return nil }
|
||||
guard let userEdKeyPair: Box.KeyPair = dependencies.storage.getUserED25519KeyPair() else { return nil }
|
||||
|
||||
/// Get a hash of any body content
|
||||
let bodyHash: Bytes? = {
|
||||
|
@ -693,51 +655,24 @@ public final class OpenGroupAPI: NSObject {
|
|||
/// `Method`
|
||||
/// `Path`
|
||||
/// `Body` is a Blake2b hash of the data (if there is a body)
|
||||
let signatureMessageBytes: Bytes = serverPublicKeyData.bytes
|
||||
let messageBytes: Bytes = serverPublicKeyData.bytes
|
||||
.appending(nonce.bytes)
|
||||
.appending(timestampBytes)
|
||||
.appending(method.bytes)
|
||||
.appending(path.bytes)
|
||||
.appending(bodyHash ?? [])
|
||||
|
||||
// Determine if we want to sign using standard or blinded keys based on the server capabilities (assume
|
||||
// unblinded if we have none)
|
||||
// TODO: Remove this (blinding will be required)
|
||||
if server?.capabilities.capabilities.contains(.blinding) == true {
|
||||
// TODO: More testing of this blinded id signing (though it seems to be working!!!)
|
||||
guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
userPublicKeyHex = IdPrefix.blinded.hexEncodedPublicKey(for: blindedKeyPair.publicKey)
|
||||
|
||||
guard let signatureResult: Bytes = Sodium().sogsSignature(message: signatureMessageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
signatureBytes = signatureResult
|
||||
}
|
||||
else {
|
||||
userPublicKeyHex = IdPrefix.unblinded.hexEncodedPublicKey(for: userEdKeyPair.publicKey)
|
||||
|
||||
// TODO: shift this to dependencies
|
||||
guard let signatureResult: Bytes = Sodium().sign.signature(message: signatureMessageBytes, secretKey: userEdKeyPair.secretKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
signatureBytes = signatureResult
|
||||
/// Sign the above message
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(messageBytes, for: serverName, using: dependencies) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
print("RAWR X-SOGS-Pubkey: \(userPublicKeyHex)")
|
||||
print("RAWR X-SOGS-Timestamp: \(timestamp)")
|
||||
print("RAWR X-SOGS-Nonce: \(nonce.base64EncodedString())")
|
||||
print("RAWR X-SOGS-Signature: \(signatureBytes.toBase64())")
|
||||
updatedRequest.allHTTPHeaderFields = (request.allHTTPHeaderFields ?? [:])
|
||||
.updated(with: [
|
||||
Header.sogsPubKey.rawValue: userPublicKeyHex,
|
||||
Header.sogsPubKey.rawValue: signResult.publicKey,
|
||||
Header.sogsTimestamp.rawValue: "\(timestamp)",
|
||||
Header.sogsNonce.rawValue: nonce.base64EncodedString(),
|
||||
Header.sogsSignature.rawValue: signatureBytes.toBase64()
|
||||
Header.sogsSignature.rawValue: signResult.signature.toBase64()
|
||||
])
|
||||
|
||||
return updatedRequest
|
||||
|
@ -756,7 +691,7 @@ public final class OpenGroupAPI: NSObject {
|
|||
urlRequest.httpBody = request.body
|
||||
|
||||
if request.useOnionRouting {
|
||||
guard let publicKey = SNMessagingKitConfiguration.shared.storage.getOpenGroupPublicKey(for: request.server) else {
|
||||
guard let publicKey = dependencies.storage.getOpenGroupPublicKey(for: request.server) else {
|
||||
return Promise(error: Error.noPublicKey)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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<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 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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,21 +5,21 @@ import PromiseKit
|
|||
import SessionSnodeKit
|
||||
|
||||
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
|
||||
try data.decoded(as: type, customError: error)
|
||||
try data.decoded(as: type, customError: error, using: dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<UInt8> = UnsafeMutablePointer<UInt8>.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<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)
|
||||
return Data(bytes: sharedSecretPtr, count: Sodium.scalarMultLength).bytes
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,15 +67,28 @@ class OpenGroupAPITests: XCTestCase {
|
|||
}
|
||||
|
||||
var testStorage: TestStorage!
|
||||
var testSodium: TestSodium!
|
||||
var testAeadXChaCha20Poly1305Ietf: TestAeadXChaCha20Poly1305Ietf!
|
||||
var testGenericHash: TestGenericHash!
|
||||
var testSign: TestSign!
|
||||
var dependencies: OpenGroupAPI.Dependencies!
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
override func setUpWithError() throws {
|
||||
testStorage = TestStorage()
|
||||
testSodium = TestSodium()
|
||||
testAeadXChaCha20Poly1305Ietf = TestAeadXChaCha20Poly1305Ietf()
|
||||
testGenericHash = TestGenericHash()
|
||||
testSign = TestSign()
|
||||
dependencies = OpenGroupAPI.Dependencies(
|
||||
api: TestApi.self,
|
||||
storage: testStorage,
|
||||
sodium: testSodium,
|
||||
aeadXChaCha20Poly1305Ietf: testAeadXChaCha20Poly1305Ietf,
|
||||
sign: testSign,
|
||||
genericHash: testGenericHash,
|
||||
ed25519: TestEd25519.self,
|
||||
nonceGenerator: TestNonceGenerator(),
|
||||
date: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
|
@ -100,6 +113,18 @@ class OpenGroupAPITests: XCTestCase {
|
|||
publicKeyData: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!,
|
||||
privateKeyData: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!
|
||||
)
|
||||
testStorage.mockData[.userEdKeyPair] = Box.KeyPair(
|
||||
publicKey: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!.bytes,
|
||||
secretKey: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!.bytes
|
||||
)
|
||||
|
||||
testGenericHash.mockData[.hashOutputLength] = []
|
||||
testSodium.mockData[.blindedKeyPair] = Box.KeyPair(
|
||||
publicKey: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!.bytes,
|
||||
secretKey: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!.bytes
|
||||
)
|
||||
testSodium.mockData[.sogsSignature] = "TestSogsSignature".bytes
|
||||
testSign.mockData[.signature] = "TestSignature".bytes
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
|
@ -415,12 +440,16 @@ class OpenGroupAPITests: XCTestCase {
|
|||
|
||||
// MARK: - Authentication
|
||||
|
||||
func testItSignsTheRequestCorrectly() throws {
|
||||
func testItSignsTheUnblindedRequestCorrectly() throws {
|
||||
class LocalTestApi: TestApi {
|
||||
override class var mockResponse: Data? {
|
||||
return try! JSONEncoder().encode([OpenGroupAPI.Room]())
|
||||
}
|
||||
}
|
||||
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
|
||||
name: "testServer",
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
|
||||
)
|
||||
dependencies = dependencies.with(api: LocalTestApi.self)
|
||||
|
||||
var response: (OnionRequestResponseInfoType, [OpenGroupAPI.Room])? = nil
|
||||
|
@ -445,14 +474,74 @@ class OpenGroupAPITests: XCTestCase {
|
|||
expect(requestData?.server).to(equal("testServer"))
|
||||
expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
|
||||
expect(requestData?.headers).to(haveCount(4))
|
||||
expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("057aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
|
||||
expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("007aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
|
||||
expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890"))
|
||||
expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg=="))
|
||||
expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("fxqLy5ZDWCsLQpwLw0Dax+4xe7cG2vPRk1NlHORIm0DPd3o9UA24KLZY"))
|
||||
expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("TestSignature".bytes.toBase64()))
|
||||
}
|
||||
|
||||
func testItSignsTheBlindedRequestCorrectly() throws {
|
||||
class LocalTestApi: TestApi {
|
||||
override class var mockResponse: Data? {
|
||||
return try! JSONEncoder().encode([OpenGroupAPI.Room]())
|
||||
}
|
||||
}
|
||||
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
|
||||
name: "testServer",
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: [])
|
||||
)
|
||||
dependencies = dependencies.with(api: LocalTestApi.self)
|
||||
|
||||
var response: (OnionRequestResponseInfoType, [OpenGroupAPI.Room])? = nil
|
||||
var error: Error? = nil
|
||||
|
||||
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
|
||||
.get { result in response = result }
|
||||
.catch { requestError in error = requestError }
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(response)
|
||||
.toEventuallyNot(
|
||||
beNil(),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
expect(error?.localizedDescription).to(beNil())
|
||||
|
||||
// Validate signature headers
|
||||
let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData
|
||||
expect(requestData?.urlString).to(equal("testServer/rooms"))
|
||||
expect(requestData?.httpMethod).to(equal("GET"))
|
||||
expect(requestData?.server).to(equal("testServer"))
|
||||
expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
|
||||
expect(requestData?.headers).to(haveCount(4))
|
||||
expect(requestData?.headers[Header.sogsPubKey.rawValue]).to(equal("157aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
|
||||
expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890"))
|
||||
expect(requestData?.headers[Header.sogsNonce.rawValue]).to(equal("pK6YRtQApl4NhECGizF0Cg=="))
|
||||
expect(requestData?.headers[Header.sogsSignature.rawValue]).to(equal("TestSogsSignature".bytes.toBase64()))
|
||||
}
|
||||
|
||||
func testItFailsToSignIfThereIsNoUserEdKeyPair() throws {
|
||||
testStorage.mockData[.userEdKeyPair] = nil
|
||||
|
||||
var response: Any? = nil
|
||||
var error: Error? = nil
|
||||
|
||||
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
|
||||
.get { result in response = result }
|
||||
.catch { requestError in error = requestError }
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.signingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
func testItFailsToSignIfTheServerPublicKeyIsInvalid() throws {
|
||||
testStorage.mockData[.openGroupPublicKeys] = ["testServer": ""]
|
||||
testStorage.mockData[.openGroupPublicKeys] = [:]
|
||||
|
||||
var response: Any? = nil
|
||||
var error: Error? = nil
|
||||
|
@ -464,44 +553,32 @@ class OpenGroupAPITests: XCTestCase {
|
|||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.signingFailed.localizedDescription),
|
||||
equal(OpenGroupAPI.Error.noPublicKey.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
func testItFailsToSignIfThereIsNoUserKeyPair() throws {
|
||||
testStorage.mockData[.userKeyPair] = nil
|
||||
|
||||
var response: Any? = nil
|
||||
var error: Error? = nil
|
||||
|
||||
OpenGroupAPI.rooms(for: "testServer", using: dependencies)
|
||||
.get { result in response = result }
|
||||
.catch { requestError in error = requestError }
|
||||
.retainUntilComplete()
|
||||
|
||||
expect(error?.localizedDescription)
|
||||
.toEventually(
|
||||
equal(OpenGroupAPI.Error.signingFailed.localizedDescription),
|
||||
timeout: .milliseconds(100)
|
||||
)
|
||||
|
||||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
func testItFailsToSignIfTheSharedSecretDoesNotGetGenerated() throws {
|
||||
func testItFailsToSignIfBlindedAndTheBlindedKeyDoesNotGetGenerated() throws {
|
||||
class InvalidSodium: SodiumType {
|
||||
func getGenericHash() -> GenericHashType { return Sodium().genericHash }
|
||||
func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? { return nil }
|
||||
func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? { return nil }
|
||||
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return Sodium().aead.xchacha20poly1305ietf }
|
||||
func getSign() -> SignType { return Sodium().sign }
|
||||
|
||||
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? {
|
||||
return nil
|
||||
}
|
||||
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { return nil }
|
||||
}
|
||||
|
||||
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
|
||||
name: "testServer",
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: [])
|
||||
)
|
||||
dependencies = dependencies.with(sodium: InvalidSodium())
|
||||
|
||||
var response: Any? = nil
|
||||
|
@ -521,15 +598,29 @@ class OpenGroupAPITests: XCTestCase {
|
|||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
func testItFailsToSignIfTheIntermediateHashDoesNotGetGenerated() throws {
|
||||
class InvalidGenericHash: GenericHashType {
|
||||
func hash(message: Bytes, key: Bytes?) -> Bytes? { return nil }
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
|
||||
func testItFailsToSignIfBlindedAndTheSogsSignatureDoesNotGetGenerated() throws {
|
||||
class InvalidSodium: SodiumType {
|
||||
func getGenericHash() -> GenericHashType { return Sodium().genericHash }
|
||||
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return Sodium().aead.xchacha20poly1305ietf }
|
||||
func getSign() -> SignType { return Sodium().sign }
|
||||
|
||||
func blindedKeyPair(serverPublicKey: String, edKeyPair: Box.KeyPair, genericHash: GenericHashType) -> Box.KeyPair? {
|
||||
return Box.KeyPair(
|
||||
publicKey: Data.data(fromHex: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d")!.bytes,
|
||||
secretKey: Data.data(fromHex: "881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4881132ee03dbd2da065aa4c94f96081f62142dc8011d1b7a00de83e4aab38ce4")!.bytes
|
||||
)
|
||||
}
|
||||
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sharedEdSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Bytes? { return nil }
|
||||
}
|
||||
|
||||
dependencies = dependencies.with(genericHash: InvalidGenericHash())
|
||||
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
|
||||
name: "testServer",
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs, .blinding], missing: [])
|
||||
)
|
||||
dependencies = dependencies.with(sodium: InvalidSodium())
|
||||
|
||||
var response: Any? = nil
|
||||
var error: Error? = nil
|
||||
|
@ -548,22 +639,16 @@ class OpenGroupAPITests: XCTestCase {
|
|||
expect(response).to(beNil())
|
||||
}
|
||||
|
||||
func testItFailsToSignIfTheSecretHashDoesNotGetGenerated() throws {
|
||||
class InvalidSecondGenericHash: GenericHashType {
|
||||
static var didSucceedOnce: Bool = false
|
||||
|
||||
func hash(message: Bytes, key: Bytes?) -> Bytes? { return nil }
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
|
||||
if !InvalidSecondGenericHash.didSucceedOnce {
|
||||
InvalidSecondGenericHash.didSucceedOnce = true
|
||||
return Data().bytes
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func testItFailsToSignIfUnblindedAndTheSignatureDoesNotGetGenerated() throws {
|
||||
class InvalidSign: SignType {
|
||||
func signature(message: Bytes, secretKey: Bytes) -> Bytes? { return nil }
|
||||
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool { return false }
|
||||
}
|
||||
|
||||
dependencies = dependencies.with(genericHash: InvalidSecondGenericHash())
|
||||
testStorage.mockData[.openGroupServer] = OpenGroupAPI.Server(
|
||||
name: "testServer",
|
||||
capabilities: OpenGroupAPI.Capabilities(capabilities: [.sogs], missing: [])
|
||||
)
|
||||
dependencies = dependencies.with(sign: InvalidSign())
|
||||
|
||||
var response: Any? = nil
|
||||
var error: Error? = nil
|
||||
|
|
|
@ -7,3 +7,9 @@ protocol Mockable {
|
|||
|
||||
var mockData: [Key: Any] { get }
|
||||
}
|
||||
|
||||
protocol StaticMockable {
|
||||
associatedtype Key: Hashable
|
||||
|
||||
static var mockData: [Key: Any] { get }
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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<Contact> { return Set() }
|
||||
|
||||
|
|
|
@ -33,16 +33,3 @@ public extension Data {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue