Merge pull request #17741 from poettering/cryptsetup-fido2

cryptsetup: add support for unlocking cryptsetup volumes via FIDO2 + TPM2 + add systemd-cryptenroll tool + more
This commit is contained in:
Lennart Poettering 2020-12-17 22:37:22 +01:00 committed by GitHub
commit 5cd35a171c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 6568 additions and 1218 deletions

View File

@ -24,7 +24,6 @@ BuildPackages=
git
gnu-efi
gperf
libiptc-dev
libacl1-dev
libaudit-dev
libblkid-dev
@ -35,19 +34,23 @@ BuildPackages=
libdbus-1-dev
libdw-dev
libfdisk-dev
libfido2-dev
libgcrypt20-dev
libgnutls28-dev
libidn2-0-dev
libiptc-dev
libkmod-dev
liblzma-dev
liblz4-dev
liblz4-tool
liblzma-dev
libmicrohttpd-dev
libmount-dev
libpam0g-dev
libqrencode-dev
libseccomp-dev
libsmartcols-dev
libssl-dev
libtss2-dev
libxkbcommon-dev
libzstd-dev
m4
@ -63,8 +66,12 @@ BuildPackages=
Packages=
gdb
libfdisk1
libfido2-1
libidn2-0
libqrencode4
# We pull in the -dev package here, since the binary ones appear to change names too often, and the -dev package pulls the right deps in automatically
libtss2-dev
locales
strace

View File

@ -66,6 +66,7 @@ BuildPackages=
python3-lxml
qrencode-devel
rpm
tpm2-tss-devel
tree
valgrind-devel
xz-devel
@ -79,6 +80,7 @@ Packages=
# procps-ng provides a set of useful utilies (ps, free, etc)
procps-ng
strace
tpm2-tss
BuildDirectory=mkosi.builddir
Cache=mkosi.cache

View File

@ -35,6 +35,7 @@ BuildPackages=
libdbus-1-dev
libdw-dev
libfdisk-dev
libfido2-dev
libgcrypt20-dev
libgnutls28-dev
libidn2-0-dev
@ -50,6 +51,8 @@ BuildPackages=
libqrencode-dev
libseccomp-dev
libsmartcols-dev
libssl-dev
libtss2-dev
libxkbcommon-dev
libxtables-dev
libzstd-dev
@ -67,8 +70,11 @@ BuildPackages=
Packages=
gdb
libfido2-1
libidn2-0
libqrencode4
# We pull in the -dev package here, since the binary ones appear to change names too often, and the -dev package pulls the right deps in automatically
libtss2-dev
locales
strace

35
TODO
View File

@ -22,8 +22,32 @@ Features:
* expose MS_NOSYMFOLLOW in various places
* Add concept for upgrading TPM2 enrollments, maybe a new switch
--pcrs=4:<hash> or so, i.e. select a PCR to include in the hash, and then
override its hash
* homed: store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with
systemd-cryptsetup, so that it can unlock homed volumes
* cryptenroll: politely refuse enrolling new keys to homed volumes, since we
we cannot update identity info
* TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades
and such
* cryptsetup: if only recovery keys are registered and no regular passphrases,
ask user for "recovery key", not "passphrase"
* cyptsetup: add option for automatically removing empty password slot on boot
* cryptsetup: optionally, when run during boot-up and password is never
entered, and we are on AC power (or so), power off machine again
entered, and we are on battery power (or so), power off machine again
* cryptsetup: when FIDO2/PKCS#11/TPM2 token/chip didn't show up after some
time, abort the attempt, fallback to asking for pw
* cryptsetup: when waiting for FIDO2/PKCS#11 token, tell plymouth that, and
allow plymouth to abort the waiting and enter pw instead
* when configuring loopback netif, and it fails due to EPERM, eat up error if
it happens to be set up alright already.
@ -200,9 +224,6 @@ Features:
thus allows defining OS images which can be A/B updated and we default to the
newest version automatically, both in nspawn and in sd-boot
* cryptsetup: support FIDO2 tokens for deriving keys (i.e. do what homed can do
also in plain cryptsetup)
* systemd-gpt-auto should probably set x-systemd.growfs on the mounts it
creates
@ -241,12 +262,6 @@ Features:
* add growvol and makevol options for /etc/crypttab, similar to
x-systemd.growfs and x-systemd-makefs.
* hook up the TPM to /etc/crypttab, with a new option that is similar to the
new PKCS#11 option in crypttab, and allows unlocking a LUKS volume via a key
unsealed from the TPM. Optionally, if TPM is not available fall back to
TPM-less mode, and set up linear DM mapping instead (inspired by kpartx), so
that the device paths stay the same, regardless if crypto is used or not.
* systemd-repart: by default generate minimized partition tables (i.e. tables
that only cover the space actually used, excluding any free space at the
end), in order to maximize dd'ability. Requires libfdisk work, see

View File

