Skip to content

Commit

Permalink
Merge pull request #7430 from vector-im/andy/user_trust
Browse files Browse the repository at this point in the history
Refactor user / room encryption trust level
  • Loading branch information
Anderas authored Mar 20, 2023
2 parents 5a3adde + 5c669a7 commit 7f5d3b4
Show file tree
Hide file tree
Showing 17 changed files with 265 additions and 62 deletions.
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

0 comments on commit 7f5d3b4

Please sign in to comment.