Skip to content

Commit

Permalink
Merge pull request #1687 from matrix-org/andy/large_accounts
Browse files Browse the repository at this point in the history
Batch migrate olm and megolm sessions
  • Loading branch information
Anderas authored Jan 23, 2023
2 parents 0d66c81 + 0fc1359 commit d969cf4
Show file tree
Hide file tree
Showing 30 changed files with 1,336 additions and 333 deletions.
64 changes: 58 additions & 6 deletions MatrixSDK.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

28 changes: 16 additions & 12 deletions MatrixSDK/Background/MXBackgroundCryptoStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -209,18 +209,6 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*)
return sessions;
}

- (NSArray<MXOlmSession *> *)sessions
{
NSArray<MXOlmSession*> *bgSessions = [bgCryptoStore sessions] ?: @[];
NSArray<MXOlmSession*> *appSessions = [cryptoStore sessions] ?: @[];

NSMutableArray<MXOlmSession*> *sessions = [NSMutableArray array];
[sessions addObjectsFromArray:bgSessions];
[sessions addObjectsFromArray:appSessions];

return sessions;
}

- (void)storeSession:(MXOlmSession*)session
{
[bgCryptoStore storeSession:session];
Expand Down Expand Up @@ -356,6 +344,22 @@ - (void)storeDeviceSyncToken:(NSString*)deviceSyncToken
NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore");
}

- (void)enumerateSessionsBy:(NSInteger)batchSize block:(void (^)(NSArray<MXOlmSession *> *, double))block
{
NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore");
}

- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize block:(void (^)(NSArray<MXOlmInboundGroupSession *> *, NSSet<NSString *> *, double))block
{
NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore");
}

- (NSUInteger)sessionsCount
{
NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore");
return 0;
}

