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