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 {
|
2020-05-30 11:13:42 +00:00
|
|
|
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;
|
2020-05-30 11:13:42 +00:00
|
|
|
} catch (_) {
|
|
|
|
return false;
|
|
|
|
} finally {
|
|
|
|
keyObj.free();
|
|
|
|
}
|
|
|
|
});
|
2020-06-05 20:03:28 +00:00
|
|
|
encryption.ssss.setValidator(USER_SIGNING_KEY, (String secret) async {
|
2020-05-30 11:13:42 +00:00
|
|
|
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;
|
2020-05-30 11:13:42 +00:00
|
|
|
} 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);
|
2020-05-27 16:50:09 +00:00
|
|
|
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();
|
|
|
|
}
|
2020-05-27 19:35:00 +00:00
|
|
|
if (masterPubkey == null ||
|
|
|
|
!client.userDeviceKeys.containsKey(client.userID) ||
|
|
|
|
!client.userDeviceKeys[client.userID].deviceKeys
|
|
|
|
.containsKey(client.deviceID)) {
|
2020-05-27 16:50:09 +00:00
|
|
|
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
|
2020-05-27 19:35:00 +00:00
|
|
|
await sign([
|
|
|
|
masterKey,
|
|
|
|
client.userDeviceKeys[client.userID].deviceKeys[client.deviceID]
|
|
|
|
]);
|
2020-05-27 16:50:09 +00:00
|
|
|
}
|
|
|
|
|
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,
|
2020-05-26 07:54:46 +00:00
|
|
|
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
|
2020-05-27 16:50:09 +00:00
|
|
|
} else {
|
2020-05-25 13:30:53 +00:00
|
|
|
// okay, we'll sign a device key with our self signing key
|
2020-05-26 07:54:46 +00:00
|
|
|
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);
|
2020-05-26 07:54:46 +00:00
|
|
|
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);
|
2020-05-26 07:54:46 +00:00
|
|
|
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 {
|
2020-05-27 16:50:09 +00:00
|
|
|
keyObj.init_with_seed(key);
|
2020-05-25 13:30:53 +00:00
|
|
|
return keyObj.sign(canonicalJson);
|
|
|
|
} finally {
|
|
|
|
keyObj.free();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|