// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import GRDB import SessionUtilitiesKit import Quick import Nimble @testable import SessionMessagingKit class CryptoSMKSpec: QuickSpec { override class func spec() { // MARK: Configuration @TestState var crypto: Crypto! = Crypto() @TestState var mockCrypto: MockCrypto! = MockCrypto() @TestState var dependencies: Dependencies! = Dependencies(storage: nil, crypto: crypto) @TestState var mockStorage: Storage! = { let result = 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) }, using: dependencies ) dependencies.storage = result return result }() // MARK: - Crypto for SessionMessagingKit describe("Crypto for SessionMessagingKit") { // MARK: -- can convert an ed25519 public key into an x25519 public key it("can convert an ed25519 public key into an x25519 public key") { let result = crypto.generate(.x25519(ed25519Pubkey: Array(Data(hex: TestConstants.edPublicKey)))) expect(result?.toHexString()) .to(equal("88672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } // MARK: -- can convert an ed25519 private key into an x25519 private key it("can convert an ed25519 private key into an x25519 private key") { let result = crypto.generate(.x25519(ed25519Seckey: Array(Data(hex: TestConstants.edSecretKey)))) expect(result?.toHexString()) .to(equal("30d796c1ddb4dc455fd998a98aa275c247494a9a7bde9c1fee86ae45cd585241")) } // MARK: -- when generating a hash describe("when generating a hash") { // MARK: ------ generates a hash correctly it("generates a hash correctly") { let result = crypto.generate(.hash(message: "TestMessage".bytes, key: "Key".bytes, length: 32)) expect(result).toNot(beNil()) expect(result?.count).to(equal(32)) expect(result?.toHexString()) .to(equal("4bb38525401d48349990f8e018aeeb3c68f9469babf4de9d3f08d960c7ae2721")) } // MARK: ------ generates a hash correctly with no key it("generates a hash correctly with no key") { let result = crypto.generate(.hash(message: "TestMessage".bytes, key: nil, length: 32)) expect(result).toNot(beNil()) expect(result?.count).to(equal(32)) expect(result?.toHexString()) .to(equal("2a48a12262e4548afb97fe2b04a912a02297d451169ee7ef2d01a28ea20286ab")) } // MARK: ------ fails if given invalid options it("fails if given invalid options") { // Max length 64 expect(crypto.generate(.hash(message: "TestMessage".bytes, key: nil, length: 65))).to(beNil()) } } // MARK: -- when encrypting with the session protocol context("when encrypting with the session protocol") { // MARK: ---- can encrypt correctly it("can encrypt correctly") { let result: Data? = mockStorage.read { db in try crypto.tryGenerate( .ciphertextWithSessionProtocol( db, plaintext: "TestMessage".data(using: .utf8)!, destination: .contact(publicKey: "05\(TestConstants.publicKey)"), using: dependencies ) ) } // 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: ---- 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 crypto.tryGenerate( .ciphertextWithSessionProtocol( db, plaintext: "TestMessage".data(using: .utf8)!, destination: .contact(publicKey: "05\(TestConstants.publicKey)"), using: dependencies ) ) } .to(throwError(MessageSenderError.noUserED25519KeyPair)) } } } // MARK: -- when decrypting with the session protocol context("when decrypting with the session protocol") { // MARK: ---- successfully decrypts a message it("successfully decrypts a message") { let result = mockStorage.read { db in crypto.generate( .plaintextWithSessionProtocol( db, ciphertext: Data( base64Encoded: "SRP0eBUWh4ez6ppWjUs5/Wph5fhnPRgB5zsWWnTz+FBAw/YI3oS2pDpIfyetMTbU" + "sFMhE5G4PbRtQFey1hsxLl221Qivc3ayaX2Mm/X89Dl8e45BC+Lb/KU9EdesxIK4pVgYXs9XrMtX3v8" + "dt0eBaXneOBfr7qB8pHwwMZjtkOu1ED07T9nszgbWabBphUfWXe2U9K3PTRisSCI=" )!, using: dependencies ) ) } expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage")) expect(result?.senderSessionIdHex) .to(equal("0588672ccb97f40bb57238989226cf429b575ba355443f47bc76c5ab144a96c65b")) } // 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 crypto.tryGenerate( .plaintextWithSessionProtocol( db, ciphertext: Data( base64Encoded: "SRP0eBUWh4ez6ppWjUs5/Wph5fhnPRgB5zsWWnTz+FBAw/YI3oS2pDpIfyetMTbU" + "sFMhE5G4PbRtQFey1hsxLl221Qivc3ayaX2Mm/X89Dl8e45BC+Lb/KU9EdesxIK4pVgYXs9XrMtX3v8" + "dt0eBaXneOBfr7qB8pHwwMZjtkOu1ED07T9nszgbWabBphUfWXe2U9K3PTRisSCI=" )!, using: dependencies ) ) } .to(throwError(MessageSenderError.noUserED25519KeyPair)) } } // MARK: ---- throws an error if the ciphertext is too short it("throws an error if the ciphertext is too short") { mockStorage.read { db in expect { try crypto.tryGenerate( .plaintextWithSessionProtocol( db, ciphertext: Data([1, 2, 3]), using: dependencies ) ) } .to(throwError(MessageReceiverError.decryptionFailed)) } } } } } }