85 lines
2.5 KiB
Python
85 lines
2.5 KiB
Python
"""Functions related to handling the password database master password."""
|
|
|
|
import secrets
|
|
from pathlib import Path
|
|
from sshecret.crypto import (
|
|
create_private_rsa_key,
|
|
load_private_key,
|
|
encrypt_string,
|
|
decode_string,
|
|
)
|
|
from sshecret_admin.core.settings import AdminServerSettings
|
|
|
|
KEY_FILENAME = "sshecret-admin-key"
|
|
|
|
|
|
def setup_master_password(
|
|
settings: AdminServerSettings,
|
|
filename: str = KEY_FILENAME,
|
|
regenerate: bool = False,
|
|
) -> str | None:
|
|
"""Setup master password.
|
|
|
|
If regenerate is True, a new key will be generated.
|
|
|
|
This method should run just after setting up the database.
|
|
"""
|
|
keyfile = Path(filename)
|
|
if settings.password_manager_directory:
|
|
keyfile = settings.password_manager_directory / filename
|
|
created = _initial_key_setup(settings, keyfile, regenerate)
|
|
if not created:
|
|
return None
|
|
|
|
return _generate_master_password(settings, keyfile)
|
|
|
|
|
|
def decrypt_master_password(
|
|
settings: AdminServerSettings, encrypted: str, filename: str = KEY_FILENAME
|
|
) -> str:
|
|
"""Retrieve master password."""
|
|
keyfile = Path(filename)
|
|
if settings.password_manager_directory:
|
|
keyfile = settings.password_manager_directory / filename
|
|
if not keyfile.exists():
|
|
raise RuntimeError("Error: Private key has not been generated yet.")
|
|
|
|
private_key = load_private_key(str(keyfile.absolute()), password=settings.secret_key)
|
|
return decode_string(encrypted, private_key)
|
|
|
|
|
|
def _generate_password() -> str:
|
|
"""Generate a password."""
|
|
return secrets.token_urlsafe(32)
|
|
|
|
|
|
def _initial_key_setup(
|
|
settings: AdminServerSettings,
|
|
keyfile: Path,
|
|
regenerate: bool = False,
|
|
) -> bool:
|
|
"""Set up initial keys."""
|
|
if keyfile.exists() and not regenerate:
|
|
return False
|
|
|
|
assert settings.secret_key is not None, (
|
|
"Error: Could not load a secret key from environment."
|
|
)
|
|
create_private_rsa_key(keyfile, password=settings.secret_key)
|
|
return True
|
|
|
|
|
|
def _generate_master_password(
|
|
settings: AdminServerSettings, keyfile: Path
|
|
) -> str:
|
|
"""Generate master password for password database.
|
|
|
|
Returns the encrypted string, base64 encoded.
|
|
"""
|
|
if not keyfile.exists():
|
|
raise RuntimeError("Error: Private key has not been generated yet.")
|
|
private_key = load_private_key(str(keyfile.absolute()), password=settings.secret_key)
|
|
public_key = private_key.public_key()
|
|
master_password = _generate_password()
|
|
return encrypt_string(master_password, public_key)
|