- (NSArray<MXOlmInboundGroupSession*> *)inboundGroupSessions
{
NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore");
Expand Down
19 changes: 19 additions & 0 deletions MatrixSDK/Contrib/Swift/Data/MXRoom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ import Foundation

public extension MXRoom {

enum Error: Swift.Error {
case missingState
}

/**
The current state of the room.
*/
func state() async throws -> MXRoomState {
return try await withCheckedThrowingContinuation { cont in
state {
if let state = $0 {
cont.resume(returning: state)
} else {
cont.resume(throwing: Error.missingState)
}
}
}
}


/**
The current list of members of the room.
Expand Down
220 changes: 220 additions & 0 deletions MatrixSDK/Crypto/Algorithms/RoomEvent/MXRoomEventEncryption.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

#if DEBUG

import MatrixSDKCrypto

/// Object responsible for encrypting room events and ensuring that room keys are distributed to room members
protocol MXRoomEventEncrypting {

/// Check if a particular room is encrypted
func isRoomEncrypted(roomId: String) -> Bool

/// Ensure that room keys have been shared with all eligible members
func ensureRoomKeysShared(roomId: String) async throws

/// Encrypt event content and return encrypted data
func encrypt(
content: [AnyHashable: Any],
eventType: String,
in room: MXRoom
) async throws -> [AnyHashable: Any]

/// Respond to `m.room.encryption` event that may be setting a room encryption algorithm
func handleRoomEncryptionEvent(_ event: MXEvent) async throws
}

struct MXRoomEventEncryption: MXRoomEventEncrypting {
enum Error: Swift.Error {
case missingRoom
case invalidEncryptionAlgorithm
}

private static let keyRotationPeriodMsgs: Int = 100 // Rotate room keys after each 100 messages
private static let keyRotationPeriodSec: Int = 7 * 24 * 3600 // Rotate room keys each week

private let handler: MXCryptoRoomEventEncrypting
private let legacyStore: MXCryptoStore
private let getRoomAction: GetRoomAction
private let log = MXNamedLog(name: "MXRoomEventEncryption")

init(
handler: MXCryptoRoomEventEncrypting,
legacyStore: MXCryptoStore,
getRoomAction: @escaping GetRoomAction
) {
self.handler = handler
self.legacyStore = legacyStore
self.getRoomAction = getRoomAction
}

func isRoomEncrypted(roomId: String) -> Bool {
// State of room encryption is not yet implemented in `MatrixSDKCrypto`
// Will be moved to `MatrixSDKCrypto` eventually
return legacyStore.algorithm(forRoom: roomId) != nil
}

func ensureRoomKeysShared(roomId: String) async throws {
let room = try room(for: roomId)
guard room.summary?.isEncrypted == true else {
log.debug("Room is not encrypted")
return
}

try await ensureEncryptionAndRoomKeys(in: room)
}

func encrypt(
content: [AnyHashable: Any],
eventType: String,
in room: MXRoom
) async throws -> [AnyHashable: Any] {

try await ensureEncryptionAndRoomKeys(in: room)

let roomId = try roomId(for: room)
return try handler.encryptRoomEvent(
content: content,
roomId: roomId,
eventType: eventType
)
}

func handleRoomEncryptionEvent(_ event: MXEvent) async throws {
guard let roomId = event.roomId else {
return
}

let room = try room(for: roomId)
let state = try await room.state()
try ensureRoomEncryption(roomId: roomId, algorithm: state.encryptionAlgorithm)

let users = try await encryptionEligibleUsers(
for: room,
historyVisibility: state.historyVisibility
)
handler.addTrackedUsers(users)
}

// MARK: - Private

/// Make sure we have adequately set the encryption algorithm for this room
/// and shared our current room key with all its members
private func ensureEncryptionAndRoomKeys(in room: MXRoom) async throws {
guard let roomId = room.roomId else {
throw Error.missingRoom
}

let state = try await room.state()
try ensureRoomEncryption(roomId: roomId, algorithm: state.encryptionAlgorithm)

let users = try await encryptionEligibleUsers(
for: room,
historyVisibility: state.historyVisibility
)

let settings = try encryptionSettings(for: state)
try await handler.shareRoomKeysIfNecessary(
roomId: roomId,
users: users,
settings: settings
)
}

/// Make sure that we recognize (and store if necessary) the claimed room encryption algorithm
private func ensureRoomEncryption(roomId: String, algorithm: String?) throws {
let existingAlgorithm = legacyStore.algorithm(forRoom: roomId)
if existingAlgorithm != nil && existingAlgorithm == algorithm {
log.debug("Encryption in room is already set to the correct algorithm")
return
}

guard let algorithm = algorithm else {
log.error("Resetting encryption is not allowed")
throw Error.invalidEncryptionAlgorithm
}

let supportedAlgorithms = Set([kMXCryptoMegolmAlgorithm])
guard supportedAlgorithms.contains(algorithm) else {
log.error("Ignoring invalid room algorithm", context: [
"room_id": roomId,
"algorithm": algorithm
])
throw Error.invalidEncryptionAlgorithm
}

if let existing = existingAlgorithm, existing != algorithm {
log.warning("New m.room.encryption event in \(roomId) with an algorithm change from \(existing) to \(algorithm)")
} else {
log.debug("New m.room.encryption event with algorithm \(algorithm)")
}

legacyStore.storeAlgorithm(forRoom: roomId, algorithm: algorithm)
}

/// Get user ids for all room members that should be able to decrypt events, based on the history visibility setting
private func encryptionEligibleUsers(
for room: MXRoom,
historyVisibility: MXRoomHistoryVisibility?
) async throws -> [String] {
guard
let members = try await room.members(),
let targetMembers = members.encryptionTargetMembers(historyVisibility?.identifier)
else {
log.error("Failed to get eligible users")
return []
}
return targetMembers.compactMap(\.userId)
}

private func encryptionSettings(for state: MXRoomState) throws -> EncryptionSettings {
guard let roomId = state.roomId else {
throw Error.missingRoom
}

return .init(
algorithm: .megolmV1AesSha2,
rotationPeriod: UInt64(Self.keyRotationPeriodSec),
rotationPeriodMsgs: UInt64(Self.keyRotationPeriodMsgs),
// If not set, history visibility defaults to `joined` as the most restrictive setting
historyVisibility: state.historyVisibility?.visibility ?? .joined,
onlyAllowTrustedDevices: onlyTrustedDevices(in: roomId)
)
}

private func onlyTrustedDevices(in roomId: String) -> Bool {
return legacyStore.globalBlacklistUnverifiedDevices || legacyStore.blacklistUnverifiedDevices(inRoom: roomId)
}

private func room(for roomId: String) throws -> MXRoom {
guard let room = getRoomAction(roomId) else {
throw Error.missingRoom
}
return room
}

private func roomId(for room: MXRoom) throws -> String {
guard let roomId = room.roomId else {
throw Error.missingRoom
}
return roomId
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

#if DEBUG

import MatrixSDKCrypto

extension MXDeviceVerification {
var localTrust: LocalTrust {
switch self {
case .unverified:
return .unset
case .verified:
return .verified
case .blocked:
return .blackListed
case .unknown:
return .unset
@unknown default:
MXNamedLog(name: "MXDeviceVerification").failure("Unknown device verification", context: self)
return .unset
}
}
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Copyright 2023 The Matrix.org Foundation C.I.C
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

#if DEBUG

import MatrixSDKCrypto

extension MXRoomHistoryVisibility {
var visibility: HistoryVisibility {
switch self {
case .worldReadable:
return .worldReadable
case .shared:
return .shared
case .invited:
return .invited
case .joined:
return .joined
}
}
}

#endif
4 changes: 4 additions & 0 deletions MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ extension MXCryptoMachine: MXCryptoUserIdentitySource {
}

extension MXCryptoMachine: MXCryptoRoomEventEncrypting {
func addTrackedUsers(_ users: [String]) {
machine.updateTrackedUsers(users: users)
}

func shareRoomKeysIfNecessary(
roomId: String,
users: [String],
Expand Down
1 change: 1 addition & 0 deletions MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ protocol MXCryptoUserIdentitySource: MXCryptoIdentity {

/// Room event encryption
protocol MXCryptoRoomEventEncrypting: MXCryptoIdentity {
func addTrackedUsers(_ users: [String])
func shareRoomKeysIfNecessary(roomId: String, users: [String], settings: EncryptionSettings) async throws
func encryptRoomEvent(content: [AnyHashable: Any], roomId: String, eventType: String) throws -> [String: Any]
func discardRoomKey(roomId: String)
Expand Down
Loading

0 comments on commit d969cf4

Please sign in to comment.