LCOV - code coverage report
Current view: top level - lib/encryption - cross_signing.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 94.6 % 92 87
Test Date: 2025-03-17 05:40:22 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:typed_data';
      20              : 
      21              : import 'package:olm/olm.dart' as olm;
      22              : 
      23              : import 'package:matrix/encryption/encryption.dart';
      24              : import 'package:matrix/encryption/ssss.dart';
      25              : import 'package:matrix/encryption/utils/base64_unpadded.dart';
      26              : import 'package:matrix/matrix.dart';
      27              : 
      28              : class CrossSigning {
      29              :   final Encryption encryption;
      30           24 :   Client get client => encryption.client;
      31           24 :   CrossSigning(this.encryption) {
      32           72 :     encryption.ssss.setValidator(EventTypes.CrossSigningSelfSigning,
      33            1 :         (String secret) async {
      34            1 :       final keyObj = olm.PkSigning();
      35              :       try {
      36            3 :         return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
      37            7 :             client.userDeviceKeys[client.userID]!.selfSigningKey!.ed25519Key;
      38              :       } catch (_) {
      39              :         return false;
      40              :       } finally {
      41            1 :         keyObj.free();
      42              :       }
      43              :     });
      44           72 :     encryption.ssss.setValidator(EventTypes.CrossSigningUserSigning,
      45            1 :         (String secret) async {
      46            1 :       final keyObj = olm.PkSigning();
      47              :       try {
      48            3 :         return keyObj.init_with_seed(base64decodeUnpadded(secret)) ==
      49            7 :             client.userDeviceKeys[client.userID]!.userSigningKey!.ed25519Key;
      50              :       } catch (_) {
      51              :         return false;
      52              :       } finally {
      53            1 :         keyObj.free();
      54              :       }
      55              :     });
      56              :   }
      57              : 
      58            7 :   bool get enabled =>
      59           21 :       encryption.ssss.isSecret(EventTypes.CrossSigningSelfSigning) &&
      60           21 :       encryption.ssss.isSecret(EventTypes.CrossSigningUserSigning) &&
      61           21 :       encryption.ssss.isSecret(EventTypes.CrossSigningMasterKey);
      62              : 
      63            4 :   Future<bool> isCached() async {
      64            8 :     await client.accountDataLoading;
      65            4 :     if (!enabled) {
      66              :       return false;
      67              :     }
      68            8 :     return (await encryption.ssss
      69            4 :                 .getCached(EventTypes.CrossSigningSelfSigning)) !=
      70              :             null &&
      71           12 :         (await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning)) !=
      72              :             null;
      73              :   }
      74              : 
      75            4 :   Future<void> selfSign({
      76              :     String? passphrase,
      77              :     String? recoveryKey,
      78              :     String? keyOrPassphrase,
      79              :     OpenSSSS? openSsss,
      80              :   }) async {
      81              :     var handle = openSsss;
      82              :     if (handle == null) {
      83            3 :       handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey);
      84            1 :       await handle.unlock(
      85              :         passphrase: passphrase,
      86              :         recoveryKey: recoveryKey,
      87              :         keyOrPassphrase: keyOrPassphrase,
      88              :         postUnlock: false,
      89              :       );
      90            1 :       await handle.maybeCacheAll();
      91              :     }
      92            4 :     final masterPrivateKey = base64decodeUnpadded(
      93            4 :       await handle.getStored(EventTypes.CrossSigningMasterKey),
      94              :     );
      95            4 :     final keyObj = olm.PkSigning();
      96              :     String? masterPubkey;
      97              :     try {
      98            4 :       masterPubkey = keyObj.init_with_seed(masterPrivateKey);
      99              :     } catch (e) {
     100              :       masterPubkey = null;
     101              :     } finally {
     102            4 :       keyObj.free();
     103              :     }
     104              :     final userDeviceKeys =
     105           36 :         client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID];
     106              :     if (masterPubkey == null || userDeviceKeys == null) {
     107            0 :       throw Exception('Master or user keys not found');
     108              :     }
     109           24 :     final masterKey = client.userDeviceKeys[client.userID]?.masterKey;
     110            8 :     if (masterKey == null || masterKey.ed25519Key != masterPubkey) {
     111            0 :       throw Exception('Master pubkey key doesn\'t match');
     112              :     }
     113              :     // master key is valid, set it to verified
     114            4 :     await masterKey.setVerified(true, false);
     115              :     // and now sign both our own key and our master key
     116            8 :     await sign([
     117              :       masterKey,
     118              :       userDeviceKeys,
     119              :     ]);
     120              :   }
     121              : 
     122           10 :   bool signable(List<SignableKey> keys) => keys.any(
     123            5 :         (key) =>
     124           11 :             key is CrossSigningKey && key.usage.contains('master') ||
     125            5 :             key is DeviceKeys &&
     126           20 :                 key.userId == client.userID &&
     127           16 :                 key.identifier != client.deviceID,
     128              :       );
     129              : 
     130            8 :   Future<void> sign(List<SignableKey> keys) async {
     131            8 :     final signedKeys = <MatrixSignableKey>[];
     132              :     Uint8List? selfSigningKey;
     133              :     Uint8List? userSigningKey;
     134           40 :     final userKeys = client.userDeviceKeys[client.userID];
     135              :     if (userKeys == null) {
     136            0 :       throw Exception('[sign] keys are not in cache but sign was called');
     137              :     }
     138              : 
     139            7 :     void addSignature(
     140              :       SignableKey key,
     141              :       SignableKey signedWith,
     142              :       String signature,
     143              :     ) {
     144            7 :       final signedKey = key.cloneForSigning();
     145            7 :       ((signedKey.signatures ??=
     146           14 :               <String, Map<String, String>>{})[signedWith.userId] ??=
     147           28 :           <String, String>{})['ed25519:${signedWith.identifier}'] = signature;
     148            7 :       signedKeys.add(signedKey);
     149              :     }
     150              : 
     151           16 :     for (final key in keys) {
     152           32 :       if (key.userId == client.userID) {
     153              :         // we are singing a key of ourself
     154            7 :         if (key is CrossSigningKey) {
     155            8 :           if (key.usage.contains('master')) {
     156              :             // okay, we'll sign our own master key
     157              :             final signature =
     158           16 :                 encryption.olmManager.signString(key.signingContent);
     159           20 :             addSignature(key, userKeys.deviceKeys[client.deviceID]!, signature);
     160              :           }
     161              :           // we don't care about signing other cross-signing keys
     162              :         } else {
     163              :           // okay, we'll sign a device key with our self signing key
     164            7 :           selfSigningKey ??= base64decodeUnpadded(
     165           14 :             await encryption.ssss
     166            7 :                     .getCached(EventTypes.CrossSigningSelfSigning) ??
     167              :                 '',
     168              :           );
     169            7 :           if (selfSigningKey.isNotEmpty) {
     170           12 :             final signature = _sign(key.signingContent, selfSigningKey);
     171           12 :             addSignature(key, userKeys.selfSigningKey!, signature);
     172              :           }
     173              :         }
     174            6 :       } else if (key is CrossSigningKey && key.usage.contains('master')) {
     175              :         // we are signing someone elses master key
     176            2 :         userSigningKey ??= base64decodeUnpadded(
     177            6 :           await encryption.ssss.getCached(EventTypes.CrossSigningUserSigning) ??
     178              :               '',
     179              :         );
     180            2 :         if (userSigningKey.isNotEmpty) {
     181            4 :           final signature = _sign(key.signingContent, userSigningKey);
     182            4 :           addSignature(key, userKeys.userSigningKey!, signature);
     183              :         }
     184              :       }
     185              :     }
     186            8 :     if (signedKeys.isNotEmpty) {
     187              :       // post our new keys!
     188            7 :       final payload = <String, Map<String, Map<String, dynamic>>>{};
     189           14 :       for (final key in signedKeys) {
     190            7 :         if (key.identifier == null ||
     191            7 :             key.signatures == null ||
     192           21 :             key.signatures?.isEmpty != false) {
     193              :           continue;
     194              :         }
     195           14 :         if (!payload.containsKey(key.userId)) {
     196           21 :           payload[key.userId] = <String, Map<String, dynamic>>{};
     197              :         }
     198           28 :         if (payload[key.userId]?[key.identifier]?['signatures'] != null) {
     199              :           // we need to merge signature objects
     200            0 :           payload[key.userId]![key.identifier]!['signatures']
     201            0 :               .addAll(key.signatures);
     202              :         } else {
     203              :           // we can just add signatures
     204           35 :           payload[key.userId]![key.identifier!] = key.toJson();
     205              :         }
     206              :       }
     207              : 
     208           14 :       await client.uploadCrossSigningSignatures(payload);
     209              :     }
     210              :   }
     211              : 
     212            7 :   String _sign(String canonicalJson, Uint8List key) {
     213            7 :     final keyObj = olm.PkSigning();
     214              :     try {
     215            7 :       keyObj.init_with_seed(key);
     216            7 :       return keyObj.sign(canonicalJson);
     217              :     } finally {
     218            7 :       keyObj.free();
     219              :     }
     220              :   }
     221              : }
        

Generated by: LCOV version 2.0-1