test: fs_helper: Support LUKS keyfile and master key
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>
This commit is contained in:
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user