in theory sign others keys
This commit is contained in:
parent
97a10c7de1
commit
c13f66c85f
|
@ -48,6 +48,7 @@ import 'package:pedantic/pedantic.dart';
|
||||||
import 'event.dart';
|
import 'event.dart';
|
||||||
import 'room.dart';
|
import 'room.dart';
|
||||||
import 'ssss.dart';
|
import 'ssss.dart';
|
||||||
|
import 'cross_signing.dart';
|
||||||
import 'sync/event_update.dart';
|
import 'sync/event_update.dart';
|
||||||
import 'sync/room_update.dart';
|
import 'sync/room_update.dart';
|
||||||
import 'sync/user_update.dart';
|
import 'sync/user_update.dart';
|
||||||
|
@ -81,6 +82,7 @@ class Client {
|
||||||
bool enableE2eeRecovery;
|
bool enableE2eeRecovery;
|
||||||
|
|
||||||
SSSS ssss;
|
SSSS ssss;
|
||||||
|
CrossSigning crossSigning;
|
||||||
|
|
||||||
/// Create a client
|
/// Create a client
|
||||||
/// clientName = unique identifier of this client
|
/// clientName = unique identifier of this client
|
||||||
|
@ -90,6 +92,7 @@ class Client {
|
||||||
Client(this.clientName,
|
Client(this.clientName,
|
||||||
{this.debug = false, this.database, this.enableE2eeRecovery = false}) {
|
{this.debug = false, this.database, this.enableE2eeRecovery = false}) {
|
||||||
ssss = SSSS(this);
|
ssss = SSSS(this);
|
||||||
|
crossSigning = CrossSigning(this);
|
||||||
onLoginStateChanged.stream.listen((loginState) {
|
onLoginStateChanged.stream.listen((loginState) {
|
||||||
print('LoginState: ${loginState.toString()}');
|
print('LoginState: ${loginState.toString()}');
|
||||||
});
|
});
|
||||||
|
@ -1845,6 +1848,11 @@ class Client {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Just gets the signature of a string
|
||||||
|
String signString(String s) {
|
||||||
|
return _olmAccount.sign(s);
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks the signature of a signed json object.
|
/// Checks the signature of a signed json object.
|
||||||
bool checkJsonSignature(String key, Map<String, dynamic> signedJson,
|
bool checkJsonSignature(String key, Map<String, dynamic> signedJson,
|
||||||
String userId, String deviceId) {
|
String userId, String deviceId) {
|
||||||
|
|
126
lib/src/cross_signing.dart
Normal file
126
lib/src/cross_signing.dart
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:olm/olm.dart' as olm;
|
||||||
|
|
||||||
|
import 'client.dart';
|
||||||
|
import 'utils/device_keys_list.dart';
|
||||||
|
|
||||||
|
const SELF_SIGNING_KEY = 'm.cross_signing.self_signing';
|
||||||
|
const USER_SIGNING_KEY = 'm.cross_signing.user_signing';
|
||||||
|
const MASTER_KEY = 'm.cross_signing.master';
|
||||||
|
|
||||||
|
class CrossSigning {
|
||||||
|
final Client client;
|
||||||
|
CrossSigning(this.client);
|
||||||
|
|
||||||
|
bool get enabled =>
|
||||||
|
client.accountData[SELF_SIGNING_KEY] != null &&
|
||||||
|
client.accountData[USER_SIGNING_KEY] != null &&
|
||||||
|
client.accountData[MASTER_KEY] != null;
|
||||||
|
|
||||||
|
Future<bool> isCached() async {
|
||||||
|
if (!enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (await client.ssss.getCached(SELF_SIGNING_KEY)) != null &&
|
||||||
|
(await client.ssss.getCached(USER_SIGNING_KEY)) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool signable(List<SignedKey> keys) {
|
||||||
|
for (final key in keys) {
|
||||||
|
if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key.userId == client.userID &&
|
||||||
|
(key is DeviceKeys) &&
|
||||||
|
key.identifier != client.deviceID) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sign(List<SignedKey> keys) async {
|
||||||
|
Uint8List selfSigningKey;
|
||||||
|
Uint8List userSigningKey;
|
||||||
|
final signatures = <String, dynamic>{};
|
||||||
|
var signedKey = false;
|
||||||
|
final addSignature =
|
||||||
|
(SignedKey key, SignedKey signedWith, String signature) {
|
||||||
|
if (key == null || signedWith == null || signature == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!signatures.containsKey(key.userId)) {
|
||||||
|
signatures[key.userId] = <String, dynamic>{};
|
||||||
|
}
|
||||||
|
if (!signatures[key.userId].containsKey(key.identifier)) {
|
||||||
|
signatures[key.userId][key.identifier] = key.toJson();
|
||||||
|
}
|
||||||
|
if (!signatures[key.userId][key.identifier].containsKey('signatures')) {
|
||||||
|
signatures[key.userId][key.identifier]
|
||||||
|
['signatures'] = <String, dynamic>{};
|
||||||
|
}
|
||||||
|
if (!signatures[key.userId][key.identifier]['signatures']
|
||||||
|
.containsKey(signedWith.userId)) {
|
||||||
|
signatures[key.userId][key.identifier]['signatures']
|
||||||
|
[signedWith.userId] = <String, dynamic>{};
|
||||||
|
}
|
||||||
|
signatures[key.userId][key.identifier]['signatures'][signedWith.userId]
|
||||||
|
[signedWith.identifier] = signature;
|
||||||
|
signedKey = true;
|
||||||
|
};
|
||||||
|
for (final key in keys) {
|
||||||
|
if (key.userId == client.userID) {
|
||||||
|
// we are singing a key of ourself
|
||||||
|
if (key is CrossSigningKey) {
|
||||||
|
if (key.usage.contains('master')) {
|
||||||
|
// okay, we'll sign our own master key
|
||||||
|
final signature = client.signString(key.signingContent);
|
||||||
|
addSignature(
|
||||||
|
key,
|
||||||
|
client.userDeviceKeys[client.userID].deviceKeys[client.deviceID],
|
||||||
|
signature);
|
||||||
|
}
|
||||||
|
// we don't care about signing other cross-signing keys
|
||||||
|
} else if (key.identifier != client.deviceID) {
|
||||||
|
// okay, we'll sign a device key with our self signing key
|
||||||
|
selfSigningKey ??=
|
||||||
|
base64.decode(await client.ssss.getCached(SELF_SIGNING_KEY) ?? '');
|
||||||
|
if (selfSigningKey != null) {
|
||||||
|
final signature = _sign(key.signingContent, selfSigningKey);
|
||||||
|
addSignature(key, client.userDeviceKeys[client.userID].selfSigningKey,
|
||||||
|
signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||||
|
// we are signing someone elses master key
|
||||||
|
userSigningKey ??=
|
||||||
|
base64.decode(await client.ssss.getCached(USER_SIGNING_KEY) ?? '');
|
||||||
|
if (userSigningKey != null) {
|
||||||
|
final signature = _sign(key.signingContent, userSigningKey);
|
||||||
|
addSignature(
|
||||||
|
key, client.userDeviceKeys[client.userID].userSigningKey, signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (signedKey) {
|
||||||
|
// post our new keys!
|
||||||
|
await client.jsonRequest(
|
||||||
|
type: HTTPType.POST,
|
||||||
|
action: '/client/r0/keys/signatures/upload',
|
||||||
|
data: signatures,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _sign(String canonicalJson, Uint8List key) {
|
||||||
|
final keyObj = olm.PkSigning();
|
||||||
|
keyObj.init_with_seed(key);
|
||||||
|
try {
|
||||||
|
return keyObj.sign(canonicalJson);
|
||||||
|
} finally {
|
||||||
|
keyObj.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:encrypt/encrypt.dart';
|
import 'package:encrypt/encrypt.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import "package:base58check/base58.dart";
|
import 'package:base58check/base58.dart';
|
||||||
import 'package:password_hash/password_hash.dart';
|
import 'package:password_hash/password_hash.dart';
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ const ZERO_STR =
|
||||||
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00';
|
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00';
|
||||||
const BASE58_ALPHABET =
|
const BASE58_ALPHABET =
|
||||||
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||||
const base58 = const Base58Codec(BASE58_ALPHABET);
|
const base58 = Base58Codec(BASE58_ALPHABET);
|
||||||
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
|
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
|
||||||
const OLM_PRIVATE_KEY_LENGTH = 32; // TODO: fetch from dart-olm
|
const OLM_PRIVATE_KEY_LENGTH = 32; // TODO: fetch from dart-olm
|
||||||
const AES_BLOCKSIZE = 16;
|
const AES_BLOCKSIZE = 16;
|
||||||
|
@ -38,7 +38,6 @@ class SSSS {
|
||||||
b[0] = 1;
|
b[0] = 1;
|
||||||
final aesKey = Hmac(sha256, prk.bytes).convert(utf8.encode(name) + b);
|
final aesKey = Hmac(sha256, prk.bytes).convert(utf8.encode(name) + b);
|
||||||
b[0] = 2;
|
b[0] = 2;
|
||||||
final a = aesKey.bytes + utf8.encode(name) + b;
|
|
||||||
final hmacKey =
|
final hmacKey =
|
||||||
Hmac(sha256, prk.bytes).convert(aesKey.bytes + utf8.encode(name) + b);
|
Hmac(sha256, prk.bytes).convert(aesKey.bytes + utf8.encode(name) + b);
|
||||||
return _DerivedKeys(aesKey: aesKey.bytes, hmacKey: hmacKey.bytes);
|
return _DerivedKeys(aesKey: aesKey.bytes, hmacKey: hmacKey.bytes);
|
||||||
|
@ -239,6 +238,15 @@ class SSSS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> maybeCacheAll(String keyId, Uint8List key) async {
|
||||||
|
for (final type in CACHE_TYPES) {
|
||||||
|
final secret = await getCached(type);
|
||||||
|
if (secret == null) {
|
||||||
|
await getStored(type, keyId, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> maybeRequestAll(List<DeviceKeys> devices) async {
|
Future<void> maybeRequestAll(List<DeviceKeys> devices) async {
|
||||||
for (final type in CACHE_TYPES) {
|
for (final type in CACHE_TYPES) {
|
||||||
final secret = await getCached(type);
|
final secret = await getCached(type);
|
||||||
|
@ -347,8 +355,7 @@ class SSSS {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (data.content['encrypted'] is Map) {
|
if (data.content['encrypted'] is Map) {
|
||||||
final keys = Set<String>();
|
final Set keys = <String>{};
|
||||||
String maybeKey;
|
|
||||||
for (final key in data.content['encrypted'].keys) {
|
for (final key in data.content['encrypted'].keys) {
|
||||||
keys.add(key);
|
keys.add(key);
|
||||||
}
|
}
|
||||||
|
@ -369,9 +376,7 @@ class SSSS {
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenSSSS open([String identifier]) {
|
OpenSSSS open([String identifier]) {
|
||||||
if (identifier == null) {
|
identifier ??= defaultKeyId;
|
||||||
identifier = defaultKeyId;
|
|
||||||
}
|
|
||||||
if (identifier == null) {
|
if (identifier == null) {
|
||||||
throw 'Dont know what to open';
|
throw 'Dont know what to open';
|
||||||
}
|
}
|
||||||
|
@ -454,4 +459,8 @@ class OpenSSSS {
|
||||||
Future<String> getStored(String type) async {
|
Future<String> getStored(String type) async {
|
||||||
return await ssss.getStored(type, keyId, privateKey);
|
return await ssss.getStored(type, keyId, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> maybeCacheAll() async {
|
||||||
|
await ssss.maybeCacheAll(keyId, privateKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,28 @@ class DeviceKeysList {
|
||||||
Map<String, DeviceKeys> deviceKeys = {};
|
Map<String, DeviceKeys> deviceKeys = {};
|
||||||
Map<String, CrossSigningKey> crossSigningKeys = {};
|
Map<String, CrossSigningKey> crossSigningKeys = {};
|
||||||
|
|
||||||
|
SignedKey getKey(String id) {
|
||||||
|
if (deviceKeys.containsKey(id)) {
|
||||||
|
return deviceKeys[id];
|
||||||
|
}
|
||||||
|
if (crossSigningKeys.containsKey(id)) {
|
||||||
|
return crossSigningKeys[id];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossSigningKey getCrossSigningKey(String type) {
|
||||||
|
final keys = crossSigningKeys.values.where((k) => k.usage.contains(type));
|
||||||
|
if (keys.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return keys.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossSigningKey get masterKey => getCrossSigningKey('master');
|
||||||
|
CrossSigningKey get selfSigningKey => getCrossSigningKey('self_signing');
|
||||||
|
CrossSigningKey get userSigningKey => getCrossSigningKey('user_signing');
|
||||||
|
|
||||||
DeviceKeysList.fromDb(
|
DeviceKeysList.fromDb(
|
||||||
DbUserDeviceKey dbEntry,
|
DbUserDeviceKey dbEntry,
|
||||||
List<DbUserDeviceKeysKey> childEntries,
|
List<DbUserDeviceKeysKey> childEntries,
|
||||||
|
@ -73,7 +95,7 @@ class DeviceKeysList {
|
||||||
DeviceKeysList(this.userId);
|
DeviceKeysList(this.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _SignedKey {
|
abstract class SignedKey {
|
||||||
Client client;
|
Client client;
|
||||||
String userId;
|
String userId;
|
||||||
String identifier;
|
String identifier;
|
||||||
|
@ -106,7 +128,7 @@ abstract class _SignedKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getSigningContent() {
|
String get signingContent {
|
||||||
final data = Map<String, dynamic>.from(content);
|
final data = Map<String, dynamic>.from(content);
|
||||||
// some old data might have the custom verified and blocked keys
|
// some old data might have the custom verified and blocked keys
|
||||||
data.remove('verified');
|
data.remove('verified');
|
||||||
|
@ -121,7 +143,7 @@ abstract class _SignedKey {
|
||||||
final olmutil = olm.Utility();
|
final olmutil = olm.Utility();
|
||||||
var valid = false;
|
var valid = false;
|
||||||
try {
|
try {
|
||||||
olmutil.ed25519_verify(pubKey, _getSigningContent(), signature);
|
olmutil.ed25519_verify(pubKey, signingContent, signature);
|
||||||
valid = true;
|
valid = true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// bad signature
|
// bad signature
|
||||||
|
@ -152,7 +174,7 @@ abstract class _SignedKey {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final keyId = fullKeyId.substring('ed25519:'.length);
|
final keyId = fullKeyId.substring('ed25519:'.length);
|
||||||
_SignedKey key;
|
SignedKey key;
|
||||||
if (client.userDeviceKeys[otherUserId].deviceKeys.containsKey(keyId)) {
|
if (client.userDeviceKeys[otherUserId].deviceKeys.containsKey(keyId)) {
|
||||||
key = client.userDeviceKeys[otherUserId].deviceKeys[keyId];
|
key = client.userDeviceKeys[otherUserId].deviceKeys[keyId];
|
||||||
} else if (client.userDeviceKeys[otherUserId].crossSigningKeys
|
} else if (client.userDeviceKeys[otherUserId].crossSigningKeys
|
||||||
|
@ -204,6 +226,10 @@ abstract class _SignedKey {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setVerified(bool newVerified);
|
||||||
|
|
||||||
|
Future<void> setBlocked(bool newBlocked);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final data = Map<String, dynamic>.from(content);
|
final data = Map<String, dynamic>.from(content);
|
||||||
// some old data may have the verified and blocked keys which are unneeded now
|
// some old data may have the verified and blocked keys which are unneeded now
|
||||||
|
@ -216,19 +242,21 @@ abstract class _SignedKey {
|
||||||
String toString() => json.encode(toJson());
|
String toString() => json.encode(toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
class CrossSigningKey extends _SignedKey {
|
class CrossSigningKey extends SignedKey {
|
||||||
String get publicKey => identifier;
|
String get publicKey => identifier;
|
||||||
List<String> usage;
|
List<String> usage;
|
||||||
|
|
||||||
bool get isValid =>
|
bool get isValid =>
|
||||||
userId != null && publicKey != null && keys != null && ed25519Key != null;
|
userId != null && publicKey != null && keys != null && ed25519Key != null;
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> setVerified(bool newVerified) {
|
Future<void> setVerified(bool newVerified) {
|
||||||
_verified = newVerified;
|
_verified = newVerified;
|
||||||
return client.database?.setVerifiedUserCrossSigningKey(
|
return client.database?.setVerifiedUserCrossSigningKey(
|
||||||
newVerified, client.id, userId, publicKey);
|
newVerified, client.id, userId, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> setBlocked(bool newBlocked) {
|
Future<void> setBlocked(bool newBlocked) {
|
||||||
blocked = newBlocked;
|
blocked = newBlocked;
|
||||||
return client.database?.setBlockedUserCrossSigningKey(
|
return client.database?.setBlockedUserCrossSigningKey(
|
||||||
|
@ -267,7 +295,7 @@ class CrossSigningKey extends _SignedKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceKeys extends _SignedKey {
|
class DeviceKeys extends SignedKey {
|
||||||
String get deviceId => identifier;
|
String get deviceId => identifier;
|
||||||
List<String> algorithms;
|
List<String> algorithms;
|
||||||
Map<String, dynamic> unsigned;
|
Map<String, dynamic> unsigned;
|
||||||
|
@ -281,12 +309,14 @@ class DeviceKeys extends _SignedKey {
|
||||||
curve25519Key != null &&
|
curve25519Key != null &&
|
||||||
ed25519Key != null;
|
ed25519Key != null;
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> setVerified(bool newVerified) {
|
Future<void> setVerified(bool newVerified) {
|
||||||
_verified = newVerified;
|
_verified = newVerified;
|
||||||
return client.database
|
return client.database
|
||||||
?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId);
|
?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> setBlocked(bool newBlocked) {
|
Future<void> setBlocked(bool newBlocked) {
|
||||||
blocked = newBlocked;
|
blocked = newBlocked;
|
||||||
for (var room in client.rooms) {
|
for (var room in client.rooms) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
import 'package:canonical_json/canonical_json.dart';
|
import 'package:canonical_json/canonical_json.dart';
|
||||||
|
import 'package:pedantic/pedantic.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
import 'device_keys_list.dart';
|
import 'device_keys_list.dart';
|
||||||
import '../client.dart';
|
import '../client.dart';
|
||||||
|
@ -44,6 +45,7 @@ import '../room.dart';
|
||||||
|
|
||||||
enum KeyVerificationState {
|
enum KeyVerificationState {
|
||||||
askAccept,
|
askAccept,
|
||||||
|
askSSSS,
|
||||||
waitingAccept,
|
waitingAccept,
|
||||||
askSas,
|
askSas,
|
||||||
waitingSas,
|
waitingSas,
|
||||||
|
@ -103,6 +105,8 @@ class KeyVerification {
|
||||||
_KeyVerificationMethod method;
|
_KeyVerificationMethod method;
|
||||||
List<String> possibleMethods;
|
List<String> possibleMethods;
|
||||||
Map<String, dynamic> startPaylaod;
|
Map<String, dynamic> startPaylaod;
|
||||||
|
String _nextAction;
|
||||||
|
List<SignedKey> _verifiedDevices;
|
||||||
|
|
||||||
DateTime lastActivity;
|
DateTime lastActivity;
|
||||||
String lastStep;
|
String lastStep;
|
||||||
|
@ -130,7 +134,7 @@ class KeyVerification {
|
||||||
: null);
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> start() async {
|
Future<void> sendStart() async {
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
transactionId =
|
transactionId =
|
||||||
randomString(512) + DateTime.now().millisecondsSinceEpoch.toString();
|
randomString(512) + DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
@ -141,6 +145,17 @@ class KeyVerification {
|
||||||
});
|
});
|
||||||
startedVerification = true;
|
startedVerification = true;
|
||||||
setState(KeyVerificationState.waitingAccept);
|
setState(KeyVerificationState.waitingAccept);
|
||||||
|
lastActivity = DateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> start() async {
|
||||||
|
if (client.crossSigning.enabled &&
|
||||||
|
!(await client.crossSigning.isCached())) {
|
||||||
|
setState(KeyVerificationState.askSSSS);
|
||||||
|
_nextAction = 'request';
|
||||||
|
} else {
|
||||||
|
await sendStart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handlePayload(String type, Map<String, dynamic> payload,
|
Future<void> handlePayload(String type, Map<String, dynamic> payload,
|
||||||
|
@ -228,6 +243,29 @@ class KeyVerification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> openSSSS(
|
||||||
|
{String password, String recoveryKey, bool skip = false}) async {
|
||||||
|
final next = () {
|
||||||
|
if (_nextAction == 'request') {
|
||||||
|
sendStart();
|
||||||
|
} else if (_nextAction == 'done') {
|
||||||
|
if (_verifiedDevices != null) {
|
||||||
|
// and now let's sign them all in the background
|
||||||
|
client.crossSigning.sign(_verifiedDevices);
|
||||||
|
}
|
||||||
|
setState(KeyVerificationState.done);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (skip) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final handle = client.ssss.open('m.cross_signing.user_signing');
|
||||||
|
await handle.unlock(password: password, recoveryKey: recoveryKey);
|
||||||
|
await handle.maybeCacheAll();
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
/// called when the user accepts an incoming verification
|
/// called when the user accepts an incoming verification
|
||||||
Future<void> acceptVerification() async {
|
Future<void> acceptVerification() async {
|
||||||
if (!(await verifyLastStep(
|
if (!(await verifyLastStep(
|
||||||
|
@ -293,8 +331,8 @@ class KeyVerification {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> verifyKeys(Map<String, String> keys,
|
Future<void> verifyKeys(Map<String, String> keys,
|
||||||
Future<bool> Function(String, dynamic) verifier) async {
|
Future<bool> Function(String, SignedKey) verifier) async {
|
||||||
final verifiedDevices = <String>[];
|
_verifiedDevices = <SignedKey>[];
|
||||||
|
|
||||||
if (!client.userDeviceKeys.containsKey(userId)) {
|
if (!client.userDeviceKeys.containsKey(userId)) {
|
||||||
await cancel('m.key_mismatch');
|
await cancel('m.key_mismatch');
|
||||||
|
@ -304,53 +342,48 @@ class KeyVerification {
|
||||||
final keyId = entry.key;
|
final keyId = entry.key;
|
||||||
final verifyDeviceId = keyId.substring('ed25519:'.length);
|
final verifyDeviceId = keyId.substring('ed25519:'.length);
|
||||||
final keyInfo = entry.value;
|
final keyInfo = entry.value;
|
||||||
if (client.userDeviceKeys[userId].deviceKeys
|
final key = client.userDeviceKeys[userId].getKey(verifyDeviceId);
|
||||||
.containsKey(verifyDeviceId)) {
|
if (key != null) {
|
||||||
if (!(await verifier(keyInfo,
|
if (!(await verifier(keyInfo, key))) {
|
||||||
client.userDeviceKeys[userId].deviceKeys[verifyDeviceId]))) {
|
|
||||||
await cancel('m.key_mismatch');
|
await cancel('m.key_mismatch');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
verifiedDevices.add(verifyDeviceId);
|
_verifiedDevices.add(key);
|
||||||
} else if (client.userDeviceKeys[userId].crossSigningKeys
|
|
||||||
.containsKey(verifyDeviceId)) {
|
|
||||||
// this is a cross signing key!
|
|
||||||
if (!(await verifier(keyInfo,
|
|
||||||
client.userDeviceKeys[userId].crossSigningKeys[verifyDeviceId]))) {
|
|
||||||
await cancel('m.key_mismatch');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
verifiedDevices.add(verifyDeviceId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// okay, we reached this far, so all the devices are verified!
|
// okay, we reached this far, so all the devices are verified!
|
||||||
var verifiedMasterKey = false;
|
var verifiedMasterKey = false;
|
||||||
final verifiedUserDevices = <DeviceKeys>[];
|
for (final key in _verifiedDevices) {
|
||||||
for (final verifyDeviceId in verifiedDevices) {
|
await key.setVerified(true);
|
||||||
if (client.userDeviceKeys[userId].deviceKeys
|
if (key is CrossSigningKey && key.usage.contains('master')) {
|
||||||
.containsKey(verifyDeviceId)) {
|
verifiedMasterKey = true;
|
||||||
final key = client.userDeviceKeys[userId].deviceKeys[verifyDeviceId];
|
|
||||||
await key.setVerified(true);
|
|
||||||
verifiedUserDevices.add(key);
|
|
||||||
} else if (client.userDeviceKeys[userId].crossSigningKeys
|
|
||||||
.containsKey(verifyDeviceId)) {
|
|
||||||
final key =
|
|
||||||
client.userDeviceKeys[userId].crossSigningKeys[verifyDeviceId];
|
|
||||||
await key.setVerified(true);
|
|
||||||
if (key.usage.contains('master')) {
|
|
||||||
verifiedMasterKey = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (verifiedMasterKey) {
|
if (verifiedMasterKey && userId == client.userID) {
|
||||||
if (userId == client.userID) {
|
// it was our own master key, let's request the cross signing keys
|
||||||
// it was our own master key, let's request the cross signing keys
|
// we do it in the background, thus no await needed here
|
||||||
// we do it in the background, thus no await needed here
|
unawaited(client.ssss.maybeRequestAll(
|
||||||
client.ssss.maybeRequestAll(verifiedUserDevices);
|
_verifiedDevices.whereType<DeviceKeys>().toList()));
|
||||||
|
}
|
||||||
|
await send('m.key.verification.done', {});
|
||||||
|
|
||||||
|
var askingSSSS = false;
|
||||||
|
if (client.crossSigning.enabled &&
|
||||||
|
client.crossSigning.signable(_verifiedDevices)) {
|
||||||
|
// these keys can be signed! Let's do so
|
||||||
|
if (await client.crossSigning.isCached()) {
|
||||||
|
// and now let's sign them all in the background
|
||||||
|
unawaited(client.crossSigning.sign(_verifiedDevices));
|
||||||
} else {
|
} else {
|
||||||
// it was someone elses master key, let's sign it
|
askingSSSS = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (askingSSSS) {
|
||||||
|
setState(KeyVerificationState.askSSSS);
|
||||||
|
_nextAction = 'done';
|
||||||
|
} else {
|
||||||
|
setState(KeyVerificationState.done);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> verifyActivity() async {
|
Future<bool> verifyActivity() async {
|
||||||
|
@ -729,22 +762,10 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
|
||||||
mac[entry.key] = entry.value;
|
mac[entry.key] = entry.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await request.verifyKeys(mac, (String mac, dynamic device) async {
|
await request.verifyKeys(mac, (String mac, SignedKey key) async {
|
||||||
if (device is DeviceKeys) {
|
return mac ==
|
||||||
return mac ==
|
_calculateMac(key.ed25519Key, baseInfo + 'ed25519:' + key.identifier);
|
||||||
_calculateMac(
|
|
||||||
device.ed25519Key, baseInfo + 'ed25519:' + device.deviceId);
|
|
||||||
} else if (device is CrossSigningKey) {
|
|
||||||
return mac ==
|
|
||||||
_calculateMac(
|
|
||||||
device.ed25519Key, baseInfo + 'ed25519:' + device.publicKey);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
await request.send('m.key.verification.done', {});
|
|
||||||
if (request.state != KeyVerificationState.error) {
|
|
||||||
request.setState(KeyVerificationState.done);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _makeCommitment(String pubKey, String canonicalJson) {
|
String _makeCommitment(String pubKey, String canonicalJson) {
|
||||||
|
|
Loading…
Reference in a new issue