LCOV - code coverage report
Current view: top level - lib/encryption - cross_signing.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 85 90 94.4 %
Date: 2024-09-28 12:47:43 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}) async {
      80             :     var handle = openSsss;
      81             :     if (handle == null) {
      82           3 :       handle = encryption.ssss.open(EventTypes.CrossSigningMasterKey);
      83           1 :       await handle.unlock(
      84             :         passphrase: passphrase,
      85             :         recoveryKey: recoveryKey,
      86             :         keyOrPassphrase: keyOrPassphrase,
      87             :         postUnlock: false,
      88             :       );
      89           1 :       await handle.maybeCacheAll();
      90             :     }
      91           4 :     final masterPrivateKey = base64decodeUnpadded(
      92           4 :         await handle.getStored(EventTypes.CrossSigningMasterKey));
      93           4 :     final keyObj = olm.PkSigning();
      94             :     String? masterPubkey;
      95             :     try {
      96           4 :       masterPubkey = keyObj.init_with_seed(masterPrivateKey);
      97             :     } catch (e) {
      98             :       masterPubkey = null;
      99             :     } finally {
     100           4 :       keyObj.free();
     101             :     }
     102             :     final userDeviceKeys =
     103          36 :         client.userDeviceKeys[client.userID]?.deviceKeys[client.deviceID];
     104             :     if (masterPubkey == null || userDeviceKeys == null) {
     105           0 :       throw Exception('Master or user keys not found');
     106             :     }
     107          24 :     final masterKey = client.userDeviceKeys[client.userID]?.masterKey;
     108           8 :     if (masterKey == null || masterKey.ed25519Key != masterPubkey) {
     109           0 :       throw Exception('Master pubkey key doesn\'t match');
     110             :     }
     111             :     // master key is valid, set it to verified
     112           4 :     await masterKey.setVerified(true, false);
     113             :     // and now sign both our own key and our master key
     114           8 :     await sign([
     115             :       masterKey,
     116             :       userDeviceKeys,
     117             :     ]);
     118             :   }
     119             : 
     120          15 :   bool signable(List<SignableKey> keys) => keys.any((key) =>
     121          11 :       key is CrossSigningKey && key.usage.contains('master') ||
     122           5 :       key is DeviceKeys &&
     123          20 :           key.userId == client.userID &&
     124          16 :           key.identifier != client.deviceID);
     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, SignableKey signedWith, String signature) {
     137           7 :       final signedKey = key.cloneForSigning();
     138           7 :       ((signedKey.signatures ??=
     139          14 :               <String, Map<String, String>>{})[signedWith.userId] ??=
     140          28 :           <String, String>{})['ed25519:${signedWith.identifier}'] = signature;
     141           7 :       signedKeys.add(signedKey);
     142             :     }
     143             : 
     144          16 :     for (final key in keys) {
     145          32 :       if (key.userId == client.userID) {
     146             :         // we are singing a key of ourself
     147           7 :         if (key is CrossSigningKey) {
     148           8 :           if (key.usage.contains('master')) {
     149             :             // okay, we'll sign our own master key
     150             :             final signature =
     151          16 :                 encryption.olmManager.signString(key.signingContent);
     152          20 :             addSignature(key, userKeys.deviceKeys[client.deviceID]!, signature);
     153             :           }
     154             :           // we don't care about signing other cross-signing keys
     155             :         } else {
     156             :           // okay, we'll sign a device key with our self signing key
     157          21 :           selfSigningKey ??= base64decodeUnpadded(await encryption.ssss
     158           7 :                   .getCached(EventTypes.CrossSigningSelfSigning) ??
     159             :               '');
     160           7 :           if (selfSigningKey.isNotEmpty) {
     161          12 :             final signature = _sign(key.signingContent, selfSigningKey);
     162          12 :             addSignature(key, userKeys.selfSigningKey!, signature);
     163             :           }
     164             :         }
     165           6 :       } else if (key is CrossSigningKey && key.usage.contains('master')) {
     166             :         // we are signing someone elses master key
     167           6 :         userSigningKey ??= base64decodeUnpadded(await encryption.ssss
     168           2 :                 .getCached(EventTypes.CrossSigningUserSigning) ??
     169             :             '');
     170           2 :         if (userSigningKey.isNotEmpty) {
     171           4 :           final signature = _sign(key.signingContent, userSigningKey);
     172           4 :           addSignature(key, userKeys.userSigningKey!, signature);
     173             :         }
     174             :       }
     175             :     }
     176           8 :     if (signedKeys.isNotEmpty) {
     177             :       // post our new keys!
     178           7 :       final payload = <String, Map<String, Map<String, dynamic>>>{};
     179          14 :       for (final key in signedKeys) {
     180           7 :         if (key.identifier == null ||
     181           7 :             key.signatures == null ||
     182          21 :             key.signatures?.isEmpty != false) {
     183             :           continue;
     184             :         }
     185          14 :         if (!payload.containsKey(key.userId)) {
     186          21 :           payload[key.userId] = <String, Map<String, dynamic>>{};
     187             :         }
     188          28 :         if (payload[key.userId]?[key.identifier]?['signatures'] != null) {
     189             :           // we need to merge signature objects
     190           0 :           payload[key.userId]![key.identifier]!['signatures']
     191           0 :               .addAll(key.signatures);
     192             :         } else {
     193             :           // we can just add signatures
     194          35 :           payload[key.userId]![key.identifier!] = key.toJson();
     195             :         }
     196             :       }
     197             : 
     198          14 :       await client.uploadCrossSigningSignatures(payload);
     199             :     }
     200             :   }
     201             : 
     202           7 :   String _sign(String canonicalJson, Uint8List key) {
     203           7 :     final keyObj = olm.PkSigning();
     204             :     try {
     205           7 :       keyObj.init_with_seed(key);
     206           7 :       return keyObj.sign(canonicalJson);
     207             :     } finally {
     208           7 :       keyObj.free();
     209             :     }
     210             :   }
     211             : }

Generated by: LCOV version 1.14