@ -45,13 +45,12 @@
The first two fields are mandatory, the remaining two are
optional.</para>
<para>Setting up encrypted block devices using this file supports
three encryption modes: LUKS, TrueCrypt and plain. See
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for more information about each mode. When no mode is specified in
the options field and the block device contains a LUKS signature,
it is opened as a LUKS device; otherwise, it is assumed to be in
raw dm-crypt (plain mode) format.</para>
<para>Setting up encrypted block devices using this file supports four encryption modes: LUKS, TrueCrypt,
BitLocker and plain. See <citerefentry
project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
more information about each mode. When no mode is specified in the options field and the block device
contains a LUKS signature, it is opened as a LUKS device; otherwise, it is assumed to be in raw dm-crypt
(plain mode) format.</para>
<para>The four fields of <filename>/etc/crypttab</filename> are defined as follows:</para>
@ -65,9 +64,10 @@
<literal>UUID=</literal> followed by the UUID.</para></listitem>
<listitem><para>The third field specifies an absolute path to a file with the encryption
key. Optionally, the path may be followed by <literal>:</literal> and an fstab device specification
(e.g. starting with <literal>LABEL=</literal> or similar); in which case the path is taken relative to
the device file system root. If the field is not present or is <literal>none</literal> or
key. Optionally, the path may be followed by <literal>:</literal> and an
<filename>/etc/fstab</filename> style device specification (e.g. starting with
<literal>LABEL=</literal> or similar); in which case the path is taken relative to the specified
device's file system root. If the field is not present or is <literal>none</literal> or
<literal>-</literal>, a key file named after the volume to unlock (i.e. the first column of the line),
suffixed with <filename>.key</filename> is automatically loaded from the
<filename>/etc/cryptsetup-keys.d/</filename> and <filename>/run/cryptsetup-keys.d/</filename>
@ -83,6 +83,60 @@
<listitem><para>The fourth field, if present, is a comma-delimited list of options. The supported
options are listed below.</para></listitem>
</orderedlist>
</refsect1>
<refsect1>
<title>Key Acquisition</title>
<para>Six different mechanisms for acquiring the decryption key or passphrase unlocking the encrypted
volume are supported. Specifically:</para>
<orderedlist>
<listitem><para>Most prominently, the user may be queried interactively during volume activation
(i.e. typically at boot), asking them to type in the necessary passphrase(s).</para></listitem>
<listitem><para>The (unencrypted) key may be read from a file on disk, possibly on removable media. The third field
of each line encodes the location, for details see above.</para></listitem>
<listitem><para>The (unencrypted) key may be requested from another service, by specifying an
<constant>AF_UNIX</constant> file system socket in place of a key file in the third field. For details
see above and below.</para></listitem>
<listitem><para>The key may be acquired via a PKCS#11 compatible hardware security token or
smartcard. In this case an encrypted key is stored on disk/removable media, acquired via
<constant>AF_UNIX</constant>, or stored in the LUKS2 JSON token metadata header. The encrypted key is
then decrypted by the PKCS#11 token with an RSA key stored on it, and then used to unlock the encrypted
volume. Use the <option>pkcs11-uri=</option> option described below to use this mechanism.</para></listitem>
<listitem><para>Similar, the key may be acquired via a FIDO2 compatible hardware security token (which
must implement the "hmac-secret" extension). In this case a (during enrollment) randomly generated key
is stored on disk/removable media, acquired via <constant>AF_UNIX</constant>, or stored in the LUKS2
JSON token metadata header. The random key is hashed via a keyed hash function (HMAC) on the FIDO2
token, using a secret key stored on the token that never leaves it. The resulting hash value is then
used as key to unlock the encrypted volume. Use the <option>fido2-device=</option> option described
below to use this mechanism.</para></listitem>
<listitem><para>Similar, the key may be acquired via a TPM2 security chip. In this case a (during
enrollment) randomly generated key — encrypted by an asymmetric key derived from the TPM2 chip's seed
key — is stored on disk/removable media, acquired via <constant>AF_UNIX</constant>, or stored in the
LUKS2 JSON token metadata header. Use the <option>tpm2-device=</option> option described below to use
this mechanism.</para></listitem>
</orderedlist>
<para>For the latter five mechanisms the source for the key material used for unlocking the volume is
primarily configured in the third field of each <filename>/etc/crypttab</filename> line, but may also
configured in <filename>/etc/cryptsetup-keys.d/</filename> and
<filename>/run/cryptsetup-keys.d/</filename> (see above) or in the LUKS2 JSON token header (in case of
the latter three). Use the
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool to enroll PKCS#11, FIDO2 and TPM2 devices in LUKS2 volumes.</para>
</refsect1>
<refsect1>
<title>Supported Options</title>
<para>The following options may be used in the fourth field of each line:</para>
<variablelist class='fstab-options'>
@ -125,10 +179,10 @@
for possible values and the default value of this
option.</para>
<para>Optionally, the path may be followed by <literal>:</literal> and an fstab device specification
(e.g. starting with <literal>UUID=</literal> or similar); in which case, the path is relative to the
device file system root. The device gets mounted automatically for LUKS device activation duration only.
</para></listitem>
<para>Optionally, the path may be followed by <literal>:</literal> and an
<filename>/etc/fstab</filename> device specification (e.g. starting with <literal>UUID=</literal> or
similar); in which case, the path is relative to the device file system root. The device gets mounted
automatically for LUKS device activation duration only.</para></listitem>
</varlistentry>
<varlistentry>
@ -198,8 +252,8 @@
<varlistentry>
<term><option>bitlk</option></term>
<listitem><para>Decrypt Bitlocker drive. Encryption parameters
are deduced by cryptsetup from Bitlocker header.</para></listitem>
<listitem><para>Decrypt BitLocker drive. Encryption parameters
are deduced by cryptsetup from BitLocker header.</para></listitem>
</varlistentry>
<varlistentry>
@ -269,7 +323,7 @@
<varlistentry>
<term><option>same-cpu-crypt</option></term>
<listitem><para>Perform encryption using the same cpu that IO was submitted on. The default is to use
<listitem><para>Perform encryption using the same CPU that IO was submitted on. The default is to use
an unbound workqueue so that encryption work is automatically balanced between available CPUs.</para>
<para>This requires kernel 4.0 or newer.</para>
@ -451,15 +505,134 @@
<varlistentry>
<term><option>pkcs11-uri=</option></term>
<listitem><para>Takes a <ulink url="https://tools.ietf.org/html/rfc7512">RFC7512 PKCS#11 URI</ulink>
pointing to a private RSA key which is used to decrypt the key specified in the third column of the
line. This is useful for unlocking encrypted volumes through security tokens or smartcards. See below
for an example how to set up this mechanism for unlocking a LUKS volume with a YubiKey security
token. The specified URI can refer directly to a private RSA key stored on a token or alternatively
<listitem><para>Takes either the special value <literal>auto</literal> or an <ulink
url="https://tools.ietf.org/html/rfc7512">RFC7512 PKCS#11 URI</ulink> pointing to a private RSA key
which is used to decrypt the encrypted key specified in the third column of the line. This is useful
for unlocking encrypted volumes through PKCS#11 compatible security tokens or smartcards. See below
for an example how to set up this mechanism for unlocking a LUKS2 volume with a YubiKey security
token.</para>
<para>If specified as <literal>auto</literal> the volume must be of type LUKS2 and must carry PKCS#11
security token metadata in its LUKS2 JSON token section. In this mode the URI and the encrypted key
are automatically read from the LUKS2 JSON token header. Use
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
as simple tool for enrolling PKCS#11 security tokens or smartcards in a way compatible with
<literal>auto</literal>. In this mode the third column of the line should remain empty (that is,
specified as <literal>-</literal>).</para>
<para>The specified URI can refer directly to a private RSA key stored on a token or alternatively
just to a slot or token, in which case a search for a suitable private RSA key will be performed. In
this case if multiple suitable objects are found the token is refused. The key configured in the
third column is passed as is to RSA decryption. The resulting decrypted key is then base64 encoded
before it is used to unlock the LUKS volume.</para></listitem>
this case if multiple suitable objects are found the token is refused. The encrypted key configured
in the third column of the line is passed as is (i.e. in binary form, unprocessed) to RSA
decryption. The resulting decrypted key is then Base64 encoded before it is used to unlock the LUKS
volume.</para>
<para>Use <command>systemd-cryptenroll --pkcs11-token-uri=list</command> to list all suitable PKCS#11
security tokens currently plugged in, along with their URIs.</para>
<para>Note that many newer security tokens that may be used as PKCS#11 security token typically also
implement the newer and simpler FIDO2 standard. Consider using <option>fido2-device=</option>
(described below) to enroll it via FIDO2 instead. Note that a security token enrolled via PKCS#11
cannot be used to unlock the volume via FIDO2, unless also enrolled via FIDO2, and vice
versa.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>fido2-device=</option></term>
<listitem><para>Takes either the special value <literal>auto</literal> or the path to a
<literal>hidraw</literal> device node (e.g. <filename>/dev/hidraw1</filename>) referring to a FIDO2
security token that implements the <literal>hmac-secret</literal> extension (most current hardware
security tokens do). See below for an example how to set up this mechanism for unlocking an encrypted
volume with a FIDO2 security token.</para>
<para>If specified as <literal>auto</literal> the FIDO2 token device is automatically discovered, as
it is plugged in.</para>
<para>FIDO2 volume unlocking requires a client ID hash (CID) to be configured via
<option>fido2-cid=</option> (see below) and a key to pass to the security token's HMAC functionality
(configured in the line's third column) to operate. If not configured and the volume is of type
LUKS2, the CID and the key are read from LUKS2 JSON token metadata instead. Use
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
as simple tool for enrolling FIDO2 security tokens, compatible with this automatic mode, which is
only available for LUKS2 volumes.</para>
<para>Use <command>systemd-cryptenroll --fido2-device=list</command> to list all suitable FIDO2
security tokens currently plugged in, along with their device nodes.</para>
<para>This option implements the following mechanism: the configured key is hashed via they HMAC
keyed hash function the FIDO2 device implements, keyed by a secret key embedded on the device. The
resulting hash value is Base64 encoded and used to unlock the LUKS2 volume. As it should not be
possible to extract the secret from the hardware token, it should not be possible to retrieve the
hashed key given the configured key — without possessing the hardware token.</para>
<para>Note that many security tokens that implement FIDO2 also implement PKCS#11, suitable for
unlocking volumes via the <option>pkcs11-uri=</option> option described above. Typically the newer,
simpler FIDO2 standard is preferable.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>fido2-cid=</option></term>
<listitem><para>Takes a Base64 encoded FIDO2 client ID to use for the FIDO2 unlock operation. If
specified, but <option>fido2-device=</option> is not, <option>fido2-device=auto</option> is
implied. If <option>fido2-device=</option> is used but <option>fido2-cid=</option> is not, the volume
must be of LUKS2 type, and the CID is read from the LUKS2 JSON token header. Use
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for enrolling a FIDO2 token in the LUKS2 header compatible with this automatic
mode.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>fido2-rp=</option></term>
<listitem><para>Takes a string, configuring the FIDO2 Relying Party (rp) for the FIDO2 unlock
operation. If not specified <literal>io.systemd.cryptsetup</literal> is used, except if the the LUKS2
JSON token header contains a different value. It should normally not be necessary to override
this.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-device=</option></term>
<listitem><para>Takes either the special value <literal>auto</literal> or the path to a device node
(e.g. <filename>/dev/tpmrm0</filename>) referring to a TPM2 security chip. See below for an example
how to set up this mechanism for unlocking an encrypted volume with a TPM2 chip.</para>
<para>Use <option>tpm2-pcrs=</option> (see below) to configure the set of TPM2 PCRs to bind the
volume unlocking to. Use
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
as simple tool for enrolling TPM2 security chips in LUKS2 volumes.</para>
<para>If specified as <literal>auto</literal> the TPM2 device is automatically discovered. Use
<command>systemd-cryptenroll --tpm2-device=list</command> to list all suitable TPM2 devices currently
available, along with their device nodes.</para>
<para>This option implements the following mechanism: when enrolling a TPM2 device via
<command>systemd-cryptenroll</command> on a LUKS2 volume, a randomized key unlocking the volume is
generated on the host and loaded into the TPM2 chip where it is encrypted with an asymmetric
"primary" key pair derived from the TPM2's internal "seed" key. Neither the seed key nor the primary
key are permitted to ever leave the TPM2 chip — however, the now encrypted randomized key may. It is
saved in the LUKS2 volume JSON token header. When unlocking the encrypted volume, the primary key
pair is generated on the TPM2 chip again (which works as long as the chip's seed key is correctly
maintained by the TPM2 chip), which is then used to decrypt (on the TPM2 chip) the encrypted key from
the LUKS2 volume JSON token header saved there during enrollment. The resulting decrypted key is then
used to unlock the volume. When the randomized key is encrypted the current values of the selected
PCRs (see below) are included in the operation, so that different PCR state results in different
encrypted keys and the decrypted key can only be recovered if the same PCR state is
reproduced.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>tpm2-pcrs=</option></term>
<listitem><para>Takes a comma separated list of numeric TPM2 PCR (i.e. "Platform Configuration
Register") indexes to bind the TPM2 volume unlocking to. This option is only useful when TPM2
enrollment metadata is not available in the LUKS2 JSON token header already, the way
<command>systemd-cryptenroll</command> writes it there. If not used (and no metadata in the LUKS2
JSON token header defines it), defaults to a list of a single entry: PCR 7. Assign an empty string to
encode a policy that binds the key to no PCRs, making the key accessible to local programs regardless
of the current PCR state.</para></listitem>
</varlistentry>
<varlistentry>
@ -523,7 +696,7 @@
<programlisting><constant>NUL</constant> <replaceable>RANDOM</replaceable> <literal>/cryptsetup/</literal> <replaceable>VOLUME</replaceable></programlisting>
<para>In other words: a <constant>NUL</constant> byte (as required for abstract namespace sockets),
followed by a random string (consisting of alphabenumeric characters only), followed by the literal
followed by a random string (consisting of alphanumeric characters only), followed by the literal
string <literal>/cryptsetup/</literal>, followed by the name of the volume to acquire they key
for. Example (for a volume <literal>myvol</literal>):</para>
@ -533,11 +706,13 @@
name with <citerefentry
project='man-pages'><refentrytitle>getpeername</refentrytitle><manvolnum>2</manvolnum></citerefentry>,
and use it to determine which key to send, allowing a single listening socket to serve keys for a
multitude of volumes. If the PKCS#11 logic is used (see below) the socket source name is picked in
identical fashion, except that the literal string <literal>/cryptsetup-pkcs11/</literal> is used. This is
multitude of volumes. If the PKCS#11 logic is used (see above) the socket source name is picked in
identical fashion, except that the literal string <literal>/cryptsetup-pkcs11/</literal> is used (similar
for FIDO2: <literal>/cryptsetup-fido2/</literal> and TPM2: <literal>/cryptsetup-tpm2/</literal>). This is
done so that services providing key material know that not a secret key is requested but an encrypted key
that will be decrypted via the PKCS#11 logic to acquire the final secret key.</para>
that will be decrypted via the PKCS#11/FIDO2/TPM2 logic to acquire the final secret key.</para>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
@ -556,25 +731,48 @@ external /dev/sda3 keyfile:LABEL=keydev keyfile-timeout=10s,cipher=xchac
</example>
<example>
<title>Yubikey-based Volume Unlocking Example</title>
<title>Yubikey-based PKCS#11 Volume Unlocking Example</title>
<para>The PKCS#11 logic allows hooking up any compatible security token that is capable of storing RSA
decryption keys. Here's an example how to set up a Yubikey security token for this purpose, using
<citerefentry project='debian'><refentrytitle>ykmap</refentrytitle><manvolnum>1</manvolnum></citerefentry>
from the yubikey-manager project:</para>
decryption keys for unlocking an encrypted volume. Here's an example how to set up a Yubikey security
token for this purpose on a LUKS2 volume, using <citerefentry
project='debian'><refentrytitle>ykmap</refentrytitle><manvolnum>1</manvolnum></citerefentry> from the
yubikey-manager project to initialize the token and
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
to add it in the LUKS2 volume:</para>
<programlisting><xi:include href="yubikey-crypttab.sh" parse="text" /></programlisting>
<para>A few notes on the above:</para>
<para>A few notes on the above:</para>
<itemizedlist>
<listitem><para>We use RSA2048, which is the longest key size current Yubikeys support</para></listitem>
<listitem><para>LUKS key size must be shorter than 2048bit due to RSA padding, hence we use 128 bytes</para></listitem>
<listitem><para>We use Yubikey key slot 9d, since that's apparently the keyslot to use for decryption purposes,
<ulink url="https://developers.yubico.com/PIV/Introduction/Certificate_slots.html">see
documentation</ulink>.</para></listitem>
</itemizedlist>
<itemizedlist>
<listitem><para>We use RSA2048, which is the longest key size current Yubikeys support</para></listitem>
<listitem><para>We use Yubikey key slot 9d, since that's apparently the keyslot to use for decryption purposes,
<ulink url="https://developers.yubico.com/PIV/Introduction/Certificate_slots.html">see
documentation</ulink>.</para></listitem>
</itemizedlist>
</example>
<example>
<title>FIDO2 Volume Unlocking Example</title>
<para>The FIDO2 logic allows using any compatible FIDO2 security token that implements the
<literal>hmac-secret</literal> extension for unlocking an encrypted volume. Here's an example how to
set up a FIDO2 security token for this purpose for a LUKS2 volume, using
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>:</para>
<programlisting><xi:include href="fido2-crypttab.sh" parse="text" /></programlisting>
</example>
<example>
<title>TPM2 Volume Unlocking Example</title>
<para>The TPM2 logic allows using any TPM2 chip supported by the Linux kernel for unlocking an
encrypted volume. Here's an example how to set up a TPM2 chip for this purpose for a LUKS2 volume,
using
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>:</para>
<programlisting><xi:include href="tpm2-crypttab.sh" parse="text" /></programlisting>
</example>
</refsect1>
@ -584,6 +782,7 @@ external /dev/sda3 keyfile:LABEL=keydev keyfile-timeout=10s,cipher=xchac
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>mkswap</refentrytitle><manvolnum>8</manvolnum></citerefentry>,

10
man/fido2-crypttab.sh Normal file
View File

@ -0,0 +1,10 @@
# Enroll the security token in the LUKS2 volume. Replace /dev/sdXn by the
# partition to use (e.g. /dev/sda1).
sudo systemd-cryptenroll --fido2-device=auto /dev/sdXn
# Test: Let's run systemd-cryptsetup to test if this worked.
sudo /usr/lib/systemd/systemd-cryptsetup attach mytest /dev/sdXn - fido2-device=auto
# If that worked, let's now add the same line persistently to /etc/crypttab,
# for the future.
sudo bash -c 'echo "mytest /dev/sdXn - fido2-device=auto" >> /etc/crypttab'

View File

@ -492,12 +492,19 @@
<varlistentry>
<term><varname>Encrypt=</varname></term>
<listitem><para>Takes a boolean parameter, defaulting to false. If true the partition will be
<listitem><para>Takes one of <literal>off</literal>, <literal>key-file</literal>,
<literal>tpm2</literal> and <literal>key-file+tpm2</literal> (alternatively, also accepts a boolean
value, which is mapped to <literal>off</literal> when false, and <literal>key-file</literal> when
true). Defaults to <literal>off</literal>. If not <literal>off</literal> the partition will be
formatted with a LUKS2 superblock, before the blocks configured with <varname>CopyBlocks=</varname>
are copied in or the file system configured with <varname>Format=</varname> is created.</para>
<para>The LUKS2 UUID is automatically derived from the partition UUID in a stable fashion. A single
key is added to the LUKS2 superblock, configurable with the <option>--key-file=</option> switch to
<para>The LUKS2 UUID is automatically derived from the partition UUID in a stable fashion. If
<literal>key-file</literal> or <literal>key-file+tpm2</literal> is used a key is added to the LUKS2
superblock, configurable with the <option>--key-file=</option> switch to
<command>systemd-repart</command>. If <literal>tpm2</literal> or <literal>key-file+tpm2</literal> is
used a key is added to the LUKS2 superblock that is enrolled to the local TPM2 chip, as configured
with the <option>--tpm2-device=</option> and <option>--tpm2-pcrs=</option> options to
<command>systemd-repart</command>.</para>
<para>When used this slightly alters the size allocation logic as the implicit, minimal size limits
@ -627,7 +634,8 @@ SizeMaxBytes=64M
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>sfdisk</refentrytitle><manvolnum>8</manvolnum></citerefentry>
<citerefentry project='man-pages'><refentrytitle>sfdisk</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -825,6 +825,7 @@ manpages = [
'8',
['systemd-coredump.socket', 'systemd-coredump@.service'],
'ENABLE_COREDUMP'],
['systemd-cryptenroll', '1', [], 'HAVE_LIBCRYPTSETUP'],
['systemd-cryptsetup-generator', '8', [], 'HAVE_LIBCRYPTSETUP'],
['systemd-cryptsetup@.service',
'8',

284
man/systemd-cryptenroll.xml Normal file
View File

@ -0,0 +1,284 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-cryptenroll" xmlns:xi="http://www.w3.org/2001/XInclude" conditional='HAVE_LIBCRYPTSETUP'>
<refentryinfo>
<title>systemd-cryptenroll</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-cryptenroll</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-cryptenroll</refname>
<refpurpose>Enroll PKCS#11, FIDO2, TPM2 token/devices to LUKS2 encrypted volumes</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-cryptenroll <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt">DEVICE</arg></command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-cryptenroll</command> is a tool for enrolling hardware security tokens and devices into a
LUKS2 encrypted volume, which may then be used to unlock the volume during boot. Specifically, it supports
tokens and credentials of the following kind to be enrolled:</para>
<orderedlist>
<listitem><para>PKCS#11 security tokens and smartcards that may carry an RSA key pair (e.g. various YubiKeys)</para></listitem>
<listitem><para>FIDO2 security tokens that implement the <literal>hmac-secret</literal> extension (most FIDO2 keys, including YubiKeys)</para></listitem>
<listitem><para>TPM2 security devices</para></listitem>
<listitem><para>Recovery keys. These are similar to regular passphrases, however are randomly generated
on the computer and thus generally have higher entropy than user chosen passphrases. Their character
set has been designed to ensure they are easy to type in, while having high entropy. They may also be
scanned off screen using QR codes. Recovery keys may be used for unlocking LUKS2 volumes wherever
passphrases are accepted. They are intended to be used in combination with an enrolled hardware
security token, as a recovery option when the token is lost.</para></listitem>
<listitem><para>Regular passphrases</para></listitem>
</orderedlist>
<para>In addition, the tool may be used to enumerate currently enrolled security tokens and wipe a subset
of them. The latter may be combined with the enrollment operation of a new security token, in order to
update or replace enrollments.</para>
<para>The tool supports only LUKS2 volumes, as it stores token meta-information in the LUKS2 JSON token
area, which is not available in other encryption formats.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</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></listitem>
</varlistentry>
<varlistentry>
<term><option>--recovery-key</option></term>
<listitem><para>Enroll a recovery key. Recovery keys are most identical to passphrases, but are
computer generated instead of human chosen, 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></listitem>
</varlistentry>
<varlistentry>
<term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>
<listitem><para>Enroll a PKCS#11 security token or smartcard (e.g. a YubiKey). Expects a PKCS#11
smart card URI referring to the token. Alternatively the special value <literal>auto</literal> may
be specified, in order to automatically determine the URI of a currently plugged in security token
(of which there must be exactly one). The special value <literal>list</literal> may be used to
enumerate all suitable PKCS#11 tokens currently plugged in. The security token must contain an RSA
key pair which is used to encrypt the randomly generated key that is used to unlock the LUKS2
volume. The encrypted key is then stored in the LUKS2 JSON token header area.</para>
<para>In order to unlock a LUKS2 volume with an enrolled PKCS#11 security token, specify the
<option>pkcs11-uri=</option> option in the respective <filename>/etc/crypttab</filename> line:</para>
<programlisting>myvolume /dev/sda1 - pkcs11-uri=auto</programlisting>
<para>See
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry> for a
more comprehensive example of a <command>systemd-cryptenroll</command> invocation and its matching
<filename>/etc/crypttab</filename> line.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fido2-device=</option><replaceable>PATH</replaceable></term>
<listitem><para>Enroll a FIDO2 security token that implements the <literal>hmac-secret</literal>
extension (e.g. a YubiKey). Expects a <filename>hidraw</filename> device referring to the FIDO2
device (e.g. <filename>/dev/hidraw1</filename>). Alternatively the special value
<literal>auto</literal> may be specified, in order to automatically determine the device node of a
currently plugged in security token (of which there must be exactly one). The special value
<literal>list</literal> may be used to enumerate all suitable FIDO2 tokens currently plugged in. Note
that many hardware security tokens that implement FIDO2 also implement the older PKCS#11
standard. Typically FIDO2 is preferable, given it's simpler to use and more modern.</para>
<para>In order to unlock a LUKS2 volume with an enrolled FIDO2 security token, specify the
<option>fido2-device=</option> option in the respective <filename>/etc/crypttab</filename> line:</para>
<programlisting>myvolume /dev/sda1 - fido2-device=auto</programlisting>
<para>See
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry> for a
more comprehensive example of a <command>systemd-cryptenroll</command> invocation and its matching
<filename>/etc/crypttab</filename> line.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-device=</option><replaceable>PATH</replaceable></term>
<listitem><para>Enroll a TPM2 security chip. Expects a device node path referring to the TPM2 chip
(e.g. <filename>/dev/tpmrm0</filename>). Alternatively the special value <literal>auto</literal> may
be specified, in order to automatically determine the device node of a currently discovered TPM2
device (of which there must be exactly one). The special value <literal>list</literal> may be used to
enumerate all suitable TPM2 devices currently discovered.</para>
<para>In order to unlock a LUKS2 volume with an enrolled TPM2 security chip, specify the
<option>tpm2-device=</option> option in the respective <filename>/etc/crypttab</filename> line:</para>
<programlisting>myvolume /dev/sda1 - tpm2-device=auto</programlisting>
<para>See
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry> for a
more comprehensive example of a <command>systemd-cryptenroll</command> invocation and its matching
<filename>/etc/crypttab</filename> line.</para>
<para>Use <option>--tpm2-pcrs=</option> (see below) to configure which TPM2 PCR indexes to bind the
enrollment to.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-pcrs=</option><arg rep="repeat">PCR</arg></term>
<listitem><para>Configures the TPM2 PCRs (Platform Configuration Registers) to bind the enrollment
requested via <option>--tpm2-device=</option> to. Takes a comma separated list of numeric PCR indexes
in the range 0…23. If not used, defaults to PCR 7 only. If an empty string is specified, binds the
enrollment to no PCRs at all. PCRs allow binding the enrollment to specific software versions and
system state, so that the enrolled unlocking key is only accessible (may be "unsealed") if specific
trusted software and/or configuration is used.</para></listitem>
<table>
<title>Well-known PCR Definitions</title>
<tgroup cols='2' align='left' colsep='1' rowsep='1'>
<colspec colname="pcr" />
<colspec colname="definition" />
<thead>
<row>
<entry>PCR</entry>
<entry>Explanation</entry>
</row>
</thead>
<tbody>
<row>
<entry>0</entry>
<entry>Core system firmware executable code; changes on firmware updates</entry>
</row>
<row>
<entry>1</entry>
<entry>Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements</entry>
</row>
<row>
<entry>2</entry>
<entry>Extended or pluggable executable code; includes option ROMs on pluggable hardware</entry>
</row>
<row>
<entry>3</entry>
<entry>Extended or pluggable firmware data; includes information about pluggable hardware</entry>
</row>
<row>
<entry>4</entry>
<entry>Boot loader; changes on boot loader updates</entry>
</row>
<row>
<entry>5</entry>
<entry>GPT/Partition table; changes when the partitions are added, modified or removed</entry>
</row>
<row>
<entry>6</entry>
<entry>Power state events; changes on system suspend/sleep</entry>
</row>
<row>
<entry>7</entry>
<entry>Secure boot state; changes when UEFI SecureBoot mode is enabled/disabled</entry>
</row>
<row>
<entry>8</entry>
<entry><citerefentry><refentrytitle>sd-boot</refentrytitle><manvolnum>8</manvolnum></citerefentry> measures the kernel command line in this PCR.</entry>
</row>
</tbody>
</tgroup>
</table>
</varlistentry>
<varlistentry>
<term><option>--wipe-slot=</option><arg rep="repeat">SLOT</arg></term>
<listitem><para>Wipes one or more LUKS2 key slots. Takes a comma separated list of numeric slot
indexes, or the special strings <literal>all</literal> (for wiping all key slots),
<literal>empty</literal> (for wiping all key slots that are unlocked by an empty passphrase),
<literal>password</literal> (for wiping all key slots that are unlocked by a traditional passphrase),
<literal>recovery</literal> (for wiping all key slots that are unlocked by a recovery key),
<literal>pkcs11</literal> (for wiping all key slots that are unlocked by a PKCS#11 token),
<literal>fido2</literal> (for wiping all key slots that are unlocked by a FIDO2 token),
<literal>tpm2</literal> (for wiping all key slots that are unlocked by a TPM2 chip), or any
combination of these strings or numeric indexes, in which case all slots matching either are
wiped. As safety precaution an operation that wipes all slots without exception (so that the volume
cannot be unlocked at all anymore, unless the volume key is known) is refused.</para>
<para>This switch may be used alone, in which case only the requested wipe operation is executed. It
may also be used in combination with any of the enrollment options listed above, in which case the
enrollment is completed first, and only when successful the wipe operation executed — and the newly
added slot is always excluded from the wiping. Combining enrollment and slot wiping may thus be used to
update existing enrollments:</para>
<programlisting>systemd-cryptenroll /dev/sda1 --wipe-slot=tpm2 --tpm2-device=auto</programlisting>
<para>The above command will enroll the TPM2 chip, and then wipe all previously crated TPM2
enrollments on the LUKS2 volume, leaving only the newly created one. Combining wiping and enrollment
may also be used to replace enrollments of different types, for example for changing from a PKCS#11
enrollment to a FIDO2 one:</para>
<programlisting>systemd-cryptenroll /dev/sda1 --wipe-slot=pkcs11 --fido2-device=auto</programlisting>
<para>Or for replacing an enrolled empty password by TPM2:</para>
<programlisting>systemd-cryptenroll /dev/sda1 --wipe-slot=empty --tpm2-device=auto</programlisting>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -228,6 +228,7 @@
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>

View File

@ -50,13 +50,14 @@
<orderedlist>
<listitem><para>If a key file is explicitly configured (via the third column in
<filename>/etc/crypttab</filename>), a key read from it is used. If a PKCS#11 token is configured
(using the <varname>pkcs11-uri=</varname> option) the key is decrypted before use.</para></listitem>
<filename>/etc/crypttab</filename>), a key read from it is used. If a PKCS#11 token, FIDO2 token or
TPM2 device is configured (using the <varname>pkcs11-uri=</varname>, <varname>fido2-device=</varname>,
<varname>tpm2-device=</varname> options) the key is decrypted before use.</para></listitem>
<listitem><para>If no key file is configured explicitly this way, a key file is automatically loaded
from <filename>/etc/cryptsetup-keys.d/<replaceable>volume</replaceable>.key</filename> and
<filename>/run/cryptsetup-keys.d/<replaceable>volume</replaceable>.key</filename>, if present. Here
too, if a PKCS#11 token is configured, any key found this way is decrypted before
too, if a PKCS#11/FIDO2/TPM2 token/device is configured, any key found this way is decrypted before
use.</para></listitem>
<listitem><para>If the <varname>try-empty-password</varname> option is specified it is then attempted
@ -77,6 +78,7 @@
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -300,12 +300,23 @@
<term><option>--key-file=</option></term>
<listitem><para>Takes a file system path. Configures the encryption key to use when setting up LUKS2
volumes configured with the <varname>Encrypt=</varname> setting in partition files. Should refer to a
regular file containing the key, or an <constant>AF_UNIX</constant> stream socket in the file
system. In the latter case a connection is made to it and the key read from it. If this switch is not
specified the empty key (i.e. zero length key) is used. This behaviour is useful for setting up encrypted
partitions during early first boot that receive their user-supplied password only in a later setup
step.</para></listitem>
volumes configured with the <varname>Encrypt=key-file</varname> setting in partition files. Should
refer to a regular file containing the key, or an <constant>AF_UNIX</constant> stream socket in the
file system. In the latter case a connection is made to it and the key read from it. If this switch
is not specified the empty key (i.e. zero length key) is used. This behaviour is useful for setting
up encrypted partitions during early first boot that receive their user-supplied password only in a
later setup step.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--tpm2-device=</option></term>
<term><option>--tpm2-pcrs=</option></term>
<listitem><para>Configures the TPM2 device and list of PCRs to use for LUKS2 volumes configured with
the <varname>Encrypt=tpm2</varname> option. These options take the same parameters as the identically
named options to
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and have the same effect on partitions where TPM2 enrollment is requested.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
@ -313,12 +324,19 @@
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
<citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>

10
man/tpm2-crypttab.sh Normal file
View File

@ -0,0 +1,10 @@
# Enroll the TPM2 security chip in the LUKS2 volume, and bind it to PCR 7
# only. Replace /dev/sdXn by the partition to use (e.g. /dev/sda1).
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/sdXn
# Test: Let's run systemd-cryptsetup to test if this worked.
sudo /usr/lib/systemd/systemd-cryptsetup attach mytest /dev/sdXn - tpm2-device=auto
# If that worked, let's now add the same line persistently to /etc/crypttab,
# for the future.
sudo bash -c 'echo "mytest /dev/sdXn - tpm2-device=auto" >> /etc/crypttab'

View File

@ -1,50 +1,26 @@
# Make sure no one can read the files we generate but us
umask 077
# Destroy any old key on the Yubikey (careful!)
ykman piv reset
# Generate a new private/public key pair on the device, store the public key in 'pubkey.pem'.
# Generate a new private/public key pair on the device, store the public key in
# 'pubkey.pem'.
ykman piv generate-key -a RSA2048 9d pubkey.pem
# Create a self-signed certificate from this public key, and store it on the
# device. The "subject" should be an arbitrary string to identify the token in
# the p11tool output below.
# device. The "subject" should be an arbitrary user-chosen string to identify
# the token with.
ykman piv generate-certificate --subject "Knobelei" 9d pubkey.pem
# Check if the newly create key on the Yubikey shows up as token in PKCS#11. Have a look at the output, and
# copy the resulting token URI to the clipboard.
p11tool --list-tokens
# Generate a (secret) random key to use as LUKS decryption key.
dd if=/dev/urandom of=plaintext.bin bs=128 count=1
# Encode the secret key also as base64 text (with all whitespace removed)
base64 < plaintext.bin | tr -d '\n\r\t ' > plaintext.base64
# Encrypt this newly generated (binary) LUKS decryption key using the public key whose private key is on the
# Yubikey, store the result in /etc/cryptsetup-keys.d/mytest.key, where we'll look for it during boot.
mkdir -p /etc/cryptsetup-keys.d
sudo openssl rsautl -encrypt -pubin -inkey pubkey.pem -in plaintext.bin -out /etc/cryptsetup-keys.d/mytest.key
# Configure the LUKS decryption key on the LUKS device. We use very low pbkdf settings since the key already
# has quite a high quality (it comes directly from /dev/urandom after all), and thus we don't need to do much
# key derivation. Replace /dev/sdXn by the partition to use (e.g. sda1)
sudo cryptsetup luksAddKey /dev/sdXn plaintext.base64 --pbkdf=pbkdf2 --pbkdf-force-iterations=1000
# Now securely delete the plain text LUKS key, we don't need it anymore, and since it contains secret key
# material it should be removed from disk thoroughly.
shred -u plaintext.bin plaintext.base64
# We don't need the public key anymore either, let's remove it too. Since this one is not security
# sensitive we just do a regular "rm" here.
# We don't need the public key anymore, let's remove it. Since it is not
# security sensitive we just do a regular "rm" here.
rm pubkey.pem
# Test: Let's run systemd-cryptsetup to test if this all worked. The option string should contain the full
# PKCS#11 URI we have in the clipboard; it tells the tool how to decipher the encrypted LUKS key. Note that
# systemd-cryptsetup automatically searches for the encrypted key in /etc/cryptsetup-keys.d/, hence we do
# not need to specify the key file path explicitly here.
sudo systemd-cryptsetup attach mytest /dev/sdXn - 'pkcs11-uri=pkcs11:…'
# Enroll the freshly initialized security token in the LUKS2 volume. Replace
# /dev/sdXn by the partition to use (e.g. /dev/sda1).
sudo systemd-cryptenroll --pkcs11-token-uri=auto /dev/sdXn
# If that worked, let's now add the same line persistently to /etc/crypttab, for the future.
sudo bash -c 'echo "mytest /dev/sdXn - \'pkcs11-uri=pkcs11:…\'" >> /etc/crypttab'
# Test: Let's run systemd-cryptsetup to test if this all worked.
sudo /usr/lib/systemd/systemd-cryptsetup attach mytest /dev/sdXn - pkcs11-uri=auto
# If that worked, let's now add the same line persistently to /etc/crypttab,
# for the future.
sudo bash -c 'echo "mytest /dev/sdXn - pkcs11-uri=auto" >> /etc/crypttab'

View File

@ -1185,6 +1185,17 @@ else
endif
conf.set10('HAVE_LIBFIDO2', have)
want_tpm2 = get_option('tpm2')
if want_tpm2 != 'false' and not skip_deps
tpm2 = dependency('tss2-esys tss2-rc tss2-mu',
required : want_tpm2 == 'true')
have = tpm2.found()
else
have = false
tpm2 = []
endif
conf.set10('HAVE_TPM2', have)
want_elfutils = get_option('elfutils')
if want_elfutils != 'false' and not skip_deps
libdw = dependency('libdw',
@ -2271,8 +2282,7 @@ if conf.get('ENABLE_HOMED') == 1
libcrypt,
libopenssl,
libfdisk,
libp11kit,
libfido2],
libp11kit],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
@ -2298,7 +2308,6 @@ if conf.get('ENABLE_HOMED') == 1
libcrypt,
libopenssl,
libp11kit,
libfido2,
libdl],
install_rpath : rootlibexecdir,
install : true,
@ -2369,9 +2378,11 @@ executable(
if conf.get('HAVE_LIBCRYPTSETUP') == 1
systemd_cryptsetup_sources = files('''
src/cryptsetup/cryptsetup-pkcs11.h
src/cryptsetup/cryptsetup-fido2.h
src/cryptsetup/cryptsetup-keyfile.c
src/cryptsetup/cryptsetup-keyfile.h
src/cryptsetup/cryptsetup-pkcs11.h
src/cryptsetup/cryptsetup-tpm2.h
src/cryptsetup/cryptsetup.c
'''.split())
@ -2379,6 +2390,14 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
systemd_cryptsetup_sources += files('src/cryptsetup/cryptsetup-pkcs11.c')
endif
if conf.get('HAVE_LIBFIDO2') == 1
systemd_cryptsetup_sources += files('src/cryptsetup/cryptsetup-fido2.c')
endif
if conf.get('HAVE_TPM2') == 1
systemd_cryptsetup_sources += files('src/cryptsetup/cryptsetup-tpm2.c')
endif
executable(
'systemd-cryptsetup',
systemd_cryptsetup_sources,
@ -2417,6 +2436,47 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
install_rpath : rootlibexecdir,
install : true,
install_dir : systemgeneratordir)
systemd_cryptenroll_sources = files('''
src/cryptenroll/cryptenroll-fido2.h
src/cryptenroll/cryptenroll-list.c
src/cryptenroll/cryptenroll-list.h
src/cryptenroll/cryptenroll-password.c
src/cryptenroll/cryptenroll-password.h
src/cryptenroll/cryptenroll-pkcs11.h
src/cryptenroll/cryptenroll-recovery.c
src/cryptenroll/cryptenroll-recovery.h
src/cryptenroll/cryptenroll-tpm2.h
src/cryptenroll/cryptenroll-wipe.c
src/cryptenroll/cryptenroll-wipe.h
src/cryptenroll/cryptenroll.c
src/cryptenroll/cryptenroll.h
'''.split())
if conf.get('HAVE_P11KIT') == 1 and conf.get('HAVE_OPENSSL') == 1
systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-pkcs11.c')
endif
if conf.get('HAVE_LIBFIDO2') == 1
systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-fido2.c')
endif
if conf.get('HAVE_TPM2') == 1
systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-tpm2.c')
endif
executable(
'systemd-cryptenroll',
systemd_cryptenroll_sources,
include_directories : includes,
link_with : [libshared],
dependencies : [libcryptsetup,
libdl,
libopenssl,
libp11kit],
install_rpath : rootlibexecdir,
install : true,
install_dir : bindir)
endif
if conf.get('HAVE_SYSV_COMPAT') == 1
@ -3737,6 +3797,7 @@ foreach tuple : [
['libfdisk'],
['p11kit'],
['libfido2'],
['tpm2'],
['AUDIT'],
['IMA'],
['AppArmor'],

View File

@ -327,6 +327,8 @@ option('p11kit', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'p11kit support')
option('libfido2', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'FIDO2 support')
option('tpm2', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'TPM2 support')
option('elfutils', type : 'combo', choices : ['auto', 'true', 'false'],
description : 'elfutils support')
option('zlib', type : 'combo', choices : ['auto', 'true', 'false'],

View File

@ -211,10 +211,6 @@ static int get_max_fd(void) {
return (int) (m - 1);
}
static int cmp_int(const int *a, const int *b) {
return CMP(*a, *b);
}
int close_all_fds(const int except[], size_t n_except) {
static bool have_close_range = true; /* Assume we live in the future */
_cleanup_closedir_ DIR *d = NULL;

View File

@ -187,6 +187,8 @@ basic_sources = files('''
ratelimit.h
raw-clone.h
raw-reboot.h
recovery-key.c
recovery-key.h
replace-var.c
replace-var.h
rlimit-util.c

View File

@ -1,10 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include "modhex.h"
#include "macro.h"
#include "memory-util.h"
#include "random-util.h"
#include "recovery-key.h"
const char modhex_alphabet[16] = {
'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v'
@ -29,24 +27,24 @@ int normalize_recovery_key(const char *password, char **ret) {
l = strlen(password);
if (!IN_SET(l,
MODHEX_RAW_LENGTH*2, /* syntax without dashes */
MODHEX_FORMATTED_LENGTH-1)) /* syntax with dashes */
RECOVERY_KEY_MODHEX_RAW_LENGTH*2, /* syntax without dashes */
RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1)) /* syntax with dashes */
return -EINVAL;
mangled = new(char, MODHEX_FORMATTED_LENGTH);
mangled = new(char, RECOVERY_KEY_MODHEX_FORMATTED_LENGTH);
if (!mangled)
return -ENOMEM;
for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) {
for (size_t i = 0, j = 0; i < RECOVERY_KEY_MODHEX_RAW_LENGTH; i++) {
size_t k;
int a, b;
if (l == MODHEX_RAW_LENGTH*2)
if (l == RECOVERY_KEY_MODHEX_RAW_LENGTH*2)
/* Syntax without dashes */
k = i * 2;
else {
/* Syntax with dashes */
assert(l == MODHEX_FORMATTED_LENGTH-1);
assert(l == RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1);
k = i * 2 + i / 4;
if (i > 0 && i % 4 == 0 && password[k-1] != '-')
@ -67,8 +65,42 @@ int normalize_recovery_key(const char *password, char **ret) {
mangled[j++] = '-';
}
mangled[MODHEX_FORMATTED_LENGTH-1] = 0;
mangled[RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1] = 0;
*ret = TAKE_PTR(mangled);
return 0;
}
int make_recovery_key(char **ret) {
_cleanup_(erase_and_freep) char *formatted = NULL;
_cleanup_(erase_and_freep) uint8_t *key = NULL;
int r;
assert(ret);
key = new(uint8_t, RECOVERY_KEY_MODHEX_RAW_LENGTH);
if (!key)
return -ENOMEM;
r = genuine_random_bytes(key, RECOVERY_KEY_MODHEX_RAW_LENGTH, RANDOM_BLOCK);
if (r < 0)
return r;
/* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */
formatted = new(char, RECOVERY_KEY_MODHEX_FORMATTED_LENGTH);
if (!formatted)
return -ENOMEM;
for (size_t i = 0, j = 0; i < RECOVERY_KEY_MODHEX_RAW_LENGTH; i++) {
formatted[j++] = modhex_alphabet[key[i] >> 4];
formatted[j++] = modhex_alphabet[key[i] & 0xF];
if (i % 4 == 3)
formatted[j++] = '-';
}
formatted[RECOVERY_KEY_MODHEX_FORMATTED_LENGTH-1] = 0;
*ret = TAKE_PTR(formatted);
return 0;
}

View File

@ -2,10 +2,12 @@
#pragma once
/* 256 bit keys = 32 bytes */
#define MODHEX_RAW_LENGTH 32
#define RECOVERY_KEY_MODHEX_RAW_LENGTH 32
/* Formatted as sequences of 64 modhex characters, with dashes inserted after multiples of 8 chars (incl. trailing NUL) */
#define MODHEX_FORMATTED_LENGTH (MODHEX_RAW_LENGTH*2/8*9)
#define RECOVERY_KEY_MODHEX_FORMATTED_LENGTH (RECOVERY_KEY_MODHEX_RAW_LENGTH*2/8*9)
int make_recovery_key(char **ret);
extern const char modhex_alphabet[16];

View File

@ -27,3 +27,7 @@ void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size,
}
return NULL;
}
int cmp_int(const int *a, const int *b) {
return CMP(*a, *b);
}

View File

@ -68,3 +68,5 @@ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, __compar_
int (*_func_)(const typeof(p[0])*, const typeof(p[0])*, typeof(userdata)) = func; \
qsort_r_safe((p), (n), sizeof((p)[0]), (__compar_d_fn_t) _func_, userdata); \
})
int cmp_int(const int *a, const int *b);

View File

@ -84,6 +84,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static)
#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,static)
/* For string conversions where numbers are also acceptable */
#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \

View File

@ -0,0 +1,88 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "cryptenroll-fido2.h"
#include "hexdecoct.h"
#include "json.h"
#include "libfido2-util.h"
#include "memory-util.h"
#include "random-util.h"
int enroll_fido2(
struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,
const char *device) {
_cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
size_t cid_size, salt_size, secret_size;
_cleanup_free_ void *cid = NULL;
const char *node, *un;
int r, keyslot;
assert_se(cd);
assert_se(volume_key);
assert_se(volume_key_size > 0);
assert_se(device);
assert_se(node = crypt_get_device_name(cd));
un = strempty(crypt_get_uuid(cd));
r = fido2_generate_hmac_hash(
device,
/* rp_id= */ "io.systemd.cryptsetup",
/* rp_name= */ "Encrypted Volume",
/* user_id= */ un, strlen(un), /* We pass the user ID and name as the same: the disk's UUID if we have it */
/* user_name= */ un,
/* user_display_name= */ node,
/* user_icon_name= */ NULL,
/* askpw_icon_name= */ "drive-harddisk",
&cid, &cid_size,
&salt, &salt_size,
&secret, &secret_size,
NULL);
if (r < 0)
return r;
/* Before we use the secret, we base64 encode it, for compat with homed, and to make it easier to type in manually */
r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
base64_encoded,
strlen(base64_encoded));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
r = json_build(&v,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-fido2")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
JSON_BUILD_PAIR("fido2-credential", JSON_BUILD_BASE64(cid, cid_size)),
JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_BASE64(salt, salt_size)),
JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup"))));
if (r < 0)
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m");
log_info("New FIDO2 token enrolled as key slot %i.", keyslot);
return keyslot;
}

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
#include "log.h"
#if HAVE_LIBFIDO2
int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device);
#else
static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 key enrollment not supported.");
}
#endif

View File

@ -0,0 +1,126 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "cryptenroll-list.h"
#include "cryptenroll.h"
#include "format-table.h"
#include "parse-util.h"
int list_enrolled(struct crypt_device *cd) {
struct keyslot_metadata {
int slot;
const char *type;
} *keyslot_metadata = NULL;
size_t n_keyslot_metadata = 0, n_keyslot_metadata_allocated = 0;
_cleanup_(table_unrefp) Table *t = NULL;
int slot_max, r;
TableCell *cell;
assert(cd);
/* First step, find out all currently used slots */
assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
for (int slot = 0; slot < slot_max; slot++) {
crypt_keyslot_info status;
status = crypt_keyslot_status(cd, slot);
if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
continue;
if (!GREEDY_REALLOC(keyslot_metadata, n_keyslot_metadata_allocated, n_keyslot_metadata+1))
return log_oom();
keyslot_metadata[n_keyslot_metadata++] = (struct keyslot_metadata) {
.slot = slot,
};
}
/* Second step, enumerate through all tokens, and update the slot table, indicating what kind of
* token they are assigned to */
for (int token = 0; token < LUKS2_TOKENS_MAX; token++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
const char *type;
JsonVariant *w, *z;
EnrollType et;
r = cryptsetup_get_token_as_json(cd, token, NULL, &v);
if (IN_SET(r, -ENOENT, -EINVAL))
continue;
if (r < 0) {
log_warning_errno(r, "Failed to read JSON token data off disk, ignoring: %m");
continue;
}
w = json_variant_by_key(v, "type");
if (!w || !json_variant_is_string(w)) {
log_warning("Token JSON data lacks type field, ignoring.");
continue;
}
et = luks2_token_type_from_string(json_variant_string(w));
if (et < 0)
type = "other";
else
type = enroll_type_to_string(et);
w = json_variant_by_key(v, "keyslots");
if (!w || !json_variant_is_array(w)) {
log_warning("Token JSON data lacks keyslots field, ignoring.");
continue;
}
JSON_VARIANT_ARRAY_FOREACH(z, w) {
unsigned u;
if (!json_variant_is_string(z)) {
log_warning("Token JSON data's keyslot field is not an array of strings, ignoring.");
continue;
}
r = safe_atou(json_variant_string(z), &u);
if (r < 0) {
log_warning_errno(r, "Token JSON data's keyslot filed is not an integer formatted as string, ignoring.");
continue;
}
for (size_t i = 0; i < n_keyslot_metadata; i++) {
if ((unsigned) keyslot_metadata[i].slot != u)
continue;
if (keyslot_metadata[i].type) /* Slot claimed multiple times? */
keyslot_metadata[i].type = POINTER_MAX;
else
keyslot_metadata[i].type = type;
}
}
}
/* Finally, create a table out of it all */
t = table_new("slot", "type");
if (!t)
return log_oom();
assert_se(cell = table_get_cell(t, 0, 0));
(void) table_set_align_percent(t, cell, 100);
for (size_t i = 0; i < n_keyslot_metadata; i++) {
r = table_add_many(
t,
TABLE_INT, keyslot_metadata[i].slot,
TABLE_STRING, keyslot_metadata[i].type == POINTER_MAX ? "conflict" :
keyslot_metadata[i].type ?: "password");
if (r < 0)
return table_log_add_error(r);
}
if (table_get_rows(t) <= 1) {
log_info("No slots found.");
return 0;
}
r = table_print(t, stdout);
if (r < 0)
return log_error_errno(r, "Failed to show slot table: %m");
return 0;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptsetup-util.h"
int list_enrolled(struct crypt_device *cd);

View File

@ -0,0 +1,105 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "ask-password-api.h"
#include "cryptenroll-password.h"
#include "escape.h"
#include "memory-util.h"
#include "pwquality-util.h"
#include "strv.h"
int enroll_password(
struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size) {
_cleanup_(erase_and_freep) char *new_password = NULL;
_cleanup_free_ char *error = NULL;
const char *node;
int r, keyslot;
char *e;
assert_se(node = crypt_get_device_name(cd));
e = getenv("NEWPASSWORD");
if (e) {
new_password = strdup(e);
if (!new_password)
return log_oom();
string_erase(e);
assert_se(unsetenv("NEWPASSWORD") == 0);
} else {
_cleanup_free_ char *disk_path = NULL;
unsigned i = 5;
const char *id;
assert_se(node = crypt_get_device_name(cd));
(void) suggest_passwords();
disk_path = cescape(node);
if (!disk_path)
return log_oom();
id = strjoina("cryptsetup:", disk_path);
for (;;) {
_cleanup_strv_free_erase_ char **passwords = NULL, **passwords2 = NULL;
_cleanup_free_ char *question = NULL;
if (--i == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
"Too many attempts, giving up:");
question = strjoin("Please enter new passphrase for disk ", node, ":");
if (!question)
return log_oom();
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords);
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");
assert(strv_length(passwords) == 1);
free(question);
question = strjoin("Please enter new passphrase for disk ", node, " (repeat):");
if (!question)
return log_oom();
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords2);
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");
assert(strv_length(passwords2) == 1);
if (strv_equal(passwords, passwords2)) {
new_password = passwords2[0];
passwords2 = mfree(passwords2);
break;
}
log_error("Password didn't match, try again.");
}
}
r = quality_check_password(new_password, NULL, &error);
if (r < 0)
return log_error_errno(r, "Failed to check password for quality: %m");
if (r == 0)
log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", error);
keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
new_password,
strlen(new_password));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new password to %s: %m", node);
log_info("New password enrolled as key slot %i.", keyslot);
return keyslot;
}

View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
int enroll_password(struct crypt_device *cd, const void *volume_key, size_t volume_key_size);

View File

@ -0,0 +1,99 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "cryptenroll-pkcs11.h"
#include "hexdecoct.h"
#include "json.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
int enroll_pkcs11(
struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,
const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
size_t decrypted_key_size, encrypted_key_size;
_cleanup_free_ void *encrypted_key = NULL;
_cleanup_(X509_freep) X509 *cert = NULL;
const char *node;
EVP_PKEY *pkey;
int keyslot, r;
assert_se(cd);
assert_se(volume_key);
assert_se(volume_key_size > 0);
assert_se(uri);
assert_se(node = crypt_get_device_name(cd));
r = pkcs11_acquire_certificate(uri, "volume enrollment operation", "drive-harddisk", &cert, NULL);
if (r < 0)
return r;
pkey = X509_get0_pubkey(cert);
if (!pkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to determine RSA public key size.");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
decrypted_key = malloc(decrypted_key_size);
if (!decrypted_key)
return log_oom();
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");
r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to encrypt key: %m");
/* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by
* keyboard, if that might ever end up being necessary.) */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
base64_encoded,
strlen(base64_encoded));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return log_oom();
r = json_build(&v,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-pkcs11")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size))));
if (r < 0)
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add PKCS#11 JSON token to LUKS2 header: %m");
log_info("New PKCS#11 token enrolled as key slot %i.", keyslot);
return keyslot;
}

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
#include "log.h"
#if HAVE_P11KIT && HAVE_OPENSSL
int enroll_pkcs11(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *uri);
#else
static inline int enroll_pkcs11(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *uri) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 key enrollment not supported.");
}
#endif

View File

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "cryptenroll-recovery.h"
#include "json.h"
#include "locale-util.h"
#include "memory-util.h"
#include "qrcode-util.h"
#include "recovery-key.h"
#include "terminal-util.h"
int enroll_recovery(
struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) char *password = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
int keyslot, r, q;
const char *node;
assert_se(cd);
assert_se(volume_key);
assert_se(volume_key_size > 0);
assert_se(node = crypt_get_device_name(cd));
r = make_recovery_key(&password);
if (r < 0)
return log_error_errno(r, "Failed to generate recovery key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
password,
strlen(password));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new recovery key to %s: %m", node);
fflush(stdout);
fprintf(stderr,
"A secret recovery key has been generated for this volume:\n\n"
" %s%s%s",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "",
emoji_enabled() ? " " : "",
ansi_highlight());
fflush(stderr);
fputs(password, stdout);
fflush(stdout);
fputs(ansi_normal(), stderr);
fflush(stderr);
fputc('\n', stdout);
fflush(stdout);
fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
"regain access to the volume if the other configured access credentials have\n"
"been lost or forgotten. The recovery key may be entered in place of a password\n"
"whenever authentication is requested.\n", stderr);
fflush(stderr);
(void) print_qrcode(stderr, "You may optionally scan the recovery key off screen", password);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) {
r = log_oom();
goto rollback;
}
r = json_build(&v,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-recovery")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string)))));
if (r < 0) {
log_error_errno(r, "Failed to prepare recovery key JSON token object: %m");
goto rollback;
}
r = cryptsetup_add_token_json(cd, v);
if (r < 0) {
log_error_errno(r, "Failed to add recovery JSON token to LUKS2 header: %m");
goto rollback;
}
log_info("New recovery key enrolled as key slot %i.", keyslot);
return keyslot;
rollback:
q = crypt_keyslot_destroy(cd, keyslot);
if (q < 0)
log_debug_errno(q, "Unable to remove key slot we just added again, can't rollback, sorry: %m");
return r;
}

View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
int enroll_recovery(struct crypt_device *cd, const void *volume_key, size_t volume_key_size);

View File

@ -0,0 +1,131 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "cryptenroll-tpm2.h"
#include "hexdecoct.h"
#include "json.h"
#include "memory-util.h"
#include "tpm2-util.h"
static int search_policy_hash(
struct crypt_device *cd,
const void *hash,
size_t hash_size) {
int r;
assert(cd);
assert(hash || hash_size == 0);
if (hash_size == 0)
return 0;
for (int token = 0; token < LUKS2_TOKENS_MAX; token ++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ void *thash = NULL;
size_t thash_size = 0;
int keyslot;
JsonVariant *w;
r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
continue;
if (r < 0)
return log_error_errno(r, "Failed to read JSON token data off disk: %m");
keyslot = cryptsetup_get_keyslot_from_token(v);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to determine keyslot of JSON token: %m");
w = json_variant_by_key(v, "tpm2-policy-hash");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field.");
r = unhexmem(json_variant_string(w), (size_t) -1, &thash, &thash_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-policy-hash' field.");
if (memcmp_nn(hash, hash_size, thash, thash_size) == 0)
return keyslot; /* Found entry with same hash. */
}
return -ENOENT; /* Not found */
}
int enroll_tpm2(struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,
const char *device,
uint32_t pcr_mask) {
_cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
size_t secret_size, secret2_size, blob_size, hash_size;
_cleanup_free_ void *blob = NULL, *hash = NULL;
const char *node;
int r, keyslot;
assert(cd);
assert(volume_key);
assert(volume_key_size > 0);
assert(pcr_mask < (1U << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
assert_se(node = crypt_get_device_name(cd));
r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size);
if (r < 0)
return r;
/* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */
r = search_policy_hash(cd, hash, hash_size);
if (r == -ENOENT)
log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
else if (r < 0)
return r;
else {
log_info("This PCR set is already enrolled, executing no operation.");
return r; /* return existing keyslot, so that wiping won't kill it */
}
/* Quick verification that everything is in order, we are not in a hurry after all. */
log_debug("Unsealing for verification...");
r = tpm2_unseal(device, pcr_mask, blob, blob_size, hash, hash_size, &secret2, &secret2_size);
if (r < 0)
return r;
if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
/* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
keyslot = crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
base64_encoded,
strlen(base64_encoded));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
r = tpm2_make_luks2_json(keyslot, pcr_mask, blob, blob_size, hash, hash_size, &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
log_info("New TPM2 token enrolled as key slot %i.", keyslot);
return keyslot;
}

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
#include "log.h"
#if HAVE_TPM2
int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask);
#else
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 key enrollment not supported.");
}
#endif

View File

@ -0,0 +1,445 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "cryptenroll-wipe.h"
#include "cryptenroll.h"
#include "json.h"
#include "memory-util.h"
#include "parse-util.h"
#include "set.h"
#include "sort-util.h"
static int find_all_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots) {
int slot_max;
assert(cd);
assert(wipe_slots);
assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
/* Finds all currently assigned slots, and adds them to 'wipe_slots', except if listed already in 'keep_slots' */
for (int slot = 0; slot < slot_max; slot++) {
crypt_keyslot_info status;
/* No need to check this slot if we already know we want to wipe it or definitely keep it. */
if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
set_contains(wipe_slots, INT_TO_PTR(slot)))
continue;
status = crypt_keyslot_status(cd, slot);
if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
continue;
if (set_put(wipe_slots, INT_TO_PTR(slot)) < 0)
return log_oom();
}
return 0;
}
static int find_empty_passphrase_slots(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots) {
size_t vks;
int r, slot_max;
assert(cd);
assert(wipe_slots);
assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
/* Finds all slots with an empty passphrase assigned (i.e. "") and adds them to 'wipe_slots', except
* if listed already in 'keep_slots' */
r = crypt_get_volume_key_size(cd);
if (r <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
vks = (size_t) r;
for (int slot = 0; slot < slot_max; slot++) {
_cleanup_(erase_and_freep) char *vk = NULL;
crypt_keyslot_info status;
/* No need to check this slot if we already know we want to wipe it or definitely keep it. */
if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
set_contains(wipe_slots, INT_TO_PTR(slot)))
continue;
status = crypt_keyslot_status(cd, slot);
if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
continue;
vk = malloc(vks);
if (!vk)
return log_oom();
r = crypt_volume_key_get(cd, slot, vk, &vks, "", 0);
if (r < 0) {
log_debug_errno(r, "Failed to acquire volume key from slot %i with empty password, ignoring: %m", slot);
continue;
}
if (set_put(wipe_slots, INT_TO_PTR(r)) < 0)
return log_oom();
}
return 0;
}
static int find_slots_by_mask(
struct crypt_device *cd,
Set *wipe_slots,
Set *keep_slots,
unsigned by_mask) {
_cleanup_(set_freep) Set *listed_slots = NULL;
int r;
assert(cd);
assert(wipe_slots);
if (by_mask == 0)
return 0;
/* Find all slots that are associated with a token of a type in the specified token type mask */
for (int token = 0; token < LUKS2_TOKENS_MAX; token++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
JsonVariant *w, *z;
EnrollType t;
r = cryptsetup_get_token_as_json(cd, token, NULL, &v);
if (IN_SET(r, -ENOENT, -EINVAL))
continue;
if (r < 0) {
log_warning_errno(r, "Failed to read JSON token data off disk, ignoring: %m");
continue;
}
w = json_variant_by_key(v, "type");
if (!w || !json_variant_is_string(w)) {
log_warning("Token JSON data lacks type field, ignoring.");
continue;
}
t = luks2_token_type_from_string(json_variant_string(w));
w = json_variant_by_key(v, "keyslots");
if (!w || !json_variant_is_array(w)) {
log_warning("Token JSON data lacks keyslots field, ignoring.");
continue;
}
JSON_VARIANT_ARRAY_FOREACH(z, w) {
int slot;
if (!json_variant_is_string(z)) {
log_warning("Token JSON data's keyslot field is not an array of strings, ignoring.");
continue;
}
r = safe_atoi(json_variant_string(z), &slot);
if (r < 0) {
log_warning_errno(r, "Token JSON data's keyslot filed is not an integer formatted as string, ignoring.");
continue;
}
if (t >= 0 && (by_mask & (1U << t)) != 0) {
/* Selected by token type */
if (set_put(wipe_slots, INT_TO_PTR(slot)) < 0)
return log_oom();
} else if ((by_mask & (1U << ENROLL_PASSWORD)) != 0) {
/* If we shall remove all plain password slots, let's maintain a list of
* slots that are listed in any tokens, since those are *NOT* plain
* passwords */
if (set_ensure_allocated(&listed_slots, NULL) < 0)
return log_oom();
if (set_put(listed_slots, INT_TO_PTR(slot)) < 0)
return log_oom();
}
}
}
/* "password" slots are those which have no token assigned. If we shall remove those, iterate through
* all slots and mark those for wiping that weren't listed in any token */
if ((by_mask & (1U << ENROLL_PASSWORD)) != 0) {
int slot_max;
assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
for (int slot = 0; slot < slot_max; slot++) {
crypt_keyslot_info status;
/* No need to check this slot if we already know we want to wipe it or definitely keep it. */
if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
set_contains(wipe_slots, INT_TO_PTR(slot)))
continue;
if (set_contains(listed_slots, INT_TO_PTR(slot))) /* This has a token, hence is not a password. */
continue;
status = crypt_keyslot_status(cd, slot);
if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST)) /* Not actually assigned? */
continue;
/* Finally, we found a password, add it to the list of slots to wipe */
if (set_put(wipe_slots, INT_TO_PTR(slot)) < 0)
return log_oom();
}
}
return 0;
}
static int find_slot_tokens(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots, Set *wipe_tokens) {
int r;
assert(cd);
assert(wipe_slots);
assert(keep_slots);
assert(wipe_tokens);
/* Find all tokens matching the slots we want to wipe, so that we can wipe them too. Also, for update
* the slots sets according to the token data: add any other slots listed in the tokens we act on. */
for (int token = 0; token < LUKS2_TOKENS_MAX; token++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
bool shall_wipe = false;
JsonVariant *w, *z;
r = cryptsetup_get_token_as_json(cd, token, NULL, &v);
if (IN_SET(r, -ENOENT, -EINVAL))
continue;
if (r < 0) {
log_warning_errno(r, "Failed to read JSON token data off disk, ignoring: %m");
continue;
}
w = json_variant_by_key(v, "keyslots");
if (!w || !json_variant_is_array(w)) {
log_warning("Token JSON data lacks keyslots field, ignoring.");
continue;
}
/* Go through the slots associated with this token: if we shall keep any slot of them, the token shall stay too. */
JSON_VARIANT_ARRAY_FOREACH(z, w) {
int slot;
if (!json_variant_is_string(z)) {
log_warning("Token JSON data's keyslot field is not an array of strings, ignoring.");
continue;
}
r = safe_atoi(json_variant_string(z), &slot);
if (r < 0) {
log_warning_errno(r, "Token JSON data's keyslot filed is not an integer formatted as string, ignoring.");
continue;
}
if (set_contains(keep_slots, INT_TO_PTR(slot))) {
shall_wipe = false;
break; /* If we shall keep this slot, then this is definite: we will keep its token too */
}
/* If there's a slot associated with this token that we shall wipe, then remove the
* token too. But we are careful here: let's continue iterating, maybe there's a slot
* that we need to keep, in which case we can reverse the decision again. */
if (set_contains(wipe_slots, INT_TO_PTR(slot)))
shall_wipe = true;
}
/* Go through the slots again, and this time add them to the list of slots to keep/remove */
JSON_VARIANT_ARRAY_FOREACH(z, w) {
int slot;
if (!json_variant_is_string(z))
continue;
if (safe_atoi(json_variant_string(z), &slot) < 0)
continue;
if (set_put(shall_wipe ? wipe_slots : keep_slots, INT_TO_PTR(slot)) < 0)
return log_oom();
}
/* And of course, als remember the tokens to remove. */
if (shall_wipe)
if (set_put(wipe_tokens, INT_TO_PTR(token)) < 0)
return log_oom();
}
return 0;
}
static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slots) {
int slot_max;
assert(cd);
assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
/* Checks if any slots remaining in the LUKS2 header if we remove all slots listed in 'wipe_slots'
* (keeping those listed in 'keep_slots') */
for (int slot = 0; slot < slot_max; slot++) {
crypt_keyslot_info status;
status = crypt_keyslot_status(cd, slot);
if (!IN_SET(status, CRYPT_SLOT_ACTIVE, CRYPT_SLOT_ACTIVE_LAST))
continue;
/* The "keep" set wins if a slot is listed in both sets. This is important so that we can
* safely add a new slot and remove all others of the same type, which in a naive
* implementation might mean we remove what we just added which we of course don't want. */
if (set_contains(keep_slots, INT_TO_PTR(slot)) ||
!set_contains(wipe_slots, INT_TO_PTR(slot)))
return true;
}
return false;
}
int wipe_slots(struct crypt_device *cd,
const int explicit_slots[],
size_t n_explicit_slots,
WipeScope by_scope,
unsigned by_mask,
int except_slot) {
_cleanup_(set_freep) Set *wipe_slots = NULL, *wipe_tokens = NULL, *keep_slots = NULL;
_cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL;
size_t n_ordered_slots = 0, n_ordered_tokens = 0;
int r, slot_max, ret;
void *e;
assert_se(cd);
/* Shortcut if nothing to wipe. */
if (n_explicit_slots == 0 && by_mask == 0 && by_scope == WIPE_EXPLICIT)
return 0;
/* So this is a bit more complicated than I'd wish, but we want support three different axis for wiping slots:
*
* 1. Wiping by slot indexes
* 2. Wiping slots of specified token types
* 3. Wiping "all" entries, or entries with an empty password (i.e. "")
*
* (or any combination of the above)
*
* Plus: We always want to remove tokens matching the slots.
* Plus: We always want to exclude the slots/tokens we just added.
*/
wipe_slots = set_new(NULL);
keep_slots = set_new(NULL);
wipe_tokens = set_new(NULL);
if (!wipe_slots || !keep_slots || !wipe_tokens)
return log_oom();
/* Let's maintain one set of slots for the slots we definitely want to keep */
if (except_slot >= 0)
if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0)
return log_oom();
assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0);
/* Maintain another set of the slots we intend to wipe */
for (size_t i = 0; i < n_explicit_slots; i++) {
if (explicit_slots[i] >= slot_max)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", explicit_slots[i]);
if (set_put(wipe_slots, INT_TO_PTR(explicit_slots[i])) < 0)
return log_oom();
}
/* Now, handle the "all" and "empty passphrase" cases. */
switch (by_scope) {
case WIPE_EXPLICIT:
break; /* Nothing to do here */
case WIPE_ALL:
r = find_all_slots(cd, wipe_slots, keep_slots);
if (r < 0)
return r;
break;
case WIPE_EMPTY_PASSPHRASE:
r = find_empty_passphrase_slots(cd, wipe_slots, keep_slots);
if (r < 0)
return r;
break;
default:
assert_not_reached("Unexpected wipe scope");
}
/* Then add all slots that match a token type */
r = find_slots_by_mask(cd, wipe_slots, keep_slots, by_mask);
if (r < 0)
return r;
/* And determine tokens that we shall remove */
r = find_slot_tokens(cd, wipe_slots, keep_slots, wipe_tokens);
if (r < 0)
return r;
/* Safety check: let's make sure that after we are done there's at least one slot remaining */
if (!slots_remain(cd, wipe_slots, keep_slots))
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Wipe operation would leave no valid slots around, can't allow that, sorry.");
/* Generated ordered lists of the slots and the tokens to remove */
ordered_slots = new(int, set_size(wipe_slots));
if (!ordered_slots)
return log_oom();
SET_FOREACH(e, wipe_slots) {
int slot = PTR_TO_INT(e);
if (set_contains(keep_slots, INT_TO_PTR(slot)))
continue;
ordered_slots[n_ordered_slots++] = slot;
}
typesafe_qsort(ordered_slots, n_ordered_slots, cmp_int);
ordered_tokens = new(int, set_size(wipe_tokens));
if (!ordered_tokens)
return log_oom();
SET_FOREACH(e, wipe_tokens)
ordered_tokens[n_ordered_tokens++] = PTR_TO_INT(e);
typesafe_qsort(ordered_tokens, n_ordered_tokens, cmp_int);
if (n_ordered_slots == 0 && n_ordered_tokens == 0) {
log_full(except_slot < 0 ? LOG_NOTICE : LOG_DEBUG,
"No slots to remove selected.");
return 0;
}
if (DEBUG_LOGGING) {
for (size_t i = 0; i < n_ordered_slots; i++)
log_debug("Going to wipe slot %i.", ordered_slots[i]);
for (size_t i = 0; i < n_ordered_tokens; i++)
log_debug("Going to wipe token %i.", ordered_tokens[i]);
}
/* Now, let's actually start wiping things. (We go from back to front, to make space at the end
* first.) */
ret = 0;
for (size_t i = n_ordered_slots; i > 0; i--) {
r = crypt_keyslot_destroy(cd, ordered_slots[i - 1]);
if (r < 0) {
log_warning_errno(r, "Failed to wipe slot %i, continuing: %m", ordered_slots[i - 1]);
if (ret == 0)
ret = r;
} else
log_info("Wiped slot %i.", ordered_slots[i - 1]);
}
for (size_t i = n_ordered_tokens; i > 0; i--) {
r = crypt_token_json_set(cd, ordered_tokens[i - 1], NULL);
if (r < 0) {
log_warning_errno(r, "Failed to wipe token %i, continuing: %m", ordered_tokens[i - 1]);
if (ret == 0)
ret = r;
}
}
return ret;
}

View File

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "cryptenroll.h"
#include "cryptsetup-util.h"
int wipe_slots(struct crypt_device *cd,
const int explicit_slots[],
size_t n_explicit_slots,
WipeScope by_scope,
unsigned by_mask,
int except_slot);

View File

@ -0,0 +1,517 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "ask-password-api.h"
#include "cryptenroll-fido2.h"
#include "cryptenroll-list.h"
#include "cryptenroll-password.h"
#include "cryptenroll-pkcs11.h"
#include "cryptenroll-recovery.h"
#include "cryptenroll-tpm2.h"
#include "cryptenroll-wipe.h"
#include "cryptenroll.h"
#include "cryptsetup-util.h"
#include "escape.h"
#include "libfido2-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "pretty-print.h"
#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
#include "tpm2-util.h"
static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
static char *arg_pkcs11_token_uri = NULL;
static char *arg_fido2_device = NULL;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static char *arg_node = NULL;
static int *arg_wipe_slots = NULL;
static size_t arg_n_wipe_slots = 0;
static WipeScope arg_wipe_slots_scope = WIPE_EXPLICIT;
static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */
assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
static bool wipe_requested(void) {
return arg_n_wipe_slots > 0 ||
arg_wipe_slots_scope != WIPE_EXPLICIT ||
arg_wipe_slots_mask != 0;
}
static const char* const enroll_type_table[_ENROLL_TYPE_MAX] = {
[ENROLL_PASSWORD] = "password",
[ENROLL_RECOVERY] = "recovery",
[ENROLL_PKCS11] = "pkcs11",
[ENROLL_FIDO2] = "fido2",
[ENROLL_TPM2] = "tpm2",
};
DEFINE_STRING_TABLE_LOOKUP(enroll_type, EnrollType);
static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = {
/* ENROLL_PASSWORD has no entry here, as slots of this type do not have a token in the LUKS2 header */
[ENROLL_RECOVERY] = "systemd-recovery",
[ENROLL_PKCS11] = "systemd-pkcs11",
[ENROLL_FIDO2] = "systemd-fido2",
[ENROLL_TPM2] = "systemd-tpm2",
};
DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType);
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("systemd-cryptenroll", "1", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] BLOCK-DEVICE\n"
"\n%sEnroll a security token or authentication credential to a LUKS volume.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --password Enroll a user-supplied password\n"
" --recovery-key Enroll a recovery key\n"
" --pkcs11-token-uri=URI\n"
" Specify PKCS#11 security token URI\n"
" --fido2-device=PATH\n"
" Enroll a FIDO2-HMAC security token\n"
" --tpm2-device=PATH\n"
" Enroll a TPM2 device\n"
" --tpm2-pcrs=PCR1,PCR2,PCR3,…\n"
" Specifiy TPM2 PCRs to seal against\n"
" --wipe-slot=SLOT1,SLOT2,…\n"
" Wipe specified slots\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, ansi_highlight(), ansi_normal()
, link
);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_PASSWORD,
ARG_RECOVERY_KEY,
ARG_PKCS11_TOKEN_URI,
ARG_FIDO2_DEVICE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
ARG_WIPE_SLOT,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "password", no_argument, NULL, ARG_PASSWORD },
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{}
};
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case ARG_PASSWORD:
if (arg_enroll_type >= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple operations specified at once, refusing.");
arg_enroll_type = ENROLL_PASSWORD;
break;
case ARG_RECOVERY_KEY:
if (arg_enroll_type >= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple operations specified at once, refusing.");
arg_enroll_type = ENROLL_RECOVERY;
break;
case ARG_PKCS11_TOKEN_URI: {
_cleanup_free_ char *uri = NULL;
if (streq(optarg, "list"))
return pkcs11_list_tokens();
if (arg_enroll_type >= 0 || arg_pkcs11_token_uri)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple operations specified at once, refusing.");
if (streq(optarg, "auto")) {
r = pkcs11_find_token_auto(&uri);
if (r < 0)
return r;
} else {
if (!pkcs11_uri_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
uri = strdup(optarg);
if (!uri)
return log_oom();
}
arg_enroll_type = ENROLL_PKCS11;
arg_pkcs11_token_uri = TAKE_PTR(uri);
break;
}
case ARG_FIDO2_DEVICE: {
_cleanup_free_ char *device = NULL;
if (streq(optarg, "list"))
return fido2_list_devices();
if (arg_enroll_type >= 0 || arg_fido2_device)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple operations specified at once, refusing.");
if (streq(optarg, "auto")) {
r = fido2_find_device_auto(&device);
if (r < 0)
return r;
} else {
device = strdup(optarg);
if (!device)
return log_oom();
}
arg_enroll_type = ENROLL_FIDO2;
arg_fido2_device = TAKE_PTR(device);
break;
}
case ARG_TPM2_DEVICE: {
_cleanup_free_ char *device = NULL;
if (streq(optarg, "list"))
return tpm2_list_devices();
if (arg_enroll_type >= 0 || arg_tpm2_device)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple operations specified at once, refusing.");
if (!streq(optarg, "auto")) {
device = strdup(optarg);
if (!device)
return log_oom();
}
arg_enroll_type = ENROLL_TPM2;
arg_tpm2_device = TAKE_PTR(device);
break;
}
case ARG_TPM2_PCRS: {
uint32_t mask;
if (isempty(optarg)) {
arg_tpm2_pcr_mask = 0;
break;
}
r = tpm2_parse_pcrs(optarg, &mask);
if (r < 0)
return r;
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = mask;
else
arg_tpm2_pcr_mask |= mask;
break;
}
case ARG_WIPE_SLOT: {
const char *p = optarg;
if (isempty(optarg)) {
arg_wipe_slots_mask = 0;
arg_wipe_slots_scope = WIPE_EXPLICIT;
break;
}
for (;;) {
_cleanup_free_ char *slot = NULL;
unsigned n;
r = extract_first_word(&p, &slot, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r == 0)
break;
if (r < 0)
return log_error_errno(r, "Failed to parse slot list: %s", optarg);
if (streq(slot, "all"))
arg_wipe_slots_scope = WIPE_ALL;
else if (streq(slot, "empty")) {
if (arg_wipe_slots_scope != WIPE_ALL) /* if "all" was specified before, that wins */
arg_wipe_slots_scope = WIPE_EMPTY_PASSPHRASE;
} else if (streq(slot, "password"))
arg_wipe_slots_mask = 1U << ENROLL_PASSWORD;
else if (streq(slot, "recovery"))
arg_wipe_slots_mask = 1U << ENROLL_RECOVERY;
else if (streq(slot, "pkcs11"))
arg_wipe_slots_mask = 1U << ENROLL_PKCS11;
else if (streq(slot, "fido2"))
arg_wipe_slots_mask = 1U << ENROLL_FIDO2;
else if (streq(slot, "tpm2"))
arg_wipe_slots_mask = 1U << ENROLL_TPM2;
else {
int *a;
r = safe_atou(slot, &n);
if (r < 0)
return log_error_errno(r, "Failed to parse slot index: %s", slot);
if (n > INT_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Slot index out of range: %u", n);
a = reallocarray(arg_wipe_slots, sizeof(int), arg_n_wipe_slots + 1);
if (!a)
return log_oom();
arg_wipe_slots = a;
arg_wipe_slots[arg_n_wipe_slots++] = (int) n;
}
}
break;
}
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
}
if (optind >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No block device node specified, refusing.");
if (argc > optind+1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Too many arguments, refusing.");
r = parse_path_argument_and_warn(argv[optind], false, &arg_node);
if (r < 0)
return r;
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
return 1;
}
static int prepare_luks(
struct crypt_device **ret_cd,
void **ret_volume_key,
size_t *ret_volume_key_size) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
char *e = NULL;
size_t vks;
int r;
assert(ret_cd);
assert(!ret_volume_key == !ret_volume_key_size);
r = crypt_init(&cd, arg_node);
if (r < 0)
return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
cryptsetup_enable_logging(cd);
r = crypt_load(cd, CRYPT_LUKS2, NULL);
if (r < 0)
return log_error_errno(r, "Failed to load LUKS2 superblock: %m");
if (!ret_volume_key) {
*ret_cd = TAKE_PTR(cd);
return 0;
}
r = crypt_get_volume_key_size(cd);
if (r <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
vks = (size_t) r;
vk = malloc(vks);
if (!vk)
return log_oom();
e = getenv("PASSWORD");
if (e) {
_cleanup_(erase_and_freep) char *password = NULL;
password = strdup(e);
if (!password)
return log_oom();
string_erase(e);
assert_se(unsetenv("PASSWORD") >= 0);
r = crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
vk,
&vks,
password,
strlen(password));
if (r < 0)
return log_error_errno(r, "Password from environent variable $PASSWORD did not work.");
} else {
AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED;
_cleanup_free_ char *question = NULL, *disk_path = NULL;
unsigned i = 5;
const char *id;
question = strjoin("Please enter current passphrase for disk ", arg_node, ":");
if (!question)
return log_oom();
disk_path = cescape(arg_node);
if (!disk_path)
return log_oom();
id = strjoina("cryptsetup:", disk_path);
for (;;) {
_cleanup_strv_free_erase_ char **passwords = NULL;
char **p;
if (--i == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
"Too many attempts, giving up:");
r = ask_password_auto(
question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY,
ask_password_flags,
&passwords);
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");
r = -EPERM;
STRV_FOREACH(p, passwords) {
r = crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
vk,
&vks,
*p,
strlen(*p));
if (r >= 0)
break;
}
if (r >= 0)
break;
log_error_errno(r, "Password not correct, please try again.");
ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
}
}
*ret_cd = TAKE_PTR(cd);
*ret_volume_key = TAKE_PTR(vk);
*ret_volume_key_size = vks;
return 0;
}
static int run(int argc, char *argv[]) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
size_t vks;
int slot, r;
log_show_color(true);
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
if (arg_enroll_type < 0)
r = prepare_luks(&cd, NULL, NULL); /* No need to unlock device if we don't need the volume key because we don't need to enroll anything */
else
r = prepare_luks(&cd, &vk, &vks);
if (r < 0)
return r;
switch (arg_enroll_type) {
case ENROLL_PASSWORD:
slot = enroll_password(cd, vk, vks);
break;
case ENROLL_RECOVERY:
slot = enroll_recovery(cd, vk, vks);
break;
case ENROLL_PKCS11:
slot = enroll_pkcs11(cd, vk, vks, arg_pkcs11_token_uri);
break;
case ENROLL_FIDO2:
slot = enroll_fido2(cd, vk, vks, arg_fido2_device);
break;
case ENROLL_TPM2:
slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask);
break;
case _ENROLL_TYPE_INVALID:
/* List enrolled slots if we are called without anything to enroll or wipe */
if (!wipe_requested())
return list_enrolled(cd);
/* Only slot wiping selected */
return wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, -1);
default:
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet.");
}
if (slot < 0)
return slot;
/* After we completed enrolling, remove user selected slots */
r = wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, slot);
if (r < 0)
return r;
return 0;
}
DEFINE_MAIN_FUNCTION(run);

View File

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
typedef enum EnrollType {
ENROLL_PASSWORD,
ENROLL_RECOVERY,
ENROLL_PKCS11,
ENROLL_FIDO2,
ENROLL_TPM2,
_ENROLL_TYPE_MAX,
_ENROLL_TYPE_INVALID = -1,
} EnrollType;
typedef enum WipeScope {
WIPE_EXPLICIT, /* only wipe the listed slots */
WIPE_ALL, /* wipe all slots */
WIPE_EMPTY_PASSPHRASE, /* wipe slots with empty passphrases plus listed slots */
_WIPE_SCOPE_MAX,
_WIPE_SCOPE_INVALID = -1,
} WipeScope;
const char* enroll_type_to_string(EnrollType t);
EnrollType enroll_type_from_string(const char *s);
const char* luks2_token_type_to_string(EnrollType t);
EnrollType luks2_token_type_from_string(const char *s);

View File

@ -0,0 +1,190 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "ask-password-api.h"
#include "cryptsetup-fido2.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "libfido2-util.h"
#include "parse-util.h"
#include "random-util.h"
#include "strv.h"
int acquire_fido2_key(
const char *volume_name,
const char *friendly_name,
const char *device,
const char *rp_id,
const void *cid,
size_t cid_size,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const void *key_data,
size_t key_data_size,
usec_t until,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED;
_cleanup_strv_free_erase_ char **pins = NULL;
_cleanup_free_ void *loaded_salt = NULL;
const char *salt;
size_t salt_size;
char *e;
int r;
assert(cid);
assert(key_file || key_data);
if (key_data) {
salt = key_data;
salt_size = key_data_size;
} else {
_cleanup_free_ char *bindname = NULL;
/* If we read the salt via AF_UNIX, make this client recognizable */
if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0)
return log_oom();
r = read_full_file_full(
AT_FDCWD, key_file,
key_file_offset == 0 ? UINT64_MAX : key_file_offset,
key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET,
bindname,
(char**) &loaded_salt, &salt_size);
if (r < 0)
return r;
salt = loaded_salt;
}
e = getenv("PIN");
if (e) {
pins = strv_new(e);
if (!pins)
return log_oom();
string_erase(e);
if (unsetenv("PIN") < 0)
return log_error_errno(errno, "Failed to unset $PIN: %m");
}
for (;;) {
r = fido2_use_hmac_hash(
device,
rp_id ?: "io.systemd.cryptsetup",
salt, salt_size,
cid, cid_size,
pins,
/* up= */ true,
ret_decrypted_key,
ret_decrypted_key_size);
if (!IN_SET(r,
-ENOANO, /* needs pin */
-ENOLCK)) /* pin incorrect */
return r;
pins = strv_free_erase(pins);
r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", until, flags, &pins);
if (r < 0)
return log_error_errno(r, "Failed to ask for user pasword: %m");
flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
}
}
int find_fido2_auto_data(
struct crypt_device *cd,
char **ret_rp_id,
void **ret_salt,
size_t *ret_salt_size,
void **ret_cid,
size_t *ret_cid_size,
int *ret_keyslot) {
_cleanup_free_ void *cid = NULL, *salt = NULL;
size_t cid_size = 0, salt_size = 0;
_cleanup_free_ char *rp = NULL;
int r, keyslot = -1;
assert(cd);
assert(ret_salt);
assert(ret_salt_size);
assert(ret_cid);
assert(ret_cid_size);
assert(ret_keyslot);
/* Loads FIDO2 metadata from LUKS2 JSON token headers. */
for (int token = 0; token < LUKS2_TOKENS_MAX; token ++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
JsonVariant *w;
r = cryptsetup_get_token_as_json(cd, token, "systemd-fido2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
continue;
if (r < 0)
return log_error_errno(r, "Failed to read JSON token data off disk: %m");
if (cid)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"Multiple FIDO2 tokens enrolled, cannot automatically determine token.");
w = json_variant_by_key(v, "fido2-credential");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 token data lacks 'fido2-credential' field.");
r = unbase64mem(json_variant_string(w), (size_t) -1, &cid, &cid_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'fido2-credential' field.");
w = json_variant_by_key(v, "fido2-salt");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 token data lacks 'fido2-salt' field.");
assert(!salt);
assert(salt_size == 0);
r = unbase64mem(json_variant_string(w), (size_t) -1, &salt, &salt_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded salt.");
assert(keyslot < 0);
keyslot = cryptsetup_get_keyslot_from_token(v);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to extract keyslot index from FIDO2 JSON data: %m");
w = json_variant_by_key(v, "fido2-rp");
if (w) {
/* The "rp" field is optional. */
if (!json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"FIDO2 token data's 'fido2-rp' field is not a string.");
assert(!rp);
rp = strdup(json_variant_string(w));
if (!rp)
return log_oom();
}
}
if (!cid)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"No valid FIDO2 token data found.");
log_info("Automatically discovered security FIDO2 token unlocks volume.");
*ret_rp_id = TAKE_PTR(rp);
*ret_cid = TAKE_PTR(cid);
*ret_cid_size = cid_size;
*ret_salt = TAKE_PTR(salt);
*ret_salt_size = salt_size;
*ret_keyslot = keyslot;
return 0;
}

View File

@ -0,0 +1,71 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
#include "log.h"
#include "time-util.h"
#if HAVE_LIBFIDO2
int acquire_fido2_key(
const char *volume_name,
const char *friendly_name,
const char *device,
const char *rp_id,
const void *cid,
size_t cid_size,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const void *key_data,
size_t key_data_size,
usec_t until,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
int find_fido2_auto_data(
struct crypt_device *cd,
char **ret_rp_id,
void **ret_salt,
size_t *ret_salt_size,
void **ret_cid,
size_t *ret_cid_size,
int *ret_keyslot);
#else
static inline int acquire_fido2_key(
const char *volume_name,
const char *friendly_name,
const char *device,
const char *rp_id,
const void *cid,
size_t cid_size,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const void *key_data,
size_t key_data_size,
usec_t until,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 token support not available.");
}
static inline int find_fido2_auto_data(
struct crypt_device *cd,
char **ret_rp_id,
void **ret_salt,
size_t *ret_salt_size,
void **ret_cid,
size_t *ret_cid_size,
int *ret_keyslot) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 token support not available.");
}
#endif

View File

@ -14,8 +14,11 @@
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "hexdecoct.h"
#include "json.h"
#include "macro.h"
#include "memory-util.h"
#include "parse-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
#include "stat-util.h"
@ -156,3 +159,80 @@ int decrypt_pkcs11_key(
return 0;
}
int find_pkcs11_auto_data(
struct crypt_device *cd,
char **ret_uri,
void **ret_encrypted_key,
size_t *ret_encrypted_key_size,
int *ret_keyslot) {
_cleanup_free_ char *uri = NULL;
_cleanup_free_ void *key = NULL;
int r, keyslot = -1;
size_t key_size = 0;
assert(cd);
assert(ret_uri);
assert(ret_encrypted_key);
assert(ret_encrypted_key_size);
assert(ret_keyslot);
/* Loads PKCS#11 metadata from LUKS2 JSON token headers. */
for (int token = 0; token < LUKS2_TOKENS_MAX; token++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
JsonVariant *w;
r = cryptsetup_get_token_as_json(cd, token, "systemd-pkcs11", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
continue;
if (r < 0)
return log_error_errno(r, "Failed to read JSON token data off disk: %m");
if (uri)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"Multiple PKCS#11 tokens enrolled, cannot automatically determine token.");
w = json_variant_by_key(v, "pkcs11-uri");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"PKCS#11 token data lacks 'pkcs11-uri' field.");
uri = strdup(json_variant_string(w));
if (!uri)
return log_oom();
if (!pkcs11_uri_valid(uri))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"PKCS#11 token data contains invalid PKCS#11 URI.");
w = json_variant_by_key(v, "pkcs11-key");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"PKCS#11 token data lacks 'pkcs11-key' field.");
assert(!key);
assert(key_size == 0);
r = unbase64mem(json_variant_string(w), (size_t) -1, &key, &key_size);
if (r < 0)
return log_error_errno(r, "Failed to decode base64 encoded key.");
assert(keyslot < 0);
keyslot = cryptsetup_get_keyslot_from_token(v);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to extract keyslot index from PKCS#11 JSON data: %m");
}
if (!uri)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"No valid PKCS#11 token data found.");
log_info("Automatically discovered security PKCS#11 token '%s' unlocks volume.", uri);
*ret_uri = TAKE_PTR(uri);
*ret_encrypted_key = TAKE_PTR(key);
*ret_encrypted_key_size = key_size;
*ret_keyslot = keyslot;
return 0;
}

View File

@ -3,6 +3,7 @@
#include <sys/types.h>
#include "cryptsetup-util.h"
#include "log.h"
#include "time-util.h"
@ -21,6 +22,13 @@ int decrypt_pkcs11_key(
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
int find_pkcs11_auto_data(
struct crypt_device *cd,
char **ret_uri,
void **ret_encrypted_key,
size_t *ret_encrypted_key_size,
int *ret_keyslot);
#else
static inline int decrypt_pkcs11_key(
@ -40,4 +48,15 @@ static inline int decrypt_pkcs11_key(
"PKCS#11 Token support not available.");
}
static inline int find_pkcs11_auto_data(
struct crypt_device *cd,
char **ret_uri,
void **ret_encrypted_key,
size_t *ret_encrypted_key_size,
int *ret_keyslot) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 Token support not available.");
}
#endif

View File

@ -0,0 +1,168 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "cryptsetup-tpm2.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "parse-util.h"
#include "random-util.h"
#include "tpm2-util.h"
int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
_cleanup_free_ void *loaded_blob = NULL;
_cleanup_free_ char *auto_device = NULL;
size_t blob_size;
const void *blob;
int r;
if (!device) {
r = tpm2_find_device_auto(LOG_DEBUG, &auto_device);
if (r == -ENODEV)
return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */
if (r < 0)
return r;
device = auto_device;
}
if (key_data) {
blob = key_data;
blob_size = key_data_size;
} else {
_cleanup_free_ char *bindname = NULL;
/* If we read the salt via AF_UNIX, make this client recognizable */
if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0)
return log_oom();
r = read_full_file_full(
AT_FDCWD, key_file,
key_file_offset == 0 ? UINT64_MAX : key_file_offset,
key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET,
bindname,
(char**) &loaded_blob, &blob_size);
if (r < 0)
return r;
blob = loaded_blob;
}
return tpm2_unseal(device, pcr_mask, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size);
}
int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
int *ret_keyslot,
int *ret_token) {
_cleanup_free_ void *blob = NULL, *policy_hash = NULL;
size_t blob_size = 0, policy_hash_size = 0;
int r, keyslot = -1, token = -1;
uint32_t pcr_mask = 0;
assert(cd);
for (token = start_token; token < LUKS2_TOKENS_MAX; token++) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
JsonVariant *w, *e;
r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
continue;
if (r < 0)
return log_error_errno(r, "Failed to read JSON token data off disk: %m");
w = json_variant_by_key(v, "tpm2-pcrs");
if (!w || !json_variant_is_array(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-pcrs' field.");
assert(pcr_mask == 0);
JSON_VARIANT_ARRAY_FOREACH(e, w) {
uintmax_t u;
if (!json_variant_is_number(e))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 PCR is not a number.");
u = json_variant_unsigned(e);
if (u >= TPM2_PCRS_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 PCR number out of range.");
pcr_mask |= UINT32_C(1) << u;
}
if (search_pcr_mask != UINT32_MAX &&
search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */
continue;
assert(!blob);
w = json_variant_by_key(v, "tpm2-blob");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-blob' field.");
r = unbase64mem(json_variant_string(w), (size_t) -1, &blob, &blob_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-blob' field.");
assert(!policy_hash);
w = json_variant_by_key(v, "tpm2-policy-hash");
if (!w || !json_variant_is_string(w))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field.");
r = unhexmem(json_variant_string(w), (size_t) -1, &policy_hash, &policy_hash_size);
if (r < 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid base64 data in 'tpm2-policy-hash' field.");
assert(keyslot < 0);
keyslot = cryptsetup_get_keyslot_from_token(v);
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to extract keyslot index from TPM2 JSON data: %m");
break;
}
if (!blob)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"No valid TPM2 token data found.");
if (start_token <= 0)
log_info("Automatically discovered security TPM2 token unlocks volume.");
*ret_pcr_mask = pcr_mask;
*ret_blob = TAKE_PTR(blob);
*ret_blob_size = blob_size;
*ret_policy_hash = TAKE_PTR(policy_hash);
*ret_policy_hash_size = policy_hash_size;
*ret_keyslot = keyslot;
*ret_token = token;
return 0;
}

View File

@ -0,0 +1,74 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "cryptsetup-util.h"
#include "log.h"
#include "time-util.h"
#if HAVE_TPM2
int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
int *ret_keyslot,
int *ret_token);
#else
static inline int acquire_tpm2_key(
const char *volume_name,
const char *device,
uint32_t pcr_mask,
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");
}
static inline int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
uint32_t *ret_pcr_mask,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
int *ret_keyslot,
int *ret_token) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "libcrypt-util.h"
#include "libfido2-util.h"
#include "locale-util.h"
#include "memory-util.h"
#include "random-util.h"
@ -109,105 +110,22 @@ static int add_fido2_salt(
}
#endif
#define FIDO2_SALT_SIZE 32
int identity_add_fido2_parameters(
JsonVariant **v,
const char *device) {
#if HAVE_LIBFIDO2
_cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
_cleanup_(fido_assert_free) fido_assert_t *a = NULL;
_cleanup_(erase_and_freep) char *used_pin = NULL;
_cleanup_(fido_cred_free) fido_cred_t *c = NULL;
_cleanup_(fido_dev_free) fido_dev_t *d = NULL;
_cleanup_(erase_and_freep) void *salt = NULL;
JsonVariant *un, *realm, *rn;
bool found_extension = false;
const void *cid, *secret;
_cleanup_(erase_and_freep) void *secret = NULL, *salt = NULL;
_cleanup_(erase_and_freep) char *used_pin = NULL;
size_t cid_size, salt_size, secret_size;
_cleanup_free_ void *cid = NULL;
const char *fido_un;
size_t n, cid_size, secret_size;
char **e;
int r;
/* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
* HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
* authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
* device never sees the volume key.
*
* S = HMAC-SHA256(I, D)
*
* with: S LUKS/account authentication key (never stored)
* I internal key on FIDO2 device (stored in the FIDO2 device)
* D salt we generate here (stored in the privileged part of the JSON record)
*
*/
assert(v);
assert(device);
salt = malloc(FIDO2_SALT_SIZE);
if (!salt)
return log_oom();
r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
d = fido_dev_new();
if (!d)
return log_oom();
r = fido_dev_open(d, device);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", device, fido_strerr(r));
if (!fido_dev_is_fido2(d))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is not a FIDO2 device.", device);
di = fido_cbor_info_new();
if (!di)
return log_oom();
r = fido_dev_get_cbor_info(d, di);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get CBOR device info for %s: %s", device, fido_strerr(r));
e = fido_cbor_info_extensions_ptr(di);
n = fido_cbor_info_extensions_len(di);
for (size_t i = 0; i < n; i++)
if (streq(e[i], "hmac-secret")) {
found_extension = true;
break;
}
if (!found_extension)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", device);
c = fido_cred_new();
if (!c)
return log_oom();
r = fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", fido_strerr(r));
r = fido_cred_set_rp(c, "io.systemd.home", "Home Directory");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential relying party ID/name: %s", fido_strerr(r));
r = fido_cred_set_type(c, COSE_ES256);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential type to ES256: %s", fido_strerr(r));
un = json_variant_by_key(*v, "userName");
if (!un)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@ -231,164 +149,37 @@ int identity_add_fido2_parameters(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"realName field of user record is not a string");
r = fido_cred_set_user(c,
(const unsigned char*) fido_un, strlen(fido_un), /* We pass the user ID and name as the same */
fido_un,
rn ? json_variant_string(rn) : NULL,
NULL /* icon URL */);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential user data: %s", fido_strerr(r));
r = fido_cred_set_clientdata_hash(c, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 client data hash: %s", fido_strerr(r));
r = fido_cred_set_rk(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 resident key option of credential: %s", fido_strerr(r));
r = fido_cred_set_uv(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 user verification option of credential: %s", fido_strerr(r));
log_info("Initializing FIDO2 credential on security token.");
log_notice("%s%s(Hint: This might require verification of user presence on security token.)",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = fido_dev_make_cred(d, c, NULL);
if (r == FIDO_ERR_PIN_REQUIRED) {
_cleanup_free_ char *text = NULL;
if (asprintf(&text, "Please enter security token PIN:") < 0)
return log_oom();
for (;;) {
_cleanup_(strv_free_erasep) char **pin = NULL;
char **i;
r = ask_password_auto(text, "user-home", NULL, "fido2-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
r = FIDO_ERR_PIN_INVALID;
STRV_FOREACH(i, pin) {
if (isempty(*i)) {
log_info("PIN may not be empty.");
continue;
}
r = fido_dev_make_cred(d, c, *i);
if (r == FIDO_OK) {
used_pin = strdup(*i);
if (!used_pin)
return log_oom();
break;
}
if (r != FIDO_ERR_PIN_INVALID)
break;
}
if (r != FIDO_ERR_PIN_INVALID)
break;
log_notice("PIN incorrect, please try again.");
}
}
if (r == FIDO_ERR_PIN_AUTH_BLOCKED)
return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
"Token PIN is currently blocked, please remove and reinsert token.");
if (r == FIDO_ERR_ACTION_TIMEOUT)
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to generate FIDO2 credential: %s", fido_strerr(r));
cid = fido_cred_id_ptr(c);
if (!cid)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get FIDO2 credential ID.");
cid_size = fido_cred_id_len(c);
a = fido_assert_new();
if (!a)
return log_oom();
r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_rp(a, "io.systemd.home");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
r = fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
r = fido_assert_set_up(a, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 assertion user presence: %s", fido_strerr(r));
log_info("Generating secret key on FIDO2 security token.");
r = fido_dev_get_assert(d, a, used_pin);
if (r == FIDO_ERR_UP_REQUIRED) {
r = fido_assert_set_up(a, FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn on FIDO2 assertion user presence: %s", fido_strerr(r));
log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = fido_dev_get_assert(d, a, used_pin);
}
if (r == FIDO_ERR_ACTION_TIMEOUT)
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", fido_strerr(r));
secret = fido_assert_hmac_secret_ptr(a, 0);
if (!secret)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
secret_size = fido_assert_hmac_secret_len(a, 0);
r = add_fido2_credential_id(v, cid, cid_size);
r = fido2_generate_hmac_hash(
device,
/* rp_id= */ "io.systemd.home",
/* rp_name= */ "Home Directory",
/* user_id= */ fido_un, strlen(fido_un), /* We pass the user ID and name as the same */
/* user_name= */ fido_un,
/* user_display_name= */ rn ? json_variant_string(rn) : NULL,
/* user_icon_name= */ NULL,
/* askpw_icon_name= */ "user-home",
&cid, &cid_size,
&salt, &salt_size,
&secret, &secret_size,
&used_pin);
if (r < 0)
return r;
r = add_fido2_salt(v,
cid,
cid_size,
salt,
FIDO2_SALT_SIZE,
secret,
secret_size);
r = add_fido2_credential_id(
v,
cid,
cid_size);
if (r < 0)
return r;
r = add_fido2_salt(
v,
cid,
cid_size,
salt,
salt_size,
secret,
secret_size);
if (r < 0)
return r;
@ -405,130 +196,3 @@ int identity_add_fido2_parameters(
"FIDO2 tokens not supported on this build.");
#endif
}
int list_fido2_devices(void) {
#if HAVE_LIBFIDO2
_cleanup_(table_unrefp) Table *t = NULL;
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
di = fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
log_info("No FIDO2 devices found.");
r = 0;
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
t = table_new("path", "manufacturer", "product");
if (!t) {
r = log_oom();
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
entry = fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
r = table_add_many(
t,
TABLE_PATH, fido_dev_info_path(entry),
TABLE_STRING, fido_dev_info_manufacturer_string(entry),
TABLE_STRING, fido_dev_info_product_string(entry));
if (r < 0) {
table_log_add_error(r);
goto finish;
}
}
r = table_print(t, stdout);
if (r < 0) {
log_error_errno(r, "Failed to show device table: %m");
goto finish;
}
r = 0;
finish:
fido_dev_info_free(&di, allocated);
return r;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 tokens not supported on this build.");
#endif
}
int find_fido2_auto(char **ret) {
#if HAVE_LIBFIDO2
_cleanup_free_ char *copy = NULL;
size_t di_size = 64, found = 0;
const fido_dev_info_t *entry;
fido_dev_info_t *di = NULL;
const char *path;
int r;
di = fido_dev_info_new(di_size);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, di_size, &found);
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No FIDO2 devices found.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
if (found > 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "More than one FIDO2 device found.");
goto finish;
}
entry = fido_dev_info_ptr(di, 0);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device 0.");
goto finish;
}
path = fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
copy = strdup(path);
if (!copy) {
r = log_oom();
goto finish;
}
*ret = TAKE_PTR(copy);
r = 0;
finish:
fido_dev_info_free(&di, di_size);
return r;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 tokens not supported on this build.");
#endif
}

View File

@ -4,7 +4,3 @@
#include "json.h"
int identity_add_fido2_parameters(JsonVariant **v, const char *device);
int list_fido2_devices(void);
int find_fido2_auto(char **ret);

View File

@ -11,125 +11,6 @@
#include "random-util.h"
#include "strv.h"
struct pkcs11_callback_data {
char *pin_used;
X509 *cert;
};
#if HAVE_P11KIT
static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
erase_and_free(data->pin_used);
X509_free(data->cert);
}
static int pkcs11_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(erase_and_freep) char *pin_used = NULL;
struct pkcs11_callback_data *data = userdata;
CK_OBJECT_HANDLE object;
int r;
assert(m);
assert(slot_info);
assert(token_info);
assert(uri);
assert(data);
/* Called for every token matching our URI */
r = pkcs11_token_login(m, session, slot_id, token_info, "home directory operation", "user-home", "pkcs11-pin", UINT64_MAX, &pin_used);
if (r < 0)
return r;
r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
if (r < 0)
return r;
r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
if (r < 0)
return r;
/* Let's read some random data off the token and write it to the kernel pool before we generate our
* random key from it. This way we can claim the quality of the RNG is at least as good as the
* kernel's and the token's pool */
(void) pkcs11_token_acquire_rng(m, session);
data->pin_used = TAKE_PTR(pin_used);
return 1;
}
#endif
static int acquire_pkcs11_certificate(
const char *uri,
X509 **ret_cert,
char **ret_pin_used) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {};
int r;
r = pkcs11_find_token(uri, pkcs11_callback, &data);
if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"Specified PKCS#11 token with URI '%s' not found.",
uri);
if (r < 0)
return r;
*ret_cert = TAKE_PTR(data.cert);
*ret_pin_used = TAKE_PTR(data.pin_used);
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 tokens not supported on this build.");
#endif
}
static int encrypt_bytes(
EVP_PKEY *pkey,
const void *decrypted_key,
size_t decrypted_key_size,
void **ret_encrypt_key,
size_t *ret_encrypt_key_size) {
_cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
_cleanup_free_ void *b = NULL;
size_t l;
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context");
if (EVP_PKEY_encrypt_init(ctx) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context");
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding");
if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
b = malloc(l);
if (!b)
return log_oom();
if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
return 0;
}
static int add_pkcs11_encrypted_key(
JsonVariant **v,
const char *uri,
@ -267,13 +148,11 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
size_t decrypted_key_size, encrypted_key_size;
_cleanup_(X509_freep) X509 *cert = NULL;
EVP_PKEY *pkey;
RSA *rsa;
int bits;
int r;
assert(v);
r = acquire_pkcs11_certificate(uri, &cert, &pin);
r = pkcs11_acquire_certificate(uri, "home directory operation", "user-home", &cert, &pin);
if (r < 0)
return r;
@ -281,22 +160,9 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
if (!pkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
rsa = EVP_PKEY_get0_RSA(pkey);
if (!rsa)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire RSA public key from X.509 certificate.");
bits = RSA_bits(rsa);
log_debug("Bits in RSA key: %i", bits);
/* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
* generate a random key half the size of the RSA length */
decrypted_key_size = bits / 8 / 2;
if (decrypted_key_size < 1)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to extract RSA key size from X509 certificate.");
log_debug("Generating %zu bytes random key.", decrypted_key_size);
@ -308,7 +174,7 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
if (r < 0)
return log_error_errno(r, "Failed to generate random key: %m");
r = encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
if (r < 0)
return log_error_errno(r, "Failed to encrypt key: %m");
@ -335,143 +201,3 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
return 0;
}
#if HAVE_P11KIT
static int list_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_free_ char *token_uri_string = NULL, *token_label = NULL, *token_manufacturer_id = NULL, *token_model = NULL;
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
Table *t = userdata;
int uri_result, r;
assert(slot_info);
assert(token_info);
/* We only care about hardware devices here with a token inserted. Let's filter everything else
* out. (Note that the user can explicitly specify non-hardware tokens if they like, but during
* enumeration we'll filter those, since software tokens are typically the system certificate store
* and such, and it's typically not what people want to bind their home directories to.) */
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
return -EAGAIN;
token_label = pkcs11_token_label(token_info);
if (!token_label)
return log_oom();
token_manufacturer_id = pkcs11_token_manufacturer_id(token_info);
if (!token_manufacturer_id)
return log_oom();
token_model = pkcs11_token_model(token_info);
if (!token_model)
return log_oom();
token_uri = uri_from_token_info(token_info);
if (!token_uri)
return log_oom();
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, &token_uri_string);
if (uri_result != P11_KIT_URI_OK)
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
r = table_add_many(
t,
TABLE_STRING, token_uri_string,
TABLE_STRING, token_label,
TABLE_STRING, token_manufacturer_id,
TABLE_STRING, token_model);
if (r < 0)
return table_log_add_error(r);
return -EAGAIN; /* keep scanning */
}
#endif
int list_pkcs11_tokens(void) {
#if HAVE_P11KIT
_cleanup_(table_unrefp) Table *t = NULL;
int r;
t = table_new("uri", "label", "manufacturer", "model");
if (!t)
return log_oom();
r = pkcs11_find_token(NULL, list_callback, t);
if (r < 0 && r != -EAGAIN)
return r;
if (table_get_rows(t) <= 1) {
log_info("No suitable PKCS#11 tokens found.");
return 0;
}
r = table_print(t, stdout);
if (r < 0)
return log_error_errno(r, "Failed to show device table: %m");
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 tokens not supported on this build.");
#endif
}
#if HAVE_P11KIT
static int auto_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
char **t = userdata;
int uri_result;
assert(slot_info);
assert(token_info);
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
return -EAGAIN;
if (*t)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"More than one suitable PKCS#11 token found.");
token_uri = uri_from_token_info(token_info);
if (!token_uri)
return log_oom();
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, t);
if (uri_result != P11_KIT_URI_OK)
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
return 0;
}
#endif
int find_pkcs11_token_auto(char **ret) {
#if HAVE_P11KIT
int r;
r = pkcs11_find_token(NULL, auto_callback, ret);
if (r == -EAGAIN)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No suitable PKCS#11 tokens found.");
if (r < 0)
return r;
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 tokens not supported on this build.");
#endif
}

View File

@ -5,46 +5,12 @@
#include "libcrypt-util.h"
#include "locale-util.h"
#include "memory-util.h"
#include "modhex.h"
#include "qrcode-util.h"
#include "random-util.h"
#include "recovery-key.h"
#include "strv.h"
#include "terminal-util.h"
static int make_recovery_key(char **ret) {
_cleanup_(erase_and_freep) char *formatted = NULL;
_cleanup_(erase_and_freep) uint8_t *key = NULL;
int r;
assert(ret);
key = new(uint8_t, MODHEX_RAW_LENGTH);
if (!key)
return log_oom();
r = genuine_random_bytes(key, MODHEX_RAW_LENGTH, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to gather entropy for recovery key: %m");
/* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */
formatted = new(char, MODHEX_FORMATTED_LENGTH);
if (!formatted)
return log_oom();
for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) {
formatted[j++] = modhex_alphabet[key[i] >> 4];
formatted[j++] = modhex_alphabet[key[i] & 0xF];
if (i % 4 == 3)
formatted[j++] = '-';
}
formatted[MODHEX_FORMATTED_LENGTH-1] = 0;
*ret = TAKE_PTR(formatted);
return 0;
}
static int add_privileged(JsonVariant **v, const char *hashed) {
_cleanup_(json_variant_unrefp) JsonVariant *e = NULL, *w = NULL, *l = NULL;
int r;
@ -144,7 +110,7 @@ int identity_add_recovery_key(JsonVariant **v) {
/* First, let's generate a secret key */
r = make_recovery_key(&password);
if (r < 0)
return r;
return log_error_errno(r, "Failed to generate recovery key: %m");
/* Let's UNIX hash it */
r = hash_password(password, &hashed);

View File

@ -18,6 +18,7 @@
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "homectl-recovery-key.h"
#include "libfido2-util.h"
#include "locale-util.h"
#include "main-func.h"
#include "memory-util.h"
@ -3146,7 +3147,7 @@ static int parse_argv(int argc, char *argv[]) {
const char *p;
if (streq(optarg, "list"))
return list_pkcs11_tokens();
return pkcs11_list_tokens();
/* If --pkcs11-token-uri= is specified we always drop everything old */
FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") {
@ -3163,7 +3164,7 @@ static int parse_argv(int argc, char *argv[]) {
if (streq(optarg, "auto")) {
_cleanup_free_ char *found = NULL;
r = find_pkcs11_token_auto(&found);
r = pkcs11_find_token_auto(&found);
if (r < 0)
return r;
r = strv_consume(&arg_pkcs11_token_uri, TAKE_PTR(found));
@ -3184,7 +3185,7 @@ static int parse_argv(int argc, char *argv[]) {
const char *p;
if (streq(optarg, "list"))
return list_fido2_devices();
return fido2_list_devices();
FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") {
r = drop_from_identity(p);
@ -3200,7 +3201,7 @@ static int parse_argv(int argc, char *argv[]) {
if (streq(optarg, "auto")) {
_cleanup_free_ char *found = NULL;
r = find_fido2_auto(&found);
r = fido2_find_device_auto(&found);
if (r < 0)
return r;

View File

@ -4,138 +4,35 @@
#include "hexdecoct.h"
#include "homework-fido2.h"
#include "strv.h"
#include "libfido2-util.h"
#include "memory-util.h"
static int fido2_use_specific_token(
const char *path,
int fido2_use_token(
UserRecord *h,
UserRecord *secret,
const Fido2HmacSalt *salt,
char **ret) {
_cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
_cleanup_(fido_assert_free) fido_assert_t *a = NULL;
_cleanup_(fido_dev_free) fido_dev_t *d = NULL;
bool found_extension = false;
size_t n, hmac_size;
const void *hmac;
char **e;
_cleanup_(erase_and_freep) void *hmac = NULL;
size_t hmac_size;
int r;
d = fido_dev_new();
if (!d)
return log_oom();
assert(h);
assert(secret);
assert(salt);
assert(ret);
r = fido_dev_open(d, path);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", path, fido_strerr(r));
if (!fido_dev_is_fido2(d))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is not a FIDO2 device.", path);
di = fido_cbor_info_new();
if (!di)
return log_oom();
r = fido_dev_get_cbor_info(d, di);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get CBOR device info for %s: %s", path, fido_strerr(r));
e = fido_cbor_info_extensions_ptr(di);
n = fido_cbor_info_extensions_len(di);
for (size_t i = 0; i < n; i++)
if (streq(e[i], "hmac-secret")) {
found_extension = true;
break;
}
if (!found_extension)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", path);
a = fido_assert_new();
if (!a)
return log_oom();
r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_hmac_salt(a, salt->salt, salt->salt_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
r = fido_assert_set_rp(a, "io.systemd.home");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
r = fido_assert_allow_cred(a, salt->credential.id, salt->credential.size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
r = fido_assert_set_up(a, h->fido2_user_presence_permitted <= 0 ? FIDO_OPT_FALSE : FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion user presence: %s", fido_strerr(r));
log_info("Asking FIDO2 token for authentication.");
r = fido_dev_get_assert(d, a, NULL); /* try without pin first */
if (r == FIDO_ERR_PIN_REQUIRED) {
char **i;
/* OK, we needed a pin, try with all pins in turn */
STRV_FOREACH(i, secret->token_pin) {
r = fido_dev_get_assert(d, a, *i);
if (r != FIDO_ERR_PIN_INVALID)
break;
}
}
switch (r) {
case FIDO_OK:
break;
case FIDO_ERR_NO_CREDENTIALS:
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
"Wrong security token; needed credentials not present on token.");
case FIDO_ERR_PIN_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
"Security token requires PIN.");
case FIDO_ERR_PIN_AUTH_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"PIN of security token is blocked, please remove/reinsert token.");
case FIDO_ERR_PIN_INVALID:
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
"PIN of security token incorrect.");
case FIDO_ERR_UP_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"User presence required.");
case FIDO_ERR_ACTION_TIMEOUT:
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
default:
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", fido_strerr(r));
}
hmac = fido_assert_hmac_secret_ptr(a, 0);
if (!hmac)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
hmac_size = fido_assert_hmac_secret_len(a, 0);
r = fido2_use_hmac_hash(
NULL,
"io.systemd.home",
salt->salt, salt->salt_size,
salt->credential.id, salt->credential.size,
secret->token_pin,
h->fido2_user_presence_permitted > 0,
&hmac,
&hmac_size);
if (r < 0)
return r;
r = base64mem(hmac, hmac_size, ret);
if (r < 0)
@ -143,55 +40,3 @@ static int fido2_use_specific_token(
return 0;
}
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret) {
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
di = fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
const char *path;
entry = fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
path = fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
r = fido2_use_specific_token(path, h, secret, salt, ret);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
-ENODEV /* device is not a FIDO2 device with HMAC-SECRET */))
goto finish;
}
r = -EAGAIN;
finish:
fido_dev_info_free(&di, allocated);
return r;
}

View File

@ -21,9 +21,9 @@
#include "main-func.h"
#include "memory-util.h"
#include "missing_magic.h"
#include "modhex.h"
#include "mount-util.h"
#include "path-util.h"
#include "recovery-key.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"

View File

@ -19,8 +19,6 @@ systemd_homework_sources = files('''
homework-quota.h
homework.c
homework.h
modhex.c
modhex.h
user-record-util.c
user-record-util.h
'''.split())
@ -52,8 +50,6 @@ systemd_homed_sources = files('''
homed-varlink.c
homed-varlink.h
homed.c
modhex.c
modhex.h
user-record-pwquality.c
user-record-pwquality.h
user-record-sign.c
@ -80,8 +76,6 @@ homectl_sources = files('''
homectl-recovery-key.c
homectl-recovery-key.h
homectl.c
modhex.c
modhex.h
user-record-pwquality.c
user-record-pwquality.h
user-record-util.c
@ -92,8 +86,6 @@ pam_systemd_home_sym = 'src/home/pam_systemd_home.sym'
pam_systemd_home_c = files('''
home-util.c
home-util.h
modhex.c
modhex.h
pam_systemd_home.c
user-record-util.c
user-record-util.h
@ -112,11 +104,3 @@ if conf.get('ENABLE_HOMED') == 1
install_dir : pkgsysconfdir)
endif
endif
tests += [
[['src/home/test-modhex.c',
'src/home/modhex.c',
'src/home/modhex.h'],
[],
[]],
]

View File

@ -7,7 +7,7 @@
#include "id128-util.h"
#include "libcrypt-util.h"
#include "memory-util.h"
#include "modhex.h"
#include "recovery-key.h"
#include "mountpoint-util.h"
#include "path-util.h"
#include "stat-util.h"

View File

@ -34,6 +34,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "gpt.h"
#include "hexdecoct.h"
#include "id128-util.h"
#include "json.h"
#include "list.h"
@ -54,9 +55,11 @@
#include "specifier.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "tpm2-util.h"
#include "user-util.h"
#include "utf8.h"
@ -107,15 +110,27 @@ static bool arg_json = false;
static JsonFormatFlags arg_json_format_flags = 0;
static void *arg_key = NULL;
static size_t arg_key_size = 0;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
typedef struct Partition Partition;
typedef struct FreeArea FreeArea;
typedef struct Context Context;
typedef enum EncryptMode {
ENCRYPT_OFF,
ENCRYPT_KEY_FILE,
ENCRYPT_TPM2,
ENCRYPT_KEY_FILE_TPM2,
_ENCRYPT_MODE_MAX,
_ENCRYPT_MODE_INVALID = -1,
} EncryptMode;
struct Partition {
char *definition_path;
@ -149,7 +164,7 @@ struct Partition {
char *format;
char **copy_files;
bool encrypt;
EncryptMode encrypt;
LIST_FIELDS(Partition, partitions);
};
@ -177,6 +192,15 @@ struct Context {
sd_id128_t seed;
};
static const char *encrypt_mode_table[_ENCRYPT_MODE_MAX] = {
[ENCRYPT_OFF] = "off",
[ENCRYPT_KEY_FILE] = "key-file",
[ENCRYPT_TPM2] = "tpm2",
[ENCRYPT_KEY_FILE_TPM2] = "key-file+tpm2",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE);
static uint64_t round_down_size(uint64_t v, uint64_t p) {
return (v / p) * p;
}
@ -388,12 +412,12 @@ static uint64_t partition_min_size(const Partition *p) {
if (!PARTITION_EXISTS(p)) {
uint64_t d = 0;
if (p->encrypt)
if (p->encrypt != ENCRYPT_OFF)
d += round_up_size(LUKS2_METADATA_SIZE, 4096);
if (p->copy_blocks_size != UINT64_MAX)
d += round_up_size(p->copy_blocks_size, 4096);
else if (p->format || p->encrypt) {
else if (p->format || p->encrypt != ENCRYPT_OFF) {
uint64_t f;
/* If we shall synthesize a file system, take minimal fs size into account (assumed to be 4K if not known) */
@ -1137,6 +1161,8 @@ static int config_parse_copy_files(
return 0;
}
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt, encrypt_mode, EncryptMode, ENCRYPT_OFF, "Invalid encryption mode");
static int partition_read_definition(Partition *p, const char *path) {
ConfigTableItem table[] = {
@ -1154,7 +1180,7 @@ static int partition_read_definition(Partition *p, const char *path) {
{ "Partition", "CopyBlocks", config_parse_path, 0, &p->copy_blocks_path },
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
{ "Partition", "Encrypt", config_parse_bool, 0, &p->encrypt },
{ "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
{}
};
int r;
@ -1188,7 +1214,7 @@ static int partition_read_definition(Partition *p, const char *path) {
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Format=swap and CopyFiles= cannot be combined, refusing.");
if (!p->format && (!strv_isempty(p->copy_files) || (p->encrypt && !p->copy_blocks_path))) {
if (!p->format && (!strv_isempty(p->copy_files) || (p->encrypt != ENCRYPT_OFF && !p->copy_blocks_path))) {
/* Pick "ext4" as file system if we are configured to copy files or encrypt the device */
p->format = strdup("ext4");
if (!p->format)
@ -2376,7 +2402,9 @@ static int partition_encrypt(
int r;
assert(p);
assert(p->encrypt);
assert(p->encrypt != ENCRYPT_OFF);
log_debug("Encryption mode for partition %" PRIu64 ": %s", p->partno, encrypt_mode_to_string(p->encrypt));
r = dlopen_cryptsetup();
if (r < 0)
@ -2425,15 +2453,61 @@ static int partition_encrypt(
if (r < 0)
return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
r = sym_crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
strempty(arg_key),
arg_key_size);
if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m");
if (IN_SET(p->encrypt, ENCRYPT_KEY_FILE, ENCRYPT_KEY_FILE_TPM2)) {
r = sym_crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
strempty(arg_key),
arg_key_size);
if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m");
}
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
#if HAVE_TPM2
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
size_t secret_size, blob_size, hash_size;
int keyslot;
r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
r = cryptsetup_set_minimal_pbkdf(cd);
if (r < 0)
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
keyslot = sym_crypt_keyslot_add_by_volume_key(
cd,
CRYPT_ANY_SLOT,
volume_key,
volume_key_size,
base64_encoded,
strlen(base64_encoded));
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, blob, blob_size, hash, hash_size, &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
r = cryptsetup_add_token_json(cd, v);
if (r < 0)
return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Support for TPM2 enrollment not enabled.");
#endif
}
r = sym_crypt_activate_by_volume_key(
cd,
@ -2521,7 +2595,7 @@ static int context_copy_blocks(Context *context) {
if (whole_fd < 0)
assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0);
if (p->encrypt) {
if (p->encrypt != ENCRYPT_OFF) {
r = loop_device_make(whole_fd, O_RDWR, p->offset, p->new_size, 0, &d);
if (r < 0)
return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno);
@ -2554,7 +2628,7 @@ static int context_copy_blocks(Context *context) {
if (fsync(target_fd) < 0)
return log_error_errno(r, "Failed to synchronize copied data blocks: %m");
if (p->encrypt) {
if (p->encrypt != ENCRYPT_OFF) {
encrypted_dev_fd = safe_close(encrypted_dev_fd);
r = deactivate_luks(cd, encrypted);
@ -2743,7 +2817,7 @@ static int context_mkfs(Context *context) {
if (r < 0)
return log_error_errno(r, "Failed to lock loopback device: %m");
if (p->encrypt) {
if (p->encrypt != ENCRYPT_OFF) {
r = partition_encrypt(p, d->node, &cd, &encrypted, &encrypted_dev_fd);
if (r < 0)
return log_error_errno(r, "Failed to encrypt device: %m");
@ -2773,7 +2847,7 @@ static int context_mkfs(Context *context) {
log_info("Successfully formatted future partition %" PRIu64 ".", p->partno);
/* The file system is now created, no need to delay udev further */
if (p->encrypt)
if (p->encrypt != ENCRYPT_OFF)
if (flock(encrypted_dev_fd, LOCK_UN) < 0)
return log_error_errno(errno, "Failed to unlock LUKS device: %m");
@ -2788,7 +2862,7 @@ static int context_mkfs(Context *context) {
* if we don't sync before detaching a block device the in-flight sectors possibly won't hit
* the disk. */
if (p->encrypt) {
if (p->encrypt != ENCRYPT_OFF) {
if (fsync(encrypted_dev_fd) < 0)
return log_error_errno(r, "Failed to synchronize LUKS volume: %m");
encrypted_dev_fd = safe_close(encrypted_dev_fd);
@ -3420,6 +3494,9 @@ static int help(void) {
" --root=PATH Operate relative to root path\n"
" --definitions=DIR Find partitions in specified directory\n"
" --key-file=PATH Key to use when encrypting partitions\n"
" --tpm2-device=PATH Path to TPM2 device node to use\n"
" --tpm2-pcrs=PCR1,PCR2,…\n"
" TPM2 PCR indexes to use for TPM2 enrollment\n"
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
" --size=BYTES Grow loopback file to specified size\n"
" --json=pretty|short|off\n"
@ -3449,6 +3526,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SIZE,
ARG_JSON,
ARG_KEY_FILE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
};
static const struct option options[] = {
@ -3466,6 +3545,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "size", required_argument, NULL, ARG_SIZE },
{ "json", required_argument, NULL, ARG_JSON },
{ "key-file", required_argument, NULL, ARG_KEY_FILE },
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
{ "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
{}
};
@ -3635,6 +3716,43 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_TPM2_DEVICE: {
_cleanup_free_ char *device = NULL;
if (streq(optarg, "list"))
return tpm2_list_devices();
if (!streq(optarg, "auto")) {
device = strdup(optarg);
if (!device)
return log_oom();
}
free(arg_tpm2_device);
arg_tpm2_device = TAKE_PTR(device);
break;
}
case ARG_TPM2_PCRS: {
uint32_t mask;
if (isempty(optarg)) {
arg_tpm2_pcr_mask = 0;
break;
}
r = tpm2_parse_pcrs(optarg, &mask);
if (r < 0)
return r;
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = mask;
else
arg_tpm2_pcr_mask |= mask;
break;
}
case '?':
return -EINVAL;
@ -3667,6 +3785,9 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
return 1;
}

View File

@ -5,6 +5,7 @@
#include "cryptsetup-util.h"
#include "dlfcn-util.h"
#include "log.h"
#include "parse-util.h"
static void *cryptsetup_dl = NULL;
@ -26,6 +27,9 @@ int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_
int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device);
void (*sym_crypt_set_debug_level)(int level);
void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr);
int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf) = NULL;
int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json) = NULL;
int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json) = NULL;
int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
int dlopen_cryptsetup(void) {
@ -61,6 +65,9 @@ int dlopen_cryptsetup(void) {
DLSYM_ARG(crypt_set_data_device),
DLSYM_ARG(crypt_set_debug_level),
DLSYM_ARG(crypt_set_log_callback),
DLSYM_ARG(crypt_set_pbkdf_type),
DLSYM_ARG(crypt_token_json_get),
DLSYM_ARG(crypt_token_json_set),
DLSYM_ARG(crypt_volume_key_get),
NULL);
if (r < 0)
@ -108,4 +115,126 @@ void cryptsetup_enable_logging(struct crypt_device *cd) {
sym_crypt_set_debug_level(DEBUG_LOGGING ? CRYPT_DEBUG_ALL : CRYPT_DEBUG_NONE);
}
int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd) {
static const struct crypt_pbkdf_type minimal_pbkdf = {
.hash = "sha512",
.type = CRYPT_KDF_PBKDF2,
.iterations = 1,
.time_ms = 1,
};
int r;
/* Sets a minimal PKBDF in case we already have a high entropy key. */
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = sym_crypt_set_pbkdf_type(cd, &minimal_pbkdf);
if (r < 0)
return r;
return 0;
}
int cryptsetup_get_token_as_json(
struct crypt_device *cd,
int idx,
const char *verify_type,
JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
const char *text;
int r;
assert(cd);
/* Extracts and parses the LUKS2 JSON token data from a LUKS2 device. Optionally verifies the type of
* the token. Returns:
*
* -EINVAL token index out of range or "type" field missing
* -ENOENT token doesn't exist
* -EMEDIUMTYPE "verify_type" specified and doesn't match token's type
*/
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = sym_crypt_token_json_get(cd, idx, &text);
if (r < 0)
return r;
r = json_parse(text, 0, &v, NULL, NULL);
if (r < 0)
return r;
if (verify_type) {
JsonVariant *w;
w = json_variant_by_key(v, "type");
if (!w)
return -EINVAL;
if (!streq_ptr(json_variant_string(w), verify_type))
return -EMEDIUMTYPE;
}
if (ret)
*ret = TAKE_PTR(v);
return 0;
}
int cryptsetup_get_keyslot_from_token(JsonVariant *v) {
int keyslot, r;
JsonVariant *w;
/* Parses the "keyslots" field of a LUKS2 token object. The field can be an array, but here we assume
* that it contains a single element only, since that's the only way we ever generate it
* ourselves. */
w = json_variant_by_key(v, "keyslots");
if (!w)
return -ENOENT;
if (!json_variant_is_array(w) || json_variant_elements(w) != 1)
return -EMEDIUMTYPE;
w = json_variant_by_index(w, 0);
if (!w)
return -ENOENT;
if (!json_variant_is_string(w))
return -EMEDIUMTYPE;
r = safe_atoi(json_variant_string(w), &keyslot);
if (r < 0)
return r;
if (keyslot < 0)
return -EINVAL;
return keyslot;
}
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) {
_cleanup_free_ char *text = NULL;
int r;
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_debug_errno(r, "Failed to format token data for LUKS: %m");
log_debug("Adding token text <%s>", text);
r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
if (r < 0)
return log_debug_errno(r, "Failed to write token data to LUKS: %m");
return 0;
}
#endif

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "json.h"
#include "macro.h"
#if HAVE_LIBCRYPTSETUP
@ -24,6 +25,9 @@ extern int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64
extern int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device);
extern void (*sym_crypt_set_debug_level)(int level);
extern void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr);
extern int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf);
extern int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json);
extern int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json);
extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size);
int dlopen_cryptsetup(void);
@ -33,4 +37,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(struct crypt_device *, sym_crypt_free);
void cryptsetup_enable_logging(struct crypt_device *cd);
int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd);
int cryptsetup_get_token_as_json(struct crypt_device *cd, int idx, const char *verify_type, JsonVariant **ret);
int cryptsetup_get_keyslot_from_token(JsonVariant *v);
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v);
/* Stolen from cryptsetup's sources. We use to iterate through all tokens defined for a volume. Ideally, we'd
* be able to query this via some API, but there appears to be none currently in libcryptsetup. */
#define LUKS2_TOKENS_MAX 32
#endif

View File

@ -433,6 +433,19 @@ int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n) {
return json_variant_new_stringn(ret, s, k);
}
int json_variant_new_hex(JsonVariant **ret, const void *p, size_t n) {
_cleanup_free_ char *s = NULL;
assert_return(ret, -EINVAL);
assert_return(n == 0 || p, -EINVAL);
s = hexmem(p, n);
if (!s)
return -ENOMEM;
return json_variant_new_stringn(ret, s, n*2);
}
int json_variant_new_id128(JsonVariant **ret, sd_id128_t id) {
char s[SD_ID128_STRING_MAX];
@ -3603,6 +3616,36 @@ int json_buildv(JsonVariant **ret, va_list ap) {
break;
}
case _JSON_BUILD_HEX: {
const void *p;
size_t n;
if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) {
r = -EINVAL;
goto finish;
}
p = va_arg(ap, const void *);
n = va_arg(ap, size_t);
if (current->n_suppress == 0) {
r = json_variant_new_hex(&add, p, n);
if (r < 0)
goto finish;
}
n_subtract = 1;
if (current->expect == EXPECT_TOPLEVEL)
current->expect = EXPECT_END;
else if (current->expect == EXPECT_OBJECT_VALUE)
current->expect = EXPECT_OBJECT_KEY;
else
assert(current->expect == EXPECT_ARRAY_ELEMENT);
break;
}
case _JSON_BUILD_ID128: {
sd_id128_t id;
@ -4405,6 +4448,14 @@ int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size) {
return unbase64mem(json_variant_string(v), (size_t) -1, ret, ret_size);
}
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) {
if (!json_variant_is_string(v))
return -EINVAL;
return unhexmem(json_variant_string(v), (size_t) -1, ret, ret_size);
}
static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = {
[JSON_VARIANT_STRING] = "string",
[JSON_VARIANT_INTEGER] = "integer",

View File

@ -58,6 +58,7 @@ typedef enum JsonVariantType {
int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n);
int json_variant_new_base64(JsonVariant **ret, const void *p, size_t n);
int json_variant_new_hex(JsonVariant **ret, const void *p, size_t n);
int json_variant_new_integer(JsonVariant **ret, intmax_t i);
int json_variant_new_unsigned(JsonVariant **ret, uintmax_t u);
int json_variant_new_real(JsonVariant **ret, long double d);
@ -227,6 +228,7 @@ enum {
_JSON_BUILD_LITERAL,
_JSON_BUILD_STRV,
_JSON_BUILD_BASE64,
_JSON_BUILD_HEX,
_JSON_BUILD_ID128,
_JSON_BUILD_BYTE_ARRAY,
_JSON_BUILD_MAX,
@ -249,6 +251,7 @@ enum {
#define JSON_BUILD_LITERAL(l) _JSON_BUILD_LITERAL, ({ const char *_x = l; _x; })
#define JSON_BUILD_STRV(l) _JSON_BUILD_STRV, ({ char **_x = l; _x; })
#define JSON_BUILD_BASE64(p, n) _JSON_BUILD_BASE64, ({ const void *_x = p; _x; }), ({ size_t _y = n; _y; })
#define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, ({ const void *_x = p; _x; }), ({ size_t _y = n; _y; })
#define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, ({ sd_id128_t _x = id; _x; })
#define JSON_BUILD_BYTE_ARRAY(v, n) _JSON_BUILD_BYTE_ARRAY, ({ const void *_x = v; _x; }), ({ size_t _y = n; _y; })
@ -351,6 +354,7 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi
})
int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size);
int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size);
const char *json_variant_type_to_string(JsonVariantType t);
JsonVariantType json_variant_type_from_string(const char *s);

874
src/shared/libfido2-util.c Normal file
View File

@ -0,0 +1,874 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libfido2-util.h"
#if HAVE_LIBFIDO2
#include "alloc-util.h"
#include "ask-password-api.h"
#include "dlfcn-util.h"
#include "format-table.h"
#include "locale-util.h"
#include "log.h"
#include "memory-util.h"
#include "random-util.h"
#include "strv.h"
static void *libfido2_dl = NULL;
int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t) = NULL;
void (*sym_fido_assert_free)(fido_assert_t **) = NULL;
size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t) = NULL;
const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t) = NULL;
fido_assert_t* (*sym_fido_assert_new)(void) = NULL;
int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t) = NULL;
int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL;
int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL;
int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL;
int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL;
size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL;
char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL;
void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL;
fido_cbor_info_t* (*sym_fido_cbor_info_new)(void) = NULL;
size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *) = NULL;
char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *) = NULL;
const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *) = NULL;
void (*sym_fido_cred_free)(fido_cred_t **) = NULL;
size_t (*sym_fido_cred_id_len)(const fido_cred_t *) = NULL;
const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *) = NULL;
fido_cred_t* (*sym_fido_cred_new)(void) = NULL;
int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t) = NULL;
int (*sym_fido_cred_set_extensions)(fido_cred_t *, int) = NULL;
int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t) = NULL;
int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *) = NULL;
int (*sym_fido_cred_set_type)(fido_cred_t *, int) = NULL;
int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *) = NULL;
int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t) = NULL;
void (*sym_fido_dev_free)(fido_dev_t **) = NULL;
int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *) = NULL;
int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *) = NULL;
void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t) = NULL;
int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *) = NULL;
const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *) = NULL;
const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *) = NULL;
fido_dev_info_t* (*sym_fido_dev_info_new)(size_t) = NULL;
const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *) = NULL;
const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t) = NULL;
bool (*sym_fido_dev_is_fido2)(const fido_dev_t *) = NULL;
int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *) = NULL;
fido_dev_t* (*sym_fido_dev_new)(void) = NULL;
int (*sym_fido_dev_open)(fido_dev_t *, const char *) = NULL;
const char* (*sym_fido_strerr)(int) = NULL;
int dlopen_libfido2(void) {
_cleanup_(dlclosep) void *dl = NULL;
int r;
if (libfido2_dl)
return 0; /* Already loaded */
dl = dlopen("libfido2.so.1", RTLD_LAZY);
if (!dl)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"libfido2 support is not installed: %s", dlerror());
r = dlsym_many_and_warn(
dl,
LOG_DEBUG,
DLSYM_ARG(fido_assert_allow_cred),
DLSYM_ARG(fido_assert_free),
DLSYM_ARG(fido_assert_hmac_secret_len),
DLSYM_ARG(fido_assert_hmac_secret_ptr),
DLSYM_ARG(fido_assert_new),
DLSYM_ARG(fido_assert_set_clientdata_hash),
DLSYM_ARG(fido_assert_set_extensions),
DLSYM_ARG(fido_assert_set_hmac_salt),
DLSYM_ARG(fido_assert_set_rp),
DLSYM_ARG(fido_assert_set_up),
DLSYM_ARG(fido_cbor_info_extensions_len),
DLSYM_ARG(fido_cbor_info_extensions_ptr),
DLSYM_ARG(fido_cbor_info_free),
DLSYM_ARG(fido_cbor_info_new),
DLSYM_ARG(fido_cbor_info_options_len),
DLSYM_ARG(fido_cbor_info_options_name_ptr),
DLSYM_ARG(fido_cbor_info_options_value_ptr),
DLSYM_ARG(fido_cred_free),
DLSYM_ARG(fido_cred_id_len),
DLSYM_ARG(fido_cred_id_ptr),
DLSYM_ARG(fido_cred_new),
DLSYM_ARG(fido_cred_set_clientdata_hash),
DLSYM_ARG(fido_cred_set_extensions),
DLSYM_ARG(fido_cred_set_rk),
DLSYM_ARG(fido_cred_set_rp),
DLSYM_ARG(fido_cred_set_type),
DLSYM_ARG(fido_cred_set_user),
DLSYM_ARG(fido_cred_set_uv),
DLSYM_ARG(fido_dev_free),
DLSYM_ARG(fido_dev_get_assert),
DLSYM_ARG(fido_dev_get_cbor_info),
DLSYM_ARG(fido_dev_info_free),
DLSYM_ARG(fido_dev_info_manifest),
DLSYM_ARG(fido_dev_info_manufacturer_string),
DLSYM_ARG(fido_dev_info_new),
DLSYM_ARG(fido_dev_info_path),
DLSYM_ARG(fido_dev_info_product_string),
DLSYM_ARG(fido_dev_info_ptr),
DLSYM_ARG(fido_dev_is_fido2),
DLSYM_ARG(fido_dev_make_cred),
DLSYM_ARG(fido_dev_new),
DLSYM_ARG(fido_dev_open),
DLSYM_ARG(fido_strerr),
NULL);
if (r < 0)
return r;
/* Note that we never release the reference here, because there's no real reason to, after all this
* was traditionally a regular shared library dependency which lives forever too. */
libfido2_dl = TAKE_PTR(dl);
return 1;
}
static int verify_features(
fido_dev_t *d,
const char *path,
int log_level, /* the log level to use when device is not FIDO2 with hmac-secret */
bool *ret_has_rk,
bool *ret_has_client_pin,
bool *ret_has_up,
bool *ret_has_uv) {
_cleanup_(fido_cbor_info_free_wrapper) fido_cbor_info_t *di = NULL;
bool found_extension = false;
char **e, **o;
const bool *b;
bool has_rk = false, has_client_pin = false, has_up = true, has_uv = false; /* Defaults are per table in 5.4 in FIDO2 spec */
size_t n;
int r;
assert(d);
assert(path);
if (!sym_fido_dev_is_fido2(d))
return log_full_errno(log_level,
SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is not a FIDO2 device.", path);
di = sym_fido_cbor_info_new();
if (!di)
return log_oom();
r = sym_fido_dev_get_cbor_info(d, di);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get CBOR device info for %s: %s", path, sym_fido_strerr(r));
e = sym_fido_cbor_info_extensions_ptr(di);
n = sym_fido_cbor_info_extensions_len(di);
for (size_t i = 0; i < n; i++) {
log_debug("FIDO2 device implements extension: %s", e[i]);
if (streq(e[i], "hmac-secret"))
found_extension = true;
}
o = sym_fido_cbor_info_options_name_ptr(di);
b = sym_fido_cbor_info_options_value_ptr(di);
n = sym_fido_cbor_info_options_len(di);
for (size_t i = 0; i < n; i++) {
log_debug("FIDO2 device implements option %s: %s", o[i], yes_no(b[i]));
if (streq(o[i], "rk"))
has_rk = b[i];
if (streq(o[i], "clientPin"))
has_client_pin = b[i];
if (streq(o[i], "up"))
has_up = b[i];
if (streq(o[i], "uv"))
has_uv = b[i];
}
if (!found_extension)
return log_full_errno(log_level,
SYNTHETIC_ERRNO(ENODEV),
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", path);
log_debug("Has rk ('Resident Key') support: %s\n"
"Has clientPin support: %s\n"
"Has up ('User Presence') support: %s\n"
"Has uv ('User Verification') support: %s\n",
yes_no(has_rk),
yes_no(has_client_pin),
yes_no(has_up),
yes_no(has_uv));
if (ret_has_rk)
*ret_has_rk = has_rk;
if (ret_has_client_pin)
*ret_has_client_pin = has_client_pin;
if (ret_has_up)
*ret_has_up = has_up;
if (ret_has_uv)
*ret_has_uv = has_uv;
return 0;
}
static int fido2_use_hmac_hash_specific_token(
const char *path,
const char *rp_id,
const void *salt,
size_t salt_size,
const void *cid,
size_t cid_size,
char **pins,
bool up, /* user presence permitted */
void **ret_hmac,
size_t *ret_hmac_size) {
_cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
_cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
_cleanup_(erase_and_freep) void *hmac_copy = NULL;
bool has_up, has_client_pin;
size_t hmac_size;
const void *hmac;
int r;
assert(path);
assert(rp_id);
assert(salt);
assert(cid);
assert(ret_hmac);
assert(ret_hmac_size);
d = sym_fido_dev_new();
if (!d)
return log_oom();
r = sym_fido_dev_open(d, path);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r));
r = verify_features(d, path, LOG_ERR, NULL, &has_client_pin, &has_up, NULL);
if (r < 0)
return r;
a = sym_fido_assert_new();
if (!a)
return log_oom();
r = sym_fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_hmac_salt(a, salt, salt_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_rp(a, rp_id);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
r = sym_fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
if (has_up) {
r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion user presence: %s", sym_fido_strerr(r));
}
log_info("Asking FIDO2 token for authentication.");
r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin and without up first */
if (r == FIDO_ERR_UP_REQUIRED && up) {
if (!has_up)
log_warning("Weird, device asked for User Presence check, but does not advertise it as feature. Ignoring.");
r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion user presence: %s", sym_fido_strerr(r));
log_info("Security token requires user presence.");
r = sym_fido_dev_get_assert(d, a, NULL); /* try without pin but with up now */
}
if (r == FIDO_ERR_PIN_REQUIRED) {
char **i;
if (!has_client_pin)
log_warning("Weird, device asked for client PIN, but does not advertise it as feature. Ignoring.");
/* OK, we needed a pin, try with all pins in turn */
STRV_FOREACH(i, pins) {
r = sym_fido_dev_get_assert(d, a, *i);
if (r != FIDO_ERR_PIN_INVALID)
break;
}
}
switch (r) {
case FIDO_OK:
break;
case FIDO_ERR_NO_CREDENTIALS:
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
"Wrong security token; needed credentials not present on token.");
case FIDO_ERR_PIN_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
"Security token requires PIN.");
case FIDO_ERR_PIN_AUTH_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"PIN of security token is blocked, please remove/reinsert token.");
case FIDO_ERR_PIN_INVALID:
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
"PIN of security token incorrect.");
case FIDO_ERR_UP_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"User presence required.");
case FIDO_ERR_ACTION_TIMEOUT:
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
default:
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", sym_fido_strerr(r));
}
hmac = sym_fido_assert_hmac_secret_ptr(a, 0);
if (!hmac)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
hmac_size = sym_fido_assert_hmac_secret_len(a, 0);
hmac_copy = memdup(hmac, hmac_size);
if (!hmac_copy)
return log_oom();
*ret_hmac = TAKE_PTR(hmac_copy);
*ret_hmac_size = hmac_size;
return 0;
}
int fido2_use_hmac_hash(
const char *device,
const char *rp_id,
const void *salt,
size_t salt_size,
const void *cid,
size_t cid_size,
char **pins,
bool up, /* user presence permitted */
void **ret_hmac,
size_t *ret_hmac_size) {
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
r = dlopen_libfido2();
if (r < 0)
return log_error_errno(r, "FIDO2 support is not installed.");
if (device)
return fido2_use_hmac_hash_specific_token(device, rp_id, salt, salt_size, cid, cid_size, pins, up, ret_hmac, ret_hmac_size);
di = sym_fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = sym_fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r));
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
const char *path;
entry = sym_fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
path = sym_fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, up, ret_hmac, ret_hmac_size);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
-ENODEV /* device is not a FIDO2 device with HMAC-SECRET */))
goto finish;
}
r = -EAGAIN;
finish:
sym_fido_dev_info_free(&di, allocated);
return r;
}
#define FIDO2_SALT_SIZE 32
int fido2_generate_hmac_hash(
const char *device,
const char *rp_id,
const char *rp_name,
const void *user_id, size_t user_id_len,
const char *user_name,
const char *user_display_name,
const char *user_icon,
const char *askpw_icon_name,
void **ret_cid, size_t *ret_cid_size,
void **ret_salt, size_t *ret_salt_size,
void **ret_secret, size_t *ret_secret_size,
char **ret_usedpin) {
_cleanup_(erase_and_freep) void *salt = NULL, *secret_copy = NULL;
_cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
_cleanup_(fido_cred_free_wrapper) fido_cred_t *c = NULL;
_cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
_cleanup_(erase_and_freep) char *used_pin = NULL;
bool has_rk, has_client_pin, has_up, has_uv;
_cleanup_free_ char *cid_copy = NULL;
size_t cid_size, secret_size;
const void *cid, *secret;
int r;
assert(device);
assert(ret_cid);
assert(ret_cid_size);
assert(ret_salt);
assert(ret_salt_size);
assert(ret_secret);
assert(ret_secret_size);
/* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to
* HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account
* authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2
* device never sees the volume key.
*
* S = HMAC-SHA256(I, D)
*
* with: S LUKS/account authentication key (never stored)
* I internal key on FIDO2 device (stored in the FIDO2 device)
* D salt we generate here (stored in the privileged part of the JSON record)
*
*/
assert(device);
r = dlopen_libfido2();
if (r < 0)
return log_error_errno(r, "FIDO2 token support is not installed.");
salt = malloc(FIDO2_SALT_SIZE);
if (!salt)
return log_oom();
r = genuine_random_bytes(salt, FIDO2_SALT_SIZE, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
d = sym_fido_dev_new();
if (!d)
return log_oom();
r = sym_fido_dev_open(d, device);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", device, sym_fido_strerr(r));
r = verify_features(d, device, LOG_ERR, &has_rk, &has_client_pin, &has_up, &has_uv);
if (r < 0)
return r;
c = sym_fido_cred_new();
if (!c)
return log_oom();
r = sym_fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", sym_fido_strerr(r));
r = sym_fido_cred_set_rp(c, rp_id, rp_name);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential relying party ID/name: %s", sym_fido_strerr(r));
r = sym_fido_cred_set_type(c, COSE_ES256);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential type to ES256: %s", sym_fido_strerr(r));
r = sym_fido_cred_set_user(
c,
user_id, user_id_len,
user_name,
user_display_name,
user_icon);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 credential user data: %s", sym_fido_strerr(r));
r = sym_fido_cred_set_clientdata_hash(c, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 client data hash: %s", sym_fido_strerr(r));
if (has_rk) {
r = sym_fido_cred_set_rk(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 resident key option of credential: %s", sym_fido_strerr(r));
}
if (has_uv) {
r = sym_fido_cred_set_uv(c, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 user verification option of credential: %s", sym_fido_strerr(r));
}
log_info("Initializing FIDO2 credential on security token.");
log_notice("%s%s(Hint: This might require verification of user presence on security token.)",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = sym_fido_dev_make_cred(d, c, NULL);
if (r == FIDO_ERR_PIN_REQUIRED) {
for (;;) {
_cleanup_(strv_free_erasep) char **pin = NULL;
char **i;
if (!has_client_pin)
log_warning("Weird, device asked for client PIN, but does not advertise it as feature. Ignoring.");
r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");
r = FIDO_ERR_PIN_INVALID;
STRV_FOREACH(i, pin) {
if (isempty(*i)) {
log_info("PIN may not be empty.");
continue;
}
r = sym_fido_dev_make_cred(d, c, *i);
if (r == FIDO_OK) {
used_pin = strdup(*i);
if (!used_pin)
return log_oom();
break;
}
if (r != FIDO_ERR_PIN_INVALID)
break;
}
if (r != FIDO_ERR_PIN_INVALID)
break;
log_notice("PIN incorrect, please try again.");
}
}
if (r == FIDO_ERR_PIN_AUTH_BLOCKED)
return log_notice_errno(SYNTHETIC_ERRNO(EPERM),
"Token PIN is currently blocked, please remove and reinsert token.");
if (r == FIDO_ERR_ACTION_TIMEOUT)
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to generate FIDO2 credential: %s", sym_fido_strerr(r));
cid = sym_fido_cred_id_ptr(c);
if (!cid)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get FIDO2 credential ID.");
cid_size = sym_fido_cred_id_len(c);
a = sym_fido_assert_new();
if (!a)
return log_oom();
r = sym_fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_rp(a, rp_id);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
r = sym_fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
if (has_up) {
r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn off FIDO2 assertion user presence: %s", sym_fido_strerr(r));
}
log_info("Generating secret key on FIDO2 security token.");
r = sym_fido_dev_get_assert(d, a, used_pin);
if (r == FIDO_ERR_UP_REQUIRED) {
if (!has_up)
log_warning("Weird, device asked for User Presence check, but does not advertise it as feature. Ignoring.");
r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to turn on FIDO2 assertion user presence: %s", sym_fido_strerr(r));
log_notice("%s%sIn order to allow secret key generation, please verify presence on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = sym_fido_dev_get_assert(d, a, used_pin);
}
if (r == FIDO_ERR_ACTION_TIMEOUT)
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", sym_fido_strerr(r));
secret = sym_fido_assert_hmac_secret_ptr(a, 0);
if (!secret)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
secret_size = sym_fido_assert_hmac_secret_len(a, 0);
secret_copy = memdup(secret, secret_size);
if (!secret_copy)
return log_oom();
cid_copy = memdup(cid, cid_size);
if (!cid_copy)
return log_oom();
*ret_cid = TAKE_PTR(cid_copy);
*ret_cid_size = cid_size;
*ret_salt = TAKE_PTR(salt);
*ret_salt_size = FIDO2_SALT_SIZE;
*ret_secret = TAKE_PTR(secret_copy);
*ret_secret_size = secret_size;
if (ret_usedpin)
*ret_usedpin = TAKE_PTR(used_pin);
return 0;
}
#endif
#if HAVE_LIBFIDO2
static int check_device_is_fido2_with_hmac_secret(const char *path) {
_cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
int r;
d = sym_fido_dev_new();
if (!d)
return log_oom();
r = sym_fido_dev_open(d, path);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r));
r = verify_features(d, path, LOG_DEBUG, NULL, NULL, NULL, NULL);
if (r == -ENODEV) /* Not a FIDO2 device, or not implementing 'hmac-secret' */
return false;
if (r < 0)
return r;
return true;
}
#endif
int fido2_list_devices(void) {
#if HAVE_LIBFIDO2
_cleanup_(table_unrefp) Table *t = NULL;
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
r = dlopen_libfido2();
if (r < 0)
return log_error_errno(r, "FIDO2 token support is not installed.");
di = sym_fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = sym_fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
log_info("No FIDO2 devices found.");
r = 0;
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r));
goto finish;
}
t = table_new("path", "manufacturer", "product");
if (!t) {
r = log_oom();
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
entry = sym_fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
r = check_device_is_fido2_with_hmac_secret(sym_fido_dev_info_path(entry));
if (r < 0)
goto finish;
if (!r)
continue;
r = table_add_many(
t,
TABLE_PATH, sym_fido_dev_info_path(entry),
TABLE_STRING, sym_fido_dev_info_manufacturer_string(entry),
TABLE_STRING, sym_fido_dev_info_product_string(entry));
if (r < 0) {
table_log_add_error(r);
goto finish;
}
}
r = table_print(t, stdout);
if (r < 0) {
log_error_errno(r, "Failed to show device table: %m");
goto finish;
}
r = 0;
finish:
sym_fido_dev_info_free(&di, allocated);
return r;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 tokens not supported on this build.");
#endif
}
int fido2_find_device_auto(char **ret) {
#if HAVE_LIBFIDO2
_cleanup_free_ char *copy = NULL;
size_t di_size = 64, found = 0;
const fido_dev_info_t *entry;
fido_dev_info_t *di = NULL;
const char *path;
int r;
r = dlopen_libfido2();
if (r < 0)
return log_error_errno(r, "FIDO2 token support is not installed.");
di = sym_fido_dev_info_new(di_size);
if (!di)
return log_oom();
r = sym_fido_dev_info_manifest(di, di_size, &found);
if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No FIDO devices found.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO devices: %s", sym_fido_strerr(r));
goto finish;
}
if (found > 1) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "More than one FIDO device found.");
goto finish;
}
entry = sym_fido_dev_info_ptr(di, 0);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device 0.");
goto finish;
}
r = check_device_is_fido2_with_hmac_secret(sym_fido_dev_info_path(entry));
if (r < 0)
goto finish;
if (!r) {
r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO device discovered does not implement FIDO2 with 'hmac-secret' extension.");
goto finish;
}
path = sym_fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
copy = strdup(path);
if (!copy) {
r = log_oom();
goto finish;
}
*ret = TAKE_PTR(copy);
r = 0;
finish:
sym_fido_dev_info_free(&di, di_size);
return r;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"FIDO2 tokens not supported on this build.");
#endif
}

