mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
Merge pull request #7509 from vector-im/andy/user_trust
Refactor encryption trust level
This commit is contained in:
commit
4ee39da160
14 changed files with 286 additions and 59 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
68
Riot/Modules/Encryption/EncryptionTrustLevel.swift
Normal file
68
Riot/Modules/Encryption/EncryptionTrustLevel.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
25
Riot/Modules/Encryption/RoomEncryptionTrustLevel.h
Normal file
25
Riot/Modules/Encryption/RoomEncryptionTrustLevel.h
Normal 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
|
||||||
|
};
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
177
RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift
Normal file
177
RiotTests/Modules/Encryption/EncryptionTrustLevelTests.swift
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue