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.
		
		
		
		
		
			
		
			
				
	
	
		
			284 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			284 lines
		
	
	
		
			13 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 mockBox: MockBox!
 | 
						|
        var mockSign: MockSign!
 | 
						|
        var mockNonce24Generator: MockNonce24Generator!
 | 
						|
        var dependencies: SMKDependencies!
 | 
						|
        
 | 
						|
        describe("a MessageSender") {
 | 
						|
            beforeEach {
 | 
						|
                mockStorage = Storage(
 | 
						|
                    customWriter: try! DatabaseQueue(),
 | 
						|
                    customMigrations: [
 | 
						|
                        SNUtilitiesKit.migrations(),
 | 
						|
                        SNMessagingKit.migrations()
 | 
						|
                    ]
 | 
						|
                )
 | 
						|
                mockBox = MockBox()
 | 
						|
                mockSign = MockSign()
 | 
						|
                mockNonce24Generator = MockNonce24Generator()
 | 
						|
                
 | 
						|
                dependencies = SMKDependencies(
 | 
						|
                    storage: mockStorage,
 | 
						|
                    box: mockBox,
 | 
						|
                    sign: mockSign,
 | 
						|
                    nonceGenerator24: mockNonce24Generator
 | 
						|
                )
 | 
						|
                
 | 
						|
                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)
 | 
						|
                }
 | 
						|
                mockNonce24Generator
 | 
						|
                    .when { $0.nonce() }
 | 
						|
                    .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
 | 
						|
            }
 | 
						|
            
 | 
						|
            context("when encrypting with the session protocol") {
 | 
						|
                beforeEach {
 | 
						|
                    mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn([1, 2, 3])
 | 
						|
                    mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn([])
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("can encrypt correctly") {
 | 
						|
                    let result = try? MessageSender.encryptWithSessionProtocol(
 | 
						|
                        "TestMessage".data(using: .utf8)!,
 | 
						|
                        for: "05\(TestConstants.publicKey)",
 | 
						|
                        using: SMKDependencies(storage: mockStorage)
 | 
						|
                    )
 | 
						|
                    
 | 
						|
                    // 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))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("returns the correct value when mocked") {
 | 
						|
                    let result = try? MessageSender.encryptWithSessionProtocol(
 | 
						|
                        "TestMessage".data(using: .utf8)!,
 | 
						|
                        for: "05\(TestConstants.publicKey)",
 | 
						|
                        using: dependencies
 | 
						|
                    )
 | 
						|
                    
 | 
						|
                    expect(result?.bytes).to(equal([1, 2, 3]))
 | 
						|
                }
 | 
						|
                
 | 
						|
                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)
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "05\(TestConstants.publicKey)",
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.noUserED25519KeyPair))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("throws an error if the signature generation fails") {
 | 
						|
                    mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil)
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "05\(TestConstants.publicKey)",
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.signingFailed))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("throws an error if the encryption fails") {
 | 
						|
                    mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn(nil)
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "05\(TestConstants.publicKey)",
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.encryptionFailed))
 | 
						|
                }
 | 
						|
            }
 | 
						|
            
 | 
						|
            context("when encrypting with the blinded session protocol") {
 | 
						|
                it("successfully encrypts") {
 | 
						|
                    let result = try? MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                        "TestMessage".data(using: .utf8)!,
 | 
						|
                        for: "15\(TestConstants.blindedPublicKey)",
 | 
						|
                        openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                        using: dependencies
 | 
						|
                    )
 | 
						|
                    
 | 
						|
                    expect(result?.toHexString())
 | 
						|
                        .to(equal(
 | 
						|
                            "00db16b6687382811d69875a5376f66acad9c49fe5e26bcf770c7e6e9c230299" +
 | 
						|
                            "f61b315299dd1fa700dd7f34305c0465af9e64dc791d7f4123f1eeafa5b4d48b" +
 | 
						|
                            "3ade4f4b2a2764762e5a2c7900f254bd91633b43"
 | 
						|
                        ))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("includes a version at the start of the encrypted value") {
 | 
						|
                    let result = try? MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                        "TestMessage".data(using: .utf8)!,
 | 
						|
                        for: "15\(TestConstants.blindedPublicKey)",
 | 
						|
                        openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                        using: dependencies
 | 
						|
                    )
 | 
						|
                    
 | 
						|
                    expect(result?.toHexString().prefix(2)).to(equal("00"))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("includes the nonce at the end of the encrypted value") {
 | 
						|
                    let maybeResult = try? MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                        "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"))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("throws an error if the recipient isn't a blinded id") {
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "05\(TestConstants.publicKey)",
 | 
						|
                            openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.signingFailed))
 | 
						|
                }
 | 
						|
                
 | 
						|
                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)
 | 
						|
                    }
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "15\(TestConstants.blindedPublicKey)",
 | 
						|
                            openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.noUserED25519KeyPair))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("throws an error if it fails to generate a blinded keyPair") {
 | 
						|
                    let mockSodium: MockSodium = MockSodium()
 | 
						|
                    let mockGenericHash: MockGenericHash = MockGenericHash()
 | 
						|
                    dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
 | 
						|
                    
 | 
						|
                    mockSodium
 | 
						|
                        .when {
 | 
						|
                            $0.blindedKeyPair(
 | 
						|
                                serverPublicKey: any(),
 | 
						|
                                edKeyPair: any(),
 | 
						|
                                genericHash: mockGenericHash
 | 
						|
                            )
 | 
						|
                        }
 | 
						|
                        .thenReturn(nil)
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "15\(TestConstants.blindedPublicKey)",
 | 
						|
                            openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.signingFailed))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("throws an error if it fails to generate an encryption key") {
 | 
						|
                    let mockSodium: MockSodium = MockSodium()
 | 
						|
                    let mockGenericHash: MockGenericHash = MockGenericHash()
 | 
						|
                    dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
 | 
						|
                    
 | 
						|
                    mockSodium
 | 
						|
                        .when {
 | 
						|
                            $0.blindedKeyPair(
 | 
						|
                                serverPublicKey: any(),
 | 
						|
                                edKeyPair: any(),
 | 
						|
                                genericHash: mockGenericHash
 | 
						|
                            )
 | 
						|
                        }
 | 
						|
                        .thenReturn(
 | 
						|
                            Box.KeyPair(
 | 
						|
                                publicKey: Data(hex: TestConstants.edPublicKey).bytes,
 | 
						|
                                secretKey: Data(hex: TestConstants.edSecretKey).bytes
 | 
						|
                            )
 | 
						|
                        )
 | 
						|
                    mockSodium
 | 
						|
                        .when {
 | 
						|
                            $0.sharedBlindedEncryptionKey(
 | 
						|
                                secretKey: anyArray(),
 | 
						|
                                otherBlindedPublicKey: anyArray(),
 | 
						|
                                fromBlindedPublicKey: anyArray(),
 | 
						|
                                toBlindedPublicKey: anyArray(),
 | 
						|
                                genericHash: mockGenericHash
 | 
						|
                            )
 | 
						|
                        }
 | 
						|
                        .thenReturn(nil)
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "15\(TestConstants.blindedPublicKey)",
 | 
						|
                            openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.signingFailed))
 | 
						|
                }
 | 
						|
                
 | 
						|
                it("throws an error if it fails to encrypt") {
 | 
						|
                    let mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
 | 
						|
                    dependencies = dependencies.with(aeadXChaCha20Poly1305Ietf: mockAeadXChaCha)
 | 
						|
                    
 | 
						|
                    mockAeadXChaCha
 | 
						|
                        .when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
 | 
						|
                        .thenReturn(nil)
 | 
						|
                    
 | 
						|
                    expect {
 | 
						|
                        try MessageSender.encryptWithSessionBlindingProtocol(
 | 
						|
                            "TestMessage".data(using: .utf8)!,
 | 
						|
                            for: "15\(TestConstants.blindedPublicKey)",
 | 
						|
                            openGroupPublicKey: TestConstants.serverPublicKey,
 | 
						|
                            using: dependencies
 | 
						|
                        )
 | 
						|
                    }
 | 
						|
                    .to(throwError(MessageSenderError.encryptionFailed))
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |