You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec...

365 lines
17 KiB
Swift

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Sodium
import SessionUtilitiesKit
import Quick
import Nimble
@testable import SessionMessagingKit
class MessageSenderEncryptionSpec: QuickSpec {
override class func spec() {
// MARK: Configuration
Merge remote-tracking branch 'upstream/dev' into feature/groups-rebuild # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es-ES.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fil.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt-BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sl.lproj/Localizable.strings # Session/Meta/Translations/sv-SE.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi.lproj/Localizable.strings # Session/Meta/Translations/zh-CN.lproj/Localizable.strings # Session/Meta/Translations/zh-TW.lproj/Localizable.strings # SessionMessagingKit/Calls/WebRTCSession.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift # SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift # SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift # SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift # SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift # SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift # SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift # SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift # SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift # SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionTests/Settings/NotificationContentViewModelSpec.swift # SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift # SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift # SessionUtilitiesKitTests/General/DependenciesSpec.swift # SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift # _SharedTestUtilities/MockCaches.swift
2 years ago
@TestState var dependencies: TestDependencies! = TestDependencies()
@TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
migrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
],
Merge remote-tracking branch 'upstream/dev' into feature/groups-rebuild # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es-ES.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fil.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt-BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sl.lproj/Localizable.strings # Session/Meta/Translations/sv-SE.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi.lproj/Localizable.strings # Session/Meta/Translations/zh-CN.lproj/Localizable.strings # Session/Meta/Translations/zh-TW.lproj/Localizable.strings # SessionMessagingKit/Calls/WebRTCSession.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift # SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift # SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift # SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift # SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift # SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift # SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift # SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift # SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift # SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionTests/Settings/NotificationContentViewModelSpec.swift # SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift # SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift # SessionUtilitiesKitTests/General/DependenciesSpec.swift # SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift # _SharedTestUtilities/MockCaches.swift
2 years ago
using: dependencies,
initialData: { db in
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
}
)
Merge remote-tracking branch 'upstream/dev' into feature/groups-rebuild # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es-ES.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fil.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt-BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sl.lproj/Localizable.strings # Session/Meta/Translations/sv-SE.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi.lproj/Localizable.strings # Session/Meta/Translations/zh-CN.lproj/Localizable.strings # Session/Meta/Translations/zh-TW.lproj/Localizable.strings # SessionMessagingKit/Calls/WebRTCSession.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Database/Migrations/_003_YDBToGRDBMigration.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift # SessionMessagingKit/SessionUtil/Config Handling/SessionUtil+Contacts.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionMessagingKitTests/Jobs/Types/MessageSendJobSpec.swift # SessionMessagingKitTests/LibSessionUtil/LibSessionSpec.swift # SessionMessagingKitTests/LibSessionUtil/SessionUtilSpec.swift # SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift # SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/Open Groups/Types/SOGSEndpointSpec.swift # SessionMessagingKitTests/Sending & Receiving/MessageReceiverDecryptionSpec.swift # SessionMessagingKitTests/Sending & Receiving/MessageSenderEncryptionSpec.swift # SessionMessagingKitTests/Shared Models/SessionThreadViewModelSpec.swift # SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionTests/Settings/NotificationContentViewModelSpec.swift # SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift # SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift # SessionUtilitiesKitTests/General/DependenciesSpec.swift # SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift # _SharedTestUtilities/MockCaches.swift
2 years ago
@TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto(
initialSetup: { crypto in
crypto
.when { $0.generate(.nonce24()) }
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
}
)
// MARK: - a MessageSender
describe("a MessageSender") {
// MARK: -- when encrypting with the session protocol
context("when encrypting with the session protocol") {
beforeEach {
mockCrypto
.when { $0.generate(.sealedBytes(message: anyArray(), recipientPublicKey: anyArray())) }
.thenReturn([1, 2, 3])
mockCrypto
.when { $0.generate(.signature(message: anyArray(), secretKey: anyArray())) }
.thenReturn(Authentication.Signature.standard(signature: []))
}
// MARK: ---- can encrypt correctly
it("can encrypt correctly") {
let result: Data? = mockStorage.read { db in
try? MessageSender.encryptWithSessionProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "05\(TestConstants.publicKey)",
using: Dependencies() // Don't mock
)
}
// Note: A Nonce is used for this so we can't compare the exact value when not mocked
expect(result).toNot(beNil())
expect(result?.count).to(equal(155))
}
// MARK: ---- returns the correct value when mocked
it("returns the correct value when mocked") {
let result: Data? = mockStorage.read { db in
try? MessageSender.encryptWithSessionProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "05\(TestConstants.publicKey)",
using: dependencies
)
}
expect(result?.bytes).to(equal([1, 2, 3]))
}
// MARK: ---- throws an error if there is no ed25519 keyPair
it("throws an error if there is no ed25519 keyPair") {
mockStorage.write { db in
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
}
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "05\(TestConstants.publicKey)",
using: dependencies
)
}
.to(throwError(MessageSenderError.noUserED25519KeyPair))
}
}
// MARK: ---- throws an error if the signature generation fails
it("throws an error if the signature generation fails") {
mockCrypto
.when { try $0.generate(.signature(message: anyArray(), secretKey: anyArray())) }
.thenReturn(nil)
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "05\(TestConstants.publicKey)",
using: dependencies
)
}
.to(throwError(MessageSenderError.signingFailed))
}
}
// MARK: ---- throws an error if the encryption fails
it("throws an error if the encryption fails") {
mockCrypto
.when { $0.generate(.sealedBytes(message: anyArray(), recipientPublicKey: anyArray())) }
.thenReturn(nil)
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "05\(TestConstants.publicKey)",
using: dependencies
)
}
.to(throwError(MessageSenderError.encryptionFailed))
}
}
}
// MARK: -- when encrypting with the blinded session protocol
context("when encrypting with the blinded session protocol") {
beforeEach {
mockCrypto
.when { $0.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: any())) }
.thenReturn(
KeyPair(
publicKey: Data(hex: TestConstants.publicKey).bytes,
secretKey: Data(hex: TestConstants.edSecretKey).bytes
)
)
mockCrypto
.when {
$0.generate(
.sharedBlindedEncryptionKey(
secretKey: anyArray(),
otherBlindedPublicKey: anyArray(),
fromBlindedPublicKey: anyArray(),
toBlindedPublicKey: anyArray(),
using: any()
)
)
}
.thenReturn([1, 2, 3])
mockCrypto
.when {
$0.generate(
.encryptedBytesAeadXChaCha20(
message: anyArray(),
secretKey: anyArray(),
nonce: anyArray(),
additionalData: anyArray(),
using: any()
)
)
}
.thenReturn([2, 3, 4])
}
// MARK: ---- can encrypt correctly
it("can encrypt correctly") {
let result: Data? = mockStorage.read { db in
try? MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: Dependencies() // Don't mock
)
}
// Note: A Nonce is used for this so we can't compare the exact value when not mocked
expect(result).toNot(beNil())
expect(result?.count).to(equal(84))
}
// MARK: ---- returns the correct value when mocked
it("returns the correct value when mocked") {
let result: Data? = mockStorage.read { db in
try? MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
expect(result?.toHexString())
.to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43"))
}
// MARK: ---- includes a version at the start of the encrypted value
it("includes a version at the start of the encrypted value") {
let result: Data? = mockStorage.read { db in
try? MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
expect(result?.toHexString().prefix(2)).to(equal("00"))
}
// MARK: ---- includes the nonce at the end of the encrypted value
it("includes the nonce at the end of the encrypted value") {
let maybeResult: Data? = mockStorage.read { db in
try? MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
let result: [UInt8] = (maybeResult?.bytes ?? [])
let nonceBytes: [UInt8] = Array(result[max(0, (result.count - 24))..<result.count])
expect(Data(nonceBytes).base64EncodedString())
.to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD"))
}
// MARK: ---- throws an error if the recipient isn't a blinded id
it("throws an error if the recipient isn't a blinded id") {
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "05\(TestConstants.publicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
.to(throwError(MessageSenderError.signingFailed))
}
}
// MARK: ---- throws an error if there is no ed25519 keyPair
it("throws an error if there is no ed25519 keyPair") {
mockStorage.write { db in
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
}
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
.to(throwError(MessageSenderError.noUserED25519KeyPair))
}
}
// MARK: ---- throws an error if it fails to generate a blinded keyPair
it("throws an error if it fails to generate a blinded keyPair") {
mockCrypto
.when { $0.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: any())) }
.thenReturn(nil)
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
.to(throwError(MessageSenderError.signingFailed))
}
}
// MARK: ---- throws an error if it fails to generate an encryption key
it("throws an error if it fails to generate an encryption key") {
mockCrypto
.when {
$0.generate(
.sharedBlindedEncryptionKey(
secretKey: anyArray(),
otherBlindedPublicKey: anyArray(),
fromBlindedPublicKey: anyArray(),
toBlindedPublicKey: anyArray(),
using: any()
)
)
}
.thenReturn(nil)
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
.to(throwError(MessageSenderError.signingFailed))
}
}
// MARK: ---- throws an error if it fails to encrypt
it("throws an error if it fails to encrypt") {
mockCrypto
.when {
$0.generate(
.encryptedBytesAeadXChaCha20(
message: anyArray(),
secretKey: anyArray(),
nonce: anyArray(),
additionalData: anyArray(),
using: dependencies
)
)
}
.thenReturn(nil)
mockStorage.read { db in
expect {
try MessageSender.encryptWithSessionBlindingProtocol(
db,
plaintext: "TestMessage".data(using: .utf8)!,
for: "15\(TestConstants.blindedPublicKey)",
openGroupPublicKey: TestConstants.serverPublicKey,
using: dependencies
)
}
.to(throwError(MessageSenderError.encryptionFailed))
}
}
}
}
}
}