mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			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.
		
		
		
		
		
			
		
			
				
	
	
		
			383 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			383 lines
		
	
	
		
			18 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 {
 | 
						|
    // MARK: - Spec
 | 
						|
 | 
						|
    override func spec() {
 | 
						|
        var mockStorage: Storage!
 | 
						|
        var mockCrypto: MockCrypto!
 | 
						|
        var dependencies: Dependencies!
 | 
						|
        
 | 
						|
        describe("a MessageSender") {
 | 
						|
            // MARK: - Configuration
 | 
						|
            
 | 
						|
            beforeEach {
 | 
						|
                mockStorage = SynchronousStorage(
 | 
						|
                    customWriter: try! DatabaseQueue(),
 | 
						|
                    customMigrationTargets: [
 | 
						|
                        SNUtilitiesKit.self,
 | 
						|
                        SNMessagingKit.self
 | 
						|
                    ]
 | 
						|
                )
 | 
						|
                mockCrypto = MockCrypto()
 | 
						|
                
 | 
						|
                dependencies = Dependencies(
 | 
						|
                    storage: mockStorage,
 | 
						|
                    crypto: mockCrypto
 | 
						|
                )
 | 
						|
                
 | 
						|
                mockStorage.write { db in
 | 
						|
                    try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
 | 
						|
                    try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
 | 
						|
                }
 | 
						|
                mockCrypto
 | 
						|
                    .when { try $0.perform(.generateNonce24()) }
 | 
						|
                    .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
 | 
						|
            }
 | 
						|
            
 | 
						|
            // MARK: - when encrypting with the session protocol
 | 
						|
            context("when encrypting with the session protocol") {
 | 
						|
                beforeEach {
 | 
						|
                    mockCrypto
 | 
						|
                        .when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
 | 
						|
                        .thenReturn([1, 2, 3])
 | 
						|
                    mockCrypto
 | 
						|
                        .when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
 | 
						|
                        .thenReturn([])
 | 
						|
                }
 | 
						|
                
 | 
						|
                // 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.perform(.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 { try $0.perform(.seal(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 { [dependencies = dependencies!] crypto in
 | 
						|
                            crypto.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: dependencies))
 | 
						|
                        }
 | 
						|
                        .thenReturn(
 | 
						|
                            KeyPair(
 | 
						|
                                publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
 | 
						|
                                secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
 | 
						|
                            )
 | 
						|
                        )
 | 
						|
                    mockCrypto
 | 
						|
                        .when { [dependencies = dependencies!] crypto in
 | 
						|
                            try crypto.perform(
 | 
						|
                                .sharedBlindedEncryptionKey(
 | 
						|
                                    secretKey: anyArray(),
 | 
						|
                                    otherBlindedPublicKey: anyArray(),
 | 
						|
                                    fromBlindedPublicKey: anyArray(),
 | 
						|
                                    toBlindedPublicKey: anyArray(),
 | 
						|
                                    using: dependencies
 | 
						|
                                )
 | 
						|
                            )
 | 
						|
                        }
 | 
						|
                        .thenReturn([1, 2, 3])
 | 
						|
                    mockCrypto
 | 
						|
                        .when { [dependencies = dependencies!] crypto in
 | 
						|
                            try crypto.perform(
 | 
						|
                                .encryptAeadXChaCha20(
 | 
						|
                                    message: anyArray(),
 | 
						|
                                    secretKey: anyArray(),
 | 
						|
                                    nonce: anyArray(),
 | 
						|
                                    additionalData: anyArray(),
 | 
						|
                                    using: dependencies
 | 
						|
                                )
 | 
						|
                            )
 | 
						|
                        }
 | 
						|
                        .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 { [dependencies = dependencies!] crypto in
 | 
						|
                            crypto.generate(
 | 
						|
                                .blindedKeyPair(
 | 
						|
                                    serverPublicKey: any(),
 | 
						|
                                    edKeyPair: any(),
 | 
						|
                                    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.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 { [dependencies = dependencies!] crypto in
 | 
						|
                            try crypto.perform(
 | 
						|
                                .sharedBlindedEncryptionKey(
 | 
						|
                                    secretKey: anyArray(),
 | 
						|
                                    otherBlindedPublicKey: anyArray(),
 | 
						|
                                    fromBlindedPublicKey: anyArray(),
 | 
						|
                                    toBlindedPublicKey: 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.signingFailed))
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                
 | 
						|
                // MARK: -- throws an error if it fails to encrypt
 | 
						|
                it("throws an error if it fails to encrypt") {
 | 
						|
                    mockCrypto
 | 
						|
                        .when {
 | 
						|
                            try $0.perform(
 | 
						|
                                .encryptAeadXChaCha20(
 | 
						|
                                    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))
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |