Merge pull request #7509 from vector-im/andy/user_trust

Refactor encryption trust level
This commit is contained in:
Anderas 2023-04-19 09:30:59 +01:00 committed by GitHub
commit 4ee39da160
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 286 additions and 59 deletions

View file

@ -20,7 +20,7 @@
#import "AvatarGenerator.h" #import "AvatarGenerator.h"
#import "MatrixKit.h" #import "MatrixKit.h"
#import "GeneratedInterface-Swift.h"
#import <objc/runtime.h> #import <objc/runtime.h>
@implementation MXRoom (Riot) @implementation MXRoom (Riot)
@ -331,30 +331,10 @@
{ {
[self.mxSession.crypto trustLevelSummaryForUserIds:@[userId] forceDownload:NO success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { [self.mxSession.crypto trustLevelSummaryForUserIds:@[userId] forceDownload:NO success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) {
UserEncryptionTrustLevel userEncryptionTrustLevel; MXCrossSigningInfo *crossSigningInfo = [self.mxSession.crypto.crossSigning crossSigningKeysForUser:userId];
double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; EncryptionTrustLevel *encryption = [[EncryptionTrustLevel alloc] init];
UserEncryptionTrustLevel userEncryptionTrustLevel = [encryption userTrustLevelWithCrossSigning:crossSigningInfo
if (trustedDevicesPercentage >= 1.0) trustedDevicesProgress:usersTrustLevelSummary.trustedDevicesProgress];
{
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;
}
onComplete(userEncryptionTrustLevel); onComplete(userEncryptionTrustLevel);
} failure:^(NSError *error) { } failure:^(NSError *error) {

View file

@ -15,17 +15,7 @@
*/ */
#import "MatrixKit.h" #import "MatrixKit.h"
#import "RoomEncryptionTrustLevel.h"
/**
RoomEncryptionTrustLevel represents the trust level in an encrypted room.
*/
typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) {
RoomEncryptionTrustLevelTrusted,
RoomEncryptionTrustLevelWarning,
RoomEncryptionTrustLevelNormal,
RoomEncryptionTrustLevelUnknown
};
/** /**
Define a `MXRoomSummary` category at Riot level. Define a `MXRoomSummary` category at Riot level.

View file

@ -33,32 +33,15 @@
- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel - (RoomEncryptionTrustLevel)roomEncryptionTrustLevel
{ {
RoomEncryptionTrustLevel roomEncryptionTrustLevel = RoomEncryptionTrustLevelUnknown; MXUsersTrustLevelSummary *trust = self.trust;
if (self.trust) if (!trust)
{ {
double trustedUsersPercentage = self.trust.trustedUsersProgress.fractionCompleted; MXLogError(@"[MXRoomSummary] roomEncryptionTrustLevel: trust is missing");
double trustedDevicesPercentage = self.trust.trustedDevicesProgress.fractionCompleted; return RoomEncryptionTrustLevelUnknown;
if (trustedUsersPercentage >= 1.0)
{
if (trustedDevicesPercentage >= 1.0)
{
roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted;
}
else
{
roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning;
}
}
else
{
roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal;
}
roomEncryptionTrustLevel = roomEncryptionTrustLevel;
} }
return roomEncryptionTrustLevel; EncryptionTrustLevel *encryption = [[EncryptionTrustLevel alloc] init];
return [encryption roomTrustLevelWithSummary:trust];
} }
- (BOOL)isJoined - (BOOL)isJoined

View file

@ -0,0 +1,68 @@
//
// 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
///
/// For legacy reasons, the trust of multiple items is represented as `Progress` object,
/// where `completedUnitCount` represents the number of trusted users / devices.
@objc class EncryptionTrustLevel: NSObject {
struct TrustSummary {
let totalCount: Int64
let trustedCount: Int64
let areAllTrusted: Bool
init(progress: Progress) {
totalCount = max(progress.totalUnitCount, progress.completedUnitCount)
trustedCount = progress.completedUnitCount
areAllTrusted = trustedCount == totalCount
}
}
/// Calculate trust level for a single user given their cross-signing info
@objc func userTrustLevel(
crossSigning: MXCrossSigningInfo?,
trustedDevicesProgress: Progress
) -> UserEncryptionTrustLevel {
let devices = TrustSummary(progress: trustedDevicesProgress)
// If we could cross-sign but we haven't, the user is simply not verified
if let crossSigning, !crossSigning.trustLevel.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 && devices.trustedCount == 0 {
return .notVerified
}
// In all other cases we check devices for trust level
return devices.areAllTrusted ? .trusted : .warning
}
/// Calculate trust level for a room given trust level of users and their devices
@objc func roomTrustLevel(summary: MXUsersTrustLevelSummary) -> RoomEncryptionTrustLevel {
let users = TrustSummary(progress: summary.trustedUsersProgress)
let devices = TrustSummary(progress: summary.trustedDevicesProgress)
guard users.totalCount > 0 && users.areAllTrusted else {
return .normal
}
return devices.areAllTrusted ? .trusted : .warning
}
}

View file

@ -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
};

View file

@ -18,6 +18,7 @@
#import "RoomBubbleCellData.h" #import "RoomBubbleCellData.h"
#import "MXKRoomBubbleTableViewCell+Riot.h" #import "MXKRoomBubbleTableViewCell+Riot.h"
#import "UserEncryptionTrustLevel.h" #import "UserEncryptionTrustLevel.h"
#import "RoomEncryptionTrustLevel.h"
#import "RoomReactionsViewSizer.h" #import "RoomReactionsViewSizer.h"
#import "RoomEncryptedDataBubbleCell.h" #import "RoomEncryptedDataBubbleCell.h"
#import "LegacyAppDelegate.h" #import "LegacyAppDelegate.h"

View file

@ -6,6 +6,8 @@
#import "AvatarGenerator.h" #import "AvatarGenerator.h"
#import "BuildInfo.h" #import "BuildInfo.h"
#import "ShareItemSender.h" #import "ShareItemSender.h"
#import "UserEncryptionTrustLevel.h"
#import "RoomEncryptionTrustLevel.h"
// MatrixKit imports // MatrixKit imports
#import "MatrixKit-Bridging-Header.h" #import "MatrixKit-Bridging-Header.h"

View file

@ -87,3 +87,4 @@ targets:
- "**/*.md" # excludes all files with the .md extension - "**/*.md" # excludes all files with the .md extension
- path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift - path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift
- path: ../Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/MatrixSDK - path: ../Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/MatrixSDK
- path: ../Riot/Modules/Encryption/EncryptionTrustLevel.swift

View file

@ -0,0 +1,177 @@
//
// 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 makeProgress(trusted: Int, total: Int) -> Progress {
let progress = Progress(totalUnitCount: Int64(total))
progress.completedUnitCount = Int64(trusted)
return progress
}
// MARK: - Users
func test_userTrustLevel_whenCrossSigningDisabled() {
let devicesToTrustLevel: [(Progress, UserEncryptionTrustLevel)] = [
(makeProgress(trusted: 0, total: 0), .notVerified),
(makeProgress(trusted: 0, total: 2), .notVerified),
(makeProgress(trusted: 1, total: 2), .warning),
(makeProgress(trusted: 3, total: 4), .warning),
(makeProgress(trusted: 5, total: 5), .trusted),
(makeProgress(trusted: 10, total: 5), .trusted)
]
for (devices, expected) in devicesToTrustLevel {
let trustLevel = encryption.userTrustLevel(
crossSigning: nil,
trustedDevicesProgress: devices
)
XCTAssertEqual(trustLevel, expected, "\(devices.completedUnitCount) trusted device(s) out of \(devices.totalUnitCount)")
}
}
func test_userTrustLevel_whenCrossSigningNotVerified() {
let devicesToTrustLevel: [(Progress, UserEncryptionTrustLevel)] = [
(makeProgress(trusted: 0, total: 0), .notVerified),
(makeProgress(trusted: 0, total: 2), .notVerified),
(makeProgress(trusted: 1, total: 2), .notVerified),
(makeProgress(trusted: 3, total: 4), .notVerified),
(makeProgress(trusted: 5, total: 5), .notVerified),
(makeProgress(trusted: 10, total: 5), .notVerified)
]
for (devices, expected) in devicesToTrustLevel {
let trustLevel = encryption.userTrustLevel(
crossSigning: makeCrossSigning(isVerified: false),
trustedDevicesProgress: devices
)
XCTAssertEqual(trustLevel, expected, "\(devices.completedUnitCount) trusted device(s) out of \(devices.totalUnitCount)")
}
}
func test_userTrustLevel_whenCrossSigningVerified() {
let devicesToTrustLevel: [(Progress, UserEncryptionTrustLevel)] = [
(makeProgress(trusted: 0, total: 0), .trusted),
(makeProgress(trusted: 0, total: 2), .warning),
(makeProgress(trusted: 1, total: 2), .warning),
(makeProgress(trusted: 3, total: 4), .warning),
(makeProgress(trusted: 5, total: 5), .trusted),
(makeProgress(trusted: 10, total: 5), .trusted)
]
for (devices, expected) in devicesToTrustLevel {
let trustLevel = encryption.userTrustLevel(
crossSigning: makeCrossSigning(isVerified: true),
trustedDevicesProgress: devices
)
XCTAssertEqual(trustLevel, expected, "\(devices.completedUnitCount) trusted device(s) out of \(devices.totalUnitCount)")
}
}
// MARK: - Rooms
func test_roomTrustLevel() {
let usersDevicesToTrustLevel: [(Progress, Progress, RoomEncryptionTrustLevel)] = [
// No users verified
(makeProgress(trusted: 0, total: 0), makeProgress(trusted: 0, total: 0), .normal),
// Only some users verified
(makeProgress(trusted: 0, total: 1), makeProgress(trusted: 0, total: 1), .normal),
(makeProgress(trusted: 3, total: 4), makeProgress(trusted: 5, total: 5), .normal),
(makeProgress(trusted: 3, total: 4), makeProgress(trusted: 5, total: 5), .normal),
// All users verified
(makeProgress(trusted: 2, total: 2), makeProgress(trusted: 0, total: 0), .trusted),
(makeProgress(trusted: 3, total: 3), makeProgress(trusted: 0, total: 1), .warning),
(makeProgress(trusted: 3, total: 3), makeProgress(trusted: 3, total: 4), .warning),
(makeProgress(trusted: 4, total: 4), makeProgress(trusted: 5, total: 5), .trusted),
(makeProgress(trusted: 10, total: 4), makeProgress(trusted: 10, total: 5), .trusted),
]
for (users, devices, expected) in usersDevicesToTrustLevel {
let trustLevel = encryption.roomTrustLevel(
summary: MXUsersTrustLevelSummary(
trustedUsersProgress: users,
andTrustedDevicesProgress: devices
)
)
XCTAssertEqual(trustLevel, expected, "\(users.completedUnitCount)/\(users.totalUnitCount) trusted users(s), \(devices.completedUnitCount)/\(devices.totalUnitCount) 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"
}
}
}