104
src/shared/libfido2-util.h Normal file
View File

@ -0,0 +1,104 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "macro.h"
#if HAVE_LIBFIDO2
#include <fido.h>
extern int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t);
extern void (*sym_fido_assert_free)(fido_assert_t **);
extern size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t);
extern const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t);
extern fido_assert_t* (*sym_fido_assert_new)(void);
extern int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t);
extern int (*sym_fido_assert_set_extensions)(fido_assert_t *, int);
extern int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t);
extern int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *);
extern int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t);
extern size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *);
extern char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *);
extern void (*sym_fido_cbor_info_free)(fido_cbor_info_t **);
extern fido_cbor_info_t* (*sym_fido_cbor_info_new)(void);
extern size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *);
extern char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *);
extern const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *);
extern void (*sym_fido_cred_free)(fido_cred_t **);
extern size_t (*sym_fido_cred_id_len)(const fido_cred_t *);
extern const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *);
extern fido_cred_t* (*sym_fido_cred_new)(void);
extern int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t);
extern int (*sym_fido_cred_set_extensions)(fido_cred_t *, int);
extern int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t);
extern int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *);
extern int (*sym_fido_cred_set_type)(fido_cred_t *, int);
extern int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *);
extern int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t);
extern void (*sym_fido_dev_free)(fido_dev_t **);
extern int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *);
extern int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *);
extern void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t);
extern int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *);
extern const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *);
extern const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *);
extern fido_dev_info_t* (*sym_fido_dev_info_new)(size_t);
extern const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *);
extern const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t);
extern bool (*sym_fido_dev_is_fido2)(const fido_dev_t *);
extern int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *);
extern fido_dev_t* (*sym_fido_dev_new)(void);
extern int (*sym_fido_dev_open)(fido_dev_t *, const char *);
extern const char* (*sym_fido_strerr)(int);
int dlopen_libfido2(void);
static inline void fido_cbor_info_free_wrapper(fido_cbor_info_t **p) {
if (*p)
sym_fido_cbor_info_free(p);
}
static inline void fido_assert_free_wrapper(fido_assert_t **p) {
if (*p)
sym_fido_assert_free(p);
}
static inline void fido_dev_free_wrapper(fido_dev_t **p) {
if (*p)
sym_fido_dev_free(p);
}
static inline void fido_cred_free_wrapper(fido_cred_t **p) {
if (*p)
sym_fido_cred_free(p);
}
int fido2_use_hmac_hash(
const char *device,
const char *rp_id,
const void *salt,
size_t salt_size,
const void *cid,
size_t cid_size,
char **pins,
bool up, /* user presence permitted */
void **ret_hmac,
size_t *ret_hmac_size);
int fido2_generate_hmac_hash(
const char *device,
const char *rp_id,
const char *rp_name,
const void *user_id, size_t user_id_len,
const char *user_name,
const char *user_display_name,
const char *user_icon,
const char *askpw_icon_name,
void **ret_cid, size_t *ret_cid_size,
void **ret_salt, size_t *ret_salt_size,
void **ret_secret, size_t *ret_secret_size,
char **ret_usedpin);
#endif
int fido2_list_devices(void);
int fido2_find_device_auto(char **ret);

