mirror of
https://github.com/systemd/systemd.git
synced 2025-02-24 17:57:34 +03:00
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:
commit
5cd35a171c
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
35
TODO
@ -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
|
||||
|
281
man/crypttab.xml
281
man/crypttab.xml
@ -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
10
man/fido2-crypttab.sh
Normal 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'
|
@ -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>
|
||||
|
||||
|
@ -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
284
man/systemd-cryptenroll.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
10
man/tpm2-crypttab.sh
Normal 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'
|
@ -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'
|
||||
|
69
meson.build
69
meson.build
@ -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'],
|
||||
|
@ -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'],
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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];
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) \
|
||||
|
88
src/cryptenroll/cryptenroll-fido2.c
Normal file
88
src/cryptenroll/cryptenroll-fido2.c
Normal 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;
|
||||
}
|
16
src/cryptenroll/cryptenroll-fido2.h
Normal file
16
src/cryptenroll/cryptenroll-fido2.h
Normal 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
|
126
src/cryptenroll/cryptenroll-list.c
Normal file
126
src/cryptenroll/cryptenroll-list.c
Normal 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;
|
||||
}
|
6
src/cryptenroll/cryptenroll-list.h
Normal file
6
src/cryptenroll/cryptenroll-list.h
Normal 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);
|
105
src/cryptenroll/cryptenroll-password.c
Normal file
105
src/cryptenroll/cryptenroll-password.c
Normal 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;
|
||||
}
|
8
src/cryptenroll/cryptenroll-password.h
Normal file
8
src/cryptenroll/cryptenroll-password.h
Normal 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);
|
99
src/cryptenroll/cryptenroll-pkcs11.c
Normal file
99
src/cryptenroll/cryptenroll-pkcs11.c
Normal 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;
|
||||
}
|
16
src/cryptenroll/cryptenroll-pkcs11.h
Normal file
16
src/cryptenroll/cryptenroll-pkcs11.h
Normal 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
|
101
src/cryptenroll/cryptenroll-recovery.c
Normal file
101
src/cryptenroll/cryptenroll-recovery.c
Normal 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;
|
||||
}
|
8
src/cryptenroll/cryptenroll-recovery.h
Normal file
8
src/cryptenroll/cryptenroll-recovery.h
Normal 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);
|
131
src/cryptenroll/cryptenroll-tpm2.c
Normal file
131
src/cryptenroll/cryptenroll-tpm2.c
Normal 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;
|
||||
}
|
16
src/cryptenroll/cryptenroll-tpm2.h
Normal file
16
src/cryptenroll/cryptenroll-tpm2.h
Normal 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
|
445
src/cryptenroll/cryptenroll-wipe.c
Normal file
445
src/cryptenroll/cryptenroll-wipe.c
Normal 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;
|
||||
}
|
12
src/cryptenroll/cryptenroll-wipe.h
Normal file
12
src/cryptenroll/cryptenroll-wipe.h
Normal 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);
|
517
src/cryptenroll/cryptenroll.c
Normal file
517
src/cryptenroll/cryptenroll.c
Normal 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);
|
26
src/cryptenroll/cryptenroll.h
Normal file
26
src/cryptenroll/cryptenroll.h
Normal 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);
|
190
src/cryptsetup/cryptsetup-fido2.c
Normal file
190
src/cryptsetup/cryptsetup-fido2.c
Normal 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;
|
||||
}
|
71
src/cryptsetup/cryptsetup-fido2.h
Normal file
71
src/cryptsetup/cryptsetup-fido2.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
168
src/cryptsetup/cryptsetup-tpm2.c
Normal file
168
src/cryptsetup/cryptsetup-tpm2.c
Normal 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;
|
||||
}
|
74
src/cryptsetup/cryptsetup-tpm2.h
Normal file
74
src/cryptsetup/cryptsetup-tpm2.h
Normal 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
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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'],
|
||||
[],
|
||||
[]],
|
||||
]
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
874
src/shared/libfido2-util.c
Normal 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
104
src/shared/libfido2-util.h
Normal 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);
|
@ -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
76
src/shared/openssl-util.c
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
998
src/shared/tpm2-util.c
Normal 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
51
src/shared/tpm2-util.h
Normal 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)
|
@ -212,6 +212,10 @@ tests += [
|
||||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-modhex.c'],
|
||||
[],
|
||||
[]],
|
||||
|
||||
[['src/test/test-libmount.c'],
|
||||
[],
|
||||
[threads,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user