/* * Famedly Matrix SDK * Copyright (C) 2020, 2021 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 . */ import 'dart:convert'; import 'dart:typed_data'; import 'package:vodozemac/vodozemac.dart' as vod; import 'package:matrix/encryption/encryption.dart'; import 'package:matrix/encryption/ssss.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/matrix.dart'; class CrossSigning { final Encryption encryption; Client get client => encryption.client; CrossSigning(this.encryption) { encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning, (String secret) async { try { final keyObj = vod.PkSigning.fromSecretKey(secret); return keyObj.publicKey.toBase64() == client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key; } catch (_) { return false; } }); encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning, (String secret) async { try { final keyObj = vod.PkSigning.fromSecretKey(secret); return keyObj.publicKey.toBase64() == client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key; } catch (_) { return false; } }); } bool get enabled => encryption.ssss.isSecret(EventTypes.CrossSigningSelfSigning) && encryption.ssss.isSecret(EventTypes.CrossSigningUserSigning) && encryption.ssss.isSecret(EventTypes.CrossSigningMasterKey); Future isCached() async { await client.accountDataLoading; if (!enabled) { return false; } return (await encryption.ssss .getCached(EventTypes.CrossSigningSelfSigning)) != null && (await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning)) != null; } Future selfSign({ String? passphrase, String? recoveryKey, String? keyOrPassphrase, OpenSSSS? openSsss, }) async { var handle = openSsss; if (handle == null) { handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey); await handle.unlock( passphrase: passphrase, recoveryKey: recoveryKey, keyOrPassphrase: keyOrPassphrase, postUnlock: false, ); await handle.maybeCacheAll(); } final masterPrivateKey = base64decodeUnpadded( await handle.getStored(EventTypes.CrossSigningMasterKey), ); String? masterPubkey; try { masterPubkey = vod.PkSigning.fromSecretKey(base64Encode(masterPrivateKey)) .publicKey .toBase64(); } catch (e) { masterPubkey = null; } final userDeviceKeys = client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID]; if (masterPubkey == null || userDeviceKeys == null) { throw Exception('Master or user keys not found'); } final masterKey = client.userDeviceKeys[client.userID]?.masterKey; if (masterKey == null || masterKey.ed25519Key != masterPubkey) { throw Exception('Master pubkey key doesn\'t match'); } // master key is valid, set it to verified await masterKey.setVerified(true, false); // and now sign both our own key and our master key await sign([ masterKey, userDeviceKeys, ]); } bool signable(List keys) => keys.any( (key) => key is CrossSigningKey && key.usage.contains('master') || key is DeviceKeys && key.userId == client.userID && key.identifier != client.deviceID, ); Future sign(List keys) async { final signedKeys = []; Uint8List? selfSigningKey; Uint8List? userSigningKey; final userKeys = client.userDeviceKeys[client.userID]; if (userKeys == null) { throw Exception('[sign] keys are not in cache but sign was called'); } void addSignature( SignableKey key, SignableKey signedWith, String signature, ) { final signedKey = key.cloneForSigning(); ((signedKey.signatures ??= >{})[signedWith.userId] ??= {})['ed25519:${signedWith.identifier}'] = signature; signedKeys.add(signedKey); } 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 = encryption.olmManager.signString(key.signingContent); addSignature(key, userKeys.deviceKeys[client.deviceID]!, signature); } // we don't care about signing other cross-signing keys } else { // okay, we'll sign a device key with our self signing key selfSigningKey ??= base64decodeUnpadded( await encryption.ssss .getCached(EventTypes.CrossSigningSelfSigning) ?? '', ); if (selfSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, selfSigningKey); addSignature(key, userKeys.selfSigningKey!, signature); } } } else if (key is CrossSigningKey && key.usage.contains('master')) { // we are signing someone elses master key userSigningKey ??= base64decodeUnpadded( await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning) ?? '', ); if (userSigningKey.isNotEmpty) { final signature = _sign(key.signingContent, userSigningKey); addSignature(key, userKeys.userSigningKey!, signature); } } } if (signedKeys.isNotEmpty) { // post our new keys! final payload = >>{}; for (final key in signedKeys) { if (key.identifier == null || key.signatures == null || key.signatures?.isEmpty != false) { continue; } if (!payload.containsKey(key.userId)) { payload[key.userId] = >{}; } if (payload[key.userId]?[key.identifier]?['signatures'] != null) { // we need to merge signature objects payload[key.userId]![key.identifier]!['signatures'] .addAll(key.signatures); } else { // we can just add signatures payload[key.userId]![key.identifier!] = key.toJson(); } } await client.uploadCrossSigningSignatures(payload); } } String _sign(String canonicalJson, Uint8List key) { final keyObj = vod.PkSigning.fromSecretKey(base64Encode(key)); return keyObj.sign(canonicalJson).toBase64(); } }