Split up the initialization of mongo_key into 2 parts: directory of mongo key initialization that happens during launch and initialization of key which happens after login or registration
This commit is contained in:
parent
fd1cb9d36d
commit
4f176939bb
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
@ -9,8 +8,13 @@ from flask_jwt_extended.exceptions import JWTExtendedException
|
||||||
from jwt import PyJWTError
|
from jwt import PyJWTError
|
||||||
|
|
||||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||||
import monkey_island.cc.resources.auth.password_utils as password_utils
|
|
||||||
import monkey_island.cc.resources.auth.user_store as user_store
|
import monkey_island.cc.resources.auth.user_store as user_store
|
||||||
|
from monkey_island.cc.resources.auth.credential_utils import (
|
||||||
|
get_creds_from_request,
|
||||||
|
get_secret_from_request,
|
||||||
|
password_matches_hash,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.server_utils.encryption.data_store_encryptor import setup_datastore_key
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -38,28 +42,20 @@ class Authenticate(flask_restful.Resource):
|
||||||
"password": "my_password"
|
"password": "my_password"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
(username, password) = _get_credentials_from_request(request)
|
username, password = get_creds_from_request(request)
|
||||||
|
|
||||||
if _credentials_match_registered_user(username, password):
|
if _credentials_match_registered_user(username, password):
|
||||||
|
setup_datastore_key(get_secret_from_request(request))
|
||||||
access_token = _create_access_token(username)
|
access_token = _create_access_token(username)
|
||||||
return make_response({"access_token": access_token, "error": ""}, 200)
|
return make_response({"access_token": access_token, "error": ""}, 200)
|
||||||
else:
|
else:
|
||||||
return make_response({"error": "Invalid credentials"}, 401)
|
return make_response({"error": "Invalid credentials"}, 401)
|
||||||
|
|
||||||
|
|
||||||
def _get_credentials_from_request(request):
|
def _credentials_match_registered_user(username: str, password: str):
|
||||||
credentials = json.loads(request.data)
|
|
||||||
|
|
||||||
username = credentials["username"]
|
|
||||||
password = credentials["password"]
|
|
||||||
|
|
||||||
return (username, password)
|
|
||||||
|
|
||||||
|
|
||||||
def _credentials_match_registered_user(username, password):
|
|
||||||
user = user_store.UserStore.username_table.get(username, None)
|
user = user_store.UserStore.username_table.get(username, None)
|
||||||
|
|
||||||
if user and password_utils.password_matches_hash(password, user.secret):
|
if user and password_matches_hash(password, user.secret):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import json
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
from flask import Request, request
|
||||||
|
|
||||||
|
from monkey_island.cc.environment.user_creds import UserCreds
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(plaintext_password):
|
||||||
|
salt = bcrypt.gensalt()
|
||||||
|
password_hash = bcrypt.hashpw(plaintext_password.encode("utf-8"), salt)
|
||||||
|
|
||||||
|
return password_hash.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def password_matches_hash(plaintext_password, password_hash):
|
||||||
|
return bcrypt.checkpw(plaintext_password.encode("utf-8"), password_hash.encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_credentials_from_request(_request) -> UserCreds:
|
||||||
|
username, password = get_creds_from_request(_request)
|
||||||
|
password_hash = hash_password(password)
|
||||||
|
|
||||||
|
return UserCreds(username, password_hash)
|
||||||
|
|
||||||
|
|
||||||
|
def get_secret_from_request(_request) -> str:
|
||||||
|
username, password = get_creds_from_request(_request)
|
||||||
|
return f"{username}:{password}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_creds_from_request(_request: Request) -> Tuple[str, str]:
|
||||||
|
cred_dict = json.loads(request.data)
|
||||||
|
username = cred_dict.get("username", "")
|
||||||
|
password = cred_dict.get("password", "")
|
||||||
|
return username, password
|
|
@ -1,12 +0,0 @@
|
||||||
import bcrypt
|
|
||||||
|
|
||||||
|
|
||||||
def hash_password(plaintext_password):
|
|
||||||
salt = bcrypt.gensalt()
|
|
||||||
password_hash = bcrypt.hashpw(plaintext_password.encode("utf-8"), salt)
|
|
||||||
|
|
||||||
return password_hash.decode()
|
|
||||||
|
|
||||||
|
|
||||||
def password_matches_hash(plaintext_password, password_hash):
|
|
||||||
return bcrypt.checkpw(plaintext_password.encode("utf-8"), password_hash.encode("utf-8"))
|
|
|
@ -1,13 +1,15 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import make_response, request
|
from flask import make_response, request
|
||||||
|
|
||||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||||
import monkey_island.cc.resources.auth.password_utils as password_utils
|
|
||||||
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
|
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
from monkey_island.cc.resources.auth.credential_utils import (
|
||||||
|
get_secret_from_request,
|
||||||
|
get_user_credentials_from_request,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.server_utils.encryption.data_store_encryptor import setup_datastore_key
|
||||||
from monkey_island.cc.setup.mongo.database_initializer import reset_database
|
from monkey_island.cc.setup.mongo.database_initializer import reset_database
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -19,21 +21,13 @@ class Registration(flask_restful.Resource):
|
||||||
return {"needs_registration": is_registration_needed}
|
return {"needs_registration": is_registration_needed}
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
credentials = _get_user_credentials_from_request(request)
|
# TODO delete the old key here, before creating new one
|
||||||
|
credentials = get_user_credentials_from_request(request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
env_singleton.env.try_add_user(credentials)
|
env_singleton.env.try_add_user(credentials)
|
||||||
|
setup_datastore_key(get_secret_from_request(request))
|
||||||
reset_database()
|
reset_database()
|
||||||
return make_response({"error": ""}, 200)
|
return make_response({"error": ""}, 200)
|
||||||
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
|
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
|
||||||
return make_response({"error": str(e)}, 400)
|
return make_response({"error": str(e)}, 400)
|
||||||
|
|
||||||
|
|
||||||
def _get_user_credentials_from_request(request):
|
|
||||||
cred_dict = json.loads(request.data)
|
|
||||||
|
|
||||||
username = cred_dict.get("user", "")
|
|
||||||
password = cred_dict.get("password", "")
|
|
||||||
password_hash = password_utils.hash_password(password)
|
|
||||||
|
|
||||||
return UserCreds(username, password_hash)
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# PyCrypto is deprecated, but we use pycryptodome, which uses the exact same imports but
|
# PyCrypto is deprecated, but we use pycryptodome, which uses the exact same imports but
|
||||||
# is maintained.
|
# is maintained.
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from Crypto import Random # noqa: DUO133 # nosec: B413
|
from Crypto import Random # noqa: DUO133 # nosec: B413
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import KeyBasedEncryptor
|
from monkey_island.cc.server_utils.encryption import KeyBasedEncryptor
|
||||||
|
@ -11,37 +15,42 @@ from monkey_island.cc.server_utils.encryption.password_based_byte_encryption imp
|
||||||
)
|
)
|
||||||
from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file
|
from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file
|
||||||
|
|
||||||
_encryptor = None
|
_encryptor: Union[None, DataStoreEncryptor] = None
|
||||||
|
|
||||||
|
|
||||||
class DataStoreEncryptor:
|
class DataStoreEncryptor:
|
||||||
_BLOCK_SIZE = 32
|
_BLOCK_SIZE = 32
|
||||||
_KEY_FILENAME = "mongo_key.bin"
|
_KEY_FILENAME = "mongo_key.bin"
|
||||||
|
|
||||||
def __init__(self, key_file_dir: str, secret: str):
|
def __init__(self, key_file_dir: str):
|
||||||
key_file = os.path.join(key_file_dir, self._KEY_FILENAME)
|
self.key_file_path = os.path.join(key_file_dir, self._KEY_FILENAME)
|
||||||
|
self._key_base_encryptor = None
|
||||||
|
|
||||||
if os.path.exists(key_file):
|
def init_key(self, secret: str):
|
||||||
self._load_existing_key(key_file, secret)
|
if os.path.exists(self.key_file_path):
|
||||||
|
self._load_existing_key(secret)
|
||||||
else:
|
else:
|
||||||
self._init_key(key_file, secret)
|
self._create_new_key(secret)
|
||||||
|
|
||||||
self._key_base_encryptor = KeyBasedEncryptor(self._cipher_key)
|
def _load_existing_key(self, secret: str):
|
||||||
|
with open(self.key_file_path, "rb") as f:
|
||||||
def _init_key(self, password_file_path: str, secret: str):
|
|
||||||
self._cipher_key = Random.new().read(self._BLOCK_SIZE)
|
|
||||||
encrypted_key = (
|
|
||||||
PasswordBasedByteEncryptor(secret).encrypt(io.BytesIO(self._cipher_key)).getvalue()
|
|
||||||
)
|
|
||||||
with open_new_securely_permissioned_file(password_file_path, "wb") as f:
|
|
||||||
f.write(encrypted_key)
|
|
||||||
|
|
||||||
def _load_existing_key(self, key_file_path: str, secret: str):
|
|
||||||
with open(key_file_path, "rb") as f:
|
|
||||||
encrypted_key = f.read()
|
encrypted_key = f.read()
|
||||||
self._cipher_key = (
|
cipher_key = (
|
||||||
PasswordBasedByteEncryptor(secret).decrypt(io.BytesIO(encrypted_key)).getvalue()
|
PasswordBasedByteEncryptor(secret).decrypt(io.BytesIO(encrypted_key)).getvalue()
|
||||||
)
|
)
|
||||||
|
self._key_base_encryptor = KeyBasedEncryptor(cipher_key)
|
||||||
|
|
||||||
|
def _create_new_key(self, secret: str):
|
||||||
|
cipher_key = Random.new().read(self._BLOCK_SIZE)
|
||||||
|
encrypted_key = (
|
||||||
|
PasswordBasedByteEncryptor(secret).encrypt(io.BytesIO(cipher_key)).getvalue()
|
||||||
|
)
|
||||||
|
with open_new_securely_permissioned_file(self.key_file_path, "wb") as f:
|
||||||
|
f.write(encrypted_key)
|
||||||
|
self._key_base_encryptor = KeyBasedEncryptor(cipher_key)
|
||||||
|
|
||||||
|
def is_key_setup(self) -> bool:
|
||||||
|
return self._key_base_encryptor is not None
|
||||||
|
|
||||||
def enc(self, message: str):
|
def enc(self, message: str):
|
||||||
return self._key_base_encryptor.encrypt(message)
|
return self._key_base_encryptor.encrypt(message)
|
||||||
|
@ -50,10 +59,22 @@ class DataStoreEncryptor:
|
||||||
return self._key_base_encryptor.decrypt(enc_message)
|
return self._key_base_encryptor.decrypt(enc_message)
|
||||||
|
|
||||||
|
|
||||||
def initialize_datastore_encryptor(key_file_dir: str, secret: str):
|
def initialize_datastore_encryptor(key_file_dir: str):
|
||||||
global _encryptor
|
global _encryptor
|
||||||
|
|
||||||
_encryptor = DataStoreEncryptor(key_file_dir, secret)
|
_encryptor = DataStoreEncryptor(key_file_dir)
|
||||||
|
|
||||||
|
|
||||||
|
class EncryptorNotInitializedError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def setup_datastore_key(secret: str):
|
||||||
|
if _encryptor is None:
|
||||||
|
raise EncryptorNotInitializedError
|
||||||
|
else:
|
||||||
|
if not _encryptor.is_key_setup():
|
||||||
|
_encryptor.init_key(secret)
|
||||||
|
|
||||||
|
|
||||||
def get_datastore_encryptor():
|
def get_datastore_encryptor():
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default class AuthService {
|
||||||
return this._authFetch(this.REGISTRATION_API_ENDPOINT, {
|
return this._authFetch(this.REGISTRATION_API_ENDPOINT, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
'user': username,
|
'username': username,
|
||||||
'password': password
|
'password': password
|
||||||
})
|
})
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
|
|
@ -11,6 +11,7 @@ from tests.unit_tests.monkey_island.cc.server_utils.encryption.test_password_bas
|
||||||
)
|
)
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import initialize_datastore_encryptor
|
from monkey_island.cc.server_utils.encryption import initialize_datastore_encryptor
|
||||||
|
from monkey_island.cc.server_utils.encryption.data_store_encryptor import setup_datastore_key
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -32,4 +33,5 @@ ENCRYPTOR_SECRET = "m0nk3y_u53r:53cr3t_p455w0rd"
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def uses_encryptor(data_for_tests_dir):
|
def uses_encryptor(data_for_tests_dir):
|
||||||
initialize_datastore_encryptor(data_for_tests_dir, ENCRYPTOR_SECRET)
|
initialize_datastore_encryptor(data_for_tests_dir)
|
||||||
|
setup_datastore_key(ENCRYPTOR_SECRET)
|
||||||
|
|
|
@ -8,6 +8,7 @@ from monkey_island.cc.server_utils.encryption import (
|
||||||
get_datastore_encryptor,
|
get_datastore_encryptor,
|
||||||
initialize_datastore_encryptor,
|
initialize_datastore_encryptor,
|
||||||
)
|
)
|
||||||
|
from monkey_island.cc.server_utils.encryption.data_store_encryptor import setup_datastore_key
|
||||||
|
|
||||||
PLAINTEXT = "Hello, Monkey!"
|
PLAINTEXT = "Hello, Monkey!"
|
||||||
|
|
||||||
|
@ -22,5 +23,6 @@ def test_encryption(data_for_tests_dir):
|
||||||
|
|
||||||
|
|
||||||
def test_create_new_password_file(tmpdir):
|
def test_create_new_password_file(tmpdir):
|
||||||
initialize_datastore_encryptor(tmpdir, ENCRYPTOR_SECRET)
|
initialize_datastore_encryptor(tmpdir)
|
||||||
|
setup_datastore_key(ENCRYPTOR_SECRET)
|
||||||
assert os.path.isfile(os.path.join(tmpdir, DataStoreEncryptor._KEY_FILENAME))
|
assert os.path.isfile(os.path.join(tmpdir, DataStoreEncryptor._KEY_FILENAME))
|
||||||
|
|
Loading…
Reference in New Issue