Compare commits

...

7 Commits

Author SHA1 Message Date
anonymix007 84e5969f55
Merge 098e44d03c into 9bf6ffe166 2024-11-22 13:01:34 +01:00
Luca Boccassi 9bf6ffe166
man: split cryptenroll man page into sections (#35297) 2024-11-22 12:01:07 +00:00
anonymix007 098e44d03c man: Document ukify --hwids= and --devicetree-auto= options 2024-11-22 13:31:19 +03:00
Lennart Poettering cc6baba720 cryptenroll: it's called PKCS#11, not PKCS11
In the --help text we really should use the official spelling, just like
in the man page.
2024-11-22 10:42:37 +01:00
Lennart Poettering 3ae48d071c man: add enrollment type sections to cryptenroll man page
We have the same sections in the --help text, hence we even more so
should have them in the man page.
2024-11-22 10:42:37 +01:00
Antonio Alvarez Feijoo 2ccacdd57c bash-completion: add --list-devices to systemd-cryptenroll
And also use it to list suitable block devices.
2024-11-22 10:38:19 +01:00
anonymix007 cf331f1c9b ukify: Switch to JSON HWID description format 2024-11-22 00:42:47 +03:00
6 changed files with 125 additions and 98 deletions

View File

@ -265,32 +265,11 @@
</refsect1> </refsect1>
<refsect1> <refsect1>
<title>Options</title> <title>Unlocking</title>
<para>The following options are understood:</para> <para>The following options are understood that may be used to unlock the device in preparation of the enrollment operations:</para>
<variablelist> <variablelist>
<varlistentry>
<term><option>--password</option></term>
<listitem><para>Enroll a regular password/passphrase. This command is mostly equivalent to
<command>cryptsetup luksAddKey</command>, however may be combined with
<option>--wipe-slot=</option> in one call, see below.</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--recovery-key</option></term>
<listitem><para>Enroll a recovery key. Recovery keys are mostly identical to passphrases, but are
computer-generated instead of being chosen by a human, and thus have a guaranteed high entropy. The
key uses a character set that is easy to type in, and may be scanned off screen via a QR code.
</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--unlock-key-file=<replaceable>PATH</replaceable></option></term> <term><option>--unlock-key-file=<replaceable>PATH</replaceable></option></term>
@ -328,7 +307,45 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem> <xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry> </varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Simple Enrollment</title>
<para>The following options are understood that may be used to enroll simple user input based
unlocking:</para>
<variablelist>
<varlistentry>
<term><option>--password</option></term>
<listitem><para>Enroll a regular password/passphrase. This command is mostly equivalent to
<command>cryptsetup luksAddKey</command>, however may be combined with
<option>--wipe-slot=</option> in one call, see below.</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--recovery-key</option></term>
<listitem><para>Enroll a recovery key. Recovery keys are mostly identical to passphrases, but are
computer-generated instead of being chosen by a human, and thus have a guaranteed high entropy. The
key uses a character set that is easy to type in, and may be scanned off screen via a QR code.
</para>
<xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>PKCS#11 Enrollment</title>
<para>The following option is understood that may be used to enroll PKCS#11 tokens:</para>
<variablelist>
<varlistentry> <varlistentry>
<term><option>--pkcs11-token-uri=<replaceable>URI</replaceable></option></term> <term><option>--pkcs11-token-uri=<replaceable>URI</replaceable></option></term>
@ -361,7 +378,15 @@
<xi:include href="version-info.xml" xpointer="v248"/></listitem> <xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry> </varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>FIDO2 Enrollment</title>
<para>The following options are understood that may be used to enroll PKCS#11 tokens:</para>
<variablelist>
<varlistentry> <varlistentry>
<term><option>--fido2-credential-algorithm=<replaceable>STRING</replaceable></option></term> <term><option>--fido2-credential-algorithm=<replaceable>STRING</replaceable></option></term>
<listitem><para>Specify COSE algorithm used in credential generation. The default value is <listitem><para>Specify COSE algorithm used in credential generation. The default value is
@ -461,7 +486,15 @@
<xi:include href="version-info.xml" xpointer="v249"/></listitem> <xi:include href="version-info.xml" xpointer="v249"/></listitem>
</varlistentry> </varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>TPM2 Enrollment</title>
<para>The following options are understood that may be used to enroll TPM2 devices:</para>
<variablelist>
<varlistentry> <varlistentry>
<term><option>--tpm2-device=<replaceable>PATH</replaceable></option></term> <term><option>--tpm2-device=<replaceable>PATH</replaceable></option></term>
@ -636,7 +669,15 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem> <xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry> </varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Other Options</title>
<para>The following additional options are understood:</para>
<variablelist>
<varlistentry> <varlistentry>
<term><option>--wipe-slot=<replaceable>SLOT<optional>,SLOT...</optional></replaceable></option></term> <term><option>--wipe-slot=<replaceable>SLOT<optional>,SLOT...</optional></replaceable></option></term>

View File

@ -71,6 +71,8 @@
<varname>Cmdline=</varname>/<option>--cmdline=</option>, <varname>Cmdline=</varname>/<option>--cmdline=</option>,
<varname>OSRelease=</varname>/<option>--os-release=</option>, <varname>OSRelease=</varname>/<option>--os-release=</option>,
<varname>DeviceTree=</varname>/<option>--devicetree=</option>, <varname>DeviceTree=</varname>/<option>--devicetree=</option>,
<varname>DeviceTreeAuto=</varname>/<option>--devicetree-auto=</option>,
<varname>HWIDs=</varname>/<option>--hwids=</option>,
<varname>Splash=</varname>/<option>--splash=</option>, <varname>Splash=</varname>/<option>--splash=</option>,
<varname>PCRPKey=</varname>/<option>--pcrpkey=</option>, <varname>PCRPKey=</varname>/<option>--pcrpkey=</option>,
<varname>Uname=</varname>/<option>--uname=</option>, <varname>Uname=</varname>/<option>--uname=</option>,
@ -373,6 +375,35 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem> <xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>DeviceTreeAuto=<replaceable>PATH</replaceable>...</varname></term>
<term><option>--devicetree-auto=<replaceable>PATH</replaceable></option></term>
<listitem><para>Zero or more automatically selectable DeviceTree files. In the configuration file, items are separated by
whitespace. Each DeviceTree will be in a separate <literal>.dtbauto</literal> section.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>HWIDs=<replaceable>PATH</replaceable></varname></term>
<term><option>--hwids=<replaceable>PATH</replaceable></option></term>
<listitem><para>The hardware ID device table (the <literal>.hwids</literal> section). The argument is a
path to a directory with JSON HWID device description files. Each file needs to contain a single JSON object with a <literal>name</literal>, <literal>compatible</literal> and <literal>hwids</literal> keys. The <literal>name</literal> and <literal>compatible</literal> keys must have string values and the <literal>hwids</literal> key must have a list of strings as value, where the strings must be valid UUIDs that represent CHIDs/HWIDs.
Example:
<programlisting><xi:include href="ukify_hwid.json.example" parse="text" /></programlisting>
Here <literal>Example Laptop 16 Gen 7</literal> is the device <literal>name</literal> (as defined by the manufacturer),
<literal>example,laptop-16-g7</literal> is the <literal>compatible</literal> (as defined by the kernel) and <literal>hwids</literal>
is an array of CHIDs/HWIDs (extracted i.e. from <command>fwupdtool hwids</command> output).
If not specified, the section will not be present. It is recommended to specify this parameter if automatically
selectable DeviceTrees are to be used.
</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>Uname=<replaceable>VERSION</replaceable></varname></term> <term><varname>Uname=<replaceable>VERSION</replaceable></varname></term>
<term><option>--uname=<replaceable>VERSION</replaceable></option></term> <term><option>--uname=<replaceable>VERSION</replaceable></option></term>

View File

@ -0,0 +1,8 @@
{
"name": "Example Laptop 16 Gen 7",
"compatible": "example,laptop-16-g7",
"hwids": [
"5dc05bf4-01f6-4089-b464-a08c47ea9295",
"3e3f8f3c-2003-46f2-811c-85554f7d5952"
]
}

View File

@ -38,19 +38,12 @@ __get_tpm2_devices() {
done done
} }
__get_block_devices() {
local i
for i in /dev/*; do
[ -b "$i" ] && printf '%s\n' "$i"
done
}
_systemd_cryptenroll() { _systemd_cryptenroll() {
local comps local comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=( local -A OPTS=(
[STANDALONE]='-h --help --version [STANDALONE]='-h --help --version
--password --recovery-key' --password --recovery-key --list-devices'
[ARG]='--unlock-key-file [ARG]='--unlock-key-file
--unlock-fido2-device --unlock-fido2-device
--unlock-tpm2-device --unlock-tpm2-device
@ -116,7 +109,7 @@ _systemd_cryptenroll() {
return 0 return 0
fi fi
comps=$(__get_block_devices) comps=$(systemd-cryptenroll --list-devices)
COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0 return 0
} }

View File

@ -193,7 +193,7 @@ static int help(void) {
"\n%3$sSimple Enrollment:%4$s\n" "\n%3$sSimple Enrollment:%4$s\n"
" --password Enroll a user-supplied password\n" " --password Enroll a user-supplied password\n"
" --recovery-key Enroll a recovery key\n" " --recovery-key Enroll a recovery key\n"
"\n%3$sPKCS11 Enrollment:%4$s\n" "\n%3$sPKCS#11 Enrollment:%4$s\n"
" --pkcs11-token-uri=URI\n" " --pkcs11-token-uri=URI\n"
" Specify PKCS#11 security token URI\n" " Specify PKCS#11 security token URI\n"
"\n%3$sFIDO2 Enrollment:%4$s\n" "\n%3$sFIDO2 Enrollment:%4$s\n"

View File

@ -42,6 +42,7 @@ import subprocess
import sys import sys
import tempfile import tempfile
import textwrap import textwrap
import uuid
from collections.abc import Iterable, Iterator, Sequence from collections.abc import Iterable, Iterator, Sequence
from hashlib import sha256 from hashlib import sha256
from pathlib import Path from pathlib import Path
@ -1013,14 +1014,9 @@ def merge_sbat(input_pe: list[Path], input_text: list[str]) -> str:
) )
# Keep in sync with EFI_GUID (src/boot/efi.h)
# uint32_t Data1, uint16_t Data2, uint16_t Data3, uint8_t Data4[8]
EFI_GUID = tuple[int, int, int, tuple[int, int, int, int, int, int, int, int]]
EFI_GUID_STRUCT_SIZE = 4 + 2 + 2 + 1 * 8
# Keep in sync with Device (DEVICE_TYPE_DEVICETREE) from src/boot/chid.h # Keep in sync with Device (DEVICE_TYPE_DEVICETREE) from src/boot/chid.h
# uint32_t descriptor, EFI_GUID chid, uint32_t name_offset, uint32_t compatible_offset # uint32_t descriptor, EFI_GUID chid, uint32_t name_offset, uint32_t compatible_offset
DEVICE_STRUCT_SIZE = 4 + EFI_GUID_STRUCT_SIZE + 4 + 4 DEVICE_STRUCT_SIZE = 4 + 16 + 4 + 4
NULL_DEVICE = b'\0' * DEVICE_STRUCT_SIZE NULL_DEVICE = b'\0' * DEVICE_STRUCT_SIZE
DEVICE_TYPE_DEVICETREE = 1 DEVICE_TYPE_DEVICETREE = 1
@ -1029,29 +1025,21 @@ def device_make_descriptor(device_type: int, size: int) -> int:
return (size) | (device_type << 28) return (size) | (device_type << 28)
def pack_device(offsets: dict[str, int], name: str, compatible: str, chids: list[EFI_GUID]) -> bytes: DEVICETREE_DESCRIPTOR = device_make_descriptor(DEVICE_TYPE_DEVICETREE, DEVICE_STRUCT_SIZE)
def pack_device(offsets: dict[str, int], name: str, compatible: str, chids: set[uuid.UUID]) -> bytes:
data = b'' data = b''
for data1, data2, data3, data4 in chids: for chid in sorted(chids):
data += struct.pack( data += struct.pack('<I', DEVICETREE_DESCRIPTOR)
'<IIHH8BII', data += chid.bytes_le
device_make_descriptor(DEVICE_TYPE_DEVICETREE, DEVICE_STRUCT_SIZE), data += struct.pack('<II', offsets[name], offsets[compatible])
data1,
data2,
data3,
*data4,
offsets[name],
offsets[compatible],
)
assert len(data) == DEVICE_STRUCT_SIZE * len(chids) assert len(data) == DEVICE_STRUCT_SIZE * len(chids)
return data return data
def hex_pairs_list(string: str) -> list[int]:
return [int(string[i : i + 2], 16) for i in range(0, len(string), 2)]
def pack_strings(strings: set[str], base: int) -> tuple[bytes, dict[str, int]]: def pack_strings(strings: set[str], base: int) -> tuple[bytes, dict[str, int]]:
blob = b'' blob = b''
offsets = {} offsets = {}
@ -1064,56 +1052,22 @@ def pack_strings(strings: set[str], base: int) -> tuple[bytes, dict[str, int]]:
def parse_hwid_dir(path: Path) -> bytes: def parse_hwid_dir(path: Path) -> bytes:
hwid_files = path.rglob('*.txt') hwid_files = path.rglob('*.json')
strings: set[str] = set() strings: set[str] = set()
devices: collections.defaultdict[tuple[str, str], list[EFI_GUID]] = collections.defaultdict(list) devices: collections.defaultdict[tuple[str, str], set[uuid.UUID]] = collections.defaultdict(set)
uuid_regexp = re.compile(
r'\{[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}\}', re.I
)
for hwid_file in hwid_files: for hwid_file in hwid_files:
content = hwid_file.open().readlines() data = json.loads(hwid_file.read_text(encoding='UTF-8'))
data: dict[str, str] = { for k in ['name', 'compatible', 'hwids']:
'Manufacturer': '', if k not in data:
'Family': '',
'Compatible': '',
}
uuids: list[EFI_GUID] = []
for line in content:
for k in data:
if line.startswith(k):
data[k] = line.split(':')[1].strip()
break
else:
uuid = uuid_regexp.match(line)
if uuid is not None:
d1, d2, d3, d4, d5 = uuid.group(0)[1:-1].split('-')
data1 = int(d1, 16)
data2 = int(d2, 16)
data3 = int(d3, 16)
data4 = cast(
tuple[int, int, int, int, int, int, int, int],
tuple(hex_pairs_list(d4) + hex_pairs_list(d5)),
)
uuids.append((data1, data2, data3, data4))
for k, v in data.items():
if not v:
raise ValueError(f'hwid description file "{hwid_file}" does not contain "{k}"') raise ValueError(f'hwid description file "{hwid_file}" does not contain "{k}"')
name = data['Manufacturer'] + ' ' + data['Family'] strings |= {data['name'], data['compatible']}
compatible = data['Compatible']
strings |= set([name, compatible]) # (name, compatible) pair uniquely identifies the device
devices[(data['name'], data['compatible'])] |= {uuid.UUID(u) for u in data['hwids']}
# (compatible, name) pair uniquely identifies the device
devices[(compatible, name)] += uuids
total_device_structs = 1 total_device_structs = 1
for dev, uuids in devices.items(): for dev, uuids in devices.items():
@ -1122,7 +1076,7 @@ def parse_hwid_dir(path: Path) -> bytes:
strings_blob, offsets = pack_strings(strings, total_device_structs * DEVICE_STRUCT_SIZE) strings_blob, offsets = pack_strings(strings, total_device_structs * DEVICE_STRUCT_SIZE)
devices_blob = b'' devices_blob = b''
for (compatible, name), uuids in devices.items(): for (name, compatible), uuids in devices.items():
devices_blob += pack_device(offsets, name, compatible, uuids) devices_blob += pack_device(offsets, name, compatible, uuids)
devices_blob += NULL_DEVICE devices_blob += NULL_DEVICE