From: Simon Glass <simon.glass@canonical.com> Add encrypt_keyfile and master_keyfile parameters to FsHelper and the image setup functions. This allows creating encrypted test images using: - A key file instead of a passphrase (encrypt_keyfile) - A specific master key for pre-derived unlock testing (master_keyfile) The keyfile takes precedence over passphrase when both are provided. Also reduce Argon2 memory parameters to values suitable for U-Boot testing. These new features will allow use of a real TKey for trying out this feature locally, as well as the emulated TKey for automated testing. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/py/img/common.py | 13 ++++++-- test/py/img/ubuntu.py | 12 +++++-- test/py/tests/fs_helper.py | 66 +++++++++++++++++++++++++++++--------- 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/test/py/img/common.py b/test/py/img/common.py index 74ea04771c7..547066b24a5 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -33,7 +33,8 @@ def copy_partition(ubman, fsfile, outname): def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script, part2_size=1, use_fde=0, luks_kdf='pbkdf2'): + script, part2_size=1, use_fde=0, luks_kdf='pbkdf2', + encrypt_keyfile=None, master_keyfile=None): """Create a 20MB disk image with a single FAT partition Args: @@ -49,6 +50,10 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. + encrypt_keyfile (str, optional): Path to key file for LUKS encryption. + If provided, takes precedence over passphrase. + master_keyfile (str, optional): Path to file containing the raw master + key. If provided, this exact key is used as the LUKS master key. """ fsh = FsHelper(config, 'vfat', 18, prefix=basename) fsh.setup() @@ -84,9 +89,11 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, ext4 = FsHelper(config, 'ext4', max(1, part2_size - 30), prefix=basename, part_mb=part2_size, - passphrase='test' if use_fde else None, + passphrase='test' if (use_fde and not encrypt_keyfile) else None, + encrypt_keyfile=encrypt_keyfile, luks_version=use_fde if use_fde else 2, - luks_kdf=luks_kdf) + luks_kdf=luks_kdf, + master_keyfile=master_keyfile) ext4.setup() bindir = os.path.join(ext4.srcdir, 'bin') diff --git a/test/py/img/ubuntu.py b/test/py/img/ubuntu.py index 243fa38d021..1f3016b79a6 100644 --- a/test/py/img/ubuntu.py +++ b/test/py/img/ubuntu.py @@ -7,7 +7,8 @@ from img.common import setup_extlinux_image def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', - use_fde=0, luks_kdf='pbkdf2'): + use_fde=0, luks_kdf='pbkdf2', encrypt_keyfile=None, + master_keyfile=None): """Create a Ubuntu disk image with a FAT partition and ext4 partition This creates a FAT partition containing extlinux files, kernel, etc. and a @@ -21,6 +22,11 @@ def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. + encrypt_keyfile (str, optional): Path to key file for LUKS encryption. + If provided, takes precedence over passphrase. + master_keyfile (str, optional): Path to file containing the raw master + key. If provided, this exact key is used as the LUKS master key, + enabling pre_derived unlock mode. """ vmlinux = 'vmlinuz-6.8.0-53-generic' initrd = 'initrd.img-6.8.0-53-generic' @@ -52,4 +58,6 @@ label l0r ''' % ((version, vmlinux, initrd) * 2) setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, script, part2_size=60 if use_fde else 1, - use_fde=use_fde, luks_kdf=luks_kdf) + use_fde=use_fde, luks_kdf=luks_kdf, + encrypt_keyfile=encrypt_keyfile, + master_keyfile=master_keyfile) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index d88cc270b95..3c4c6e2df6f 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -62,12 +62,21 @@ class FsHelper: fsh.mk_fs() # Creates and encrypts the FS with LUKS2+Argon2 ... + To create an encrypted LUKS2 partition with a key file: + + with FsHelper(ubman.config, 'ext4', 10, 'mmc1', + encrypt_keyfile='/path/to/keyfile') as fsh: + # create files in the fsh.srcdir directory + fsh.mk_fs() # Creates and encrypts the filesystem with key file + ... + Properties: fs_img (str): Filename for the filesystem image; this is set to a default value but can be overwritten """ def __init__(self, config, fs_type, size_mb, prefix, part_mb=None, - passphrase=None, luks_version=2, luks_kdf='pbkdf2'): + passphrase=None, encrypt_keyfile=None, luks_version=2, + luks_kdf='pbkdf2', master_keyfile=None): """Set up a new object Args: @@ -81,9 +90,14 @@ class FsHelper: the filesystem, to create space for disk-encryption metadata passphrase (str, optional): If provided, encrypt the filesystem with LUKS using this passphrase + encrypt_keyfile (str, optional): Path to key file for LUKS + encryption. If provided, takes precedence over passphrase. luks_version (int): LUKS version to use (1 or 2). Defaults to 2. luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. + master_keyfile (str, optional): Path to file containing the raw + master key. If provided, this exact key is used as the LUKS + master key (via --master-key-file), enabling pre_derived unlock. """ if ('fat' not in fs_type and 'ext' not in fs_type and fs_type not in ['exfat', 'fs_generic']): @@ -96,8 +110,10 @@ class FsHelper: self.prefix = prefix self.quiet = True self.passphrase = passphrase + self.encrypt_keyfile = encrypt_keyfile self.luks_version = luks_version self.luks_kdf = luks_kdf + self.master_keyfile = master_keyfile # Use a default filename; the caller can adjust it leaf = f'{prefix}.{fs_type}.img' @@ -170,8 +186,8 @@ class FsHelper: shell=True) # Encrypt the filesystem if requested - if self.passphrase: - self.encrypt_luks(self.passphrase) + if self.passphrase or self.encrypt_keyfile: + self.encrypt_luks() def setup(self): """Set up the srcdir ready to receive files""" @@ -186,22 +202,29 @@ class FsHelper: self.tmpdir = tempfile.TemporaryDirectory('fs_helper') self.srcdir = self.tmpdir.name - def encrypt_luks(self, passphrase): + def encrypt_luks(self): """Encrypt the filesystem image with LUKS This replaces the filesystem image with a LUKS-encrypted version. The LUKS version is determined by self.luks_version. - - Args: - passphrase (str): Passphrase for the LUKS container + Uses either passphrase or keyfile for encryption. Returns: str: Path to the encrypted image Raises: CalledProcessError: If cryptsetup is not available or fails - ValueError: If an unsupported LUKS version is specified + ValueError: If an unsupported LUKS version is specified or if neither + passphrase nor keyfile is provided + Exception: If anything else goes wrong """ + # Validate that we have either passphrase or keyfile + if not self.passphrase and not self.encrypt_keyfile: + raise ValueError('Either encrypt_passphrase or encrypt_keyfile must be provided') + + # If both are provided, keyfile takes precedence + use_keyfile = self.encrypt_keyfile is not None + use_master_key = self.master_keyfile is not None # LUKS encryption parameters if self.luks_version == 1: # LUKS1 parameters @@ -216,7 +239,7 @@ class FsHelper: hash_alg = 'sha256' luks_type = 'luks2' else: - raise ValueError(f"Unsupported LUKS version: {self.luks_version}") + raise ValueError(f'Unsupported LUKS version: {self.luks_version}') key_size_str = str(key_size) @@ -236,7 +259,7 @@ class FsHelper: result = run(['sudo', 'modprobe', 'dm_mod'], stdout=DEVNULL, stderr=DEVNULL, check=False) if result.returncode != 0: - raise RuntimeError( + raise ValueError( 'Device-mapper is not available. Please ensure the dm_mod ' 'kernel module is loaded and you have permission to use ' 'device-mapper. This is required for LUKS encryption tests.') @@ -262,22 +285,33 @@ class FsHelper: # For Argon2, use low memory/time settings suitable for testing if self.luks_kdf == 'argon2id': cmd.extend([ - '--pbkdf-memory', '65536', # 64MB - '--pbkdf-parallel', '4', + '--pbkdf-memory', '8192', # 8MB (reduced for U-Boot) + '--pbkdf-parallel', '1', # Single thread for simplicity ]) + # Add master key file option if provided + if use_master_key: + cmd.extend(['--master-key-file', self.master_keyfile]) + + # Add key file or passphrase option + if use_keyfile: + cmd.extend(['--key-file', self.encrypt_keyfile]) + cmd.append(luks_img) + # When using passphrase, provide it via stdin; otherwise set input=None run(cmd, - input=f'{passphrase}\n'.encode(), + input=f'{self.passphrase}\n'.encode() if not use_keyfile else None, stdout=DEVNULL if self.quiet else None, stderr=DEVNULL if self.quiet else None, check=True) # Open the LUKS device (requires sudo) - # Use --key-file=- to read passphrase from stdin - result = run(['sudo', 'cryptsetup', 'open', '--key-file=-', - luks_img, device_name], input=passphrase.encode(), + # Use --key-file with file path or '-' for stdin + result = run(['sudo', 'cryptsetup', 'open', + '--key-file', self.encrypt_keyfile if use_keyfile else '-', + luks_img, device_name], + input=self.passphrase.encode() if not use_keyfile else None, stdout=DEVNULL if self.quiet else None, stderr=None, check=True) # Copy the filesystem data into the LUKS container -- 2.43.0