Pull the key from server on sign in
This commit is contained in:
parent
f645e00b4e
commit
3d3c1496e7
5 changed files with 97 additions and 35 deletions
|
@ -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() {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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());
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Reference in a new issue