mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
server: Run crypto methods in seperate isolate
This commit is contained in:
parent
fa2e9ebacd
commit
e897465fd7
3 changed files with 132 additions and 16 deletions
|
|
@ -1,12 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:aqueduct/aqueduct.dart';
|
import 'package:aqueduct/aqueduct.dart';
|
||||||
import 'package:corsac_jwt/corsac_jwt.dart';
|
import 'package:corsac_jwt/corsac_jwt.dart';
|
||||||
import 'package:steel_crypt/steel_crypt.dart';
|
|
||||||
|
|
||||||
|
import 'compute.dart';
|
||||||
|
import 'crypt.dart';
|
||||||
import 'database.dart';
|
import 'database.dart';
|
||||||
|
|
||||||
/// Information on the rights of the user making the request.
|
/// Information on the rights of the user making the request.
|
||||||
|
|
@ -54,9 +53,6 @@ class RequestUser {
|
||||||
class RegisterController extends Controller {
|
class RegisterController extends Controller {
|
||||||
final ServerDatabase db;
|
final ServerDatabase db;
|
||||||
|
|
||||||
final _crypt = PassCrypt();
|
|
||||||
final _rand = Random.secure();
|
|
||||||
|
|
||||||
RegisterController(this.db);
|
RegisterController(this.db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -69,21 +65,20 @@ class RegisterController extends Controller {
|
||||||
final existingUser = await db.getUser(requestUser.name);
|
final existingUser = await db.getUser(requestUser.name);
|
||||||
if (existingUser != null) {
|
if (existingUser != null) {
|
||||||
// Returning something different than 200 here has the security
|
// Returning something different than 200 here has the security
|
||||||
// implication that an attacker can check for existing user names. At the
|
// implication that an attacker can check for existing user names. At
|
||||||
// moment, I don't see any alternatives, because we don't use email
|
// the moment, I don't see any alternatives, because we don't use email
|
||||||
// addresses for identification. The client needs to know, whether the
|
// addresses for identification. The client needs to know, whether the
|
||||||
// user name is already given.
|
// user name is already given.
|
||||||
return Response.conflict();
|
return Response.conflict();
|
||||||
} else {
|
} else {
|
||||||
final bytes = List.generate(32, (i) => _rand.nextInt(256));
|
// This will take a long time, so we run it in a new isolate.
|
||||||
final salt = base64UrlEncode(bytes);
|
final result = await compute(Crypt.hashPassword, requestUser.password);
|
||||||
final hash = _crypt.hashPass(salt, requestUser.password);
|
|
||||||
|
|
||||||
db.updateUser(User(
|
db.updateUser(User(
|
||||||
name: requestUser.name,
|
name: requestUser.name,
|
||||||
email: requestUser.email,
|
email: requestUser.email,
|
||||||
salt: salt,
|
salt: result.salt,
|
||||||
hash: hash,
|
hash: result.hash,
|
||||||
mayUpload: true,
|
mayUpload: true,
|
||||||
mayEdit: false,
|
mayEdit: false,
|
||||||
mayDelete: false,
|
mayDelete: false,
|
||||||
|
|
@ -106,7 +101,6 @@ class LoginController extends Controller {
|
||||||
/// The secret that will be used for signing the token.
|
/// The secret that will be used for signing the token.
|
||||||
final String secret;
|
final String secret;
|
||||||
|
|
||||||
final _crypt = PassCrypt();
|
|
||||||
final JWTHmacSha256Signer _signer;
|
final JWTHmacSha256Signer _signer;
|
||||||
|
|
||||||
LoginController(this.db, this.secret) : _signer = JWTHmacSha256Signer(secret);
|
LoginController(this.db, this.secret) : _signer = JWTHmacSha256Signer(secret);
|
||||||
|
|
@ -119,8 +113,16 @@ class LoginController extends Controller {
|
||||||
|
|
||||||
final realUser = await db.getUser(requestUser.name);
|
final realUser = await db.getUser(requestUser.name);
|
||||||
if (realUser != null) {
|
if (realUser != null) {
|
||||||
if (_crypt.checkPassKey(
|
// We check the password in a new isolate, because this can take a long
|
||||||
realUser.salt, requestUser.password, realUser.hash)) {
|
// time.
|
||||||
|
if (await compute(
|
||||||
|
Crypt.checkPassword,
|
||||||
|
CheckPasswordRequest(
|
||||||
|
password: requestUser.password,
|
||||||
|
salt: realUser.salt,
|
||||||
|
hash: realUser.hash,
|
||||||
|
),
|
||||||
|
)) {
|
||||||
final builder = JWTBuilder()
|
final builder = JWTBuilder()
|
||||||
..expiresAt = DateTime.now().add(Duration(minutes: 30))
|
..expiresAt = DateTime.now().add(Duration(minutes: 30))
|
||||||
..setClaim('user', requestUser.name);
|
..setClaim('user', requestUser.name);
|
||||||
|
|
|
||||||
52
server/lib/src/compute.dart
Normal file
52
server/lib/src/compute.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// This function will run within the new isolate.
|
||||||
|
void _isolateEntrypoint<T, S>(_ComputeRequest<T, S> request) {
|
||||||
|
final result = request.compute();
|
||||||
|
request.sendPort.send(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bundle of information to pass to the isolate.
|
||||||
|
class _ComputeRequest<T, S> {
|
||||||
|
/// The function to call.
|
||||||
|
T Function(S parameter) function;
|
||||||
|
|
||||||
|
/// The parameter to pass to the function.
|
||||||
|
S parameter;
|
||||||
|
|
||||||
|
/// The port through which the result will be sent.
|
||||||
|
SendPort sendPort;
|
||||||
|
|
||||||
|
_ComputeRequest({
|
||||||
|
@required this.function,
|
||||||
|
@required this.parameter,
|
||||||
|
@required this.sendPort,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Call [function] with [parameter] and return the result.
|
||||||
|
///
|
||||||
|
/// This function exists to avoid type errors within the isolate.
|
||||||
|
T compute() => function(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a function in a new isolate and await the result.
|
||||||
|
///
|
||||||
|
/// The function has to be a static function. If the result is not a primitive
|
||||||
|
/// value or a list or map of such, this won't work
|
||||||
|
/// (see https://api.dart.dev/stable/2.8.1/dart-isolate/SendPort/send.html).
|
||||||
|
Future<T> compute<T, S>(T Function(S parameter) function, S parameter) async {
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
|
||||||
|
Isolate.spawn(
|
||||||
|
_isolateEntrypoint,
|
||||||
|
_ComputeRequest<T, S>(
|
||||||
|
function: function,
|
||||||
|
parameter: parameter,
|
||||||
|
sendPort: receivePort.sendPort,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return await receivePort.first as T;
|
||||||
|
}
|
||||||
62
server/lib/src/crypt.dart
Normal file
62
server/lib/src/crypt.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:steel_crypt/steel_crypt.dart';
|
||||||
|
|
||||||
|
/// Result of [hashPassword].
|
||||||
|
class HashPasswordResult {
|
||||||
|
/// The computed hash.
|
||||||
|
final String hash;
|
||||||
|
|
||||||
|
/// A randomly generated string.
|
||||||
|
final String salt;
|
||||||
|
|
||||||
|
HashPasswordResult({
|
||||||
|
@required this.hash,
|
||||||
|
@required this.salt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters for [checkPassword].
|
||||||
|
class CheckPasswordRequest {
|
||||||
|
/// The password to check.
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
/// The salt that was used for computing the hash.
|
||||||
|
final String salt;
|
||||||
|
|
||||||
|
/// The hash value to check against.
|
||||||
|
final String hash;
|
||||||
|
|
||||||
|
CheckPasswordRequest({
|
||||||
|
@required this.password,
|
||||||
|
@required this.salt,
|
||||||
|
@required this.hash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Methods for handling passwords.
|
||||||
|
class Crypt {
|
||||||
|
static final _crypt = PassCrypt();
|
||||||
|
static final _rand = Random.secure();
|
||||||
|
|
||||||
|
/// Compute a hash for a password.
|
||||||
|
///
|
||||||
|
/// The result will contain the hash and a randomly generated salt.
|
||||||
|
static HashPasswordResult hashPassword(String password) {
|
||||||
|
final bytes = List.generate(32, (i) => _rand.nextInt(256));
|
||||||
|
final salt = base64UrlEncode(bytes);
|
||||||
|
final hash = _crypt.hashPass(salt, password);
|
||||||
|
|
||||||
|
return HashPasswordResult(
|
||||||
|
hash: hash,
|
||||||
|
salt: salt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether a password matches a hash value.
|
||||||
|
static bool checkPassword(CheckPasswordRequest request) {
|
||||||
|
return _crypt.checkPassKey(request.salt, request.password, request.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue