Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 : import 'dart:typed_data';
4 :
5 : import 'package:at_client/at_client.dart';
6 : import 'package:at_commons/at_builders.dart';
7 : import 'package:at_commons/at_commons.dart';
8 : import 'package:at_lookup/at_lookup.dart';
9 : import 'package:at_utils/at_logger.dart';
10 : import 'package:crypton/crypton.dart';
11 :
12 : class EncryptionService {
13 : RemoteSecondary? remoteSecondary;
14 :
15 : LocalSecondary? localSecondary;
16 :
17 : String? currentAtSign;
18 :
19 : var logger = AtSignLogger('EncryptionService');
20 :
21 0 : Future<String> encrypt(String? key, String value, String sharedWith) async {
22 : var isSharedKeyAvailable = false;
23 : var currentAtSignPublicKey =
24 0 : await localSecondary!.getEncryptionPublicKey(currentAtSign!);
25 : var currentAtSignPrivateKey =
26 0 : await localSecondary!.getEncryptionPrivateKey();
27 0 : var sharedWithUser = sharedWith.replaceFirst('@', '');
28 :
29 : //1. Get/Generate AES key for sharedWith atsign
30 0 : var llookupVerbBuilder = LLookupVerbBuilder()
31 0 : ..atKey = '$AT_ENCRYPTION_SHARED_KEY.$sharedWithUser'
32 0 : ..sharedBy = currentAtSign;
33 : String? sharedKey;
34 : try {
35 0 : sharedKey = await localSecondary!.executeVerb(llookupVerbBuilder);
36 0 : } on KeyNotFoundException {
37 0 : logger.finer(
38 0 : '${llookupVerbBuilder.atKey}$currentAtSign not found in local secondary. Fetching from cloud secondary.');
39 : }
40 : // If sharedKey is not found in localSecondary, search in remote secondary.
41 : try {
42 0 : if (sharedKey == null || sharedKey == 'data:null') {
43 0 : sharedKey = await remoteSecondary!.executeVerb(llookupVerbBuilder);
44 : }
45 0 : } on AtLookUpException {
46 0 : logger.finer(
47 0 : '${llookupVerbBuilder.atKey}$currentAtSign not found in remote secondary. Generating a new shared key');
48 : }
49 : // If sharedKey is null, generate a new sharedKey,
50 : // Else decrypt the existing shared key.
51 0 : if (sharedKey == null || sharedKey == 'data:null') {
52 0 : logger.finer('Generated a AES Key for $sharedWithUser');
53 0 : sharedKey = EncryptionUtil.generateAESKey();
54 : } else {
55 : isSharedKeyAvailable = true;
56 0 : sharedKey = sharedKey.replaceFirst('data:', '');
57 : sharedKey =
58 0 : EncryptionUtil.decryptKey(sharedKey, currentAtSignPrivateKey!);
59 : }
60 : //2. Verify if encryptedSharedKey for sharedWith atSign is available.
61 0 : var lookupEncryptionSharedKey = LLookupVerbBuilder()
62 0 : ..sharedWith = sharedWith
63 0 : ..sharedBy = currentAtSign
64 0 : ..atKey = AT_ENCRYPTION_SHARED_KEY;
65 : String? result;
66 : try {
67 0 : result = await localSecondary!.executeVerb(lookupEncryptionSharedKey);
68 0 : } on KeyNotFoundException {
69 0 : logger.finer(
70 0 : '$sharedWith:$AT_ENCRYPTION_SHARED_KEY@$currentAtSign not found in local secondary. Fetching from cloud secondary');
71 : }
72 :
73 : //3. Create the encryptedSharedKey if
74 : // a. encryptedSharedKey not available (or)
75 : // b. If the sharedKey is changed.
76 0 : if (result == null || result == 'data:null' || !isSharedKeyAvailable) {
77 : var sharedWithPublicKey;
78 : try {
79 0 : sharedWithPublicKey = await _getSharedWithPublicKey(sharedWithUser);
80 0 : } on Exception {
81 : rethrow;
82 : }
83 : //Encrypt shared key with public key of sharedWith atsign.
84 : var encryptedSharedKey =
85 0 : EncryptionUtil.encryptKey(sharedKey, sharedWithPublicKey);
86 : // Store the encryptedSharedWith Key. Set ttr to enable sharedWith atsign to cache the encryptedSharedKey.
87 0 : var updateSharedKeyBuilder = UpdateVerbBuilder()
88 0 : ..sharedWith = sharedWith
89 0 : ..sharedBy = currentAtSign
90 0 : ..atKey = AT_ENCRYPTION_SHARED_KEY
91 0 : ..value = encryptedSharedKey
92 0 : ..ttr = 3888000;
93 0 : await localSecondary!.executeVerb(updateSharedKeyBuilder, sync: true);
94 : }
95 :
96 : //4. Store the sharedKey for future retrival.
97 : if (!isSharedKeyAvailable) {
98 : // Encrypt the sharedKey with currentAtSign Public key and store it.
99 : var encryptedSharedKeyForCurrentAtSign =
100 0 : EncryptionUtil.encryptKey(sharedKey, currentAtSignPublicKey!);
101 :
102 0 : var updateSharedKeyForCurrentAtSignBuilder = UpdateVerbBuilder()
103 0 : ..sharedBy = currentAtSign
104 0 : ..atKey = '$AT_ENCRYPTION_SHARED_KEY.$sharedWithUser'
105 0 : ..value = encryptedSharedKeyForCurrentAtSign;
106 0 : await localSecondary!
107 0 : .executeVerb(updateSharedKeyForCurrentAtSignBuilder, sync: true);
108 : }
109 :
110 : //5. Encrypt value using sharedKey
111 0 : var encryptedValue = EncryptionUtil.encryptValue(value, sharedKey);
112 : return encryptedValue;
113 : }
114 :
115 : /// Returns the decrypted value for the given encrypted value.
116 : /// Throws [IllegalArgumentException] if encrypted value is null.
117 : /// Throws [KeyNotFoundException] if encryption keys are not found.
118 0 : Future<String> decrypt(String encryptedValue, String sharedBy) async {
119 0 : if (encryptedValue == null || encryptedValue.isEmpty) {
120 0 : throw IllegalArgumentException(
121 : 'Decryption failed. Encrypted value is null');
122 : }
123 0 : sharedBy = sharedBy.replaceFirst('@', '');
124 : var encryptedSharedKey;
125 : //1. Get encrypted shared key
126 0 : encryptedSharedKey = await _getEncryptedSharedKey(sharedBy);
127 0 : if (encryptedSharedKey == 'null' || encryptedSharedKey.isEmpty) {
128 0 : throw KeyNotFoundException('encrypted Shared key not found');
129 : }
130 :
131 : //2. decrypt shared key using private key
132 : var currentAtSignPrivateKey =
133 0 : await (localSecondary!.getEncryptionPrivateKey());
134 : if (currentAtSignPrivateKey == null) {
135 0 : throw KeyNotFoundException('encryption private not found');
136 : }
137 : var sharedKey =
138 0 : EncryptionUtil.decryptKey(encryptedSharedKey, currentAtSignPrivateKey);
139 :
140 : //3. decrypt value using shared key
141 0 : var decryptedValue = EncryptionUtil.decryptValue(encryptedValue, sharedKey);
142 : return decryptedValue;
143 : }
144 :
145 : ///Returns `decrypted value` on successful decryption.
146 : /// Used for local lookup @bob:phone@alice
147 : /// Throws [IllegalArgumentException] if encrypted value is null.
148 0 : Future<String?> decryptLocal(String? encryptedValue, String? currentAtSign,
149 : String sharedWithUser) async {
150 0 : if (encryptedValue == null || encryptedValue.isEmpty) {
151 0 : throw IllegalArgumentException(
152 : 'Decryption failed. Encrypted value is null');
153 : }
154 0 : sharedWithUser = sharedWithUser.replaceFirst('@', '');
155 : var currentAtSignPrivateKey =
156 0 : await localSecondary!.getEncryptionPrivateKey();
157 0 : var llookupVerbBuilder = LLookupVerbBuilder()
158 0 : ..atKey = '$AT_ENCRYPTION_SHARED_KEY.$sharedWithUser'
159 0 : ..sharedBy = currentAtSign;
160 0 : var sharedKey = await localSecondary!.executeVerb(llookupVerbBuilder);
161 : if (sharedKey == null) {
162 0 : logger.severe('Decryption failed. SharedKey is null');
163 0 : throw AtClientException('AT0014', 'Decryption failed. SharedKey is null');
164 : }
165 : //trying to llookup a value without shared key. throw exception or return null}
166 0 : sharedKey = sharedKey.replaceFirst('data:', '');
167 : var decryptedSharedKey =
168 0 : EncryptionUtil.decryptKey(sharedKey, currentAtSignPrivateKey!);
169 : var decryptedValue =
170 0 : EncryptionUtil.decryptValue(encryptedValue, decryptedSharedKey);
171 :
172 : return decryptedValue;
173 : }
174 :
175 : /// returns encrypted value
176 0 : Future<String?> encryptForSelf(String? key, String value) async {
177 : try {
178 : // //1. Get AES key for current atsign
179 0 : var selfEncryptionKey = await _getSelfEncryptionKey();
180 0 : if (selfEncryptionKey == null || selfEncryptionKey == 'data:null') {
181 0 : throw Exception(
182 0 : 'Self encryption key is not set for atsign $currentAtSign');
183 : } else {
184 0 : selfEncryptionKey = selfEncryptionKey.replaceFirst('data:', '');
185 : }
186 :
187 : // Encrypt value using sharedKey
188 : var encryptedValue =
189 0 : EncryptionUtil.encryptValue(value, selfEncryptionKey);
190 : return encryptedValue;
191 0 : } on Exception catch (e) {
192 0 : logger.severe(
193 0 : 'Exception while encrypting value for key $key: ${e.toString()}');
194 : return null;
195 : }
196 : }
197 :
198 : /// Returns decrypted value
199 : /// Used for local lookup @alice:phone@alice
200 : /// Throws [IllegalArgumentException] if encrypted value is null.
201 0 : Future<String?> decryptForSelf(
202 : String? encryptedValue, bool isEncrypted) async {
203 0 : if (encryptedValue == null || encryptedValue == 'null') {
204 0 : throw IllegalArgumentException(
205 : 'Decryption failed. Encrypted value is null');
206 : }
207 : if (!isEncrypted) {
208 0 : logger.info(
209 : 'isEncrypted is set to false, Returning the original value without decrypting');
210 : return encryptedValue;
211 : }
212 : try {
213 0 : var selfEncryptionKey = await _getSelfEncryptionKey();
214 0 : if (selfEncryptionKey == null || selfEncryptionKey == 'data:null') {
215 : return encryptedValue;
216 : }
217 0 : selfEncryptionKey = selfEncryptionKey.toString().replaceAll('data:', '');
218 : // decrypt value using self encryption key
219 : var decryptedValue =
220 0 : EncryptionUtil.decryptValue(encryptedValue, selfEncryptionKey);
221 : return decryptedValue;
222 0 : } on Exception catch (e) {
223 0 : logger.severe('Exception while decrypting value: ${e.toString()}');
224 : return null;
225 0 : } on Error catch (e) {
226 0 : logger.severe('Exception while decrypting value: ${e.toString()}');
227 : return null;
228 : }
229 : }
230 :
231 : //TODO remove code duplication - encrypt and encryptStream
232 0 : Future<List<int>> encryptStream(List<int> value, String sharedWith) async {
233 : var currentAtSignPublicKey =
234 0 : await (localSecondary!.getEncryptionPublicKey(currentAtSign!));
235 : var currentAtSignPrivateKey =
236 0 : await localSecondary!.getEncryptionPrivateKey();
237 0 : var sharedWithUser = sharedWith.replaceFirst('@', '');
238 : // //1. Get/Generate AES key for sharedWith atsign
239 0 : var llookupVerbBuilder = LLookupVerbBuilder()
240 0 : ..atKey = '$AT_ENCRYPTION_SHARED_KEY.$sharedWithUser'
241 0 : ..sharedBy = currentAtSign;
242 : String? sharedKey;
243 : try {
244 0 : sharedKey = await localSecondary!.executeVerb(llookupVerbBuilder);
245 0 : } on KeyNotFoundException {
246 0 : logger.finer('${llookupVerbBuilder.atKey} not found in local secondary');
247 : }
248 0 : if (sharedKey == null || sharedKey == 'data:null') {
249 0 : sharedKey = EncryptionUtil.generateAESKey();
250 : } else {
251 0 : sharedKey = sharedKey.replaceFirst('data:', '');
252 : sharedKey =
253 0 : EncryptionUtil.decryptKey(sharedKey, currentAtSignPrivateKey!);
254 : }
255 : //2. Lookup public key of sharedWith atsign
256 0 : var plookupBuilder = PLookupVerbBuilder()
257 0 : ..atKey = 'publickey'
258 0 : ..sharedBy = sharedWith;
259 : var sharedWithPublicKey =
260 0 : await remoteSecondary!.executeAndParse(plookupBuilder);
261 0 : if (sharedWithPublicKey == 'null' || sharedWithPublicKey.isEmpty) {
262 0 : throw KeyNotFoundException(
263 : 'shared key not found. data sharing is forbidden.');
264 : }
265 : //3. Encrypt shared key with public key of sharedWith atsign and store
266 : var encryptedSharedKey =
267 0 : EncryptionUtil.encryptKey(sharedKey, sharedWithPublicKey);
268 :
269 0 : var updateSharedKeyBuilder = UpdateVerbBuilder()
270 0 : ..sharedWith = sharedWith
271 0 : ..sharedBy = currentAtSign
272 0 : ..atKey = AT_ENCRYPTION_SHARED_KEY
273 0 : ..value = encryptedSharedKey;
274 0 : await localSecondary!.executeVerb(updateSharedKeyBuilder, sync: true);
275 :
276 : //4. Store the shared key for future retrieval
277 : if (currentAtSignPublicKey == null) {
278 0 : throw KeyNotFoundException('encryption public key not found');
279 : }
280 : var encryptedSharedKeyForCurrentAtSign =
281 0 : EncryptionUtil.encryptKey(sharedKey, currentAtSignPublicKey);
282 :
283 0 : var updateSharedKeyForCurrentAtSignBuilder = UpdateVerbBuilder()
284 0 : ..sharedBy = currentAtSign
285 0 : ..atKey = '$AT_ENCRYPTION_SHARED_KEY.$sharedWithUser'
286 0 : ..value = encryptedSharedKeyForCurrentAtSign;
287 0 : await localSecondary!
288 0 : .executeVerb(updateSharedKeyForCurrentAtSignBuilder, sync: true);
289 :
290 : //5. Encrypt value using sharedKey
291 0 : var encryptedValue = EncryptionUtil.encryptBytes(value, sharedKey);
292 : return encryptedValue;
293 : }
294 :
295 0 : List<int> decryptStream(List<int> encryptedValue, String sharedKey) {
296 : //decrypt stream using decrypted aes shared key
297 0 : var decryptedValue = EncryptionUtil.decryptBytes(encryptedValue, sharedKey);
298 : return decryptedValue;
299 : }
300 :
301 0 : Future<bool> verifyPublicDataSignature(
302 : String sharedBy, String dataSignature, String value) async {
303 0 : var cachedPublicKeyBuilder = LLookupVerbBuilder()
304 0 : ..atKey = 'publickey.$sharedBy'
305 0 : ..sharedBy = currentAtSign;
306 : String? sharedByPublicKey;
307 : try {
308 : sharedByPublicKey =
309 0 : await localSecondary!.executeVerb(cachedPublicKeyBuilder);
310 0 : } on KeyNotFoundException {
311 0 : logger.finer(
312 0 : '${cachedPublicKeyBuilder.atKey} not found in local secondary');
313 : }
314 0 : if (sharedByPublicKey == null || sharedByPublicKey == 'data:null') {
315 0 : var plookupBuilder = PLookupVerbBuilder()
316 0 : ..atKey = 'publickey'
317 0 : ..sharedBy = sharedBy;
318 : sharedByPublicKey =
319 0 : await remoteSecondary!.executeAndParse(plookupBuilder);
320 : //4.b store sharedWith public key for future retrieval
321 0 : var sharedWithPublicKeyBuilder = UpdateVerbBuilder()
322 0 : ..atKey = 'publickey.$sharedBy'
323 0 : ..sharedBy = currentAtSign
324 0 : ..value = sharedByPublicKey;
325 0 : await localSecondary!
326 0 : .executeVerb(sharedWithPublicKeyBuilder, sync: false);
327 : } else {
328 0 : sharedByPublicKey = sharedByPublicKey.replaceFirst('data:', '');
329 : }
330 : // if (sharedByPublicKey == null) {
331 : // logger.severe('unable to verify public data sharedBy: $sharedBy');
332 : // return false;
333 : // }
334 0 : var publicKey = RSAPublicKey.fromString(sharedByPublicKey);
335 0 : var isDataValid = publicKey.verifySHA256Signature(
336 0 : utf8.encode(value) as Uint8List, base64Decode(dataSignature));
337 : return isDataValid;
338 : }
339 :
340 0 : String signPublicData(String encryptionPrivateKey, dynamic value) {
341 0 : var privateKey = RSAPrivateKey.fromString(encryptionPrivateKey);
342 : var dataSignature =
343 0 : privateKey.createSHA256Signature(utf8.encode(value) as Uint8List);
344 0 : return base64Encode(dataSignature);
345 : }
346 :
347 : @Deprecated('Not in use')
348 0 : Future<void> encryptUnencryptedData() async {
349 0 : var atClient = await (AtClientImpl.getClient(currentAtSign));
350 : if (atClient == null) {
351 : return;
352 : }
353 0 : var selfKeys = await atClient.getAtKeys(sharedBy: currentAtSign);
354 0 : selfKeys.forEach((atKey) async {
355 0 : var key = atKey.key!;
356 0 : if (!(key.startsWith(AT_PKAM_PRIVATE_KEY) ||
357 0 : key.startsWith(AT_PKAM_PUBLIC_KEY) ||
358 0 : key.startsWith(AT_ENCRYPTION_PRIVATE_KEY) ||
359 0 : key.startsWith(AT_SIGNING_PRIVATE_KEY) ||
360 0 : key.startsWith(AT_ENCRYPTION_SHARED_KEY) ||
361 0 : key.startsWith('_'))) {
362 0 : var sharedWith = atKey.sharedWith;
363 : var isPublic = false;
364 : var isCached = false;
365 0 : if (atKey.metadata != null) {
366 0 : isPublic = atKey.metadata?.isPublic ?? false;
367 0 : isCached = atKey.metadata?.isCached ?? false;
368 : }
369 : if (!isPublic && !isCached) {
370 0 : if (sharedWith == null || sharedWith == currentAtSign) {
371 0 : var atValue = await atClient.get(atKey);
372 : var metadata =
373 0 : (atValue.metadata != null) ? atValue.metadata! : Metadata();
374 : var isEncrypted =
375 0 : (metadata.isEncrypted != null) ? metadata.isEncrypted! : false;
376 : if (!isEncrypted) {
377 0 : var value = atValue.value;
378 0 : metadata.isEncrypted = true;
379 0 : metadata.isBinary =
380 0 : (metadata.isBinary != null) ? metadata.isBinary : false;
381 0 : atKey.metadata = metadata;
382 0 : await atClient.put(atKey, value);
383 : }
384 : }
385 : }
386 : }
387 : });
388 0 : await atClient.getSyncManager()!.sync();
389 : }
390 :
391 0 : Future<String?> _getSelfEncryptionKey() async {
392 0 : var selfEncryptionKey = await localSecondary!.getEncryptionSelfKey();
393 : return selfEncryptionKey;
394 : }
395 :
396 : /// Returns sharedWith atSign publicKey.
397 : /// Throws [KeyNotFoundException] if sharedWith atSign publicKey is not found.
398 0 : Future<String?> _getSharedWithPublicKey(String sharedWithUser) async {
399 : //a local lookup the cached public key of sharedWith atsign.
400 : String? sharedWithPublicKey;
401 0 : var cachedPublicKeyBuilder = LLookupVerbBuilder()
402 0 : ..atKey = 'publickey.$sharedWithUser'
403 0 : ..sharedBy = currentAtSign;
404 : try {
405 : sharedWithPublicKey =
406 0 : await localSecondary!.executeVerb(cachedPublicKeyBuilder);
407 0 : } on KeyNotFoundException {
408 0 : logger.finer(
409 0 : '${cachedPublicKeyBuilder.atKey}@$currentAtSign not found in local secondary. Fetching from cloud secondary');
410 : }
411 0 : if (sharedWithPublicKey != null && sharedWithPublicKey != 'data:null') {
412 : sharedWithPublicKey =
413 0 : sharedWithPublicKey.toString().replaceAll('data:', '');
414 : return sharedWithPublicKey;
415 : }
416 :
417 : //b Lookup public key of sharedWith atsign
418 0 : var plookupBuilder = PLookupVerbBuilder()
419 0 : ..atKey = 'publickey'
420 0 : ..sharedBy = sharedWithUser;
421 : sharedWithPublicKey =
422 0 : await remoteSecondary!.executeAndParse(plookupBuilder);
423 :
424 : // If SharedWith PublicKey is not found throw KeyNotFoundException.
425 0 : if (sharedWithPublicKey == 'null' || sharedWithPublicKey.isEmpty) {
426 0 : throw KeyNotFoundException(
427 : 'public key not found. data sharing is forbidden.');
428 : }
429 : //Cache the sharedWithPublicKey
430 0 : var sharedWithPublicKeyBuilder = UpdateVerbBuilder()
431 0 : ..atKey = 'publickey.$sharedWithUser'
432 0 : ..sharedBy = currentAtSign
433 0 : ..value = sharedWithPublicKey;
434 0 : await localSecondary!.executeVerb(sharedWithPublicKeyBuilder, sync: true);
435 : return sharedWithPublicKey;
436 : }
437 :
438 0 : Future<String?> _getEncryptedSharedKey(String sharedBy) async {
439 : String? encryptedSharedKey;
440 0 : var localLookupSharedKeyBuilder = LLookupVerbBuilder()
441 0 : ..isCached = true
442 0 : ..sharedBy = sharedBy
443 0 : ..sharedWith = currentAtSign
444 0 : ..atKey = AT_ENCRYPTION_SHARED_KEY;
445 : try {
446 : encryptedSharedKey =
447 0 : await localSecondary!.executeVerb(localLookupSharedKeyBuilder);
448 0 : } on KeyNotFoundException {
449 0 : logger.finer(
450 0 : '$sharedBy:${localLookupSharedKeyBuilder.atKey}@$currentAtSign not found in local secondary. Fetching from cloud secondary');
451 : }
452 0 : if (encryptedSharedKey == null || encryptedSharedKey == 'data:null') {
453 0 : var sharedKeyLookUpBuilder = LookupVerbBuilder()
454 0 : ..atKey = AT_ENCRYPTION_SHARED_KEY
455 0 : ..sharedBy = sharedBy
456 0 : ..auth = true;
457 : encryptedSharedKey =
458 0 : await remoteSecondary!.executeAndParse(sharedKeyLookUpBuilder);
459 : }
460 0 : if (encryptedSharedKey.isNotEmpty) {
461 0 : encryptedSharedKey = encryptedSharedKey.replaceFirst('data:', '');
462 : }
463 : return encryptedSharedKey;
464 : }
465 :
466 0 : Future<String> getSharedKey(String sharedBy) async {
467 0 : sharedBy = sharedBy.replaceFirst('@', '');
468 :
469 0 : var encryptedSharedKey = await _getEncryptedSharedKey(sharedBy);
470 0 : if (encryptedSharedKey == null || encryptedSharedKey == 'null') {
471 0 : throw KeyNotFoundException('encrypted Shared key not found');
472 : }
473 : //2. decrypt shared key using private key
474 : var currentAtSignPrivateKey =
475 0 : await (localSecondary!.getEncryptionPrivateKey());
476 : if (currentAtSignPrivateKey == null) {
477 0 : throw KeyNotFoundException('private encryption key not found');
478 : }
479 : var sharedKey =
480 0 : EncryptionUtil.decryptKey(encryptedSharedKey, currentAtSignPrivateKey);
481 : return sharedKey;
482 : }
483 :
484 0 : String generateFileEncryptionKey() {
485 0 : return EncryptionUtil.generateAESKey();
486 : }
487 :
488 0 : List<int> encryptFile(List<int> fileContent, String fileEncryptionKey) {
489 0 : return EncryptionUtil.encryptBytes(fileContent, fileEncryptionKey);
490 : }
491 :
492 0 : List<int> decryptFile(List<int> fileContent, String decryptionKey) {
493 : var encryptedValue =
494 0 : EncryptionUtil.decryptBytes(fileContent, decryptionKey);
495 : return encryptedValue;
496 : }
497 : }
|