View File

@ -146,6 +146,8 @@ shared_sources = files('''
json.h
libcrypt-util.c
libcrypt-util.h
libfido2-util.c
libfido2-util.h
libmount-util.h
linux/auto_dev-ioctl.h
linux/bpf.h
@ -183,6 +185,7 @@ shared_sources = files('''
nsflags.h
numa-util.c
numa-util.h
openssl-util.c
openssl-util.h
os-util.c
os-util.h
@ -234,6 +237,8 @@ shared_sources = files('''
tmpfile-util-label.h
tomoyo-util.c
tomoyo-util.h
tpm2-util.c
tpm2-util.h
udev-util.c
udev-util.h
uid-range.c

76
src/shared/openssl-util.c Normal file
View File

@ -0,0 +1,76 @@
#include "openssl-util.h"
#include "alloc-util.h"
#if HAVE_OPENSSL
int rsa_encrypt_bytes(
EVP_PKEY *pkey,
const void *decrypted_key,
size_t decrypted_key_size,
void **ret_encrypt_key,
size_t *ret_encrypt_key_size) {
_cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
_cleanup_free_ void *b = NULL;
size_t l;
ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context");
if (EVP_PKEY_encrypt_init(ctx) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context");
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding");
if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
b = malloc(l);
if (!b)
return -ENOMEM;
if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
*ret_encrypt_key = TAKE_PTR(b);
*ret_encrypt_key_size = l;
return 0;
}
int rsa_pkey_to_suitable_key_size(
EVP_PKEY *pkey,
size_t *ret_suitable_key_size) {
size_t suitable_key_size;
RSA *rsa;
int bits;
assert_se(pkey);
assert_se(ret_suitable_key_size);
/* Analyzes the specified public key and that it is RSA. If so, will return a suitable size for a
* disk encryption key to encrypt with RSA for use in PKCS#11 security token schemes. */
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
rsa = EVP_PKEY_get0_RSA(pkey);
if (!rsa)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire RSA public key from X.509 certificate.");
bits = RSA_bits(rsa);
log_debug("Bits in RSA key: %i", bits);
/* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
* generate a random key half the size of the RSA length */
suitable_key_size = bits / 8 / 2;
if (suitable_key_size < 1)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
*ret_suitable_key_size = suitable_key_size;
return 0;
}
#endif

View File

@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "macro.h"
#if HAVE_OPENSSL
# include <openssl/pem.h>
@ -9,4 +11,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(X509_NAME*, X509_NAME_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_PKEY_CTX*, EVP_PKEY_CTX_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free);
int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);
int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size);
#endif

View File

@ -5,6 +5,7 @@
#include "ask-password-api.h"
#include "escape.h"
#include "fd-util.h"
#include "format-table.h"
#include "io-util.h"
#include "memory-util.h"
#if HAVE_OPENSSL
@ -924,4 +925,229 @@ int pkcs11_find_token(
return -EAGAIN;
}
#if HAVE_OPENSSL
struct pkcs11_acquire_certificate_callback_data {
char *pin_used;
X509 *cert;
const char *askpw_friendly_name, *askpw_icon_name;
};
static void pkcs11_acquire_certificate_callback_data_release(struct pkcs11_acquire_certificate_callback_data *data) {
erase_and_free(data->pin_used);
X509_free(data->cert);
}
static int pkcs11_acquire_certificate_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(erase_and_freep) char *pin_used = NULL;
struct pkcs11_acquire_certificate_callback_data *data = userdata;
CK_OBJECT_HANDLE object;
int r;
assert(m);
assert(slot_info);
assert(token_info);
assert(uri);
assert(data);
/* Called for every token matching our URI */
r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", UINT64_MAX, &pin_used);
if (r < 0)
return r;
r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
if (r < 0)
return r;
r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
if (r < 0)
return r;
/* Let's read some random data off the token and write it to the kernel pool before we generate our
* random key from it. This way we can claim the quality of the RNG is at least as good as the
* kernel's and the token's pool */
(void) pkcs11_token_acquire_rng(m, session);
data->pin_used = TAKE_PTR(pin_used);
return 1;
}
int pkcs11_acquire_certificate(
const char *uri,
const char *askpw_friendly_name,
const char *askpw_icon_name,
X509 **ret_cert,
char **ret_pin_used) {
_cleanup_(pkcs11_acquire_certificate_callback_data_release) struct pkcs11_acquire_certificate_callback_data data = {
.askpw_friendly_name = askpw_friendly_name,
.askpw_icon_name = askpw_icon_name,
};
int r;
assert(uri);
assert(ret_cert);
r = pkcs11_find_token(uri, pkcs11_acquire_certificate_callback, &data);
if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
"Specified PKCS#11 token with URI '%s' not found.",
uri);
if (r < 0)
return r;
*ret_cert = TAKE_PTR(data.cert);
if (ret_pin_used)
*ret_pin_used = TAKE_PTR(data.pin_used);
return 0;
}
#endif
static int list_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_free_ char *token_uri_string = NULL, *token_label = NULL, *token_manufacturer_id = NULL, *token_model = NULL;
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
Table *t = userdata;
int uri_result, r;
assert(slot_info);
assert(token_info);
/* We only care about hardware devices here with a token inserted. Let's filter everything else
* out. (Note that the user can explicitly specify non-hardware tokens if they like, but during
* enumeration we'll filter those, since software tokens are typically the system certificate store
* and such, and it's typically not what people want to bind their home directories to.) */
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
return -EAGAIN;
token_label = pkcs11_token_label(token_info);
if (!token_label)
return log_oom();
token_manufacturer_id = pkcs11_token_manufacturer_id(token_info);
if (!token_manufacturer_id)
return log_oom();
token_model = pkcs11_token_model(token_info);
if (!token_model)
return log_oom();
token_uri = uri_from_token_info(token_info);
if (!token_uri)
return log_oom();
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, &token_uri_string);
if (uri_result != P11_KIT_URI_OK)
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
r = table_add_many(
t,
TABLE_STRING, token_uri_string,
TABLE_STRING, token_label,
TABLE_STRING, token_manufacturer_id,
TABLE_STRING, token_model);
if (r < 0)
return table_log_add_error(r);
return -EAGAIN; /* keep scanning */
}
#endif
int pkcs11_list_tokens(void) {
#if HAVE_P11KIT
_cleanup_(table_unrefp) Table *t = NULL;
int r;
t = table_new("uri", "label", "manufacturer", "model");
if (!t)
return log_oom();
r = pkcs11_find_token(NULL, list_callback, t);
if (r < 0 && r != -EAGAIN)
return r;
if (table_get_rows(t) <= 1) {
log_info("No suitable PKCS#11 tokens found.");
return 0;
}
r = table_print(t, stdout);
if (r < 0)
return log_error_errno(r, "Failed to show device table: %m");
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 tokens not supported on this build.");
#endif
}
#if HAVE_P11KIT
static int auto_callback(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
CK_SLOT_ID slot_id,
const CK_SLOT_INFO *slot_info,
const CK_TOKEN_INFO *token_info,
P11KitUri *uri,
void *userdata) {
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
char **t = userdata;
int uri_result;
assert(slot_info);
assert(token_info);
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
return -EAGAIN;
if (*t)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"More than one suitable PKCS#11 token found.");
token_uri = uri_from_token_info(token_info);
if (!token_uri)
return log_oom();
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, t);
if (uri_result != P11_KIT_URI_OK)
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
return 0;
}
#endif
int pkcs11_find_token_auto(char **ret) {
#if HAVE_P11KIT
int r;
r = pkcs11_find_token(NULL, auto_callback, ret);
if (r == -EAGAIN)
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No suitable PKCS#11 tokens found.");
if (r < 0)
return r;
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"PKCS#11 tokens not supported on this build.");
#endif
}

View File

@ -44,4 +44,12 @@ int pkcs11_token_acquire_rng(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session);
typedef int (*pkcs11_find_token_callback_t)(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_SLOT_INFO *slot_info, const CK_TOKEN_INFO *token_info, P11KitUri *uri, void *userdata);
int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callback, void *userdata);
#if HAVE_OPENSSL
int pkcs11_acquire_certificate(const char *uri, const char *askpw_friendly_name, const char *askpw_icon_name, X509 **ret_cert, char **ret_pin_used);
#endif
#endif
int pkcs11_list_tokens(void);
int pkcs11_find_token_auto(char **ret);

998
src/shared/tpm2-util.c Normal file
View File

@ -0,0 +1,998 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "extract-word.h"
#include "parse-util.h"
#include "tpm2-util.h"
#if HAVE_TPM2
#include "alloc-util.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "fd-util.h"
#include "format-table.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "memory-util.h"
#include "random-util.h"
#include "time-util.h"
static void *libtss2_esys_dl = NULL;
static void *libtss2_rc_dl = NULL;
static void *libtss2_mu_dl = NULL;
TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
void (*sym_Esys_Free)(void *ptr) = NULL;
TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL;
TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL;
TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL;
TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL;
int dlopen_tpm2(void) {
int r, k = 0;
if (!libtss2_esys_dl) {
_cleanup_(dlclosep) void *dl = NULL;
dl = dlopen("libtss2-esys.so.0", RTLD_LAZY);
if (!dl)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support is not installed: %s", dlerror());
r = dlsym_many_and_warn(
dl,
LOG_DEBUG,
DLSYM_ARG(Esys_Create),
DLSYM_ARG(Esys_CreatePrimary),
DLSYM_ARG(Esys_Finalize),
DLSYM_ARG(Esys_FlushContext),
DLSYM_ARG(Esys_Free),
DLSYM_ARG(Esys_GetRandom),
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyPCR),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
DLSYM_ARG(Esys_Unseal),
NULL);
if (r < 0)
return r;
libtss2_esys_dl = TAKE_PTR(dl);
k++;
}
if (!libtss2_rc_dl) {
_cleanup_(dlclosep) void *dl = NULL;
dl = dlopen("libtss2-rc.so.0", RTLD_LAZY);
if (!dl)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support is not installed: %s", dlerror());
r = dlsym_many_and_warn(
dl,
LOG_DEBUG,
DLSYM_ARG(Tss2_RC_Decode),
NULL);
if (r < 0)
return r;
libtss2_rc_dl = TAKE_PTR(dl);
k++;
}
if (!libtss2_mu_dl) {
_cleanup_(dlclosep) void *dl = NULL;
dl = dlopen("libtss2-mu.so.0", RTLD_LAZY);
if (!dl)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support is not installed: %s", dlerror());
r = dlsym_many_and_warn(
dl,
LOG_DEBUG,
DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal),
DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal),
DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal),
NULL);
if (r < 0)
return r;
libtss2_mu_dl = TAKE_PTR(dl);
k++;
}
return k;
}
struct tpm2_context {
ESYS_CONTEXT *esys_context;
void *tcti_dl;
TSS2_TCTI_CONTEXT *tcti_context;
};
static void tpm2_context_destroy(struct tpm2_context *c) {
assert(c);
if (c->esys_context)
sym_Esys_Finalize(&c->esys_context);
c->tcti_context = mfree(c->tcti_context);
if (c->tcti_dl) {
dlclose(c->tcti_dl);
c->tcti_dl = NULL;
}
}
static inline void Esys_Finalize_wrapper(ESYS_CONTEXT **c) {
/* A wrapper around Esys_Finalize() for use with _cleanup_(). Only reasons we need this wrapper is
* because the function itself warn logs if we'd pass a pointer to NULL, and we don't want that. */
if (*c)
sym_Esys_Finalize(c);
}
static inline void Esys_Freep(void *p) {
if (*(void**) p)
sym_Esys_Free(*(void**) p);
}
static ESYS_TR flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) {
TSS2_RC rc;
if (!c || handle == ESYS_TR_NONE)
return ESYS_TR_NONE;
rc = sym_Esys_FlushContext(c, handle);
if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called
* in error paths, where we cannot do anything about failures anymore. And
* when it is called in successful codepaths by this time we already did
* what we wanted to do, and got the results we wanted so there's no
* reason to make this fail more loudly than necessary. */
log_debug("Failed to get flush context of TPM, ignoring: %s", sym_Tss2_RC_Decode(rc));
return ESYS_TR_NONE;
}
static int tpm2_init(const char *device, struct tpm2_context *ret) {
_cleanup_(Esys_Finalize_wrapper) ESYS_CONTEXT *c = NULL;
_cleanup_free_ TSS2_TCTI_CONTEXT *tcti = NULL;
_cleanup_(dlclosep) void *dl = NULL;
TSS2_RC rc;
int r;
r = dlopen_tpm2();
if (r < 0)
return log_error_errno(r, "TPM2 support not installed: %m");
if (!device)
device = secure_getenv("SYSTEMD_TPM2_DEVICE");
if (device) {
const char *param, *driver, *fn;
const TSS2_TCTI_INFO* info;
TSS2_TCTI_INFO_FUNC func;
size_t sz = 0;
param = strchr(device, ':');
if (param) {
driver = strndupa(device, param - device);
param++;
} else {
driver = "device";
param = device;
}
fn = strjoina("libtss2-tcti-", driver, ".so.0");
dl = dlopen(fn, RTLD_NOW);
if (!dl)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror());
func = dlsym(dl, TSS2_TCTI_INFO_SYMBOL);
if (!func)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to find TCTI info symbol " TSS2_TCTI_INFO_SYMBOL ": %s",
dlerror());
info = func();
if (!info)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to get TCTI info data.");
log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version);
rc = info->init(NULL, &sz, NULL);
if (rc != TPM2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc));
tcti = malloc0(sz);
if (!tcti)
return log_oom();
rc = info->init(tcti, &sz, device);
if (rc != TPM2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc));
}
rc = sym_Esys_Initialize(&c, tcti, NULL);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to initialize TPM context: %s", sym_Tss2_RC_Decode(rc));
rc = sym_Esys_Startup(c, TPM2_SU_CLEAR);
if (rc == TPM2_RC_INITIALIZE)
log_debug("TPM already started up.");
else if (rc == TSS2_RC_SUCCESS)
log_debug("TPM successfully started up.");
else
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc));
*ret = (struct tpm2_context) {
.esys_context = TAKE_PTR(c),
.tcti_context = TAKE_PTR(tcti),
.tcti_dl = TAKE_PTR(dl),
};
return 0;
}
static int tpm2_credit_random(ESYS_CONTEXT *c) {
size_t rps, done = 0;
TSS2_RC rc;
int r;
assert(c);
/* Pulls some entropy from the TPM and adds it into the kernel RNG pool. That way we can say that the
* key we will ultimately generate with the kernel random pool is at least as good as the TPM's RNG,
* but likely better. Note that we don't trust the TPM RNG very much, hence do not actually credit
* any entropy. */
for (rps = random_pool_size(); rps > 0;) {
_cleanup_(Esys_Freep) TPM2B_DIGEST *buffer = NULL;
rc = sym_Esys_GetRandom(
c,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
MIN(rps, 32U), /* 32 is supposedly a safe choice, given that AES 256bit keys are this long, and TPM2 baseline requires support for those. */
&buffer);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to acquire entropy from TPM: %s", sym_Tss2_RC_Decode(rc));
if (buffer->size == 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Zero-sized entropy returned from TPM.");
r = random_write_entropy(-1, buffer->buffer, buffer->size, false);
if (r < 0)
return log_error_errno(r, "Failed wo write entropy to kernel: %m");
done += buffer->size;
rps = LESS_BY(rps, buffer->size);
}
log_debug("Added %zu bytes of entropy to the kernel random pool.", done);
return 0;
}
static int tpm2_make_primary(
ESYS_CONTEXT *c,
ESYS_TR *ret_primary) {
static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
static const TPM2B_PUBLIC primary_template = {
.size = sizeof(TPMT_PUBLIC),
.publicArea = {
.type = TPM2_ALG_ECC,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
.parameters = {
.eccDetail = {
.symmetric = {
.algorithm = TPM2_ALG_AES,
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
},
.scheme.scheme = TPM2_ALG_NULL,
.curveID = TPM2_ECC_NIST_P256,
.kdf.scheme = TPM2_ALG_NULL,
},
},
},
};
static const TPML_PCR_SELECTION creation_pcr = {};
ESYS_TR primary = ESYS_TR_NONE;
TSS2_RC rc;
log_debug("Creating primary key on TPM.");
rc = sym_Esys_CreatePrimary(
c,
ESYS_TR_RH_OWNER,
ESYS_TR_PASSWORD,
ESYS_TR_NONE,
ESYS_TR_NONE,
&primary_sensitive,
&primary_template,
NULL,
&creation_pcr,
&primary,
NULL,
NULL,
NULL,
NULL);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to generate primary key in TPM: %s", sym_Tss2_RC_Decode(rc));
log_debug("Successfully created primary key on TPM.");
*ret_primary = primary;
return 0;
}
static int tpm2_make_pcr_session(
ESYS_CONTEXT *c,
uint32_t pcr_mask,
ESYS_TR *ret_session,
TPM2B_DIGEST **ret_policy_digest) {
static const TPMT_SYM_DEF symmetric = {
.algorithm = TPM2_ALG_AES,
.keyBits = {
.aes = 128
},
.mode = {
.aes = TPM2_ALG_CFB,
}
};
TPML_PCR_SELECTION pcr_selection = {
.count = 1,
.pcrSelections[0].hash = TPM2_ALG_SHA256,
.pcrSelections[0].sizeofSelect = 3,
.pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF,
.pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF,
.pcrSelections[0].pcrSelect[2] = (pcr_mask >> 16) & 0xFF,
};
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
ESYS_TR session = ESYS_TR_NONE;
TSS2_RC rc;
int r;
assert(c);
log_debug("Starting authentication session.");
rc = sym_Esys_StartAuthSession(
c,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
NULL,
TPM2_SE_POLICY,
&symmetric,
TPM2_ALG_SHA256,
&session);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
log_debug("Configuring PCR policy.");
rc = sym_Esys_PolicyPCR(
c,
session,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
NULL,
&pcr_selection);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
if (DEBUG_LOGGING || ret_policy_digest) {
log_debug("Acquiring policy digest.");
rc = sym_Esys_PolicyGetDigest(
c,
session,
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
&policy_digest);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
if (DEBUG_LOGGING) {
_cleanup_free_ char *h = NULL;
h = hexmem(policy_digest->buffer, policy_digest->size);
if (!h) {
r = log_oom();
goto finish;
}
log_debug("Session policy digest: %s", h);
}
}
if (ret_session) {
*ret_session = session;
session = ESYS_TR_NONE;
}
if (ret_policy_digest)
*ret_policy_digest = TAKE_PTR(policy_digest);
r = 0;
finish:
session = flush_context_verbose(c, session);
return r;
}
int tpm2_seal(
const char *device,
uint32_t pcr_mask,
void **ret_secret,
size_t *ret_secret_size,
void **ret_blob,
size_t *ret_blob_size,
void **ret_pcr_hash,
size_t *ret_pcr_hash_size) {
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
static const TPML_PCR_SELECTION creation_pcr = {};
_cleanup_(erase_and_freep) void *secret = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
TPM2B_SENSITIVE_CREATE hmac_sensitive;
ESYS_TR primary = ESYS_TR_NONE;
TPM2B_PUBLIC hmac_template;
size_t k, blob_size;
usec_t start;
TSS2_RC rc;
int r;
assert(ret_secret);
assert(ret_secret_size);
assert(ret_blob);
assert(ret_blob_size);
assert(ret_pcr_hash);
assert(ret_pcr_hash_size);
assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
* generate a "primary" key pair derived from that (RSA). Given the seed remains fixed this will
* result in the same key pair whenever we specify the exact same parameters for it. We then create a
* PCR-bound policy session, which calculates a hash on the current PCR values of the indexes we
* specify. We then generate a randomized key on the host (which is the key we actually enroll in the
* LUKS2 keyslots), which we upload into the TPM2, where it is encrypted with the "primary" key,
* taking the PCR policy session into account. We then download the encrypted key from the TPM2
* ("sealing") and marshall it into binary form, which is ultimately placed in the LUKS2 JSON header.
*
* The TPM2 "seed" key and "primary" keys never leave the TPM2 chip (and cannot be extracted at
* all). The random key we enroll in LUKS2 we generate on the host using the Linux random device. It
* is stored in the LUKS2 JSON only in encrypted form with the "primary" key of the TPM2 chip, thus
* binding the unlocking to the TPM2 chip. */
start = now(CLOCK_MONOTONIC);
r = tpm2_init(device, &c);
if (r < 0)
return r;
r = tpm2_make_primary(c.esys_context, &primary);
if (r < 0)
return r;
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, NULL, &policy_digest);
if (r < 0)
goto finish;
/* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the
* LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it
* because it's a key type that is universally supported and suitable for symmetric binary blobs. */
hmac_template = (TPM2B_PUBLIC) {
.size = sizeof(TPMT_PUBLIC),
.publicArea = {
.type = TPM2_ALG_KEYEDHASH,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT,
.parameters = {
.keyedHashDetail = {
.scheme.scheme = TPM2_ALG_NULL,
},
},
.unique = {
.keyedHash = {
.size = 32,
},
},
.authPolicy = *policy_digest,
},
};
hmac_sensitive = (TPM2B_SENSITIVE_CREATE) {
.size = sizeof(hmac_sensitive.sensitive),
.sensitive.data.size = 32,
};
assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size);
(void) tpm2_credit_random(c.esys_context);
log_debug("Generating secret key data.");
r = genuine_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size, RANDOM_BLOCK);
if (r < 0) {
log_error_errno(r, "Failed to generate secret key: %m");
goto finish;
}
log_debug("Creating HMAC key.");
rc = sym_Esys_Create(
c.esys_context,
primary,
ESYS_TR_PASSWORD,
ESYS_TR_NONE,
ESYS_TR_NONE,
&hmac_sensitive,
&hmac_template,
NULL,
&creation_pcr,
&private,
&public,
NULL,
NULL,
NULL);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
explicit_bzero_safe(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size);
if (!secret) {
r = log_oom();
goto finish;
}
log_debug("Marshalling private and public part of HMAC key.");
k = ALIGN8(sizeof(*private)) + ALIGN8(sizeof(*public)); /* Some roughly sensible start value */
for (;;) {
_cleanup_free_ void *buf = NULL;
size_t offset = 0;
buf = malloc(k);
if (!buf) {
r = log_oom();
goto finish;
}
rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, buf, k, &offset);
if (rc == TSS2_RC_SUCCESS) {
rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, buf, k, &offset);
if (rc == TSS2_RC_SUCCESS) {
blob = TAKE_PTR(buf);
blob_size = offset;
break;
}
}
if (rc != TSS2_MU_RC_INSUFFICIENT_BUFFER) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to marshal private/public key: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
if (k > SIZE_MAX / 2) {
r = log_oom();
goto finish;
}
k *= 2;
}
hash = memdup(policy_digest->buffer, policy_digest->size);
if (!hash)
return log_oom();
if (DEBUG_LOGGING) {
char buf[FORMAT_TIMESPAN_MAX];
log_debug("Completed TPM2 key sealing in %s.", format_timespan(buf, sizeof(buf), now(CLOCK_MONOTONIC) - start, 1));
}
*ret_secret = TAKE_PTR(secret);
*ret_secret_size = hmac_sensitive.sensitive.data.size;
*ret_blob = TAKE_PTR(blob);
*ret_blob_size = blob_size;
*ret_pcr_hash = TAKE_PTR(hash);
*ret_pcr_hash_size = policy_digest->size;
r = 0;
finish:
primary = flush_context_verbose(c.esys_context, primary);
return r;
}
int tpm2_unseal(
const char *device,
uint32_t pcr_mask,
const void *blob,
size_t blob_size,
const void *known_policy_hash,
size_t known_policy_hash_size,
void **ret_secret,
size_t *ret_secret_size) {
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE, hmac_key = ESYS_TR_NONE;
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
_cleanup_(erase_and_freep) char *secret = NULL;
TPM2B_PRIVATE private = {};
TPM2B_PUBLIC public = {};
size_t offset = 0;
TSS2_RC rc;
usec_t start;
int r;
assert(blob);
assert(blob_size > 0);
assert(known_policy_hash_size == 0 || known_policy_hash);
assert(ret_secret);
assert(ret_secret_size);
assert(pcr_mask < (UINT32_C(1) << TPM2_PCRS_MAX)); /* Support 24 PCR banks */
/* So here's what we do here: We connect to the TPM2 chip. As we do when sealing we generate a
* "primary" key on the TPM2 chip, with the same parameters as well as a PCR-bound policy
* session. Given we pass the same parameters, this will result in the same "primary" key, and same
* policy hash (the latter of course, only if the PCR values didn't change in between). We unmarshal
* the encrypted key we stored in the LUKS2 JSON token header and upload it into the TPM2, where it
* is decrypted if the seed and the PCR policy were right ("unsealing"). We then download the result,
* and use it to unlock the LUKS2 volume. */
start = now(CLOCK_MONOTONIC);
log_debug("Unmarshalling private part of HMAC key.");
rc = sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal(blob, blob_size, &offset, &private);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unmarshal private key: %s", sym_Tss2_RC_Decode(rc));
log_debug("Unmarshalling public part of HMAC key.");
rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(blob, blob_size, &offset, &public);
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc));
r = tpm2_init(device, &c);
if (r < 0)
return r;
r = tpm2_make_pcr_session(c.esys_context, pcr_mask, &session, &policy_digest);
if (r < 0)
goto finish;
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
* wait until the TPM2 tells us to go away. */
if (known_policy_hash_size > 0 &&
memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Current policy digest does not match stored policy digest, cancelling TPM2 authentication attempt.");
r = tpm2_make_primary(c.esys_context, &primary);
if (r < 0)
return r;
log_debug("Loading HMAC key into TPM.");
rc = sym_Esys_Load(
c.esys_context,
primary,
ESYS_TR_PASSWORD,
ESYS_TR_NONE,
ESYS_TR_NONE,
&private,
&public,
&hmac_key);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to load HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
log_debug("Unsealing HMAC key.");
rc = sym_Esys_Unseal(
c.esys_context,
hmac_key,
session,
ESYS_TR_NONE,
ESYS_TR_NONE,
&unsealed);
if (rc != TSS2_RC_SUCCESS) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
goto finish;
}
secret = memdup(unsealed->buffer, unsealed->size);
explicit_bzero_safe(unsealed->buffer, unsealed->size);
if (!secret) {
r = log_oom();
goto finish;
}
if (DEBUG_LOGGING) {
char buf[FORMAT_TIMESPAN_MAX];
log_debug("Completed TPM2 key unsealing in %s.", format_timespan(buf, sizeof(buf), now(CLOCK_MONOTONIC) - start, 1));
}
*ret_secret = TAKE_PTR(secret);
*ret_secret_size = unsealed->size;
r = 0;
finish:
primary = flush_context_verbose(c.esys_context, primary);
session = flush_context_verbose(c.esys_context, session);
hmac_key = flush_context_verbose(c.esys_context, hmac_key);
return r;
}
#endif
int tpm2_list_devices(void) {
#if HAVE_TPM2
_cleanup_(table_unrefp) Table *t = NULL;
_cleanup_(closedirp) DIR *d = NULL;
int r;
r = dlopen_tpm2();
if (r < 0)
return log_error_errno(r, "TPM2 support is not installed.");
t = table_new("path", "device", "driver");
if (!t)
return log_oom();
d = opendir("/sys/class/tpmrm");
if (!d) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open /sys/class/tpmrm: %m");
if (errno != ENOENT)
return -errno;
} else {
for (;;) {
_cleanup_free_ char *device_path = NULL, *device = NULL, *driver_path = NULL, *driver = NULL, *node = NULL;
struct dirent *de;
de = readdir_no_dot(d);
if (!de)
break;
device_path = path_join("/sys/class/tpmrm", de->d_name, "device");
if (!device_path)
return log_oom();
r = readlink_malloc(device_path, &device);
if (r < 0)
log_debug_errno(r, "Failed to read device symlink %s, ignoring: %m", device_path);
else {
driver_path = path_join(device_path, "driver");
if (!driver_path)
return log_oom();
r = readlink_malloc(driver_path, &driver);
if (r < 0)
log_debug_errno(r, "Failed to read driver symlink %s, ignoring: %m", driver_path);
}
node = path_join("/dev", de->d_name);
if (!node)
return log_oom();
r = table_add_many(
t,
TABLE_PATH, node,
TABLE_STRING, device ? last_path_component(device) : NULL,
TABLE_STRING, driver ? last_path_component(driver) : NULL);
if (r < 0)
return table_log_add_error(r);
}
}
if (table_get_rows(t) <= 1) {
log_info("No suitable TPM2 devices found.");
return 0;
}
r = table_print(t, stdout);
if (r < 0)
return log_error_errno(r, "Failed to show device table: %m");
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 not supported on this build.");
#endif
}
int tpm2_find_device_auto(
int log_level, /* log level when no device is found */
char **ret) {
#if HAVE_TPM2
_cleanup_(closedirp) DIR *d = NULL;
int r;
r = dlopen_tpm2();
if (r < 0)
return log_error_errno(r, "TPM2 support is not installed.");
d = opendir("/sys/class/tpmrm");
if (!d) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
"Failed to open /sys/class/tpmrm: %m");
if (errno != ENOENT)
return -errno;
} else {
_cleanup_free_ char *node = NULL;
for (;;) {
struct dirent *de;
de = readdir_no_dot(d);
if (!de)
break;
if (node)
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
"More than one TPM2 (tpmrm) device found.");
node = path_join("/dev", de->d_name);
if (!node)
return log_oom();
}
if (node) {
*ret = TAKE_PTR(node);
return 0;
}
}
return log_full_errno(log_level, SYNTHETIC_ERRNO(ENODEV), "No TPM2 (tpmrm) device found.");
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 not supported on this build.");
#endif
}
int tpm2_parse_pcrs(const char *s, uint32_t *ret) {
const char *p = s;
uint32_t mask = 0;
int r;
/* Parses a comma-separated list of PCR indexes */
for (;;) {
_cleanup_free_ char *pcr = NULL;
unsigned n;
r = extract_first_word(&p, &pcr, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r == 0)
break;
if (r < 0)
return log_error_errno(r, "Failed to parse PCR list: %s", s);
r = safe_atou(pcr, &n);
if (r < 0)
return log_error_errno(r, "Failed to parse PCR number: %s", pcr);
if (n >= TPM2_PCRS_MAX)
return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
"PCR number out of range (valid range 0…23): %u", n);
mask |= UINT32_C(1) << n;
}
*ret = mask;
return 0;
}
int tpm2_make_luks2_json(
int keyslot,
uint32_t pcr_mask,
const void *blob,
size_t blob_size,
const void *policy_hash,
size_t policy_hash_size,
JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
JsonVariant* pcr_array[TPM2_PCRS_MAX];
unsigned n_pcrs = 0;
int r;
assert(blob || blob_size == 0);
assert(policy_hash || policy_hash_size == 0);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM;
for (unsigned i = 0; i < ELEMENTSOF(pcr_array); i++) {
if ((pcr_mask & (UINT32_C(1) << i)) == 0)
continue;
r = json_variant_new_integer(pcr_array + n_pcrs, i);
if (r < 0) {
json_variant_unref_many(pcr_array, n_pcrs);
return -ENOMEM;
}
n_pcrs++;
}
r = json_variant_new_array(&a, pcr_array, n_pcrs);
json_variant_unref_many(pcr_array, n_pcrs);
if (r < 0)
return -ENOMEM;
r = json_build(&v,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-tpm2")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)),
JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)),
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size))));
if (r < 0)
return r;
if (ret)
*ret = TAKE_PTR(v);
return keyslot;
}

51
src/shared/tpm2-util.h Normal file
View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "json.h"
#include "macro.h"
#if HAVE_TPM2
#include <tss2/tss2_esys.h>
#include <tss2/tss2_mu.h>
#include <tss2/tss2_rc.h>
extern TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket);
extern TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket);
extern void (*sym_Esys_Finalize)(ESYS_CONTEXT **context);
extern TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle);
extern void (*sym_Esys_Free)(void *ptr);
extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes);
extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest);
extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs);
extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle);
extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType);
extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData);
extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc);
extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset);
extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest);
extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset);
extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest);
int dlopen_tpm2(void);
int tpm2_seal(const char *device, uint32_t pcr_mask, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size);
int tpm2_unseal(const char *device, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size);
#endif
int tpm2_list_devices(void);
int tpm2_find_device_auto(int log_level, char **ret);
int tpm2_parse_pcrs(const char *s, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret);
#define TPM2_PCRS_MAX 24
/* Default to PCR 7 only */
#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)

View File

@ -212,6 +212,10 @@ tests += [
[],
[]],
[['src/test/test-modhex.c'],
[],
[]],
[['src/test/test-libmount.c'],
[],
[threads,

View File

@ -5,11 +5,13 @@
#include "cryptsetup-util.h"
#include "idn-util.h"
#include "libfido2-util.h"
#include "macro.h"
#include "main-func.h"
#include "pwquality-util.h"
#include "qrcode-util.h"
#include "tests.h"
#include "tpm2-util.h"
static int run(int argc, char **argv) {
test_setup_logging(LOG_DEBUG);
@ -34,6 +36,14 @@ static int run(int argc, char **argv) {
assert_se(dlopen_qrencode() >= 0);
#endif
#if HAVE_TPM2
assert_se(dlopen_tpm2() >= 0);
#endif
#if HAVE_LIBFIDO2
assert_se(dlopen_libfido2() >= 0);
#endif
return 0;
}

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "modhex.h"
#include "recovery-key.h"
#include "alloc-util.h"
#include "string-util.h"

View File

@ -697,7 +697,7 @@ install_missing_libraries() {
# A number of dependencies is now optional via dlopen, so the install
# script will not pick them up, since it looks at linkage.
for lib in libcryptsetup libidn libidn2 pwquality libqrencode; do
for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu libfido2; do
if pkg-config --exists ${lib}; then
path=$(pkg-config --variable=libdir ${lib})
if ! [[ ${lib} =~ ^lib ]]; then