LCOV - code coverage report
Current view: top level - lib/encryption - cross_signing.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 87 92 94.6 %
Date: 2024-11-12 07:37:08 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 1.14