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 : }