Skip to content

Commit

Permalink
Unify verification event processing
Browse files Browse the repository at this point in the history
  • Loading branch information
Anderas committed Feb 13, 2023
1 parent 7d59096 commit 8092495
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ actor MXRoomEventDecryption: MXRoomEventDecrypting {
event.content?["algorithm"] as? String == kMXCryptoMegolmAlgorithm,
let sessionId = sessionId(for: event)
else {
log.debug("Ignoring unencrypted or non-room event `\(eventId)`")
if !event.isEncrypted {
log.debug("Ignoring unencrypted event`\(eventId)`")
} else if event.clear != nil {
log.debug("Ignoring already decrypted event`\(eventId)`")
} else {
log.debug("Ignoring non-room event `\(eventId)`")
}

let result = MXEventDecryptionResult()
result.clearEvent = event.clear?.jsonDictionary()
Expand Down
36 changes: 29 additions & 7 deletions MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,13 @@ extension MXCryptoMachine: MXCryptoRoomEventDecrypting {
log.failure("Invalid event")
throw Error.invalidEvent
}
return try machine.decryptRoomEvent(event: eventString, roomId: roomId, handleVerificatonEvents: true)
return try machine.decryptRoomEvent(
event: eventString,
roomId: roomId,
// Handling verification events automatically during event decryption is now a deprecated behavior,
// all verification events are handled manually via `receiveVerificationEvent`
handleVerificatonEvents: false
)
}

func requestRoomKey(event: MXEvent) async throws {
Expand Down Expand Up @@ -521,12 +527,9 @@ extension MXCryptoMachine: MXCryptoCrossSigning {
}

extension MXCryptoMachine: MXCryptoVerifying {
func receiveUnencryptedVerificationEvent(event: MXEvent, roomId: String) async throws {
guard let string = event.jsonString() else {
throw Error.invalidEvent
}

try machine.receiveUnencryptedVerificationEvent(event: string, roomId: roomId)
func receiveVerificationEvent(event: MXEvent, roomId: String) async throws {
let event = try verificationEventString(for: event)
try machine.receiveVerificationEvent(event: event, roomId: roomId)

// Out-of-sync check if there are any verification events to sent out as a result of
// the event just received
Expand Down Expand Up @@ -641,6 +644,25 @@ extension MXCryptoMachine: MXCryptoVerifying {
try await group.waitForAll()
}
}

private func verificationEventString(for event: MXEvent) throws -> String {
guard var dictionary = event.jsonDictionary() else {
throw Error.invalidEvent
}

// If this is a decrypted event, we need to swap out `type` and `content` properties
// as this is what the crypto machine expects decrypted events to look like
if let clear = event.clear {
dictionary["type"] = clear.type
dictionary["content"] = clear.content
}

guard let string = MXTools.serialiseJSONObject(dictionary) else {
throw Error.invalidEvent
}

return string
}
}

extension MXCryptoMachine: MXCryptoBackup {
Expand Down
2 changes: 1 addition & 1 deletion MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ protocol MXCryptoCrossSigning: MXCryptoUserIdentitySource {
/// Verification functionality
protocol MXCryptoVerifying: MXCryptoIdentity {
func downloadKeysIfNecessary(users: [String]) async throws
func receiveUnencryptedVerificationEvent(event: MXEvent, roomId: String) async throws
func receiveVerificationEvent(event: MXEvent, roomId: String) async throws
func requestSelfVerification(methods: [String]) async throws -> VerificationRequestProtocol
func requestVerification(userId: String, roomId: String, methods: [String]) async throws -> VerificationRequestProtocol
func requestVerification(userId: String, deviceId: String, methods: [String]) async throws -> VerificationRequestProtocol
Expand Down
26 changes: 18 additions & 8 deletions MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,17 +213,26 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager {
log.error("There is no pending verification request")
return nil
}

do {
let qr = try activeRequest.startQrVerification()
log.debug("Starting new QR verification")
return addQrTransaction(for: request, qrCode: qr, isIncoming: false)
} catch {

let theirMethods = request.theirSupportedMethods() ?? []
if theirMethods.contains(MXKeyVerificationMethodQRCodeScan) {
do {
let qr = try activeRequest.startQrVerification()
log.debug("Starting new QR verification")
return addQrTransaction(for: request, qrCode: qr, isIncoming: false)
} catch {
log.error("Cannot start QR verification", context: error)
return nil
}
} else if theirMethods.contains(MXKeyVerificationMethodQRCodeShow) {
/// Placehoder QR transaction generated in case we cannot start a QR verification flow
/// (the other device cannot scan our code) but we may be able to scan theirs
log.debug("Adding placeholder QR verification")
return addQrTransaction(for: request, qrCode: nil, isIncoming: false)
}

log.debug("No support for QR verification flow")
return nil
}

func removeQRCodeTransaction(withTransactionId transactionId: String) {
Expand Down Expand Up @@ -269,8 +278,9 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager {
return
}

if !event.isEncrypted, let roomId = event.roomId {
try await handler.receiveUnencryptedVerificationEvent(event: event, roomId: roomId)
if let roomId = event.roomId {
log.debug("Recieved new verification event \(event.eventType)")
try await handler.receiveVerificationEvent(event: event, roomId: roomId)
}

let newUserId: String?
Expand Down
105 changes: 101 additions & 4 deletions MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ import MatrixSDKCrypto
@testable import MatrixSDK

class MXCryptoMachineUnitTests: XCTestCase {
enum Error: Swift.Error {
case invalidEvent
}

class KeyProvider: NSObject, MXKeyProviderDelegate {
func isEncryptionAvailableForData(ofType dataType: String) -> Bool {
return true
Expand All @@ -33,15 +37,18 @@ class MXCryptoMachineUnitTests: XCTestCase {
}
}

var userId = "@alice:matrix.org"
var restClient: MXRestClient!
var myUserId = "@alice:localhost"
var otherUserId = "@bob:localhost"
var roomId = "!1234:localhost"
var verificationRequestId = "$12345"
var restClient: MXRestClientStub!
var machine: MXCryptoMachine!

override func setUp() {
restClient = MXRestClientStub()
MXKeyProvider.sharedInstance().delegate = KeyProvider()
machine = try! MXCryptoMachine(
userId: userId,
userId: myUserId,
deviceId: "ABCD",
restClient: restClient,
getRoomAction: {
Expand All @@ -52,7 +59,7 @@ class MXCryptoMachineUnitTests: XCTestCase {

override func tearDown() {
do {
let url = try MXCryptoMachineStore.storeURL(for: userId)
let url = try MXCryptoMachineStore.storeURL(for: myUserId)
guard FileManager.default.fileExists(atPath: url.path) else {
return
}
Expand All @@ -62,6 +69,8 @@ class MXCryptoMachineUnitTests: XCTestCase {
}
}

// MARK: - Sync response

func test_handleSyncResponse_canProcessEmptyResponse() async throws {
let result = try await machine.handleSyncResponse(
toDevice: nil,
Expand Down Expand Up @@ -89,4 +98,92 @@ class MXCryptoMachineUnitTests: XCTestCase {
)
XCTAssertEqual(result.events.count, 1)
}

// MARK: - Verification events

func test_receiveUnencryptedVerificationEvent() async throws {
let event = try makeUnencryptedRequestEvent()

try await machine.receiveVerificationEvent(event: event, roomId: roomId)

let requests = machine.verificationRequests(userId: otherUserId)
XCTAssertEqual(requests.count, 1)
XCTAssertEqual(requests.first?.state(), .requested)
}

func test_receiveEncryptedVerificationEvent() async throws {
// Start verification by recieving `m.key.verifiaction.request` from the other user
let requestEvent = try makeUnencryptedRequestEvent()
try await machine.receiveVerificationEvent(event: requestEvent, roomId: roomId)
let request = machine.verificationRequests(userId: otherUserId).first
XCTAssertNotNil(request)

let cancelEvent = try makeDecryptedCancelEvent()

try await machine.receiveVerificationEvent(event: cancelEvent, roomId: roomId)

XCTAssertEqual(request?.state(), .cancelled(
cancelInfo: .init(
cancelCode: "m.user",
reason: "The user cancelled the verification.",
cancelledByUs: false
)
))
}

// MARK: - Helpers

private func makeUnencryptedRequestEvent() throws -> MXEvent {
guard let event = MXEvent(fromJSON: [
"origin_server_ts": Date().timeIntervalSince1970 * 1000,
"event_id": verificationRequestId,
"sender": otherUserId,
"type": "m.room.message",
"content": [
"msgtype": "m.key.verification.request",
"from_device": "ABC",
"to": myUserId,
"body": "",
"methods": ["m.sas.v1"]
]
]) else {
throw Error.invalidEvent
}
return event
}

private func makeDecryptedCancelEvent() throws -> MXEvent {
guard let decrypted = MXEvent(fromJSON: [
"origin_server_ts": Date().timeIntervalSince1970 * 1000,
"event_id": verificationRequestId,
"sender": otherUserId,
"type": "m.room.encrypted",
"content": [
"algorithm": kMXCryptoMegolmAlgorithm,
"ciphertext": "ABCDEFGH",
"sender_key": "ABCD",
"device_id": "ABCD",
"session_id": "1234"
]
]) else {
throw Error.invalidEvent
}

let result = try MXEventDecryptionResult(event: .stub(clearEvent: [
"event_id": "$6789",
"sender": otherUserId,
"type": "m.key.verification.cancel",
"content": [
"code": "m.user",
"reason": "User rejected the key verification request",
"transaction_id": verificationRequestId,
"m.relates_to": [
"event_id": verificationRequestId,
"rel_type": "m.reference"
]
]
]))
decrypted.setClearData(result)
return decrypted
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ extension CryptoVerificationStub: MXCryptoVerifying {
func downloadKeysIfNecessary(users: [String]) async throws {
}

func receiveUnencryptedVerificationEvent(event: MXEvent, roomId: String) {
func receiveVerificationEvent(event: MXEvent, roomId: String) {
}

func requestSelfVerification(methods: [String]) async throws -> VerificationRequestProtocol {
Expand Down
12 changes: 10 additions & 2 deletions MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,18 @@ private extension MXRoom {
case cannotSendMessage
}

@MainActor
func sendTextMessage(_ text: String) async throws -> MXEvent {
var event: MXEvent?
_ = try await performCallbackRequest {
sendTextMessage(text, localEcho: &event, completion: $0)
_ = try await withCheckedThrowingContinuation { continuation in
sendTextMessage(text, localEcho: &event) { response in
switch response {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}

guard let event = event else {
Expand Down
1 change: 1 addition & 0 deletions changelog.d/pr-1717.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CryptoV2: Unify verification event processing

0 comments on commit 8092495

Please sign in to comment.