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