diff --git a/archinstall/lib/disk/device_handler.py b/archinstall/lib/disk/device_handler.py index cee5b9bf78..836c64a1f4 100644 --- a/archinstall/lib/disk/device_handler.py +++ b/archinstall/lib/disk/device_handler.py @@ -14,6 +14,7 @@ from ..general import SysCommand, SysCommandWorker from ..luks import Luks2 from ..models.device_model import ( + DEFAULT_ITER_TIME, BDevice, BtrfsMountOption, DeviceModification, @@ -308,6 +309,7 @@ def encrypt( mapper_name: str | None, enc_password: Password | None, lock_after_create: bool = True, + iter_time: int = DEFAULT_ITER_TIME, ) -> Luks2: luks_handler = Luks2( dev_path, @@ -315,7 +317,7 @@ def encrypt( password=enc_password, ) - key_file = luks_handler.encrypt() + key_file = luks_handler.encrypt(iter_time=iter_time) self.udev_sync() @@ -346,7 +348,7 @@ def format_encrypted( password=enc_conf.encryption_password, ) - key_file = luks_handler.encrypt() + key_file = luks_handler.encrypt(iter_time=enc_conf.iter_time) self.udev_sync() diff --git a/archinstall/lib/disk/disk_menu.py b/archinstall/lib/disk/disk_menu.py index 09dcd55e89..ddbaf047e6 100644 --- a/archinstall/lib/disk/disk_menu.py +++ b/archinstall/lib/disk/disk_menu.py @@ -3,6 +3,7 @@ from archinstall.lib.disk.encryption_menu import DiskEncryptionMenu from archinstall.lib.models.device_model import ( + DEFAULT_ITER_TIME, BtrfsOptions, DiskEncryption, DiskLayoutConfiguration, @@ -255,12 +256,15 @@ def _prev_disk_encryption(self, item: MenuItem) -> str | None: return tr('LVM disk encryption with more than 2 partitions is currently not supported') if enc_config: - enc_type = EncryptionType.type_to_text(enc_config.encryption_type) - output = tr('Encryption type') + f': {enc_type}\n' + enc_type = enc_config.encryption_type + output = tr('Encryption type') + f': {EncryptionType.type_to_text(enc_type)}\n' if enc_config.encryption_password: output += tr('Password') + f': {enc_config.encryption_password.hidden()}\n' + if enc_type != EncryptionType.NoEncryption: + output += tr('Iteration time') + f': {enc_config.iter_time or DEFAULT_ITER_TIME}ms\n' + if enc_config.partitions: output += f'Partitions: {len(enc_config.partitions)} selected\n' elif enc_config.lvm_volumes: diff --git a/archinstall/lib/disk/encryption_menu.py b/archinstall/lib/disk/encryption_menu.py index 738fc019a6..dbbaf73c8d 100644 --- a/archinstall/lib/disk/encryption_menu.py +++ b/archinstall/lib/disk/encryption_menu.py @@ -11,13 +11,13 @@ PartitionModification, ) from archinstall.lib.translationhandler import tr -from archinstall.tui.curses_menu import SelectMenu +from archinstall.tui.curses_menu import EditMenu, SelectMenu from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType from archinstall.tui.types import Alignment, FrameProperties from ..menu.abstract_menu import AbstractSubMenu -from ..models.device_model import Fido2Device +from ..models.device_model import DEFAULT_ITER_TIME, Fido2Device from ..models.users import Password from ..output import FormattedOutput from ..utils.util import get_password @@ -65,6 +65,14 @@ def _define_menu_options(self) -> list[MenuItem]: preview_action=self._preview, key='encryption_password', ), + MenuItem( + text=tr('Iteration time'), + action=select_iteration_time, + value=self._enc_config.iter_time, + dependencies=[self._check_dep_enc_type], + preview_action=self._preview, + key='iter_time', + ), MenuItem( text=tr('Partitions'), action=lambda x: select_partitions_to_encrypt(self._device_modifications, x), @@ -120,6 +128,7 @@ def run(self, additional_title: str | None = None) -> DiskEncryption | None: enc_type: EncryptionType | None = self._item_group.find_by_key('encryption_type').value enc_password: Password | None = self._item_group.find_by_key('encryption_password').value + iter_time: int | None = self._item_group.find_by_key('iter_time').value enc_partitions = self._item_group.find_by_key('partitions').value enc_lvm_vols = self._item_group.find_by_key('lvm_volumes').value @@ -140,6 +149,7 @@ def run(self, additional_title: str | None = None) -> DiskEncryption | None: partitions=enc_partitions, lvm_volumes=enc_lvm_vols, hsm_device=self._enc_config.hsm_device, + iter_time=iter_time or DEFAULT_ITER_TIME, ) return None @@ -153,6 +163,9 @@ def _preview(self, item: MenuItem) -> str | None: if (enc_pwd := self._prev_password()) is not None: output += f'\n{enc_pwd}' + if (iter_time := self._prev_iter_time()) is not None: + output += f'\n{iter_time}' + if (fido_device := self._prev_hsm()) is not None: output += f'\n{fido_device}' @@ -214,6 +227,15 @@ def _prev_hsm(self) -> str | None: output += f' ({fido_device.manufacturer}, {fido_device.product})' return f'{tr("HSM device")}: {output}' + def _prev_iter_time(self) -> str | None: + iter_time = self._item_group.find_by_key('iter_time').value + enc_type = self._item_group.find_by_key('encryption_type').value + + if iter_time and enc_type != EncryptionType.NoEncryption: + return f'{tr("Iteration time")}: {iter_time}ms' + + return None + def select_encryption_type( device_modifications: list[DeviceModification], @@ -354,3 +376,42 @@ def select_lvm_vols_to_encrypt( return volumes return [] + + +def select_iteration_time(preset: int | None = None) -> int | None: + header = tr('Enter iteration time for LUKS encryption (in milliseconds)') + '\n' + header += tr('Higher values increase security but slow down boot time') + '\n' + header += tr(f'Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000') + '\n' + + def validate_iter_time(value: str | None) -> str | None: + if not value: + return None + + try: + iter_time = int(value) + if iter_time < 100: + return tr('Iteration time must be at least 100ms') + if iter_time > 120000: + return tr('Iteration time must be at most 120000ms') + return None + except ValueError: + return tr('Please enter a valid number') + + result = EditMenu( + tr('Iteration time'), + header=header, + alignment=Alignment.CENTER, + allow_skip=True, + default_text=str(preset) if preset else str(DEFAULT_ITER_TIME), + validator=validate_iter_time, + ).input() + + match result.type_: + case ResultType.Skip: + return preset + case ResultType.Selection: + if not result.text(): + return preset + return int(result.text()) + case ResultType.Reset: + return None diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index 94e5b627af..e1f9504302 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -287,6 +287,7 @@ def _encrypt_lvm_vols( vol.mapper_name, enc_config.encryption_password, lock_after_create, + iter_time=enc_config.iter_time, ) enc_vols[vol] = luks_handler @@ -317,6 +318,7 @@ def _encrypt_partitions( part_mod.mapper_name, enc_config.encryption_password, lock_after_create=lock_after_create, + iter_time=enc_config.iter_time, ) enc_mods[part_mod] = luks_handler diff --git a/archinstall/lib/luks.py b/archinstall/lib/luks.py index e31c361b54..5845f84330 100644 --- a/archinstall/lib/luks.py +++ b/archinstall/lib/luks.py @@ -7,6 +7,7 @@ from types import TracebackType from archinstall.lib.disk.utils import get_lsblk_info, umount +from archinstall.lib.models.device_model import DEFAULT_ITER_TIME from .exceptions import DiskError, SysCallError from .general import SysCommand, SysCommandWorker, generate_password, run @@ -76,7 +77,7 @@ def encrypt( self, key_size: int = 512, hash_type: str = 'sha512', - iter_time: int = 10000, + iter_time: int = DEFAULT_ITER_TIME, key_file: Path | None = None, ) -> Path | None: debug(f'Luks2 encrypting: {self.luks_dev_path}') diff --git a/archinstall/lib/models/device_model.py b/archinstall/lib/models/device_model.py index d459a699ef..ef1b88dbc2 100644 --- a/archinstall/lib/models/device_model.py +++ b/archinstall/lib/models/device_model.py @@ -19,6 +19,7 @@ from ..output import debug ENC_IDENTIFIER = 'ainst' +DEFAULT_ITER_TIME = 10000 class DiskLayoutType(Enum): @@ -1471,6 +1472,7 @@ class _DiskEncryptionSerialization(TypedDict): partitions: list[str] lvm_volumes: list[str] hsm_device: NotRequired[_Fido2DeviceSerialization] + iter_time: NotRequired[int] @dataclass @@ -1480,6 +1482,7 @@ class DiskEncryption: partitions: list[PartitionModification] = field(default_factory=list) lvm_volumes: list[LvmVolume] = field(default_factory=list) hsm_device: Fido2Device | None = None + iter_time: int = DEFAULT_ITER_TIME def __post_init__(self) -> None: if self.encryption_type in [EncryptionType.Luks, EncryptionType.LvmOnLuks] and not self.partitions: @@ -1504,6 +1507,9 @@ def json(self) -> _DiskEncryptionSerialization: if self.hsm_device: obj['hsm_device'] = self.hsm_device.json() + if self.iter_time != DEFAULT_ITER_TIME: # Only include if not default + obj['iter_time'] = self.iter_time + return obj @classmethod @@ -1559,6 +1565,9 @@ def parse_arg( if hsm := disk_encryption.get('hsm_device', None): enc.hsm_device = Fido2Device.parse_arg(hsm) + if iter_time := disk_encryption.get('iter_time', None): + enc.iter_time = iter_time + return enc diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 223dd96977..2b5602aef8 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -959,6 +959,33 @@ msgstr "" msgid "Encryption type" msgstr "" +msgid "Iteration time" +msgstr "" + +msgid "Enter iteration time for LUKS encryption (in milliseconds)" +msgstr "" + +msgid "Higher values increase security but slow down boot time" +msgstr "" + +msgid "Default: 10000ms, Recommended range: 1000-60000" +msgstr "" + +msgid "Iteration time" +msgstr "" + +msgid "Iteration time cannot be empty" +msgstr "" + +msgid "Iteration time must be at least 100ms" +msgstr "" + +msgid "Iteration time must be at most 120000ms" +msgstr "" + +msgid "Please enter a valid number" +msgstr "" + msgid "Partitions" msgstr "" diff --git a/archinstall/tui/curses_menu.py b/archinstall/tui/curses_menu.py index 32da868248..d1ab11e188 100644 --- a/archinstall/tui/curses_menu.py +++ b/archinstall/tui/curses_menu.py @@ -566,7 +566,10 @@ def _get_input_text(self) -> str | None: entry = ViewportEntry(err, 0, 0, STYLE.ERROR) self._info_vp.update([entry], 0) self._set_default_info = False - self._real_input = '' + + if self._hide_input: + self._real_input = '' + return None return text @@ -586,7 +589,7 @@ def _draw(self) -> None: if self._set_default_info and self._info_vp: self._info_vp.update([self._only_ascii_text], 0) - self._input_vp.edit(default_text=self._current_text) + self._input_vp.edit(default_text=self._real_input) @override def kickoff(self, win: curses.window) -> Result[str]: