famedlysdk/lib/encryption/cross_signing.dart

184 lines
6.2 KiB
Dart
Raw Normal View History

2020-06-05 20:03:28 +00:00
/*
* Famedly Matrix SDK
* Copyright (C) 2020 Famedly GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2020-05-25 13:30:53 +00:00
import 'dart:typed_data';
import 'dart:convert';
import 'package:olm/olm.dart' as olm;
2020-06-05 20:03:28 +00:00
import 'package:famedlysdk/famedlysdk.dart';
2020-05-25 13:30:53 +00:00
2020-06-05 20:03:28 +00:00
import 'encryption.dart';
2020-05-25 13:30:53 +00:00
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 {
2020-06-05 20:03:28 +00:00
final Encryption encryption;
Client get client => encryption.client;
CrossSigning(this.encryption) {
encryption.ssss.setValidator(SELF_SIGNING_KEY, (String secret) async {
final keyObj = olm.PkSigning();
try {
2020-05-30 11:55:09 +00:00
return keyObj.init_with_seed(base64.decode(secret)) ==
client.userDeviceKeys[client.userID].selfSigningKey.ed25519Key;
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
2020-06-05 20:03:28 +00:00
encryption.ssss.setValidator(USER_SIGNING_KEY, (String secret) async {
final keyObj = olm.PkSigning();
try {
2020-05-30 11:55:09 +00:00
return keyObj.init_with_seed(base64.decode(secret)) ==
client.userDeviceKeys[client.userID].userSigningKey.ed25519Key;
} catch (_) {
return false;
} finally {
keyObj.free();
}
});
}
2020-05-25 13:30:53 +00:00
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;
}
2020-06-05 20:03:28 +00:00
return (await encryption.ssss.getCached(SELF_SIGNING_KEY)) != null &&
(await encryption.ssss.getCached(USER_SIGNING_KEY)) != null;
2020-05-25 13:30:53 +00:00
}
2020-06-06 10:40:52 +00:00
Future<void> selfSign({String passphrase, String recoveryKey}) async {
2020-06-05 20:03:28 +00:00
final handle = encryption.ssss.open(MASTER_KEY);
2020-06-06 10:40:52 +00:00
await handle.unlock(passphrase: passphrase, recoveryKey: recoveryKey);
await handle.maybeCacheAll();
final masterPrivateKey = base64.decode(await handle.getStored(MASTER_KEY));
final keyObj = olm.PkSigning();
String masterPubkey;
try {
masterPubkey = keyObj.init_with_seed(masterPrivateKey);
} finally {
keyObj.free();
}
if (masterPubkey == null ||
!client.userDeviceKeys.containsKey(client.userID) ||
!client.userDeviceKeys[client.userID].deviceKeys
.containsKey(client.deviceID)) {
throw 'Master or user keys not found';
}
final masterKey = client.userDeviceKeys[client.userID].masterKey;
if (masterKey == null || masterKey.ed25519Key != masterPubkey) {
throw 'Master pubkey key doesn\'t match';
}
// master key is valid, set it to verified
2020-05-27 19:40:58 +00:00
await masterKey.setVerified(true, false);
2020-06-13 16:10:24 +00:00
// and now sign both our own key and our master key
await sign([
masterKey,
client.userDeviceKeys[client.userID].deviceKeys[client.deviceID]
]);
}
2020-06-06 12:28:18 +00:00
bool signable(List<SignableKey> keys) {
2020-05-25 13:30:53 +00:00
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;
}
2020-06-06 12:28:18 +00:00
Future<void> sign(List<SignableKey> keys) async {
2020-05-25 13:30:53 +00:00
Uint8List selfSigningKey;
Uint8List userSigningKey;
2020-06-06 13:17:05 +00:00
final signedKeys = <MatrixSignableKey>[];
2020-05-25 13:30:53 +00:00
final addSignature =
2020-06-06 12:28:18 +00:00
(SignableKey key, SignableKey signedWith, String signature) {
2020-05-25 13:30:53 +00:00
if (key == null || signedWith == null || signature == null) {
return;
}
2020-06-06 13:19:44 +00:00
final signedKey = key.cloneForSigning();
signedKey.signatures[signedWith.userId] = <String, String>{};
2020-06-06 13:17:05 +00:00
signedKey.signatures[signedWith.userId]
2020-05-25 13:58:37 +00:00
['ed25519:${signedWith.identifier}'] = signature;
2020-06-06 13:17:05 +00:00
signedKeys.add(signedKey);
2020-05-25 13:30:53 +00:00
};
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
2020-06-06 11:47:37 +00:00
final signature =
encryption.olmManager.signString(key.signingContent);
2020-05-25 13:30:53 +00:00
addSignature(
key,
client
.userDeviceKeys[client.userID].deviceKeys[client.deviceID],
2020-05-25 13:30:53 +00:00
signature);
}
// we don't care about signing other cross-signing keys
} else {
2020-05-25 13:30:53 +00:00
// okay, we'll sign a device key with our self signing key
selfSigningKey ??= base64
2020-06-05 20:03:28 +00:00
.decode(await encryption.ssss.getCached(SELF_SIGNING_KEY) ?? '');
if (selfSigningKey.isNotEmpty) {
2020-05-25 13:30:53 +00:00
final signature = _sign(key.signingContent, selfSigningKey);
addSignature(key,
client.userDeviceKeys[client.userID].selfSigningKey, signature);
2020-05-25 13:30:53 +00:00
}
}
} else if (key is CrossSigningKey && key.usage.contains('master')) {
// we are signing someone elses master key
2020-06-06 11:47:37 +00:00
userSigningKey ??= base64
.decode(await encryption.ssss.getCached(USER_SIGNING_KEY) ?? '');
2020-06-05 20:03:28 +00:00
if (userSigningKey.isNotEmpty) {
2020-05-25 13:30:53 +00:00
final signature = _sign(key.signingContent, userSigningKey);
addSignature(key, client.userDeviceKeys[client.userID].userSigningKey,
signature);
2020-05-25 13:30:53 +00:00
}
}
}
2020-06-06 13:17:05 +00:00
if (signedKeys.isNotEmpty) {
2020-05-25 13:30:53 +00:00
// post our new keys!
2020-06-06 13:17:05 +00:00
await client.api.uploadKeySignatures(signedKeys);
2020-05-25 13:30:53 +00:00
}
}
String _sign(String canonicalJson, Uint8List key) {
final keyObj = olm.PkSigning();
try {
keyObj.init_with_seed(key);
2020-05-25 13:30:53 +00:00
return keyObj.sign(canonicalJson);
} finally {
keyObj.free();
}
}
}