Pull the key from server on sign in

This commit is contained in:
Vishnu Mohandas 2020-08-15 06:52:14 +05:30
parent f645e00b4e
commit 3d3c1496e7
5 changed files with 97 additions and 35 deletions

View file

@ -1,3 +1,6 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart';
import 'package:photos/utils/crypto_util.dart';
@ -14,6 +17,9 @@ class Configuration {
static const passwordKey = "password";
static const hasOptedForE2EKey = "has_opted_for_e2e_encryption";
static const keyKey = "key";
static const keyEncryptedKey = "encrypted_key";
static final String iv = base64.encode(List.filled(16, 0));
SharedPreferences _preferences;
String _documentsDirectory;
@ -29,6 +35,15 @@ class Configuration {
new io.Directory(_thumbnailsDirectory).createSync(recursive: true);
}
Future<void> generateAndSaveKey(String passphrase) async {
final key = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
await setKey(key);
final hashedPassphrase = sha256.convert(passphrase.codeUnits);
final encryptedKey = CryptoUtil.encryptToBase64(
key, base64.encode(hashedPassphrase.bytes), iv);
await setEncryptedKey(encryptedKey);
}
String getEndpoint() {
return _preferences.getString(endpointKey);
}
@ -85,15 +100,30 @@ class Configuration {
// return _preferences.getBool(hasOptedForE2EKey);
}
Future<void> generateAndSaveKey(String passphrase) async {
final key = CryptoUtil.getBase64EncodedSecureRandomString(length: 32);
Future<void> setEncryptedKey(String encryptedKey) async {
await _preferences.setString(keyEncryptedKey, encryptedKey);
}
String getEncryptedKey() {
return _preferences.getString(keyEncryptedKey);
}
Future<void> setKey(String key) async {
await _preferences.setString(keyKey, key);
}
// TODO: Encrypt with a passphrase and store in secure storage
Future<void> decryptEncryptedKey(String passphrase) async {
final hashedPassphrase = sha256.convert(passphrase.codeUnits);
final encryptedKey = getEncryptedKey();
final key = CryptoUtil.decryptFromBase64(
encryptedKey, base64.encode(hashedPassphrase.bytes), iv);
await setKey(key);
}
// TODO: Store in secure storage
String getKey() {
return "8qD++K3xkgjIl3dIsGiTze5PhYtxiS5AtOeZw+Bl1z0=";
// return _preferences.getString(keyKey);
// return "8qD++K3xkgjIl3dIsGiTze5PhYtxiS5AtOeZw+Bl1z0=";
return _preferences.getString(keyKey);
}
String getDocumentsDirectory() {

View file

@ -11,6 +11,7 @@ import 'package:photos/events/photo_upload_event.dart';
import 'package:photos/events/user_authenticated_event.dart';
import 'package:photos/file_repository.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:photos/models/file_type.dart';
import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/file_name_util.dart';
import 'package:photos/utils/file_util.dart';
@ -201,6 +202,9 @@ class PhotoSyncManager {
File file = photosToBeUploaded[i];
_logger.info("Uploading " + file.toString());
try {
if (file.fileType == FileType.video) {
continue;
}
var uploadedFile;
if (Configuration.instance.hasOptedForE2E()) {
uploadedFile = await _uploadEncryptedFile(file);
@ -279,10 +283,9 @@ class PhotoSyncManager {
file.ownerID = json["ownerID"];
file.updationTime = json["updationTime"];
file.isEncrypted = true;
final String metadataIV = json["metadataIV"];
Map<String, dynamic> metadata = jsonDecode(CryptoUtil.decryptFromBase64(
json["metadata"],
Configuration.instance.getKey(),
json["metadataIV"]));
json["metadata"], Configuration.instance.getKey(), metadataIV));
file.applyMetadata(metadata);
return file;
}).toList();
@ -312,8 +315,12 @@ class PhotoSyncManager {
final metadata = jsonEncode(file.getMetadata());
final metadataIV =
CryptoUtil.getBase64EncodedSecureRandomString(length: 16);
final encryptedMetadata =
CryptoUtil.encryptToBase64(metadata, key, metadataIV);
var encryptedMetadata;
try {
encryptedMetadata = CryptoUtil.encryptToBase64(metadata, key, metadataIV);
} catch (e, stacktrace) {
_logger.severe(e, stacktrace);
}
final formData = FormData.fromMap({
"file": MultipartFile.fromFileSync(encryptedFilePath,
filename: encryptedFileName),

View file

@ -138,6 +138,15 @@ class _SignInWidgetState extends State<SignInWidget> {
.login(_usernameController.text, _passwordController.text);
if (loggedIn) {
Navigator.of(context).pop();
if (Configuration.instance.getEncryptedKey() != null) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PassphraseDialog(false);
},
);
}
} else {
_showAuthenticationFailedErrorDialog();
}
@ -276,7 +285,13 @@ class SelectEncryptionLevelWidget extends StatelessWidget {
onPressed: () {
Navigator.of(context).pop();
Configuration.instance.setOptInForE2E(true);
_showEnterPassphraseDialog(context);
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PassphraseDialog(true);
},
);
},
),
CupertinoDialogAction(
@ -290,28 +305,21 @@ class SelectEncryptionLevelWidget extends StatelessWidget {
],
);
}
void _showEnterPassphraseDialog(BuildContext context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PassphraseWidget();
},
);
}
}
class PassphraseWidget extends StatefulWidget {
const PassphraseWidget({
class PassphraseDialog extends StatefulWidget {
final bool isFreshRegistration;
const PassphraseDialog(
this.isFreshRegistration, {
Key key,
}) : super(key: key);
@override
_PassphraseWidgetState createState() => _PassphraseWidgetState();
_PassphraseDialogState createState() => _PassphraseDialogState();
}
class _PassphraseWidgetState extends State<PassphraseWidget> {
class _PassphraseDialogState extends State<PassphraseDialog> {
String _passphrase = "";
@override
@ -322,8 +330,6 @@ class _PassphraseWidgetState extends State<PassphraseWidget> {
padding: const EdgeInsets.fromLTRB(0, 16, 0, 0),
child: Column(
children: [
Text("Do not forget this passphrase!"),
Padding(padding: EdgeInsets.all(8)),
CupertinoTextField(
autofocus: true,
style: Theme.of(context).textTheme.subtitle1,
@ -340,7 +346,12 @@ class _PassphraseWidgetState extends State<PassphraseWidget> {
child: Text('Save'),
onPressed: () async {
Navigator.of(context).pop();
await Configuration.instance.generateAndSaveKey(_passphrase);
if (widget.isFreshRegistration) {
await Configuration.instance.generateAndSaveKey(_passphrase);
await UserAuthenticator.instance.setEncryptedKeyOnServer();
} else {
await Configuration.instance.decryptEncryptedKey(_passphrase);
}
Bus.instance.fire(UserAuthenticatedEvent());
},
)

View file

@ -56,10 +56,26 @@ class UserAuthenticator {
});
}
Future<void> setEncryptedKeyOnServer() {
return _dio.put(
Configuration.instance.getHttpEndpoint() + "/users/encrypted-key",
data: {
"encryptedKey": Configuration.instance.getEncryptedKey(),
},
options: Options(headers: {
"X-Auth-Token": Configuration.instance.getToken(),
}),
);
}
void _saveConfiguration(String username, String password, Response response) {
Configuration.instance.setUsername(username);
Configuration.instance.setPassword(password);
Configuration.instance.setUserID(response.data["id"]);
Configuration.instance.setToken(response.data["token"]);
final String encryptedKey = response.data["encryptedKey"];
if (encryptedKey != null && encryptedKey.isNotEmpty) {
Configuration.instance.setEncryptedKey(encryptedKey);
}
}
}

View file

@ -15,7 +15,7 @@ class CryptoUtil {
final encrypter = AES(Key.fromBase64(base64Key));
return encrypter
.encrypt(
Uint8List.fromList(utf8.encode(plainText)),
utf8.encode(plainText),
iv: IV.fromBase64(base64IV),
)
.base64;
@ -24,12 +24,10 @@ class CryptoUtil {
static String decryptFromBase64(
String base64CipherText, String base64Key, String base64IV) {
final encrypter = AES(Key.fromBase64(base64Key));
return utf8.decode(encrypter
.decrypt(
Encrypted.fromBase64(base64CipherText),
iv: IV.fromBase64(base64IV),
)
.toList());
return utf8.decode(encrypter.decrypt(
Encrypted.fromBase64(base64CipherText),
iv: IV.fromBase64(base64IV),
));
}
static Future<String> encryptFileToFile(