Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor user / room encryption trust level #7430

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 5 additions & 25 deletions Riot/Categories/MXRoom+Riot.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

#import "AvatarGenerator.h"
#import "MatrixKit.h"

#import "GeneratedInterface-Swift.h"
#import <objc/runtime.h>

@implementation MXRoom (Riot)
Expand Down Expand Up @@ -331,30 +331,10 @@ - (void)encryptionTrustLevelForUserId:(NSString*)userId onComplete:(void (^)(Use
{
[self.mxSession.crypto trustLevelSummaryForUserIds:@[userId] forceDownload:NO success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) {

UserEncryptionTrustLevel userEncryptionTrustLevel;
double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted;

if (trustedDevicesPercentage >= 1.0)
{
userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted;
}
else if (trustedDevicesPercentage == 0.0)
{
// Verify if the user has the user has cross-signing enabled
if ([self.mxSession.crypto.crossSigning crossSigningKeysForUser:userId])
{
userEncryptionTrustLevel = UserEncryptionTrustLevelNotVerified;
}
else
{
userEncryptionTrustLevel = UserEncryptionTrustLevelNoCrossSigning;
}
}
else
{
userEncryptionTrustLevel = UserEncryptionTrustLevelWarning;
}

MXCrossSigningInfo *crossSigningInfo = [self.mxSession.crypto.crossSigning crossSigningKeysForUser:userId];
EncryptionTrustLevel *encryption = [[EncryptionTrustLevel alloc] init];
UserEncryptionTrustLevel userEncryptionTrustLevel = [encryption userTrustLevelWithCrossSigning:crossSigningInfo
devicesTrust:usersTrustLevelSummary.devicesTrust];
onComplete(userEncryptionTrustLevel);

} failure:^(NSError *error) {
Expand Down
12 changes: 1 addition & 11 deletions Riot/Categories/MXRoomSummary+Riot.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,7 @@
*/

#import "MatrixKit.h"

/**
RoomEncryptionTrustLevel represents the trust level in an encrypted room.
*/
typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) {
RoomEncryptionTrustLevelTrusted,
RoomEncryptionTrustLevelWarning,
RoomEncryptionTrustLevelNormal,
RoomEncryptionTrustLevelUnknown
};

#import "RoomEncryptionTrustLevel.h"

/**
Define a `MXRoomSummary` category at Riot level.
Expand Down
29 changes: 6 additions & 23 deletions Riot/Categories/MXRoomSummary+Riot.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,15 @@ - (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView

- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel
{
RoomEncryptionTrustLevel roomEncryptionTrustLevel = RoomEncryptionTrustLevelUnknown;
if (self.trust)
MXUsersTrustLevelSummary *trust = self.trust;
if (!trust)
{
double trustedUsersPercentage = self.trust.trustedUsersProgress.fractionCompleted;
double trustedDevicesPercentage = self.trust.trustedDevicesProgress.fractionCompleted;

if (trustedUsersPercentage >= 1.0)
{
if (trustedDevicesPercentage >= 1.0)
{
roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted;
}
else
{
roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning;
}
}
else
{
roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal;
}

roomEncryptionTrustLevel = roomEncryptionTrustLevel;
MXLogError(@"[MXRoomSummary] roomEncryptionTrustLevel: trust is missing");
return RoomEncryptionTrustLevelUnknown;
}

return roomEncryptionTrustLevel;
EncryptionTrustLevel *encryption = [[EncryptionTrustLevel alloc] init];
return [encryption roomTrustLevelWithSummary:trust];
}

- (BOOL)isJoined
Expand Down
49 changes: 49 additions & 0 deletions Riot/Modules/Encryption/EncryptionTrustLevel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Copyright 2023 New Vector Ltd
//
// 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

/// Object responsible for calculating user and room trust level
@objc class EncryptionTrustLevel: NSObject {

/// Calculate trust level for a single user given their cross-signing info
@objc func userTrustLevel(
crossSigning: MXCrossSigningInfo?,
devicesTrust: MXTrustSummary
) -> UserEncryptionTrustLevel {

// If we could cross-sign but we haven't, the user is simply not verified
if let crossSigning, !crossSigning.isVerified {
return .notVerified

// If we cannot cross-sign the user (legacy behaviour) and have not signed
// any devices manually, the user is not verified
} else if crossSigning == nil && devicesTrust.trustedCount == 0 {
return .notVerified
}

// In all other cases we check devices for trust level
return devicesTrust.areAllTrusted ? .trusted : .warning
}

/// Calculate trust level for a room given trust level of users and their devices
@objc func roomTrustLevel(summary: MXUsersTrustLevelSummary) -> RoomEncryptionTrustLevel {
guard summary.usersTrust.totalCount > 0 && summary.usersTrust.areAllTrusted else {
return .normal
}
return summary.devicesTrust.areAllTrusted ? .trusted : .warning
}
}
25 changes: 25 additions & 0 deletions Riot/Modules/Encryption/RoomEncryptionTrustLevel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Copyright 2023 New Vector Ltd
//
// 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.
//

/**
RoomEncryptionTrustLevel represents the trust level in an encrypted room.
*/
typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) {
RoomEncryptionTrustLevelTrusted,
RoomEncryptionTrustLevelWarning,
RoomEncryptionTrustLevelNormal,
RoomEncryptionTrustLevelUnknown
};
4 changes: 2 additions & 2 deletions Riot/Modules/MatrixKit/Models/Room/MXKRoomBubbleComponent.m
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@ - (EventEncryptionDecoration)encryptionDecorationForEvent:(MXEvent*)event roomSt
// Only show a warning badge if there are trust issues.
if (event.sender)
{
MXUserTrustLevel *userTrustLevel = [session.crypto trustLevelForUser:event.sender];
BOOL isUserVerified = [session.crypto isUserVerified:event.sender];
MXDeviceInfo *deviceInfo = [session.crypto eventDeviceInfo:event];

if (userTrustLevel.isVerified && !deviceInfo.trustLevel.isVerified)
if (isUserVerified && !deviceInfo.trustLevel.isVerified)
{
return EventEncryptionDecorationUntrustedDevice;
}
Expand Down
1 change: 1 addition & 0 deletions Riot/SupportingFiles/Riot-Bridging-Header.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import "RoomBubbleCellData.h"
#import "MXKRoomBubbleTableViewCell+Riot.h"
#import "UserEncryptionTrustLevel.h"
#import "RoomEncryptionTrustLevel.h"
#import "RoomReactionsViewSizer.h"
#import "RoomEncryptedDataBubbleCell.h"
#import "LegacyAppDelegate.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#import "AvatarGenerator.h"
#import "BuildInfo.h"
#import "ShareItemSender.h"
#import "UserEncryptionTrustLevel.h"
#import "RoomEncryptionTrustLevel.h"

// MatrixKit imports
#import "MatrixKit-Bridging-Header.h"
1 change: 1 addition & 0 deletions RiotShareExtension/target.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,4 @@ targets:
- "**/*.md" # excludes all files with the .md extension
- path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift
- path: ../Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/MatrixSDK
- path: ../Riot/Modules/Encryption/EncryptionTrustLevel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ class QRLoginService: NSObject, QRLoginServiceProtocol {

MXLog.debug("[QRLoginService] Marking the received master key as trusted")
let mskVerificationResult = await withCheckedContinuation { (continuation: CheckedContinuation<Bool, Never>) in
session.crypto.setUserVerification(true, forUser: session.myUserId) {
session.crypto.setUserVerificationForUserId(session.myUserId) {
MXLog.debug("[QRLoginService] Successfully marked the received master key as trusted")
continuation.resume(returning: true)
} failure: { error in
Expand Down
171 changes: 171 additions & 0 deletions RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//
// Copyright 2023 New Vector Ltd
//
// 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
import XCTest
@testable import Element
@testable import MatrixSDK

class EncryptionTrustLevelTests: XCTestCase {

var encryption: EncryptionTrustLevel!
override func setUp() {
encryption = EncryptionTrustLevel()
}

// MARK: - Helpers

func makeCrossSigning(isVerified: Bool) -> MXCrossSigningInfo {
return .init(
userIdentity: .init(
identity: .other(
userId: "Bob",
masterKey: "MSK",
selfSigningKey: "SSK"
),
isVerified: isVerified
)
)
}

func makeSummary(trusted: Int, total: Int) -> MXTrustSummary {
MXTrustSummary(trustedCount: trusted, totalCount: total)
}

// MARK: - Users

func test_userTrustLevel_whenCrossSigningDisabled() {
let devicesToTrustLevel: [(MXTrustSummary, UserEncryptionTrustLevel)] = [
(makeSummary(trusted: 0, total: 0), .notVerified),
(makeSummary(trusted: 0, total: 2), .notVerified),
(makeSummary(trusted: 1, total: 2), .warning),
(makeSummary(trusted: 3, total: 4), .warning),
(makeSummary(trusted: 5, total: 5), .trusted)
]

for (devices, expected) in devicesToTrustLevel {
let trustLevel = encryption.userTrustLevel(
crossSigning: nil,
devicesTrust: devices
)
XCTAssertEqual(trustLevel, expected, "\(devices.trustedCount) trusted device(s) out of \(devices.totalCount)")
}
}

func test_userTrustLevel_whenCrossSigningNotVerified() {
let devicesToTrustLevel: [(MXTrustSummary, UserEncryptionTrustLevel)] = [
(makeSummary(trusted: 0, total: 0), .notVerified),
(makeSummary(trusted: 0, total: 2), .notVerified),
(makeSummary(trusted: 1, total: 2), .notVerified),
(makeSummary(trusted: 3, total: 4), .notVerified),
(makeSummary(trusted: 5, total: 5), .notVerified)
]

for (devices, expected) in devicesToTrustLevel {
let trustLevel = encryption.userTrustLevel(
crossSigning: makeCrossSigning(isVerified: false),
devicesTrust: devices
)
XCTAssertEqual(trustLevel, expected, "\(devices.trustedCount) trusted device(s) out of \(devices.totalCount)")
}
}

func test_userTrustLevel_whenCrossSigningVerified() {
let devicesToTrustLevel: [(MXTrustSummary, UserEncryptionTrustLevel)] = [
(makeSummary(trusted: 0, total: 0), .trusted),
(makeSummary(trusted: 0, total: 2), .warning),
(makeSummary(trusted: 1, total: 2), .warning),
(makeSummary(trusted: 3, total: 4), .warning),
(makeSummary(trusted: 5, total: 5), .trusted)
]

for (devices, expected) in devicesToTrustLevel {
let trustLevel = encryption.userTrustLevel(
crossSigning: makeCrossSigning(isVerified: true),
devicesTrust: devices
)
XCTAssertEqual(trustLevel, expected, "\(devices.trustedCount) trusted device(s) out of \(devices.totalCount)")
}
}

// MARK: - Rooms

func test_roomTrustLevel() {
let usersDevicesToTrustLevel: [(MXTrustSummary, MXTrustSummary, RoomEncryptionTrustLevel)] = [
// No users verified
(makeSummary(trusted: 0, total: 0), makeSummary(trusted: 0, total: 0), .normal),

// Only some users verified
(makeSummary(trusted: 0, total: 1), makeSummary(trusted: 0, total: 1), .normal),
(makeSummary(trusted: 3, total: 4), makeSummary(trusted: 5, total: 5), .normal),
(makeSummary(trusted: 3, total: 4), makeSummary(trusted: 5, total: 5), .normal),

// All users verified
(makeSummary(trusted: 2, total: 2), makeSummary(trusted: 0, total: 0), .trusted),
(makeSummary(trusted: 3, total: 3), makeSummary(trusted: 0, total: 1), .warning),
(makeSummary(trusted: 3, total: 3), makeSummary(trusted: 3, total: 4), .warning),
(makeSummary(trusted: 4, total: 4), makeSummary(trusted: 5, total: 5), .trusted),
]

for (users, devices, expected) in usersDevicesToTrustLevel {
let trustLevel = encryption.roomTrustLevel(
summary: MXUsersTrustLevelSummary(
usersTrust: users,
devicesTrust: devices
)
)
XCTAssertEqual(trustLevel, expected, "\(users.trustedCount)/\(users.totalCount) trusted users(s), \(devices.trustedCount)/\(devices.totalCount) trusted device(s)")
}
}
}

extension UserEncryptionTrustLevel: CustomStringConvertible {
public var description: String {
switch self {
case .trusted:
return "trusted"
case .warning:
return "warning"
case .notVerified:
return "notVerified"
case .noCrossSigning:
return "noCrossSigning"
case .none:
return "none"
case .unknown:
return "unknown"
@unknown default:
return "unknown"
}
}
}

extension RoomEncryptionTrustLevel: CustomStringConvertible {
public var description: String {
switch self {
case .trusted:
return "trusted"
case .warning:
return "warning"
case .normal:
return "normal"
case .unknown:
return "unknown"
@unknown default:
return "unknown"
}
}
}
1 change: 1 addition & 0 deletions changelog.d/pr-7430.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Encryption: Refactor user / room encryption trust level