LCOV - code coverage report
Current view: top level - lib/encryption - cross_signing.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 93.3 % 89 83
Test Date: 2025-10-31 08:22:39 Functions: - 0 0

            Line data    Source code
       1              : /*
       2              :  *   Famedly Matrix SDK
       3              :  *   Copyright (C) 2020, 2021 Famedly GmbH
       4              :  *
       5              :  *   This program is free software: you can redistribute it and/or modify
       6              :  *   it under the terms of the GNU Affero General Public License as
       7              :  *   published by the Free Software Foundation, either version 3 of the
       8              :  *   License, or (at your option) any later version.
       9              :  *
      10              :  *   This program is distributed in the hope that it will be useful,
      11              :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13              :  *   GNU Affero General Public License for more details.
      14              :  *
      15              :  *   You should have received a copy of the GNU Affero General Public License
      16              :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17              :  */
      18              : 
      19              : import 'dart:convert';
      20              : import 'dart:typed_data';
      21              : 
      22              : import 'package:vodozemac/vodozemac.dart' as vod;
      23              : 
      24              : import 'package:matrix/encryption/encryption.dart';
      25              : import 'package:matrix/encryption/ssss.dart';
      26              : import 'package:matrix/encryption/utils/base64_unpadded.dart';
      27              : import 'package:matrix/matrix.dart';
      28              : 
      29              : class CrossSigning {
      30              :   final Encryption encryption;
      31           24 :   Client get client => encryption.client;
      32           28 :   CrossSigning(this.encryption) {
      33           84 :     encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
      34            1 :         (String secret) async {
      35              :       try {
      36            1 :         final keyObj = vod.PkSigning.fromSecretKey(secret);
      37            3 :         return keyObj.publicKey.toBase64() ==
      38            7 :             client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
      39              :       } catch (_) {
      40              :         return false;
      41              :       }
      42              :     });
      43           84 :     encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
      44            1 :         (String secret) async {
      45              :       try {
      46            1 :         final keyObj = vod.PkSigning.fromSecretKey(secret);
      47            3 :         return keyObj.publicKey.toBase64() ==
      48            7 :             client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
      49              :       } catch (_) {
      50              :         return false;
      51              :       }
      52              :     });
      53              :   }
      54              : 
      55            7 :   bool get enabled =>
      56           21 :       encryption.ssss.isSecret(EventTypes.CrossSigningSelfSigning) &&
      57           21 :       encryption.ssss.isSecret(EventTypes.CrossSigningUserSigning) &&
      58           21 :       encryption.ssss.isSecret(EventTypes.CrossSigningMasterKey);
      59              : 
      60            4 :   Future<bool> isCached() async {
      61            8 :     await client.accountDataLoading;
      62            4 :     if (!enabled) {
      63              :       return false;
      64              :     }
      65            8 :     return (await encryption.ssss
      66            4 :                 .getCached(EventTypes.CrossSigningSelfSigning)) !=
      67              :             null &&
      68           12 :         (await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning)) !=
      69              :             null;
      70              :   }
      71              : 
      72            4 :   Future<void> selfSign({
      73              :     String? passphrase,
      74              :     String? recoveryKey,
      75              :     String? keyOrPassphrase,
      76              :     OpenSSSS? openSsss,
      77              :   }) async {
      78              :     var handle = openSsss;
      79              :     if (handle == null) {
      80            3 :       handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey);
      81            1 :       await handle.unlock(
      82              :         passphrase: passphrase,
      83              :         recoveryKey: recoveryKey,
      84              :         keyOrPassphrase: keyOrPassphrase,
      85              :         postUnlock: false,
      86              :       );
      87            1 :       await handle.maybeCacheAll();
      88              :     }
      89            4 :     final masterPrivateKey = base64decodeUnpadded(
      90            4 :       await handle.getStored(EventTypes.CrossSigningMasterKey),
      91              :     );
      92              :     String? masterPubkey;
      93              :     try {
      94            8 :       masterPubkey = vod.PkSigning.fromSecretKey(base64Encode(masterPrivateKey))
      95            4 :           .publicKey
      96            4 :           .toBase64();
      97              :     } catch (e) {
      98              :       masterPubkey = null;
      99              :     }
     100              :     final userDeviceKeys =
     101           36 :         client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID];
     102              :     if (masterPubkey == null || userDeviceKeys == null) {
     103            0 :       throw Exception('Master or user keys not found');
     104              :     }
     105           24 :     final masterKey = client.userDeviceKeys[client.userID]?.masterKey;
     106            8 :     if (masterKey == null || masterKey.ed25519Key != masterPubkey) {
     107            0 :       throw Exception('Master pubkey key doesn\'t match');
     108              :     }
     109              :     // master key is valid, set it to verified
     110            4 :     await masterKey.setVerified(true, false);
     111              :     // and now sign both our own key and our master key
     112            8 :     await sign([
     113              :       masterKey,
     114              :       userDeviceKeys,
     115              :     ]);
     116              :   }
     117              : 
     118           10 :   bool signable(List<SignableKey> keys) => keys.any(
     119            5 :         (key) =>
     120           11 :             key is CrossSigningKey && key.usage.contains('master') ||
     121            5 :             key is DeviceKeys &&
     122           20 :                 key.userId == client.userID &&
     123           16 :                 key.identifier != client.deviceID,
     124              :       );
     125              : 
     126            8 :   Future<void> sign(List<SignableKey> keys) async {
     127            8 :     final signedKeys = <MatrixSignableKey>[];
     128              :     Uint8List? selfSigningKey;
     129              :     Uint8List? userSigningKey;
     130           40 :     final userKeys = client.userDeviceKeys[client.userID];
     131              :     if (userKeys == null) {
     132            0 :       throw Exception('[sign] keys are not in cache but sign was called');
     133              :     }
     134              : 
     135            7 :     void addSignature(
     136              :       SignableKey key,
     137              :       SignableKey signedWith,
     138              :       String signature,
     139              :     ) {
     140            7 :       final signedKey = key.cloneForSigning();
     141            7 :       ((signedKey.signatures ??=
     142           14 :               <String, Map<String, String>>{})[signedWith.userId] ??=
     143           28 :           <String, String>{})['ed25519:${signedWith.identifier}'] = signature;
     144            7 :       signedKeys.add(signedKey);
     145              :     }
     146              : 
     147           16 :     for (final key in keys) {
     148           32 :       if (key.userId == client.userID) {
     149              :         // we are singing a key of ourself
     150            7 :         if (key is CrossSigningKey) {
     151            8 :           if (key.usage.contains('master')) {
     152              :             // okay, we'll sign our own master key
     153              :             final signature =
     154           16 :                 encryption.olmManager.signString(key.signingContent);
     155           20 :             addSignature(key, userKeys.deviceKeys[client.deviceID]!, signature);
     156              :           }
     157              :           // we don't care about signing other cross-signing keys
     158              :         } else {
     159              :           // okay, we'll sign a device key with our self signing key
     160            7 :           selfSigningKey ??= base64decodeUnpadded(
     161           14 :             await encryption.ssss
     162            7 :                     .getCached(EventTypes.CrossSigningSelfSigning) ??
     163              :                 '',
     164              :           );
     165            7 :           if (selfSigningKey.isNotEmpty) {
     166           12 :             final signature = _sign(key.signingContent, selfSigningKey);
     167           12 :             addSignature(key, userKeys.selfSigningKey!, signature);
     168              :           }
     169              :         }
     170            6 :       } else if (key is CrossSigningKey && key.usage.contains('master')) {
     171              :         // we are signing someone elses master key
     172            2 :         userSigningKey ??= base64decodeUnpadded(
     173            6 :           await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning) ??
     174              :               '',
     175              :         );
     176            2 :         if (userSigningKey.isNotEmpty) {
     177            4 :           final signature = _sign(key.signingContent, userSigningKey);
     178            4 :           addSignature(key, userKeys.userSigningKey!, signature);
     179              :         }
     180              :       }
     181              :     }
     182            8 :     if (signedKeys.isNotEmpty) {
     183              :       // post our new keys!
     184            7 :       final payload = <String, Map<String, Map<String, Object?>>>{};
     185           14 :       for (final key in signedKeys) {
     186            7 :         final signatures = key.signatures;
     187            7 :         final identifier = key.identifier;
     188            7 :         if (identifier == null || signatures == null || signatures.isEmpty) {
     189              :           continue;
     190              :         }
     191           14 :         if (!payload.containsKey(key.userId)) {
     192           21 :           payload[key.userId] = <String, Map<String, Object?>>{};
     193              :         }
     194           28 :         if (payload[key.userId]?[key.identifier]?['signatures'] != null) {
     195              :           // we need to merge signature objects
     196            0 :           payload[key.userId]![key.identifier]!
     197            0 :               .tryGetMap<String, Map<String, String>>('signatures')!
     198            0 :               .addAll(signatures);
     199              :         } else {
     200              :           // we can just add signatures
     201           28 :           payload[key.userId]![identifier] = key.toJson();
     202              :         }
     203              :       }
     204              : 
     205           14 :       await client.uploadCrossSigningSignatures(payload);
     206              :     }
     207              :   }
     208              : 
     209            7 :   String _sign(String canonicalJson, Uint8List key) {
     210           14 :     final keyObj = vod.PkSigning.fromSecretKey(base64Encode(key));
     211           14 :     return keyObj.sign(canonicalJson).toBase64();
     212              :   }
     213              : }
        

Generated by: LCOV version 2.0-1