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.
		
		
		
		
		
			
		
			
				
	
	
		
			377 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			377 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 {
 | |
|     override class func spec() {
 | |
|         // MARK: Configuration
 | |
|         
 | |
|         @TestState var mockStorage: Storage! = SynchronousStorage(
 | |
|             customWriter: try! DatabaseQueue(),
 | |
|             migrationTargets: [
 | |
|                 SNUtilitiesKit.self,
 | |
|                 SNMessagingKit.self
 | |
|             ],
 | |
|             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)
 | |
|             }
 | |
|         )
 | |
|         @TestState var mockCrypto: MockCrypto! = MockCrypto(
 | |
|             initialSetup: { crypto in
 | |
|                 crypto
 | |
|                     .when { try $0.perform(.generateNonce24()) }
 | |
|                     .thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
 | |
|             }
 | |
|         )
 | |
|         @TestState var dependencies: Dependencies! = Dependencies(
 | |
|             storage: mockStorage,
 | |
|             crypto: mockCrypto
 | |
|         )
 | |
|         
 | |
|         // MARK: - a MessageSender
 | |
|         describe("a MessageSender") {
 | |
|             // 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))
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |