Skip to content

Commit f23577a

Browse files
committed
chore: Not storing the common salt using Storage.
All TOTPs are storing their salt, so we just have to use it. This allows to directly clean empty accounts in Firebase.
1 parent 1e82b95 commit f23577a

File tree

14 files changed

+107
-178
lines changed

14 files changed

+107
-178
lines changed

analysis_options.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
include: package:flutter_lints/flutter.yaml
22

33
analyzer:
4+
errors:
5+
unreachable_switch_default: ignore
46
plugins:
57
- custom_lint
68
exclude:

lib/i18n/en/error.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
},
4343
"storageMigration(map)": {
4444
"backupError": "An error occurred while doing the backup. Please try again.",
45-
"saltError": "\"Salt\" error while migrating your data. Please try again later.",
4645
"currentStoragePasswordMismatch": "Invalid master password entered.",
4746
"encryptionKeyChangeFailed": "An error occurred while encrypting your data. Please try again later.",
4847
"genericError": "An error occurred while migrating your data. Please try again later."

lib/i18n/fr/error.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
},
4343
"storageMigration(map)": {
4444
"backupError": "Une erreur est survenue durant la sauvegarde. Veuillez réessayer.",
45-
"saltError": "Erreur de \"Sel\" pendant la migration de vos données. Veuillez réessayer plus tard.",
4645
"currentStoragePasswordMismatch": "Mot de passe maître invalide.",
4746
"encryptionKeyChangeFailed": "Une erreur est survenue pendant le chiffrement de vos données. Veuillez réessayer plus tard.",
4847
"genericError": "Une erreur est survenue pendant la migration de vos données. Veuillez réessayer plus tard."

lib/model/backup.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class Backup implements Comparable<Backup> {
101101

102102
CryptoStore cryptoStore = await CryptoStore.fromPassword(password, Salt.fromRawValue(value: base64.decode(jsonData[kSaltKey])));
103103
HmacSecretKey hmacSecretKey = await HmacSecretKey.importRawKey(await cryptoStore.key.exportRawKey(), Hash.sha256);
104-
if(!(await hmacSecretKey.verifyBytes(base64.decode(jsonData[kPasswordSignatureKey]), utf8.encode(password)))) {
104+
if (!(await hmacSecretKey.verifyBytes(base64.decode(jsonData[kPasswordSignatureKey]), utf8.encode(password)))) {
105105
throw _InvalidPasswordException();
106106
}
107107

@@ -150,7 +150,9 @@ class Backup implements Comparable<Backup> {
150150
file.writeAsString(jsonEncode({
151151
kPasswordSignatureKey: base64.encode(await hmacSecretKey.signBytes(utf8.encode(password))),
152152
kSaltKey: base64.encode(newStore.salt.value),
153-
kTotpsKey: toBackup.map((totp) => totp.toJson()).toList(),
153+
kTotpsKey: [
154+
for (Totp totp in toBackup) totp.toJson(),
155+
],
154156
}));
155157
return const ResultSuccess();
156158
} catch (ex, stacktrace) {
@@ -194,7 +196,9 @@ class _BackupFileDoesNotExistException implements Exception {
194196
final String path;
195197

196198
/// Creates a new backup file doesn't exist exception instance.
197-
_BackupFileDoesNotExistException({required this.path,});
199+
_BackupFileDoesNotExistException({
200+
required this.path,
201+
});
198202

199203
@override
200204
String toString() => 'Backup file does not exist : "$path"';
@@ -218,7 +222,9 @@ class _EncryptionError implements Exception {
218222
final String operationName;
219223

220224
/// Creates a new encryption error instance.
221-
_EncryptionError({required this.operationName,});
225+
_EncryptionError({
226+
required this.operationName,
227+
});
222228

223229
@override
224230
String toString() => 'Error while doing $operationName.';

lib/model/storage/local.dart

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class Totps extends Table {
5151
final localStorageProvider = Provider.autoDispose<LocalStorage>((ref) {
5252
LocalStorage storage = LocalStorage();
5353
ref.onDispose(storage.close);
54-
ref.cacheFor(const Duration(seconds: 3));
54+
ref.cacheFor(const Duration(seconds: 1));
5555
return storage;
5656
});
5757

@@ -116,15 +116,28 @@ class LocalStorage extends _$LocalStorage with Storage {
116116
}
117117

118118
@override
119-
Future<List<Totp>> listTotps() async {
120-
List<_DriftTotp> list = await (select(totps)..orderBy([(table) => OrderingTerm(expression: table.issuer)])).get();
121-
return list.map((totp) => totp.asTotp).toList();
119+
Future<List<Totp>> listTotps({int? limit}) async {
120+
List<_DriftTotp> list = await _listDriftTotps(limit: limit);
121+
return [
122+
for (_DriftTotp driftTotp in list) driftTotp.asTotp,
123+
];
122124
}
123125

124126
@override
125-
Future<List<String>> listUuids() async {
126-
List<_DriftTotp> list = await (select(totps)..orderBy([(table) => OrderingTerm(expression: table.issuer)])).get();
127-
return list.map((totp) => totp.uuid).toList();
127+
Future<List<String>> listUuids({int? limit}) async {
128+
List<_DriftTotp> list = await _listDriftTotps(limit: limit);
129+
return [
130+
for (_DriftTotp driftTotp in list) driftTotp.uuid,
131+
];
132+
}
133+
134+
/// List the Drift TOTPs.
135+
Future<List<_DriftTotp>> _listDriftTotps({int? limit}) async {
136+
SimpleSelectStatement<$TotpsTable, _DriftTotp> query = select(totps)..orderBy([(table) => OrderingTerm(expression: table.issuer)]);
137+
if (limit != null) {
138+
query = query..limit(limit);
139+
}
140+
return await query.get();
128141
}
129142

130143
@override
@@ -133,15 +146,6 @@ class LocalStorage extends _$LocalStorage with Storage {
133146
batch.insertAll(totps, newTotps.map((totp) => totp.asDriftTotp));
134147
});
135148

136-
@override
137-
Future<Salt?> readSecretsSalt() async => await Salt.readFromLocalStorage();
138-
139-
@override
140-
Future<void> saveSecretsSalt(Salt salt) => salt.saveToLocalStorage();
141-
142-
@override
143-
Future<void> deleteSecretsSalt() => Salt.deleteFromLocalStorage();
144-
145149
@override
146150
Future<void> onStorageTypeChanged({bool close = true}) async {
147151
await clearTotps();

lib/model/storage/online.dart

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import 'dart:async';
2-
import 'dart:typed_data';
32

43
import 'package:cloud_firestore/cloud_firestore.dart';
54
import 'package:firebase_core/firebase_core.dart';
6-
import 'package:flutter/foundation.dart';
75
import 'package:flutter_riverpod/flutter_riverpod.dart';
86
import 'package:open_authenticator/app.dart';
97
import 'package:open_authenticator/model/authentication/firebase_authentication.dart';
108
import 'package:open_authenticator/model/authentication/state.dart';
11-
import 'package:open_authenticator/model/crypto.dart';
129
import 'package:open_authenticator/model/storage/storage.dart';
1310
import 'package:open_authenticator/model/storage/type.dart';
1411
import 'package:open_authenticator/model/totp/json.dart';
@@ -33,9 +30,6 @@ class OnlineStorage with Storage {
3330
/// The last updated key.
3431
static const String _kUpdatedKey = 'updated';
3532

36-
/// The salt key.
37-
static const String _kSaltKey = 'salt';
38-
3933
/// The user id.
4034
final String? _userId;
4135

@@ -108,10 +102,10 @@ class OnlineStorage with Storage {
108102
}
109103

110104
@override
111-
Future<List<Totp>> listTotps({GetOptions? getOptions}) async {
112-
QuerySnapshot result = await _totpsCollection.orderBy(Totp.kIssuerKey).get(getOptions);
105+
Future<List<Totp>> listTotps({int? limit, GetOptions? getOptions}) async {
106+
List<QueryDocumentSnapshot> docs = await _listTotpDocs(limit: limit, getOptions: getOptions);
113107
List<Totp> totps = [];
114-
for (QueryDocumentSnapshot doc in result.docs) {
108+
for (QueryDocumentSnapshot doc in docs) {
115109
Totp? totp = _FirestoreTotp.fromFirestore(doc);
116110
if (totp != null) {
117111
totps.add(totp);
@@ -121,10 +115,10 @@ class OnlineStorage with Storage {
121115
}
122116

123117
@override
124-
Future<List<String>> listUuids() async {
125-
QuerySnapshot result = await _totpsCollection.orderBy(Totp.kIssuerKey).get();
118+
Future<List<String>> listUuids({int? limit}) async {
119+
List<QueryDocumentSnapshot> docs = await _listTotpDocs(limit: limit);
126120
List<String> uuids = [];
127-
for (QueryDocumentSnapshot snapshot in result.docs) {
121+
for (QueryDocumentSnapshot snapshot in docs) {
128122
Object? data = snapshot.data();
129123
if (data is Map<String, Object?> && data.containsKey(Totp.kUuidKey)) {
130124
uuids.add(data[Totp.kUuidKey]!.toString());
@@ -133,6 +127,16 @@ class OnlineStorage with Storage {
133127
return uuids;
134128
}
135129

130+
/// List the TOTPs documents.
131+
Future<List<QueryDocumentSnapshot>> _listTotpDocs({int? limit, GetOptions? getOptions}) async {
132+
Query query = _totpsCollection.orderBy(Totp.kIssuerKey);
133+
if (limit != null) {
134+
query = query.limit(limit);
135+
}
136+
QuerySnapshot result = await query.get(getOptions);
137+
return result.docs;
138+
}
139+
136140
@override
137141
Future<void> replaceTotps(List<Totp> newTotps) async {
138142
CollectionReference? collection = _totpsCollection;
@@ -147,43 +151,6 @@ class OnlineStorage with Storage {
147151
await batch.commit();
148152
}
149153

150-
@override
151-
Future<Salt?> readSecretsSalt() async {
152-
DocumentSnapshot<Map<String, dynamic>> userDoc = await _userDocument.get();
153-
if (!userDoc.exists) {
154-
return null;
155-
}
156-
List salt = (userDoc.data() as Map<String, dynamic>)[_kSaltKey];
157-
return Salt.fromRawValue(value: Uint8List.fromList(salt.cast<int>()));
158-
}
159-
160-
@override
161-
Future<void> saveSecretsSalt(Salt salt) async {
162-
DocumentReference<Map<String, dynamic>> userDoc = _userDocument;
163-
await userDoc.set(
164-
{
165-
_kSaltKey: salt.value,
166-
_kUpdatedKey: FieldValue.serverTimestamp(),
167-
},
168-
SetOptions(merge: true),
169-
);
170-
}
171-
172-
@override
173-
Future<void> deleteSecretsSalt() async {
174-
DocumentSnapshot<Map<String, dynamic>> userDoc = await _userDocument.get();
175-
if (!userDoc.exists) {
176-
return;
177-
}
178-
Map<String, dynamic> data = userDoc.data() as Map<String, dynamic>;
179-
data.remove(_kSaltKey);
180-
if (data.isEmpty || (data.keys.length == 1 && data.keys.first == _kUpdatedKey)) {
181-
await _userDocument.delete();
182-
} else {
183-
await _userDocument.set(data);
184-
}
185-
}
186-
187154
@override
188155
Future<void> close() async {
189156
_cancelSubscription();

lib/model/storage/storage.dart

Lines changed: 21 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@ class StorageNotifier extends AutoDisposeAsyncNotifier<Storage> {
4343
return const ResultSuccess();
4444
}
4545

46-
Salt? oldSalt = await currentStorage.readSecretsSalt();
47-
if (oldSalt == null) {
48-
throw SaltError();
49-
}
50-
5146
if (backupPassword != null) {
5247
Result<Backup> backupResult = await ref.read(backupStoreProvider.notifier).doBackup(backupPassword);
5348
if (backupResult is! ResultSuccess) {
@@ -74,17 +69,22 @@ class StorageNotifier extends AutoDisposeAsyncNotifier<Storage> {
7469
}
7570
}
7671

77-
CryptoStore currentCryptoStore = await CryptoStore.fromPassword(masterPassword, oldSalt);
78-
Salt? salt = await newStorage.readSecretsSalt();
79-
List<Totp> totps = await currentStorage.listTotps();
72+
List<Totp> currentTotps = await currentStorage.listTotps();
73+
Totp? firstTotp = (await newStorage.listTotps(limit: 1)).firstOrNull;
8074
List<Totp> toAdd = [];
81-
if (salt == null) {
82-
await newStorage.saveSecretsSalt(oldSalt);
83-
toAdd.addAll(totps);
75+
if (firstTotp == null) {
76+
toAdd.addAll(currentTotps);
8477
} else {
85-
CryptoStore newCryptoStore = await CryptoStore.fromPassword(masterPassword, salt);
86-
for (Totp totp in totps) {
87-
DecryptedTotp? decryptedTotp = await totp.changeEncryptionKey(currentCryptoStore, newCryptoStore);
78+
CryptoStore? currentCryptoStore = ref.read(cryptoStoreProvider).value;
79+
CryptoStore newCryptoStore = await CryptoStore.fromPassword(masterPassword, firstTotp.encryptedData.encryptionSalt);
80+
for (Totp totp in currentTotps) {
81+
CryptoStore oldCryptoStore = currentCryptoStore?.salt == totp.encryptedData.encryptionSalt
82+
? currentCryptoStore!
83+
: await CryptoStore.fromPassword(
84+
masterPassword,
85+
totp.encryptedData.encryptionSalt,
86+
);
87+
DecryptedTotp? decryptedTotp = await totp.changeEncryptionKey(oldCryptoStore, newCryptoStore);
8888
toAdd.add(decryptedTotp ?? totp);
8989
}
9090
await ref.read(cryptoStoreProvider.notifier).saveAndUse(newCryptoStore);
@@ -135,7 +135,7 @@ class GenericMigrationError extends StorageMigrationException {
135135
/// Whether we should ask for a different [StorageMigrationDeletedTotpPolicy].
136136
class ShouldAskForDifferentDeletedTotpPolicyException extends StorageMigrationException {
137137
/// The error code.
138-
static const String _code = 'genericError';
138+
static const String _code = 'shouldAskForDifferentDeletedTotpPolicy';
139139

140140
/// Creates a new storage migration policy exception instance.
141141
ShouldAskForDifferentDeletedTotpPolicyException()
@@ -144,28 +144,13 @@ class ShouldAskForDifferentDeletedTotpPolicyException extends StorageMigrationEx
144144
);
145145

146146
@override
147-
String toString() => 'StorageMigrationDeletedTotpPolicy error';
148-
}
149-
150-
/// When there is a salt error.
151-
class SaltError extends StorageMigrationException {
152-
/// The error code.
153-
static const String _code = 'genericError';
154-
155-
/// Creates a new salt error instance.
156-
SaltError()
157-
: super(
158-
code: _code,
159-
);
160-
161-
@override
162-
String toString() => 'Salt error';
147+
String toString() => 'Another deleted TOTP policy should be used';
163148
}
164149

165150
/// When we haven't succeeded to do the asked backup.
166151
class BackupException extends StorageMigrationException {
167152
/// The error code.
168-
static const String _code = 'genericError';
153+
static const String _code = 'backupError';
169154

170155
/// Creates a new backup exception instance.
171156
BackupException()
@@ -180,7 +165,7 @@ class BackupException extends StorageMigrationException {
180165
/// When the provided password don't match the one that has been using on the old storage.
181166
class CurrentStoragePasswordMismatchException extends StorageMigrationException {
182167
/// The error code.
183-
static const String _code = 'genericError';
168+
static const String _code = 'currentStoragePasswordMismatch';
184169

185170
/// Creates a new current storage password mismatch exception instance.
186171
CurrentStoragePasswordMismatchException()
@@ -192,25 +177,10 @@ class CurrentStoragePasswordMismatchException extends StorageMigrationException
192177
String toString() => 'Current storage password is incorrect';
193178
}
194179

195-
/// When the provided password don't match the one that has been using on the new storage.
196-
class NewStoragePasswordMismatchException extends StorageMigrationException {
197-
/// The error code.
198-
static const String _code = 'genericError';
199-
200-
/// Creates a new new storage password mismatch exception instance.
201-
NewStoragePasswordMismatchException()
202-
: super(
203-
code: _code,
204-
);
205-
206-
@override
207-
String toString() => 'New storage password is incorrect';
208-
}
209-
210180
/// When there is an error while trying to change the encryption key of the old storage.
211181
class EncryptionKeyChangeFailedError extends StorageMigrationException {
212182
/// The error code.
213-
static const String _code = 'genericError';
183+
static const String _code = 'encryptionKeyChangeFailed';
214184

215185
/// Creates a new encryption key change error instance.
216186
EncryptionKeyChangeFailedError()
@@ -264,26 +234,17 @@ mixin Storage {
264234
Future<Totp?> getTotp(String uuid);
265235

266236
/// Lists all TOTPs.
267-
Future<List<Totp>> listTotps();
237+
Future<List<Totp>> listTotps({int? limit});
268238

269239
/// Lists all TOTPs UUID.
270-
Future<List<String>> listUuids();
240+
Future<List<String>> listUuids({int? limit});
271241

272242
/// Replace all current TOTPs by [newTotps].
273243
Future<void> replaceTotps(List<Totp> newTotps) async {
274244
await clearTotps();
275245
await addTotps(newTotps);
276246
}
277247

278-
/// Loads the salt that allows to encrypt secrets.
279-
Future<Salt?> readSecretsSalt();
280-
281-
/// Saves the salt that allows to encrypt secrets.
282-
Future<void> saveSecretsSalt(Salt salt);
283-
284-
/// Deletes the salt that allows to encrypt secrets.
285-
Future<void> deleteSecretsSalt();
286-
287248
/// Closes this storage instance.
288249
Future<void> close();
289250

0 commit comments

Comments
 (0)