Compare commits

...

25 Commits

Author SHA1 Message Date
Zbigniew Jędrzejewski-Szmek 071be2fa9f
Merge pull request #15442 from poettering/fido2
add fido2 authentication support to homed
2020-07-03 17:27:15 +02:00
gzjsgdsb 33d943d168 initialize arg_clock_usec 2020-07-03 14:52:20 +02:00
Zbigniew Jędrzejewski-Szmek 9e99bfbd5a
Merge pull request #16348 from yuwata/news-sriov
NEWS: two updates about networkd
2020-07-03 12:13:14 +02:00
Yu Watanabe 71af436c63 NEWS: mention .network [SR-IOV] section 2020-07-03 17:58:41 +09:00
Yu Watanabe 554a97ba23 NEWS: WithoutRA= is not a boolean option anymore 2020-07-03 15:25:28 +09:00
Lennart Poettering c8fe23d45c update TODO 2020-07-01 11:20:26 +02:00
Lennart Poettering 4442c26942 man: update homectl man page with documentation for new features 2020-07-01 11:20:26 +02:00
Lennart Poettering fe2520fbb5 docs: document new FIDO2 user record fields 2020-07-01 11:20:26 +02:00
Lennart Poettering 0eb3be4644 homectl: add --pkcs11-uri=auto and --pkcs-11-uri=list support
We have the same for FIDO2 devices, for listing suitable devices, or
picking the right one automatically, let's add that for PKCS11 too.
2020-07-01 11:20:26 +02:00
Lennart Poettering 2af3966af3 homectl: add acquired fido2 PIN to user record
If we successfully acquired the PIN for the fido2 key, let's add it to
our user record, so that we can pass it to homed, which will need it
too.
2020-07-01 11:20:26 +02:00
Lennart Poettering 93295a2501 homectl: split out pkcs#11 related code bits into own .c/.h file
There's some highly specific PKCS#11 code in homectl.c. Let's split that
out, since it is easily isolatable, to make homectl.c a bit more
readable.

No funcional changes, just some moving around and renaming two functions
to make them more suitably named when exported.
2020-07-01 11:20:25 +02:00
Lennart Poettering c98811d837 homectl: rework how we log when doing a home directory update
When updating a home directory we might update the record first, then
resize the image and finally synchronize the passwords to the storage
layers. These are three individually authenticated operations. Since
each might require touching a FIDO2 or PKCS#11 key we should say what we
are doing. Hence do so.

Usually we are pretty quiet with what we do, and let's stick to that.
Hence show this information only if we actually do more than one thing.
If we only update (and do not resize/sync passwords) then let's be quiet
as usual, as the command line then sufficiently clarifies what we are
doing.
2020-07-01 11:17:28 +02:00
Lennart Poettering 8e62dfb12a homectl: do generic error handling/retry also when creating a home directory
After all, when creating we might need interaction with the security
token too, and our initial attempt to create the user will fail, since
we do not allow interactive auth on the security token, so that we then
can print a log message and retry with interactive auth then enabled.
2020-07-01 11:17:28 +02:00
Lennart Poettering f737186ab1 homectl: show touch emoji when asking for PKCS#11 protected auth path 2020-07-01 11:17:28 +02:00
Lennart Poettering 85b1294488 pam-systemd-home: print helpful message when token's PIN is locked 2020-07-01 11:17:28 +02:00
Lennart Poettering 7b78db28e5 homed: add support for authenticating with fido2 hmac-secret tokens 2020-07-01 11:17:28 +02:00
Lennart Poettering 1c0c4a43c6 homectl: add support for enrolling FIDO2 HMAC-SECRET tokens 2020-07-01 11:17:28 +02:00
Lennart Poettering 5e4fa456fa user-record: add fields for FIDO2 HMAC authentication options 2020-07-01 11:17:28 +02:00
Lennart Poettering af4fbd463f build-sys: add libfido2 as optional dependency 2020-07-01 11:17:28 +02:00
Lennart Poettering c0bde0d240 user-record: rename JSON field "pkcs11Pin" to "tokenPin"
We'd like to use it for FIDO2 tokens too, and the concept is entirely
generic, hence let's just reuse the field, but rename it. Read the old
name for compatibility, and treat the old name and the new name as
identical for most purposes.
2020-07-01 11:17:28 +02:00
Lennart Poettering d00f318323 user-record: securely erase pkcs#11 when assigned NULL too 2020-07-01 11:17:28 +02:00
Lennart Poettering 7b8d55b72c homectl: add missing log messages when json_variant_format() fails 2020-07-01 11:17:28 +02:00
Lennart Poettering c63ec11bf5 pkcs11-util: reduce scope of a variable 2020-07-01 11:17:28 +02:00
Lennart Poettering 1146b664e6 macro: add new FOREACH_POINTER() macro magic
This allows us to iterate through a series of specified pointers. It's a
bit like FOREACH_STRING(), but for all kinds of pointers.
2020-07-01 11:17:28 +02:00
Lennart Poettering 428d32afea locale-util: add support for touch emoji
We can use this to highlight when users are supposed to touch their
security tokens.
2020-07-01 11:17:27 +02:00
42 changed files with 2369 additions and 751 deletions

15
NEWS
View File

@ -147,6 +147,9 @@ CHANGES WITH 246 in spe:
* networkd.conf gained a new boolean setting ManageForeignRoutes=. If
enabled systemd-networkd manages all routes configured by other tools.
* .network files managed by systemd-networkd gained a new section
[SR-IOV], in order to configure SR-IOV capable network devices.
* systemd-networkd's [IPv6Prefix] section in .network files gained a
new boolean setting Assign=. If enabled an address from the prefix is
automatically assigned to the interface.
@ -178,12 +181,12 @@ CHANGES WITH 246 in spe:
traffic). DataBitRate=, DataSamplePoint=, FDMode=, FDNonISO= have
been added to configure various CAN-FD aspects.
* systemd-networkd's [DHCPv6] section gained a new boolean option
WithoutRA=. When enabled, DHCPv6 will be attempted right-away without
requiring an Router Advertisement packet suggesting it first
(i.e. without the 'M' or 'O' flags set). The [IPv6AcceptRA] section
gained a boolean option DHCPv6Client= that may be used to turn off
the DHCPv6 client even if the RA packets suggest it.
* systemd-networkd's [DHCPv6] section gained a new option WithoutRA=.
When enabled, DHCPv6 will be attempted right-away without requiring an
Router Advertisement packet suggesting it first (i.e. without the 'M'
or 'O' flags set). The [IPv6AcceptRA] section gained a boolean option
DHCPv6Client= that may be used to turn off the DHCPv6 client even if
the RA packets suggest it.
* systemd-networkd's [DHCPv4] section gained a new setting UseGateway=
which may be used to turn off use of the gateway information provided

4
TODO
View File

@ -150,8 +150,8 @@ 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/homed: also support FIDO2 HMAC password logic for unlocking
devices. (see: https://github.com/mjec/fido2-hmac-secret)
* 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

View File

@ -546,6 +546,11 @@ below). It's undefined how precise the URI is: during log-in it is tested
against all plugged in security tokens and if there's exactly one matching
private key found with it it is used.
`fido2HmacCredential` → An array of strings, each with a Base64-encoded FIDO2
credential ID that shell be used for authentication with FIDO2 devices that
implement the `hmac-secret` extension. The salt to pass to the FIDO2 device is
found in `fido2HmacSalt`.
`privileged` → An object, which contains the fields of the `privileged` section
of the user record, see below.
@ -594,7 +599,7 @@ as the lines in the traditional `~/.ssh/authorized_key` file.
`pkcs11EncryptedKey` → An array of objects. Each element of the array should be
an object consisting of three string fields: `uri` shall contain a PKCS#11
security token URI, `data` shall contain a Base64 encoded encrypted key and
security token URI, `data` shall contain a Base64-encoded encrypted key and
`hashedPassword` shall contain a UNIX password hash to test the key
against. Authenticating with a security token against this account shall work
as follows: the encrypted secret key is converted from its Base64
@ -602,13 +607,29 @@ representation into binary, then decrypted with the PKCS#11 `C_Decrypt()`
function of the PKCS#11 module referenced by the specified URI, using the
private key found on the same token. The resulting decrypted key is then
Base64-encoded and tested against the specified UNIX hashed password. The
Base64-enceded decrypted key may also be used to unlock further resources
Base64-encoded decrypted key may also be used to unlock further resources
during log-in, for example the LUKS or `fscrypt` storage backend. It is
generally recommended that for each entry in `pkcs11EncryptedKey` there's also
a matching one in `pkcs11TokenUri` and vice versa, with the same URI, appearing
in the same order, but this should not be required by applications processing
user records.
`fido2HmacSalt` → An array of objects, implementing authentication support with
FIDO2 devices that implement the `hmac-secret` extension. Each element of the
array should be an object consisting of three string fields: `credential`,
`salt`, `hashedPassword`. The first two shall contain Base64-encoded binary
data: the FIDO2 credential ID and the salt value to pass to the FIDO2
device. During authentication this salt along with the credential ID is sent to
the FIDO2 token, which will HMAC hash the salt with its internal secret key and
return the result. This resulting binary key should then be Base64-encoded and
used as string password for the further layers of the stack. The
`hashedPassword` field of the `fido2HmacSalt` field shall be a UNIX password
hash to test this derived secret key against for authentication. It is
generally recommended that for each entry in `fido2HmacSalt` there's also a
matching one in `fido2HmacCredential`, and vice versa, with the same credential
ID, appearing in the same order, but this should not be required by
applications processing user recrods.
## Fields in the `perMachine` section
As mentioned, the `perMachine` section contains settings that shall apply to
@ -652,13 +673,13 @@ that may be used in this section are identical to the equally named ones in the
`mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`,
`cifsUserName`, `cifsService`, `imagePath`, `uid`, `gid`, `memberOf`,
`fileSystemType`, `partitionUuid`, `luksUuid`, `fileSystemUuid`, `luksDiscard`,
`luksOfflineDiscard`, `luksOfflineDiscard`, `luksCipher`, `luksCipherMode`,
`luksVolumeKeySize`, `luksPbkdfHashAlgorithm`, `luksPbkdfType`,
`luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`,
`rateLimitIntervalUSec`, `rateLimitBurst`, `enforcePasswordPolicy`,
`autoLogin`, `stopDelayUSec`, `killProcesses`, `passwordChangeMinUSec`,
`passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`.
`luksOfflineDiscard`, `luksCipher`, `luksCipherMode`, `luksVolumeKeySize`,
`luksPbkdfHashAlgorithm`, `luksPbkdfType`, `luksPbkdfTimeCostUSec`,
`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `rateLimitIntervalUSec`,
`rateLimitBurst`, `enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`,
`killProcesses`, `passwordChangeMinUSec`, `passwordChangeMaxUSec`,
`passwordChangeWarnUSec`, `passwordChangeInactiveUSec`, `passwordChangeNow`,
`pkcs11TokenUri`, `fido2HmacCredential`.
## Fields in the `binding` section
@ -810,7 +831,7 @@ public key.
The `signature` field in the top-level user record object is an array of
objects. Each object encapsulates one signature and has two fields: `data` and
`key` (both are strings). The `data` field contains the actual signature,
encoded in base64, the `key` field contains a copy of the public key whose
encoded in Base64, the `key` field contains a copy of the public key whose
private key was used to make the signature, in PEM format. Currently only
signatures with Ed25519 keys are defined.
@ -864,13 +885,20 @@ The `secret` field of the top-level user record contains the following fields:
`password` → an array of strings, each containing a plain text password.
`pkcs11Pin` → an array of strings, each containing a plain text PIN, suitable
for unlocking PKCS#11 security tokens that require that.
`tokenPin` → an array of strings, each containing a plain text PIN, suitable
for unlocking security tokens that require that. (The field `pkcs11Pin` should
be considered a compatibility alias for this field, and merged with `tokenPin`
in case both are set.)
`pkcs11ProtectedAuthenticationPathPermitted` → a boolean. If set to true allows
the receiver to use the PKCS#11 "protected authentication path" (i.e. a
physical button/touch element on the security token) for authenticating the
user. If false or unset authentication this way shall not be attempted.
user. If false or unset, authentication this way shall not be attempted.
`fido2UserPresencePermitted` → a boolean. If set to true allows the receiver to
use the FIDO2 "user presence" flag. This is similar to the concept of
`pkcs11ProtectedAuthenticationPathPermitted`, but exposes the FIDO2 concept
behind it. If false or unset authentication this way shall not be attempted.
## Mapping to `struct passwd` and `struct spwd`

View File

@ -332,7 +332,49 @@
then generated, encrypted with the public key of the X.509 certificate, and stored as part of the
user record. At login time it is decrypted with the PKCS#11 module and then used to unlock the
account and associated resources. See below for an example how to set up authentication with security
token.</para></listitem>
token.</para>
<para>Instead of a valid PKCS#11 URI, the special strings <literal>list</literal> and
<literal>auto</literal> may be specified. If <literal>list</literal> is passed, a brief table of
suitable, currently plugged in PKCS#11 hardware tokens is shown, along with their URIs. If
<literal>auto</literal> is passed, a suitable PKCS#11 hardware token is automatically selected (this
operation will fail if there isn't exactly one suitable token discovered). The latter is a useful
shortcut for the most common case where a single PKCS#11 hardware token is plugged in.</para>
<para>Note that many hardware security tokens implement both PKCS#11/PIV and FIDO2 with the
<literal>hmac-secret</literal> extension (for example: the YubiKey 5 series), as supported with the
<option>--fido2-device=</option> option below. Both mechanisms are similarly powerful, though FIDO2
is the more modern technology. PKCS#11/PIV tokens have the benefit of being recognizable before
authentication and hence can be used for implying the user identity to use for logging in, which
FIDO2 does not allow. PKCS#11/PIV devices generally require initialization (i.e. storing a
private/public key pair on them, see example below) before they can be used; FIDO2 security tokens
generally do not required that, and work out of the box.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--fido2-device=</option><replaceable>PATH</replaceable></term>
<listitem><para>Takes a path to a Linux <literal>hidraw</literal> device
(e.g. <filename>/dev/hidraw1</filename>), referring to a FIDO2 security token implementing the
<literal>hmac-secret</literal> extension, that shall be able to unlock the user account. If used, a
random salt value is generated on the host, which is passed to the FIDO2 device, which calculates a
HMAC hash of it, keyed by its internal secret key. The result is then used as key for unlocking the
user account. The random salt is included in the user record, so that whenever authentication is
needed it can be passed again to the FIDO2 token, to retrieve the actual key.</para>
<para>Instead of a valid path to a FIDO2 <literal>hidraw</literal> device the special strings
<literal>list</literal> and <literal>auto</literal> may be specified. If <literal>list</literal> is
passed, a brief table of suitable discovered FIDO2 devices is shown. If <literal>auto</literal> is
passed, a suitable FIDO2 token is automatically selected, if exactly one is discovered. The latter is
a useful shortcut for the most common case where a single FIDO2 hardware token is plugged in.</para>
<para>Note that FIDO2 devices suitable for this option must implement the
<literal>hmac-secret</literal> extension. Most current devices (such as the YubiKey 5 series) do. If
the extension is not implemented the device cannot be used for unlocking home directories.</para>
<para>Note that many hardware security tokens implement both FIDO2 and PKCS#11/PIV (and thus may be
used with either <option>--fido2-device=</option> or <option>--pkcs11-token-uri=</option>), for a
discussion see above.</para></listitem>
</varlistentry>
<varlistentry>
@ -810,7 +852,7 @@
</example>
<example>
<title>Set up authentication with a YubiKey security token:</title>
<title>Set up authentication with a YubiKey security token using PKCS#11/PIV:</title>
<programlisting># Clear the Yubikey from any old keys (careful!)
ykman piv reset
@ -821,16 +863,18 @@ ykman piv generate-key -a RSA2048 9d pubkey.pem
# Create a self-signed certificate from this public key, and store it on the device.
ykman piv generate-certificate --subject "Knobelei" 9d pubkey.pem
# We don't need the publibc key on disk anymore
# We don't need the public key on disk anymore
rm 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
# Allow the security token to unlock the account of user 'lafcadio'.
homectl update lafcadio --pkcs11-token-uri=auto</programlisting>
</example>
# Allow the security token referenced by the determined PKCS#11 URI to unlock the account of user
# 'lafcadio'. (Replace the '…' by the URI from the clipboard.)
homectl update lafcadio --pkcs11-token-uri=…</programlisting>
<example>
<title>Set up authentication with a FIDO2 security token:</title>
<programlisting># Allow a FIDO2 security token to unlock the account of user 'nihilbaxter'.
homectl update nihilbaxter --fido2-device=auto</programlisting>
</example>
</refsect1>

View File

@ -1153,6 +1153,17 @@ else
endif
conf.set10('HAVE_P11KIT', have)
want_libfido2 = get_option('libfido2')
if want_libfido2 != 'false' and not skip_deps
libfido2 = dependency('libfido2',
required : want_libfido2 == 'true')
have = libfido2.found()
else
have = false
libfido2 = []
endif
conf.set10('HAVE_LIBFIDO2', have)
want_elfutils = get_option('elfutils')
if want_elfutils != 'false' and not skip_deps
libdw = dependency('libdw',
@ -2146,7 +2157,8 @@ if conf.get('ENABLE_HOMED') == 1
libcrypt,
libopenssl,
libfdisk,
libp11kit],
libp11kit,
libfido2],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
@ -2173,6 +2185,7 @@ if conf.get('ENABLE_HOMED') == 1
libcrypt,
libopenssl,
libp11kit,
libfido2,
libpwquality],
install_rpath : rootlibexecdir,
install : true,
@ -3575,6 +3588,7 @@ foreach tuple : [
['pwquality'],
['libfdisk'],
['p11kit'],
['libfido2'],
['AUDIT'],
['IMA'],
['AppArmor'],

View File

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

View File

@ -373,7 +373,8 @@ const char *special_glyph(SpecialGlyph code) {
[SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = ":-(",
[SPECIAL_GLYPH_UNHAPPY_SMILEY] = ":-{",
[SPECIAL_GLYPH_DEPRESSED_SMILEY] = ":-[",
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,"
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,",
[SPECIAL_GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */
},
/* UTF-8 */
@ -415,6 +416,9 @@ const char *special_glyph(SpecialGlyph code) {
/* This emoji is a single character cell glyph in Unicode, and three in ASCII */
[SPECIAL_GLYPH_LOCK_AND_KEY] = "\360\237\224\220", /* 🔐 (actually called: CLOSED LOCK WITH KEY) */
/* This emoji is a single character cell glyph in Unicode, and two in ASCII */
[SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* 👆 (actually called: BACKHAND INDEX POINTING UP */
},
};

View File

@ -65,7 +65,8 @@ typedef enum {
SPECIAL_GLYPH_UNHAPPY_SMILEY,
SPECIAL_GLYPH_DEPRESSED_SMILEY,
SPECIAL_GLYPH_LOCK_AND_KEY,
_SPECIAL_GLYPH_MAX
SPECIAL_GLYPH_TOUCH,
_SPECIAL_GLYPH_MAX,
} SpecialGlyph;
const char *special_glyph(SpecialGlyph code) _const_;

View File

@ -538,6 +538,12 @@ static inline int __coverity_check_and_return__(int condition) {
(y) = (_t); \
} while (false)
/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses (void*) -1 as internal marker for EOL. */
#define FOREACH_POINTER(p, x, ...) \
for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, (void*) -1 }; \
p != (typeof(p)) (void*) -1; \
p = *(++_l))
/* Define C11 thread_local attribute even on older gcc compiler
* version */
#ifndef thread_local

View File

@ -2320,6 +2320,7 @@ static void reset_arguments(void) {
arg_random_seed = mfree(arg_random_seed);
arg_random_seed_size = 0;
arg_clock_usec = 0;
}
static int parse_configuration(const struct rlimit *saved_rlimit_nofile,

539
src/home/homectl-fido2.c Normal file
View File

@ -0,0 +1,539 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_LIBFIDO2
#include <fido.h>
#endif
#include "ask-password-api.h"
#include "errno-util.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "libcrypt-util.h"
#include "locale-util.h"
#include "memory-util.h"
#include "random-util.h"
#include "strv.h"
#if HAVE_LIBFIDO2
static int add_fido2_credential_id(
JsonVariant **v,
const void *cid,
size_t cid_size) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *escaped = NULL;
int r;
assert(v);
assert(cid);
r = base64mem(cid, cid_size, &escaped);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m");
w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential"));
if (w) {
r = json_variant_strv(w, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse FIDO2 credential ID list: %m");
if (strv_contains(l, escaped))
return 0;
}
r = strv_extend(&l, escaped);
if (r < 0)
return log_oom();
w = json_variant_unref(w);
r = json_variant_new_array_strv(&w, l);
if (r < 0)
return log_error_errno(r, "Failed to create FIDO2 credential ID JSON: %m");
r = json_variant_set_field(v, "fido2HmacCredential", w);
if (r < 0)
return log_error_errno(r, "Failed to update FIDO2 credential ID: %m");
return 0;
}
static int add_fido2_salt(
JsonVariant **v,
const void *cid,
size_t cid_size,
const void *fido2_salt,
size_t fido2_salt_size,
const void *secret,
size_t secret_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *unix_salt = NULL;
struct crypt_data cd = {};
char *k;
int r;
r = make_salt(&unix_salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, unix_salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0)
return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
l = json_variant_ref(json_variant_by_key(w, "fido2HmacSalt"));
r = json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append FIDO2 salt: %m");
r = json_variant_set_field(&w, "fido2HmacSalt", l);
if (r < 0)
return log_error_errno(r, "Failed to set FDO2 salt: %m");
r = json_variant_set_field(v, "privileged", w);
if (r < 0)
return log_error_errno(r, "Failed to update privileged field: %m");
return 0;
}
#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;
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),
"userName field of user record is missing");
if (!json_variant_is_string(un))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"userName field of user record is not a string");
realm = json_variant_by_key(*v, "realm");
if (realm) {
if (!json_variant_is_string(realm))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"realm field of user record is not a string");
fido_un = strjoina(json_variant_string(un), json_variant_string(realm));
} else
fido_un = json_variant_string(un);
rn = json_variant_by_key(*v, "realName");
if (rn && !json_variant_is_string(rn))
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);
if (r < 0)
return r;
r = add_fido2_salt(v,
cid,
cid_size,
salt,
FIDO2_SALT_SIZE,
secret,
secret_size);
if (r < 0)
return r;
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
* fscrypt. */
r = identity_add_token_pin(v, used_pin);
if (r < 0)
return r;
return 0;
#else
return log_error_errno(EOPNOTSUPP, "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(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(EOPNOTSUPP, "FIDO2 tokens not supported on this build.");
#endif
}

10
src/home/homectl-fido2.h Normal file
View File

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "json.h"
int identity_add_fido2_parameters(JsonVariant **v, const char *device);
int list_fido2_devices(void);
int find_fido2_auto(char **ret);

480
src/home/homectl-pkcs11.c Normal file
View File

@ -0,0 +1,480 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "errno-util.h"
#include "format-table.h"
#include "hexdecoct.h"
#include "homectl-pkcs11.h"
#include "libcrypt-util.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "pkcs11-util.h"
#include "random-util.h"
#include "strv.h"
struct pkcs11_callback_data {
char *pin_used;
X509 *cert;
};
static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
erase_and_free(data->pin_used);
X509_free(data->cert);
}
#if HAVE_P11KIT
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(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(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,
const void *encrypted_key, size_t encrypted_key_size,
const void *decrypted_key, size_t decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
int r;
assert(v);
assert(uri);
assert(encrypted_key);
assert(encrypted_key_size > 0);
assert(decrypted_key);
assert(decrypted_key_size > 0);
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey"));
r = json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m");
r = json_variant_set_field(&w, "pkcs11EncryptedKey", l);
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %m");
r = json_variant_set_field(v, "privileged", w);
if (r < 0)
return log_error_errno(r, "Failed to update privileged field: %m");
return 0;
}
static int add_pkcs11_token_uri(JsonVariant **v, const char *uri) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
int r;
assert(v);
assert(uri);
w = json_variant_ref(json_variant_by_key(*v, "pkcs11TokenUri"));
if (w) {
r = json_variant_strv(w, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 token list: %m");
if (strv_contains(l, uri))
return 0;
}
r = strv_extend(&l, uri);
if (r < 0)
return log_oom();
w = json_variant_unref(w);
r = json_variant_new_array_strv(&w, l);
if (r < 0)
return log_error_errno(r, "Failed to create PKCS#11 token URI JSON: %m");
r = json_variant_set_field(v, "pkcs11TokenUri", w);
if (r < 0)
return log_error_errno(r, "Failed to update PKCS#11 token URI list: %m");
return 0;
}
int identity_add_token_pin(JsonVariant **v, const char *pin) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
_cleanup_(strv_free_erasep) char **pins = NULL;
int r;
assert(v);
if (isempty(pin))
return 0;
w = json_variant_ref(json_variant_by_key(*v, "secret"));
l = json_variant_ref(json_variant_by_key(w, "tokenPin"));
r = json_variant_strv(l, &pins);
if (r < 0)
return log_error_errno(r, "Failed to convert PIN array: %m");
if (strv_find(pins, pin))
return 0;
r = strv_extend(&pins, pin);
if (r < 0)
return log_oom();
strv_uniq(pins);
l = json_variant_unref(l);
r = json_variant_new_array_strv(&l, pins);
if (r < 0)
return log_error_errno(r, "Failed to allocate new PIN array JSON: %m");
json_variant_sensitive(l);
r = json_variant_set_field(&w, "tokenPin", l);
if (r < 0)
return log_error_errno(r, "Failed to update PIN field: %m");
r = json_variant_set_field(v, "secret", w);
if (r < 0)
return log_error_errno(r, "Failed to update secret object: %m");
return 1;
}
int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
_cleanup_(erase_and_freep) char *pin = NULL;
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);
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.");
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?");
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 = 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");
/* Add the token URI to the public part of the record. */
r = add_pkcs11_token_uri(v, uri);
if (r < 0)
return r;
/* Include the encrypted version of the random key we just generated in the privileged part of the record */
r = add_pkcs11_encrypted_key(
v,
uri,
encrypted_key, encrypted_key_size,
decrypted_key, decrypted_key_size);
if (r < 0)
return r;
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
* fscrypt. */
r = identity_add_token_pin(v, pin);
if (r < 0)
return r;
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(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(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
#endif
}

11
src/home/homectl-pkcs11.h Normal file
View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "json.h"
int identity_add_token_pin(JsonVariant **v, const char *pin);
int identity_add_pkcs11_key_data(JsonVariant **v, const char *token_uri);
int list_pkcs11_tokens(void);
int find_pkcs11_token_auto(char **ret);

View File

@ -4,7 +4,6 @@
#include "sd-bus.h"
#include "alloc-util.h"
#include "ask-password-api.h"
#include "bus-common-errors.h"
#include "bus-error.h"
@ -15,15 +14,12 @@
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "home-util.h"
#include "libcrypt-util.h"
#include "homectl-fido2.h"
#include "homectl-pkcs11.h"
#include "locale-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "openssl-util.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
@ -31,7 +27,6 @@
#include "pretty-print.h"
#include "process-util.h"
#include "pwquality-util.h"
#include "random-util.h"
#include "rlimit-util.h"
#include "spawn-polkit-agent.h"
#include "terminal-util.h"
@ -56,6 +51,7 @@ static char **arg_identity_filter_rlimits = NULL;
static uint64_t arg_disk_size = UINT64_MAX;
static uint64_t arg_disk_size_relative = UINT64_MAX;
static char **arg_pkcs11_token_uri = NULL;
static char **arg_fido2_device = NULL;
static bool arg_json = false;
static JsonFormatFlags arg_json_format_flags = 0;
static bool arg_and_resize = false;
@ -73,6 +69,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_rlimits, json_variant_unrefp);
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep);
static bool identity_properties_specified(void) {
return
@ -83,7 +80,8 @@ static bool identity_properties_specified(void) {
!json_variant_is_blank_object(arg_identity_extra_rlimits) ||
!strv_isempty(arg_identity_filter) ||
!strv_isempty(arg_identity_filter_rlimits) ||
!strv_isempty(arg_pkcs11_token_uri);
!strv_isempty(arg_pkcs11_token_uri) ||
!strv_isempty(arg_fido2_device);
}
static int acquire_bus(sd_bus **bus) {
@ -236,7 +234,7 @@ static int acquire_existing_password(const char *user_name, UserRecord *hr, bool
return 0;
}
static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
static int acquire_token_pin(const char *user_name, UserRecord *hr) {
_cleanup_(strv_free_erasep) char **pin = NULL;
_cleanup_free_ char *question = NULL;
char *e;
@ -247,9 +245,9 @@ static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
e = getenv("PIN");
if (e) {
r = user_record_set_pkcs11_pin(hr, STRV_MAKE(e), false);
r = user_record_set_token_pin(hr, STRV_MAKE(e), false);
if (r < 0)
return log_error_errno(r, "Failed to store PKCS#11 PIN: %m");
return log_error_errno(r, "Failed to store token PIN: %m");
string_erase(e);
@ -263,11 +261,11 @@ static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
return log_oom();
/* We never cache or use cached PINs, since usually there are only very few attempts allowed before the PIN is blocked */
r = ask_password_auto(question, "user-home", NULL, "pkcs11-pin", USEC_INFINITY, 0, &pin);
r = ask_password_auto(question, "user-home", NULL, "token-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire security token PIN: %m");
r = user_record_set_pkcs11_pin(hr, pin, false);
r = user_record_set_token_pin(hr, pin, false);
if (r < 0)
return log_error_errno(r, "Failed to store security token PIN: %m");
@ -315,26 +313,38 @@ static int handle_generic_user_record_error(
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
log_notice("Please authenticate physically on security token.");
log_notice("%s%sPlease authenticate physically on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, true);
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 protected authentication path permitted flag: %m");
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
log_notice("%s%sAuthentication requires presence verification on security token.",
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
emoji_enabled() ? " " : "");
r = user_record_set_fido2_user_presence_permitted(hr, true);
if (r < 0)
return log_error_errno(r, "Failed to set FIDO2 user presence permitted flag: %m");
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED))
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock security token PIN first.");
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)");
else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
log_notice("Security token PIN incorrect, please try again.");
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
@ -342,7 +352,7 @@ static int handle_generic_user_record_error(
log_notice("Security token PIN incorrect, please try again (only a few tries left!).");
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
@ -350,7 +360,7 @@ static int handle_generic_user_record_error(
log_notice("Security token PIN incorrect, please try again (only one try left!).");
r = acquire_pkcs11_pin(user_name, hr);
r = acquire_token_pin(user_name, hr);
if (r < 0)
return r;
} else
@ -889,336 +899,6 @@ static int add_disposition(JsonVariant **v) {
return 1;
}
struct pkcs11_callback_data {
char *pin_used;
X509 *cert;
};
static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
erase_and_free(data->pin_used);
X509_free(data->cert);
}
#if HAVE_P11KIT
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(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(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_pin(JsonVariant **v, const char *pin) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
_cleanup_(strv_free_erasep) char **pins = NULL;
int r;
assert(v);
if (isempty(pin))
return 0;
w = json_variant_ref(json_variant_by_key(*v, "secret"));
l = json_variant_ref(json_variant_by_key(w, "pkcs11Pin"));
r = json_variant_strv(l, &pins);
if (r < 0)
return log_error_errno(r, "Failed to convert PIN array: %m");
if (strv_find(pins, pin))
return 0;
r = strv_extend(&pins, pin);
if (r < 0)
return log_oom();
strv_uniq(pins);
l = json_variant_unref(l);
r = json_variant_new_array_strv(&l, pins);
if (r < 0)
return log_error_errno(r, "Failed to allocate new PIN array JSON: %m");
json_variant_sensitive(l);
r = json_variant_set_field(&w, "pkcs11Pin", l);
if (r < 0)
return log_error_errno(r, "Failed to update PIN field: %m");
r = json_variant_set_field(v, "secret", w);
if (r < 0)
return log_error_errno(r, "Failed to update secret object: %m");
return 1;
}
static int add_pkcs11_encrypted_key(
JsonVariant **v,
const char *uri,
const void *encrypted_key, size_t encrypted_key_size,
const void *decrypted_key, size_t decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
int r;
assert(v);
assert(uri);
assert(encrypted_key);
assert(encrypted_key_size > 0);
assert(decrypted_key);
assert(decrypted_key_size > 0);
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m");
errno = 0;
k = crypt_r(base64_encoded, salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey"));
r = json_variant_append_array(&l, e);
if (r < 0)
return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m");
r = json_variant_set_field(&w, "pkcs11EncryptedKey", l);
if (r < 0)
return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %m");
r = json_variant_set_field(v, "privileged", w);
if (r < 0)
return log_error_errno(r, "Failed to update privileged field: %m");
return 0;
}
static int add_pkcs11_token_uri(JsonVariant **v, const char *uri) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_strv_free_ char **l = NULL;
int r;
assert(v);
assert(uri);
w = json_variant_ref(json_variant_by_key(*v, "pkcs11TokenUri"));
if (w) {
r = json_variant_strv(w, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 token list: %m");
if (strv_contains(l, uri))
return 0;
}
r = strv_extend(&l, uri);
if (r < 0)
return log_oom();
w = json_variant_unref(w);
r = json_variant_new_array_strv(&w, l);
if (r < 0)
return log_error_errno(r, "Failed to create PKCS#11 token URI JSON: %m");
r = json_variant_set_field(v, "pkcs11TokenUri", w);
if (r < 0)
return log_error_errno(r, "Failed to update PKCS#11 token URI list: %m");
return 0;
}
static int add_pkcs11_key_data(JsonVariant **v, const char *uri) {
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
_cleanup_(erase_and_freep) char *pin = NULL;
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);
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.");
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?");
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 = 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");
/* Add the token URI to the public part of the record. */
r = add_pkcs11_token_uri(v, uri);
if (r < 0)
return r;
/* Include the encrypted version of the random key we just generated in the privileged part of the record */
r = add_pkcs11_encrypted_key(
v,
uri,
encrypted_key, encrypted_key_size,
decrypted_key, decrypted_key_size);
if (r < 0)
return r;
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
* fscrypt. */
r = add_pkcs11_pin(v, pin);
if (r < 0)
return r;
return 0;
}
static int acquire_new_home_record(UserRecord **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
@ -1246,7 +926,13 @@ static int acquire_new_home_record(UserRecord **ret) {
return r;
STRV_FOREACH(i, arg_pkcs11_token_uri) {
r = add_pkcs11_key_data(&v, *i);
r = identity_add_pkcs11_key_data(&v, *i);
if (r < 0)
return r;
}
STRV_FOREACH(i, arg_fido2_device) {
r = identity_add_fido2_parameters(&v, *i);
if (r < 0)
return r;
}
@ -1423,7 +1109,7 @@ static int create_home(int argc, char *argv[], void *userdata) {
r = json_variant_format(hr->json, 0, &formatted);
if (r < 0)
return r;
return log_error_errno(r, "Failed to format user record: %m");
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "CreateHome");
if (r < 0)
@ -1437,25 +1123,28 @@ static int create_home(int argc, char *argv[], void *userdata) {
r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
if (r < 0) {
if (!sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY))
return log_error_errno(r, "Failed to create user home: %s", bus_error_message(&error, r));
if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) {
log_error_errno(r, "%s", bus_error_message(&error, r));
log_info("(Use --enforce-password-policy=no to turn off password quality checks for this account.)");
log_error_errno(r, "%s", bus_error_message(&error, r));
log_info("(Use --enforce-password-policy=no to turn off password quality checks for this account.)");
r = user_record_set_hashed_password(hr, original_hashed_passwords);
if (r < 0)
return r;
r = acquire_new_password(hr->user_name, hr, /* suggest = */ false);
if (r < 0)
return r;
r = user_record_make_hashed_password(hr, hr->password, /* extend = */ true);
if (r < 0)
return log_error_errno(r, "Failed to hash passwords: %m");
} else {
r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
if (r < 0)
return r;
}
} else
break; /* done */
r = user_record_set_hashed_password(hr, original_hashed_passwords);
if (r < 0)
return r;
r = acquire_new_password(hr->user_name, hr, /* suggest = */ false);
if (r < 0)
return r;
r = user_record_make_hashed_password(hr, hr->password, /* extend = */ true);
if (r < 0)
return log_error_errno(r, "Failed to hash passwords: %m");
}
return 0;
@ -1566,14 +1255,20 @@ static int acquire_updated_home_record(
return r;
STRV_FOREACH(i, arg_pkcs11_token_uri) {
r = add_pkcs11_key_data(&json, *i);
r = identity_add_pkcs11_key_data(&json, *i);
if (r < 0)
return r;
}
STRV_FOREACH(i, arg_fido2_device) {
r = identity_add_fido2_parameters(&json, *i);
if (r < 0)
return r;
}
/* If the user supplied a full record, then add in lastChange, but do not override. Otherwise always
* override. */
r = update_last_change(&json, !!arg_pkcs11_token_uri, !arg_identity);
r = update_last_change(&json, arg_pkcs11_token_uri || arg_fido2_device, !arg_identity);
if (r < 0)
return r;
@ -1592,6 +1287,26 @@ static int acquire_updated_home_record(
return 0;
}
static int home_record_reset_human_interaction_permission(UserRecord *hr) {
int r;
assert(hr);
/* When we execute multiple operations one after the other, let's reset the permission to ask the
* user each time, so that if interaction is necessary we will be told so again and thus can print a
* nice message to the user, telling the user so. */
r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, -1);
if (r < 0)
return log_error_errno(r, "Failed to reset PKCS#11 protected authentication path permission flag: %m");
r = user_record_set_fido2_user_presence_permitted(hr, -1);
if (r < 0)
return log_error_errno(r, "Failed to reset FIDO2 user presence permission flag: %m");
return 0;
}
static int update_home(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
@ -1620,6 +1335,12 @@ static int update_home(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
/* If we do multiple operations, let's output things more verbosely, since otherwise the repeated
* authentication might be confusing. */
if (arg_and_resize || arg_and_change_password)
log_info("Updating home directory.");
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
@ -1631,7 +1352,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
r = json_variant_format(hr->json, 0, &formatted);
if (r < 0)
return r;
return log_error_errno(r, "Failed to format user record: %m");
(void) sd_bus_message_sensitive(m);
@ -1655,13 +1376,16 @@ static int update_home(int argc, char *argv[], void *userdata) {
break;
}
if (arg_and_resize)
log_info("Resizing home.");
(void) home_record_reset_human_interaction_permission(hr);
/* Also sync down disk size to underlying LUKS/fscrypt/quota */
while (arg_and_resize) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
log_debug("Resizing");
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ResizeHome");
if (r < 0)
return bus_log_create_error(r);
@ -1688,13 +1412,16 @@ static int update_home(int argc, char *argv[], void *userdata) {
break;
}
if (arg_and_change_password)
log_info("Synchronizing passwords and encryption keys.");
(void) home_record_reset_human_interaction_permission(hr);
/* Also sync down passwords to underlying LUKS/fscrypt */
while (arg_and_change_password) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
log_debug("Propagating password");
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "ChangePasswordHome");
if (r < 0)
return bus_log_create_error(r);
@ -1732,6 +1459,8 @@ static int passwd_home(int argc, char *argv[], void *userdata) {
if (arg_pkcs11_token_uri)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the PKCS#11 security token use 'homectl update --pkcs11-token-uri=…'.");
if (arg_fido2_device)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the FIDO2 security token use 'homectl update --fido2-device=…'.");
if (identity_properties_specified())
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The 'passwd' verb does not permit changing other record properties at the same time.");
@ -2182,6 +1911,8 @@ static int help(int argc, char *argv[], void *userdata) {
" Specify SSH public keys\n"
" --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n"
" private key and matching X.509 certificate\n"
" --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n"
" extension\n"
"\n%4$sAccount Management User Record Properties:%5$s\n"
" --locked=BOOL Set locked account state\n"
" --not-before=TIMESTAMP Do not allow logins before\n"
@ -2328,6 +2059,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_EXPORT_FORMAT,
ARG_AUTO_LOGIN,
ARG_PKCS11_TOKEN_URI,
ARG_FIDO2_DEVICE,
ARG_AND_RESIZE,
ARG_AND_CHANGE_PASSWORD,
};
@ -2405,6 +2137,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "json", required_argument, NULL, ARG_JSON },
{ "export-format", required_argument, NULL, ARG_EXPORT_FORMAT },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
{ "and-resize", required_argument, NULL, ARG_AND_RESIZE },
{ "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD },
{}
@ -3365,6 +3098,9 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_PKCS11_TOKEN_URI: {
const char *p;
if (streq(optarg, "list"))
return list_pkcs11_tokens();
/* If --pkcs11-token-uri= is specified we always drop everything old */
FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") {
r = drop_from_identity(p);
@ -3377,10 +3113,19 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
if (!pkcs11_uri_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
if (streq(optarg, "auto")) {
_cleanup_free_ char *found = NULL;
r = strv_extend(&arg_pkcs11_token_uri, optarg);
r = find_pkcs11_token_auto(&found);
if (r < 0)
return r;
r = strv_consume(&arg_pkcs11_token_uri, TAKE_PTR(found));
} else {
if (!pkcs11_uri_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
r = strv_extend(&arg_pkcs11_token_uri, optarg);
}
if (r < 0)
return r;
@ -3388,6 +3133,41 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_FIDO2_DEVICE: {
const char *p;
if (streq(optarg, "list"))
return list_fido2_devices();
FOREACH_STRING(p, "fido2HmacCredential", "fido2HmacSalt") {
r = drop_from_identity(p);
if (r < 0)
return r;
}
if (isempty(optarg)) {
arg_fido2_device = strv_free(arg_fido2_device);
break;
}
if (streq(optarg, "auto")) {
_cleanup_free_ char *found = NULL;
r = find_fido2_auto(&found);
if (r < 0)
return r;
r = strv_consume(&arg_fido2_device, TAKE_PTR(found));
} else
r = strv_extend(&arg_fido2_device, optarg);
if (r < 0)
return r;
strv_uniq(arg_fido2_device);
break;
}
case 'j':
arg_json = true;
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
@ -3458,7 +3238,7 @@ static int parse_argv(int argc, char *argv[]) {
}
}
if (!strv_isempty(arg_pkcs11_token_uri))
if (!strv_isempty(arg_pkcs11_token_uri) || !strv_isempty(arg_fido2_device))
arg_and_change_password = true;
if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX)

View File

@ -457,6 +457,10 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required.");
case -ERFKILL:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, "Security token requires protected authentication path.");
case -EMEDIUMTYPE:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, "Security token requires user presence.");
case -ENOSTR:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_ACTION_TIMEOUT, "Token action timeout. (User was supposed to verify presence or similar, by interacting with the token, and didn't do that in time.)");
case -EOWNERDEAD:
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_LOCKED, "PIN of security token locked.");
case -ENOLCK:
@ -1357,7 +1361,13 @@ static int user_record_extend_with_binding(UserRecord *hr, UserRecord *with_bind
return 0;
}
static int home_update_internal(Home *h, const char *verb, UserRecord *hr, UserRecord *secret, sd_bus_error *error) {
static int home_update_internal(
Home *h,
const char *verb,
UserRecord *hr,
UserRecord *secret,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
int r, c;

View File

@ -98,7 +98,7 @@ int home_prepare_cifs(
int home_activate_cifs(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
@ -120,7 +120,7 @@ int home_activate_cifs(
if (r < 0)
return r;
r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
r = home_refresh(h, &setup, NULL, cache, NULL, &new_home);
if (r < 0)
return r;

View File

@ -6,6 +6,6 @@
int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_cifs(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_create_cifs(UserRecord *h, UserRecord **ret_home);

View File

@ -26,7 +26,7 @@ int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *set
int home_activate_directory(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
@ -44,11 +44,11 @@ int home_activate_directory(
assert_se(hdo = user_record_home_directory(h));
hd = strdupa(hdo);
r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, false, cache, &setup, &header_home);
if (r < 0)
return r;
r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, NULL, &new_home);
r = home_refresh(h, &setup, header_home, cache, NULL, &new_home);
if (r < 0)
return r;
@ -193,7 +193,7 @@ int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) {
int home_resize_directory(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_home) {
@ -205,11 +205,11 @@ int home_resize_directory(
assert(ret_home);
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
r = home_prepare(h, already_activated, cache, setup, NULL);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
if (r < 0)
return r;

View File

@ -5,6 +5,6 @@
#include "user-record.h"
int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup);
int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_directory(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
int home_resize_directory(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);

197
src/home/homework-fido2.c Normal file
View File

@ -0,0 +1,197 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fido.h>
#include "hexdecoct.h"
#include "homework-fido2.h"
#include "strv.h"
static int fido2_use_specific_token(
const char *path,
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;
int r;
d = fido_dev_new();
if (!d)
return log_oom();
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 = base64mem(hmac, hmac_size, ret);
if (r < 0)
return log_error_errno(r, "Failed to base64 encode HMAC secret: %m");
return 0;
}
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret) {
size_t allocated = 64, found = 0;
fido_dev_info_t *di = NULL;
int r;
di = fido_dev_info_new(allocated);
if (!di)
return log_oom();
r = fido_dev_info_manifest(di, allocated, &found);
if (r == FIDO_ERR_INTERNAL) {
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
goto finish;
}
if (r != FIDO_OK) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
goto finish;
}
for (size_t i = 0; i < found; i++) {
const fido_dev_info_t *entry;
const char *path;
entry = fido_dev_info_ptr(di, i);
if (!entry) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to get device information for FIDO device %zu.", i);
goto finish;
}
path = fido_dev_info_path(entry);
if (!path) {
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to query FIDO device path.");
goto finish;
}
r = fido2_use_specific_token(path, h, secret, salt, ret);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
-ENODEV /* device is not a FIDO2 device with HMAC-SECRET */))
goto finish;
}
r = -EAGAIN;
finish:
fido_dev_info_free(&di, allocated);
return r;
}

View File

@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "user-record.h"
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret);

View File

@ -208,7 +208,7 @@ static int fscrypt_slot_try_many(
}
static int fscrypt_setup(
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
char **password,
HomeSetup *setup,
void **ret_volume_key,
@ -230,6 +230,7 @@ static int fscrypt_setup(
_cleanup_free_ char *value = NULL;
size_t salt_size, encrypted_size;
const char *nr, *e;
char **list;
int n;
/* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
@ -256,19 +257,17 @@ static int fscrypt_setup(
if (r < 0)
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
r = fscrypt_slot_try_many(
pkcs11_decrypted_passwords,
salt, salt_size,
encrypted, encrypted_size,
setup->fscrypt_key_descriptor,
ret_volume_key, ret_volume_key_size);
if (r == -ENOANO)
r = -ENOANO;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, password) {
r = fscrypt_slot_try_many(
password,
list,
salt, salt_size,
encrypted, encrypted_size,
setup->fscrypt_key_descriptor,
ret_volume_key, ret_volume_key_size);
if (r != -ENOANO)
break;
}
if (r < 0) {
if (r != -ENOANO)
return r;
@ -282,7 +281,7 @@ static int fscrypt_setup(
int home_prepare_fscrypt(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
@ -314,7 +313,7 @@ int home_prepare_fscrypt(
memcpy(setup->fscrypt_key_descriptor, policy.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
r = fscrypt_setup(
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
h->password,
setup,
&volume_key,
@ -584,7 +583,7 @@ int home_create_fscrypt(
int home_passwd_fscrypt(
UserRecord *h,
HomeSetup *setup,
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
char **effective_passwords /* new passwords */) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
@ -600,7 +599,7 @@ int home_passwd_fscrypt(
assert(setup);
r = fscrypt_setup(
pkcs11_decrypted_passwords,
cache,
h->password,
setup,
&volume_key,

View File

@ -4,7 +4,7 @@
#include "homework.h"
#include "user-record.h"
int home_prepare_fscrypt(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup);
int home_prepare_fscrypt(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup);
int home_create_fscrypt(UserRecord *h, char **effective_passwords, UserRecord **ret_home);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);

View File

@ -216,7 +216,7 @@ static int luks_setup(
const char *cipher_mode,
uint64_t volume_key_size,
char **passwords,
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
bool discard,
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
@ -227,6 +227,7 @@ static int luks_setup(
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
size_t vks;
char **list;
int r;
assert(node);
@ -278,12 +279,14 @@ static int luks_setup(
if (!vk)
return log_oom();
r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
if (r == -ENOKEY) {
r = luks_try_passwords(cd, passwords, vk, &vks);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
r = luks_try_passwords(cd, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@ -312,7 +315,7 @@ static int luks_setup(
static int luks_open(
const char *dm_name,
char **passwords,
char **pkcs11_decrypted_passwords,
PasswordCache *cache,
struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
void **ret_volume_key,
@ -321,6 +324,7 @@ static int luks_open(
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
char **list;
size_t vks;
int r;
@ -361,12 +365,14 @@ static int luks_open(
if (!vk)
return log_oom();
r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
if (r == -ENOKEY) {
r = luks_try_passwords(cd, passwords, vk, &vks);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
r = luks_try_passwords(cd, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@ -622,7 +628,7 @@ static int luks_validate_home_record(
struct crypt_device *cd,
UserRecord *h,
const void *volume_key,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_luks_home_record) {
int r, token;
@ -727,7 +733,7 @@ static int luks_validate_home_record(
if (!user_record_compatible(h, lhr))
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "LUKS home record not compatible with host record, refusing.");
r = user_record_authenticate(lhr, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(lhr, h, cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -982,7 +988,7 @@ int home_prepare_luks(
UserRecord *h,
bool already_activated,
const char *force_image_path,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_luks_home) {
@ -1010,7 +1016,7 @@ int home_prepare_luks(
r = luks_open(setup->dm_name,
h->password,
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
&cd,
&found_luks_uuid,
&volume_key,
@ -1018,7 +1024,7 @@ int home_prepare_luks(
if (r < 0)
return r;
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
if (r < 0)
return r;
@ -1133,7 +1139,7 @@ int home_prepare_luks(
h->luks_cipher_mode,
h->luks_volume_key_size,
h->password,
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
cache,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
&cd,
&found_luks_uuid,
@ -1144,7 +1150,7 @@ int home_prepare_luks(
dm_activated = true;
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
if (r < 0)
goto fail;
@ -1218,7 +1224,7 @@ static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, stru
int home_activate_luks(
UserRecord *h,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *luks_home_record = NULL;
@ -1250,7 +1256,7 @@ int home_activate_luks(
h,
false,
NULL,
pkcs11_decrypted_passwords,
cache,
&setup,
&luks_home_record);
if (r < 0)
@ -1268,7 +1274,7 @@ int home_activate_luks(
h,
&setup,
luks_home_record,
pkcs11_decrypted_passwords,
cache,
&sfs,
&new_home);
if (r < 0)
@ -1464,7 +1470,7 @@ static int luks_format(
const char *dm_name,
sd_id128_t uuid,
const char *label,
char **pkcs11_decrypted_passwords,
const PasswordCache *cache,
char **effective_passwords,
bool discard,
UserRecord *hr,
@ -1533,7 +1539,8 @@ static int luks_format(
STRV_FOREACH(pp, effective_passwords) {
if (strv_contains(pkcs11_decrypted_passwords, *pp)) {
if (strv_contains(cache->pkcs11_passwords, *pp) ||
strv_contains(cache->fido2_passwords, *pp)) {
log_debug("Using minimal PBKDF for slot %i", slot);
r = crypt_set_pbkdf_type(cd, &minimal_pbkdf);
} else {
@ -1858,7 +1865,7 @@ static int home_truncate(
int home_create_luks(
UserRecord *h,
char **pkcs11_decrypted_passwords,
PasswordCache *cache,
char **effective_passwords,
UserRecord **ret_home) {
@ -2055,7 +2062,7 @@ int home_create_luks(
dm_name,
luks_uuid,
user_record_user_name_and_realm(h),
pkcs11_decrypted_passwords,
cache,
effective_passwords,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
h,
@ -2561,7 +2568,7 @@ static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_ta
int home_resize_luks(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_home) {
@ -2647,11 +2654,11 @@ int home_resize_luks(
}
}
r = home_prepare_luks(h, already_activated, whole_disk, pkcs11_decrypted_passwords, setup, &header_home);
r = home_prepare_luks(h, already_activated, whole_disk, cache, setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
if (r < 0)
return r;
@ -2855,13 +2862,14 @@ int home_resize_luks(
int home_passwd_luks(
UserRecord *h,
HomeSetup *setup,
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
char **effective_passwords /* new passwords */) {
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
char **effective_passwords /* new passwords */) {
size_t volume_key_size, i, max_key_slots, n_effective;
_cleanup_(erase_and_freep) void *volume_key = NULL;
struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf;
const char *type;
char **list;
int r;
assert(h);
@ -2886,12 +2894,14 @@ int home_passwd_luks(
if (!volume_key)
return log_oom();
r = luks_try_passwords(setup->crypt_device, pkcs11_decrypted_passwords, volume_key, &volume_key_size);
if (r == -ENOKEY) {
r = luks_try_passwords(setup->crypt_device, h->password, volume_key, &volume_key_size);
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, h->password) {
r = luks_try_passwords(setup->crypt_device, list, volume_key, &volume_key_size);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
@ -2911,7 +2921,8 @@ int home_passwd_luks(
continue;
}
if (strv_find(pkcs11_decrypted_passwords, effective_passwords[i])) {
if (strv_contains(cache->pkcs11_passwords, effective_passwords[i]) ||
strv_contains(cache->fido2_passwords, effective_passwords[i])) {
log_debug("Using minimal PBKDF for slot %zu", i);
r = crypt_set_pbkdf_type(setup->crypt_device, &minimal_pbkdf);
} else {
@ -3008,9 +3019,10 @@ static int luks_try_resume(
return -ENOKEY;
}
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
int home_unlock_luks(UserRecord *h, PasswordCache *cache) {
_cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
char **list;
int r;
assert(h);
@ -3026,12 +3038,14 @@ int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
log_info("Discovered used LUKS device %s.", dm_node);
crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
r = luks_try_resume(cd, dm_name, pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL);
if (r == -ENOKEY) {
r = luks_try_resume(cd, dm_name, h->password);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
r = -ENOKEY;
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, h->password) {
r = luks_try_resume(cd, dm_name, list);
if (r != -ENOKEY)
break;
}
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
return log_error_errno(r, "Failed to resume LUKS superblock: %m");

View File

@ -5,24 +5,24 @@
#include "homework.h"
#include "user-record.h"
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_luks_home);
int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_luks_home);
int home_activate_luks(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
int home_activate_luks(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
int home_deactivate_luks(UserRecord *h);
int home_trim_luks(UserRecord *h);
int home_store_header_identity_luks(UserRecord *h, HomeSetup *setup, UserRecord *old_home);
int home_create_luks(UserRecord *h, char **pkcs11_decrypted_passwords, char **effective_passwords, UserRecord **ret_home);
int home_create_luks(UserRecord *h, PasswordCache *cache, char **effective_passwords, UserRecord **ret_home);
int home_validate_update_luks(UserRecord *h, HomeSetup *setup);
int home_resize_luks(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
int home_resize_luks(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);
int home_lock_luks(UserRecord *h);
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords);
int home_unlock_luks(UserRecord *h, PasswordCache *cache);
static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
int k;

View File

@ -62,10 +62,10 @@ int pkcs11_callback(
goto decrypt;
}
if (strv_isempty(data->secret->pkcs11_pin))
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security Token requires PIN.");
if (strv_isempty(data->secret->token_pin))
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security token requires PIN.");
STRV_FOREACH(i, data->secret->pkcs11_pin) {
STRV_FOREACH(i, data->secret->token_pin) {
rv = m->C_Login(session, CKU_USER, (CK_UTF8CHAR*) *i, strlen(*i));
if (rv == CKR_OK) {
log_info("Successfully logged into security token '%s' with PIN.", token_label);

View File

@ -11,6 +11,7 @@
#include "home-util.h"
#include "homework-cifs.h"
#include "homework-directory.h"
#include "homework-fido2.h"
#include "homework-fscrypt.h"
#include "homework-luks.h"
#include "homework-mount.h"
@ -21,7 +22,6 @@
#include "missing_magic.h"
#include "mount-util.h"
#include "path-util.h"
#include "pkcs11-util.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "strv.h"
@ -32,14 +32,22 @@
/* Make sure a bad password always results in a 3s delay, no matter what */
#define BAD_PASSWORD_DELAY_USEC (3 * USEC_PER_SEC)
void password_cache_free(PasswordCache *cache) {
if (!cache)
return;
cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords);
cache->fido2_passwords = strv_free_erase(cache->fido2_passwords);
}
int user_record_authenticate(
UserRecord *h,
UserRecord *secret,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
bool strict_verify) {
bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false,
pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false;
bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false, need_user_presence_permitted = false,
pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false, token_action_timeout = false;
int r;
assert(h);
@ -47,14 +55,14 @@ int user_record_authenticate(
/* Tries to authenticate a user record with the supplied secrets. i.e. checks whether at least one
* supplied plaintext passwords matches a hashed password field of the user record. Or if a
* configured PKCS#11 token is around and can unlock the record.
* configured PKCS#11 or FIDO2 token is around and can unlock the record.
*
* Note that the pkcs11_decrypted_passwords parameter is both an input and and output parameter: it
* is a list of configured, decrypted PKCS#11 passwords. We typically have to call this function
* multiple times over the course of an operation (think: on login we authenticate the host user
* record, the record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a
* list of passwords we already decrypted, so that we don't have to do the (slow an potentially
* interactive) PKCS#11 dance for the relevant token again and again. */
* Note that the 'cache' parameter is both an input and output parameter: it contains lists of
* configured, decrypted PKCS#11/FIDO2 passwords. We typically have to call this function multiple
* times over the course of an operation (think: on login we authenticate the host user record, the
* record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of
* passwords we already decrypted, so that we don't have to do the (slow and potentially interactive)
* PKCS#11/FIDO2 dance for the relevant token again and again. */
/* First, let's see if the supplied plain-text passwords work? */
r = user_record_test_secret(h, secret);
@ -70,19 +78,12 @@ int user_record_authenticate(
return 1;
}
/* Second, let's see if any of the PKCS#11 security tokens are plugged in and help us */
/* Second, test cached PKCS#11 passwords */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
.encrypted_key = h->pkcs11_encrypted_key + n,
};
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, *pkcs11_decrypted_passwords) {
r = test_password_one(data.encrypted_key->hashed_password, *pp);
STRV_FOREACH(pp, cache->pkcs11_passwords) {
r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
if (r > 0) {
@ -90,6 +91,32 @@ int user_record_authenticate(
return 1;
}
}
}
/* Third, test cached FIDO2 passwords */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
char **pp;
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, cache->fido2_passwords) {
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, *pp);
if (r < 0)
return log_error_errno(r, "Failed to check supplied FIDO2 password: %m");
if (r > 0) {
log_info("Previously acquired FIDO2 password unlocks user record.");
return 0;
}
}
}
/* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
.user_record = h,
.secret = secret,
.encrypted_key = h->pkcs11_encrypted_key + n,
};
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
switch (r) {
@ -126,7 +153,56 @@ int user_record_authenticate(
log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
r = strv_extend(pkcs11_decrypted_passwords, data.decrypted_password);
r = strv_extend(&cache->pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
return 0;
}
#else
need_token = true;
break;
#endif
}
/* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
r = fido2_use_token(h, secret, h->fido2_hmac_salt + n, &decrypted_password);
switch (r) {
case -EAGAIN:
need_token = true;
break;
case -ENOANO:
need_pin = true;
break;
case -EOWNERDEAD:
pin_locked = true;
break;
case -ENOLCK:
pin_incorrect = true;
break;
case -EMEDIUMTYPE:
need_user_presence_permitted = true;
break;
case -ENOSTR:
token_action_timeout = true;
break;
default:
if (r < 0)
return r;
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured FIDO2 security token does not decrypt encrypted key correctly.");
log_info("Decrypted password from FIDO2 security token unlocks user record.");
r = strv_extend(&cache->fido2_passwords, decrypted_password);
if (r < 0)
return log_oom();
@ -147,8 +223,12 @@ int user_record_authenticate(
return -ENOLCK;
if (pin_locked)
return -EOWNERDEAD;
if (token_action_timeout)
return -ENOSTR;
if (need_protected_authentication_path_permitted)
return -ERFKILL;
if (need_user_presence_permitted)
return -EMEDIUMTYPE;
if (need_pin)
return -ENOANO;
if (need_token)
@ -156,10 +236,11 @@ int user_record_authenticate(
if (need_password)
return -ENOKEY;
/* Hmm, this means neither PCKS#11 nor classic hashed passwords were supplied, we cannot authenticate this reasonably */
/* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords were supplied, we cannot
* authenticate this reasonably */
if (strict_verify)
return log_debug_errno(SYNTHETIC_ERRNO(EKEYREVOKED),
"No hashed passwords and no PKCS#11 tokens defined, cannot authenticate user record, refusing.");
"No hashed passwords and no PKCS#11/FIDO2 tokens defined, cannot authenticate user record, refusing.");
/* If strict verification is off this means we are possibly in the case where we encountered an
* unfixated record, i.e. a synthetic one that accordingly lacks any authentication data. In this
@ -230,7 +311,7 @@ int home_setup_undo(HomeSetup *setup) {
int home_prepare(
UserRecord *h,
bool already_activated,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
HomeSetup *setup,
UserRecord **ret_header_home) {
@ -249,7 +330,7 @@ int home_prepare(
switch (user_record_storage(h)) {
case USER_LUKS:
return home_prepare_luks(h, already_activated, NULL, pkcs11_decrypted_passwords, setup, ret_header_home);
return home_prepare_luks(h, already_activated, NULL, cache, setup, ret_header_home);
case USER_SUBVOLUME:
case USER_DIRECTORY:
@ -257,7 +338,7 @@ int home_prepare(
break;
case USER_FSCRYPT:
r = home_prepare_fscrypt(h, already_activated, pkcs11_decrypted_passwords, setup);
r = home_prepare_fscrypt(h, already_activated, cache, setup);
break;
case USER_CIFS:
@ -387,7 +468,7 @@ int home_load_embedded_identity(
int root_fd,
UserRecord *header_home,
UserReconcileMode mode,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
UserRecord **ret_embedded_home,
UserRecord **ret_new_home) {
@ -414,7 +495,7 @@ int home_load_embedded_identity(
return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "Embedded home record not compatible with host record, refusing.");
/* Insist that credentials the user supplies also unlocks any embedded records. */
r = user_record_authenticate(embedded_home, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(embedded_home, h, cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -576,7 +657,7 @@ int home_refresh(
UserRecord *h,
HomeSetup *setup,
UserRecord *header_home,
char ***pkcs11_decrypted_passwords,
PasswordCache *cache,
struct statfs *ret_statfs,
UserRecord **ret_new_home) {
@ -590,7 +671,7 @@ int home_refresh(
/* When activating a home directory, does the identity work: loads the identity from the $HOME
* directory, reconciles it with our idea, chown()s everything. */
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home);
if (r < 0)
return r;
@ -615,7 +696,7 @@ int home_refresh(
}
static int home_activate(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
int r;
@ -628,7 +709,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Activating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
@ -647,7 +728,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_activate_luks(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_luks(h, &cache, &new_home);
if (r < 0)
return r;
@ -656,14 +737,14 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
case USER_SUBVOLUME:
case USER_DIRECTORY:
case USER_FSCRYPT:
r = home_activate_directory(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_directory(h, &cache, &new_home);
if (r < 0)
return r;
break;
case USER_CIFS:
r = home_activate_cifs(h, &pkcs11_decrypted_passwords, &new_home);
r = home_activate_cifs(h, &cache, &new_home);
if (r < 0)
return r;
@ -783,15 +864,16 @@ int home_populate(UserRecord *h, int dir_fd) {
static int user_record_compile_effective_passwords(
UserRecord *h,
char ***ret_effective_passwords,
char ***ret_pkcs11_decrypted_passwords) {
PasswordCache *cache,
char ***ret_effective_passwords) {
_cleanup_(strv_free_erasep) char **effective = NULL, **pkcs11_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective = NULL;
size_t n;
char **i;
int r;
assert(h);
assert(cache);
/* We insist on at least one classic hashed password to be defined in addition to any PKCS#11 one, as
* a safe fallback, but also to simplify the password changing algorithm: there we require providing
@ -858,11 +940,37 @@ static int user_record_compile_effective_passwords(
return log_oom();
}
if (ret_pkcs11_decrypted_passwords) {
r = strv_extend(&pkcs11_passwords, data.decrypted_password);
r = strv_extend(&cache->pkcs11_passwords, data.decrypted_password);
if (r < 0)
return log_oom();
#else
return -EBADSLT;
#endif
}
for (n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
r = fido2_use_token(h, h, h->fido2_hmac_salt + n, &decrypted_password);
if (r < 0)
return r;
r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password);
if (r < 0)
return log_error_errno(r, "Failed to test FIDO2 password: %m");
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Decrypted password from token is not correct, refusing.");
if (ret_effective_passwords) {
r = strv_extend(&effective, decrypted_password);
if (r < 0)
return log_oom();
}
r = strv_extend(&cache->fido2_passwords, decrypted_password);
if (r < 0)
return log_oom();
#else
return -EBADSLT;
#endif
@ -870,8 +978,6 @@ static int user_record_compile_effective_passwords(
if (ret_effective_passwords)
*ret_effective_passwords = TAKE_PTR(effective);
if (ret_pkcs11_decrypted_passwords)
*ret_pkcs11_decrypted_passwords = TAKE_PTR(pkcs11_passwords);
return 0;
}
@ -934,8 +1040,9 @@ static int determine_default_storage(UserStorage *ret) {
}
static int home_create(UserRecord *h, UserRecord **ret_home) {
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
UserStorage new_storage = _USER_STORAGE_INVALID;
const char *new_fs = NULL;
int r;
@ -947,7 +1054,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
if (r < 0)
return r;
@ -996,7 +1103,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_create_luks(h, pkcs11_decrypted_passwords, effective_passwords, &new_home);
r = home_create_luks(h, &cache, effective_passwords, &new_home);
break;
case USER_DIRECTORY:
@ -1182,15 +1289,15 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup) {
static int home_update(UserRecord *h, UserRecord **ret) {
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
assert(h);
assert(ret);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -1201,11 +1308,11 @@ static int home_update(UserRecord *h, UserRecord **ret) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &cache, &embedded_home, &new_home);
if (r < 0)
return r;
@ -1237,7 +1344,7 @@ static int home_update(UserRecord *h, UserRecord **ret) {
static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
@ -1247,7 +1354,7 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
assert(r > 0); /* Insist that a password was verified */
@ -1261,12 +1368,12 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
switch (user_record_storage(h)) {
case USER_LUKS:
return home_resize_luks(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
return home_resize_luks(h, already_activated, &cache, &setup, ret);
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
return home_resize_directory(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
return home_resize_directory(h, already_activated, &cache, &setup, ret);
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Resizing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
@ -1275,8 +1382,9 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
static int home_passwd(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
@ -1286,7 +1394,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Changing password of home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
r = user_record_compile_effective_passwords(h, &cache, &effective_passwords);
if (r < 0)
return r;
@ -1296,24 +1404,24 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home);
if (r < 0)
return r;
switch (user_record_storage(h)) {
case USER_LUKS:
r = home_passwd_luks(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
r = home_passwd_luks(h, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
case USER_FSCRYPT:
r = home_passwd_fscrypt(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
r = home_passwd_fscrypt(h, &setup, &cache, effective_passwords);
if (r < 0)
return r;
break;
@ -1351,14 +1459,14 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
static int home_inspect(UserRecord *h, UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *new_home = NULL;
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
bool already_activated = false;
int r;
assert(h);
assert(ret_home);
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
@ -1368,11 +1476,11 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
already_activated = r > 0;
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
if (r < 0)
return r;
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &pkcs11_decrypted_passwords, NULL, &new_home);
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &cache, NULL, &new_home);
if (r < 0)
return r;
@ -1415,7 +1523,7 @@ static int home_lock(UserRecord *h) {
}
static int home_unlock(UserRecord *h) {
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
_cleanup_(password_cache_free) PasswordCache cache = {};
int r;
assert(h);
@ -1428,11 +1536,11 @@ static int home_unlock(UserRecord *h) {
/* Note that we don't check if $HOME is actually mounted, since we want to avoid disk accesses on
* that mount until we have resumed the device. */
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
if (r < 0)
return r;
r = home_unlock_luks(h, &pkcs11_decrypted_passwords);
r = home_unlock_luks(h, &cache);
if (r < 0)
return r;
@ -1495,10 +1603,12 @@ static int run(int argc, char *argv[]) {
* ESOCKTNOSUPPORT operation not support on this file system
* ENOKEY password incorrect (or not sufficient, or not supplied)
* EBADSLT similar, but PKCS#11 device is defined and might be able to provide password, if it was plugged in which it is not
* ENOANO suitable PKCS#11 device found, but PIN is missing to unlock it
* ENOANO suitable PKCS#11/FIDO2 device found, but PIN is missing to unlock it
* ERFKILL suitable PKCS#11 device found, but OK to ask for on-device interactive authentication not given
* EOWNERDEAD suitable PKCS#11 device found, but its PIN is locked
* ENOLCK suitable PKCS#11 device found, but PIN incorrect
* EMEDIUMTYPE suitable FIDO2 device found, but OK to ask for user presence not given
* ENOSTR suitable FIDO2 device found, but user didn't react to action request on token quickly enough
* EOWNERDEAD suitable PKCS#11/FIDO2 device found, but its PIN is locked
* ENOLCK suitable PKCS#11/FIDO2 device found, but PIN incorrect
* ETOOMANYREFS suitable PKCS#11 device found, but PIN incorrect, and only few tries left
* EUCLEAN suitable PKCS#11 device found, but PIN incorrect, and only one try left
* EBUSY file system is currently active

View File

@ -36,6 +36,14 @@ typedef struct HomeSetup {
uint64_t partition_size;
} HomeSetup;
typedef struct PasswordCache {
/* Decoding passwords from security tokens is expensive and typically requires user interaction, hence cache any we already figured out. */
char **pkcs11_passwords;
char **fido2_passwords;
} PasswordCache;
void password_cache_free(PasswordCache *cache);
#define HOME_SETUP_INIT \
{ \
.root_fd = -1, \
@ -46,16 +54,16 @@ typedef struct HomeSetup {
int home_setup_undo(HomeSetup *setup);
int home_prepare(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_header_home);
int home_prepare(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_header_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, char ***pkcs11_decrypted_passwords, struct statfs *ret_statfs, UserRecord **ret_new_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home);
int home_populate(UserRecord *h, int dir_fd);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, char ***pkcs11_decrypted_passwords, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home);
int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home);
int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
int user_record_authenticate(UserRecord *h, UserRecord *secret, char ***pkcs11_decrypted_passwords, bool strict_verify);
int user_record_authenticate(UserRecord *h, UserRecord *secret, PasswordCache *cache, bool strict_verify);
int home_sync_and_statfs(int root_fd, struct statfs *ret);

View File

@ -14,6 +14,7 @@ systemd_homework_sources = files('''
homework-mount.c
homework-mount.h
homework-pkcs11.h
homework-fido2.h
homework-quota.c
homework-quota.h
homework.c
@ -25,6 +26,9 @@ systemd_homework_sources = files('''
if conf.get('HAVE_P11KIT') == 1
systemd_homework_sources += files('homework-pkcs11.c')
endif
if conf.get('HAVE_LIBFIDO2') == 1
systemd_homework_sources += files('homework-fido2.c')
endif
systemd_homed_sources = files('''
home-util.c
@ -65,6 +69,10 @@ systemd_homed_sources += [homed_gperf_c]
homectl_sources = files('''
home-util.c
home-util.h
homectl-fido2.c
homectl-fido2.h
homectl-pkcs11.c
homectl-pkcs11.h
homectl.c
pwquality-util.c
pwquality-util.h

View File

@ -359,7 +359,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
@ -375,6 +375,21 @@ static int handle_generic_user_record_error(
return PAM_SERVICE_ERR;
}
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED)) {
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Please verify presence on security token of user %s.", user_name);
r = user_record_set_fido2_user_presence_permitted(secret, true);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to set FIDO2 user presence permitted flag: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
}
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) {
(void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)");
return PAM_SERVICE_ERR;
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
_cleanup_(erase_and_freep) char *newp = NULL;
@ -388,7 +403,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
@ -407,7 +422,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;
@ -426,7 +441,7 @@ static int handle_generic_user_record_error(
return PAM_AUTHTOK_ERR;
}
r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
if (r < 0) {
pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
return PAM_SERVICE_ERR;

View File

@ -887,7 +887,7 @@ int user_record_set_password(UserRecord *h, char **password, bool prepend) {
return 0;
}
int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_(strv_free_erasep) char **e = NULL;
int r;
@ -899,17 +899,17 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
if (!e)
return -ENOMEM;
r = strv_extend_strv(&e, h->pkcs11_pin, true);
r = strv_extend_strv(&e, h->token_pin, true);
if (r < 0)
return r;
strv_uniq(e);
if (strv_equal(h->pkcs11_pin, e))
if (strv_equal(h->token_pin, e))
return 0;
} else {
if (strv_equal(h->pkcs11_pin, pin))
if (strv_equal(h->token_pin, pin))
return 0;
e = strv_copy(pin);
@ -922,7 +922,7 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
w = json_variant_ref(json_variant_by_key(h->json, "secret"));
if (strv_isempty(e))
r = json_variant_filter(&w, STRV_MAKE("pkcs11Pin"));
r = json_variant_filter(&w, STRV_MAKE("tokenPin"));
else {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
@ -932,7 +932,7 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
json_variant_sensitive(l);
r = json_variant_set_field(&w, "pkcs11Pin", l);
r = json_variant_set_field(&w, "tokenPin", l);
}
if (r < 0)
return r;
@ -943,7 +943,7 @@ int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
if (r < 0)
return r;
strv_free_and_replace(h->pkcs11_pin, e);
strv_free_and_replace(h->token_pin, e);
SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
return 0;
@ -980,6 +980,34 @@ int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h
return 0;
}
int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
int r;
assert(h);
w = json_variant_ref(json_variant_by_key(h->json, "secret"));
if (b < 0)
r = json_variant_filter(&w, STRV_MAKE("fido2UserPresencePermitted"));
else
r = json_variant_set_field_boolean(&w, "fido2UserPresencePermitted", b);
if (r < 0)
return r;
if (json_variant_is_blank_object(w))
r = json_variant_filter(&h->json, STRV_MAKE("secret"));
else
r = json_variant_set_field(&h->json, "secret", w);
if (r < 0)
return r;
h->fido2_user_presence_permitted = b;
SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
return 0;
}
static bool per_machine_entry_empty(JsonVariant *v) {
const char *k;
_unused_ JsonVariant *e;
@ -1062,12 +1090,22 @@ int user_record_merge_secret(UserRecord *h, UserRecord *secret) {
if (r < 0)
return r;
r = user_record_set_pkcs11_pin(h, secret->pkcs11_pin, true);
r = user_record_set_token_pin(h, secret->token_pin, true);
if (r < 0)
return r;
if (secret->pkcs11_protected_authentication_path_permitted >= 0) {
r = user_record_set_pkcs11_protected_authentication_path_permitted(h, secret->pkcs11_protected_authentication_path_permitted);
r = user_record_set_pkcs11_protected_authentication_path_permitted(
h,
secret->pkcs11_protected_authentication_path_permitted);
if (r < 0)
return r;
}
if (secret->fido2_user_presence_permitted >= 0) {
r = user_record_set_fido2_user_presence_permitted(
h,
secret->fido2_user_presence_permitted);
if (r < 0)
return r;
}

View File

@ -47,8 +47,9 @@ int user_record_set_disk_size(UserRecord *h, uint64_t disk_size);
int user_record_set_password(UserRecord *h, char **password, bool prepend);
int user_record_make_hashed_password(UserRecord *h, char **password, bool extend);
int user_record_set_hashed_password(UserRecord *h, char **hashed_password);
int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend);
int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend);
int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b);
int user_record_set_fido2_user_presence_permitted(UserRecord *h, int b);
int user_record_set_password_change_now(UserRecord *h, int b);
int user_record_merge_secret(UserRecord *h, UserRecord *secret);
int user_record_good_authentication(UserRecord *h);

View File

@ -120,6 +120,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, EBADSLT),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED, ENOANO),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, EMEDIUMTYPE),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_ACTION_TIMEOUT, ENOSTR),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED, EOWNERDEAD),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN, ENOLCK),
SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),

View File

@ -100,6 +100,8 @@
#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
#define BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED "org.freedesktop.home1.TokenUserPresenceNeeded"
#define BUS_ERROR_TOKEN_ACTION_TIMEOUT "org.freedesktop.home1.TokenActionTimeout"
#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"

View File

@ -151,6 +151,28 @@ char *pkcs11_token_label(const CK_TOKEN_INFO *token_info) {
return t;
}
char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info) {
char *t;
t = strndup((char*) token_info->manufacturerID, sizeof(token_info->manufacturerID));
if (!t)
return NULL;
strstrip(t);
return t;
}
char *pkcs11_token_model(const CK_TOKEN_INFO *token_info) {
char *t;
t = strndup((char*) token_info->model, sizeof(token_info->model));
if (!t)
return NULL;
strstrip(t);
return t;
}
int pkcs11_token_login(
CK_FUNCTION_LIST *m,
CK_SESSION_HANDLE session,
@ -165,9 +187,8 @@ int pkcs11_token_login(
_cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL;
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
CK_TOKEN_INFO updated_token_info;
int uri_result;
int uri_result, r;
CK_RV rv;
int r;
assert(m);
assert(token_info);
@ -211,28 +232,8 @@ int pkcs11_token_login(
for (unsigned tries = 0; tries < 3; tries++) {
_cleanup_strv_free_erase_ char **passwords = NULL;
_cleanup_free_ char *text = NULL;
char **i, *e;
if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY))
r = asprintf(&text,
"Please enter correct PIN for security token '%s' in order to unlock %s (final try):",
token_label, friendly_name);
else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW))
r = asprintf(&text,
"PIN has been entered incorrectly previously, please enter correct PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else if (tries == 0)
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s (try #%u):",
token_label, friendly_name, tries+1);
if (r < 0)
return log_oom();
e = getenv("PIN");
if (e) {
passwords = strv_new(e);
@ -243,6 +244,27 @@ int pkcs11_token_login(
if (unsetenv("PIN") < 0)
return log_error_errno(errno, "Failed to unset $PIN: %m");
} else {
_cleanup_free_ char *text = NULL;
if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY))
r = asprintf(&text,
"Please enter correct PIN for security token '%s' in order to unlock %s (final try):",
token_label, friendly_name);
else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW))
r = asprintf(&text,
"PIN has been entered incorrectly previously, please enter correct PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else if (tries == 0)
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s:",
token_label, friendly_name);
else
r = asprintf(&text,
"Please enter PIN for security token '%s' in order to unlock %s (try #%u):",
token_label, friendly_name, tries+1);
if (r < 0)
return log_oom();
/* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */
r = ask_password_auto(text, icon_name, id, keyname, until, 0, &passwords);
if (r < 0)
@ -702,7 +724,6 @@ static int token_process(
assert(m);
assert(slot_info);
assert(token_info);
assert(search_uri);
token_label = pkcs11_token_label(token_info);
if (!token_label)
@ -740,7 +761,6 @@ static int slot_process(
CK_RV rv;
assert(m);
assert(search_uri);
/* We return -EAGAIN for all failures we can attribute to a specific slot in some way, so that the
* caller might try other slots before giving up. */
@ -786,7 +806,7 @@ static int slot_process(
return -EAGAIN;
}
if (!p11_kit_uri_match_token_info(search_uri, &token_info)) {
if (search_uri && !p11_kit_uri_match_token_info(search_uri, &token_info)) {
log_debug("Found non-matching token with URI %s.", token_uri_string);
return -EAGAIN;
}
@ -820,7 +840,6 @@ static int module_process(
int r;
assert(m);
assert(search_uri);
/* We ignore most errors from modules here, in order to skip over faulty modules: one faulty module
* should not have the effect that we don't try the others anymore. We indicate such per-module
@ -883,14 +902,14 @@ int pkcs11_find_token(
_cleanup_(p11_kit_uri_freep) P11KitUri *search_uri = NULL;
int r;
assert(pkcs11_uri);
/* Execute the specified callback for each matching token found. If nothing is found returns
* -EAGAIN. Logs about all errors, except for EAGAIN, which the caller has to log about. */
r = uri_from_string(pkcs11_uri, &search_uri);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", pkcs11_uri);
if (pkcs11_uri) {
r = uri_from_string(pkcs11_uri, &search_uri);
if (r < 0)
return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", pkcs11_uri);
}
modules = p11_kit_modules_load_and_initialize(0);
if (!modules)

View File

@ -27,6 +27,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(CK_FUNCTION_LIST**, p11_kit_modules_finalize_and_rel
CK_RV pkcs11_get_slot_list_malloc(CK_FUNCTION_LIST *m, CK_SLOT_ID **ret_slotids, CK_ULONG *ret_n_slotids);
char *pkcs11_token_label(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_model(const CK_TOKEN_INFO *token_info);
int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *keyname, usec_t until, char **ret_used_pin);

View File

@ -472,10 +472,13 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
STRV_FOREACH(i, hr->pkcs11_token_uri)
printf(i == hr->pkcs11_token_uri ?
" Sec. Token: %s\n" :
"PKCS11 Token: %s\n" :
" %s\n", *i);
}
if (hr->n_fido2_hmac_credential > 0)
printf(" FIDO2 Token: %zu\n", hr->n_fido2_hmac_credential);
k = strv_length(hr->hashed_password);
if (k == 0)
printf(" Passwords: %snone%s\n",

View File

@ -81,6 +81,7 @@ UserRecord* user_record_new(void) {
.password_change_inactive_usec = UINT64_MAX,
.password_change_now = -1,
.pkcs11_protected_authentication_path_permitted = -1,
.fido2_user_presence_permitted = -1,
};
return h;
@ -95,6 +96,22 @@ static void pkcs11_encrypted_key_done(Pkcs11EncryptedKey *k) {
erase_and_free(k->hashed_password);
}
static void fido2_hmac_credential_done(Fido2HmacCredential *c) {
if (!c)
return;
free(c->id);
}
static void fido2_hmac_salt_done(Fido2HmacSalt *s) {
if (!s)
return;
fido2_hmac_credential_done(&s->credential);
erase_and_free(s->salt);
erase_and_free(s->hashed_password);
}
static UserRecord* user_record_free(UserRecord *h) {
if (!h)
return NULL;
@ -120,7 +137,7 @@ static UserRecord* user_record_free(UserRecord *h) {
strv_free_erase(h->hashed_password);
strv_free_erase(h->ssh_authorized_keys);
strv_free_erase(h->password);
strv_free_erase(h->pkcs11_pin);
strv_free_erase(h->token_pin);
free(h->cifs_service);
free(h->cifs_user_name);
@ -147,6 +164,11 @@ static UserRecord* user_record_free(UserRecord *h) {
pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i);
free(h->pkcs11_encrypted_key);
for (size_t i = 0; i < h->n_fido2_hmac_credential; i++)
fido2_hmac_credential_done(h->fido2_hmac_credential + i);
for (size_t i = 0; i < h->n_fido2_hmac_salt; i++)
fido2_hmac_salt_done(h->fido2_hmac_salt + i);
json_variant_unref(h->json);
return mfree(h);
@ -620,8 +642,10 @@ static int dispatch_secret(const char *name, JsonVariant *variant, JsonDispatchF
static const JsonDispatch secret_dispatch_table[] = {
{ "password", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, password), 0 },
{ "pkcs11Pin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, pkcs11_pin), 0 },
{ "tokenPin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
{ "pkcs11Pin", /* legacy alias */ _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, token_pin), 0 },
{ "pkcs11ProtectedAuthenticationPathPermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, pkcs11_protected_authentication_path_permitted), 0 },
{ "fido2UserPresencePermitted", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, fido2_user_presence_permitted), 0 },
{},
};
@ -706,7 +730,7 @@ static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, Json
int r;
if (json_variant_is_null(variant)) {
k->data = mfree(k->data);
k->data = erase_and_free(k->data);
k->size = 0;
return 0;
}
@ -766,13 +790,141 @@ static int dispatch_pkcs11_key(const char *name, JsonVariant *variant, JsonDispa
return 0;
}
static int dispatch_fido2_hmac_credential(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
Fido2HmacCredential *k = userdata;
size_t l;
void *b;
int r;
if (json_variant_is_null(variant)) {
k->id = mfree(k->id);
k->size = 0;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
r = unbase64mem(json_variant_string(variant), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
free_and_replace(k->id, b);
k->size = l;
return 0;
}
static int dispatch_fido2_hmac_credential_array(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
UserRecord *h = userdata;
JsonVariant *e;
int r;
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
Fido2HmacCredential *array;
size_t l;
void *b;
if (!json_variant_is_string(e))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a string.");
array = reallocarray(h->fido2_hmac_credential, h->n_fido2_hmac_credential + 1, sizeof(Fido2HmacCredential));
if (!array)
return log_oom();
r = unbase64mem(json_variant_string(e), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m");
h->fido2_hmac_credential = array;
h->fido2_hmac_credential[h->n_fido2_hmac_credential++] = (Fido2HmacCredential) {
.id = b,
.size = l,
};
}
return 0;
}
static int dispatch_fido2_hmac_salt_value(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
Fido2HmacSalt *k = userdata;
size_t l;
void *b;
int r;
if (json_variant_is_null(variant)) {
k->salt = erase_and_free(k->salt);
k->salt_size = 0;
return 0;
}
if (!json_variant_is_string(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name));
r = unbase64mem(json_variant_string(variant), (size_t) -1, &b, &l);
if (r < 0)
return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m");
erase_and_free(k->salt);
k->salt = b;
k->salt_size = l;
return 0;
}
static int dispatch_fido2_hmac_salt(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
UserRecord *h = userdata;
JsonVariant *e;
int r;
if (!json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
Fido2HmacSalt *array, *k;
static const JsonDispatch fido2_hmac_salt_dispatch_table[] = {
{ "credential", JSON_VARIANT_STRING, dispatch_fido2_hmac_credential, offsetof(Fido2HmacSalt, credential), JSON_MANDATORY },
{ "salt", JSON_VARIANT_STRING, dispatch_fido2_hmac_salt_value, 0, JSON_MANDATORY },
{ "hashedPassword", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Fido2HmacSalt, hashed_password), JSON_MANDATORY },
{},
};
if (!json_variant_is_object(e))
return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not an object.");
array = reallocarray(h->fido2_hmac_salt, h->n_fido2_hmac_salt + 1, sizeof(Fido2HmacSalt));
if (!array)
return log_oom();
h->fido2_hmac_salt = array;
k = h->fido2_hmac_salt + h->n_fido2_hmac_salt;
*k = (Fido2HmacSalt) {};
r = json_dispatch(e, fido2_hmac_salt_dispatch_table, NULL, flags, k);
if (r < 0) {
fido2_hmac_salt_done(k);
return r;
}
h->n_fido2_hmac_salt++;
}
return 0;
}
static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch privileged_dispatch_table[] = {
{ "passwordHint", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, password_hint), 0 },
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, hashed_password), JSON_SAFE },
{ "sshAuthorizedKeys", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, ssh_authorized_keys), 0 },
{ "pkcs11EncryptedKey", JSON_VARIANT_ARRAY, dispatch_pkcs11_key, 0, 0 },
{ "passwordHint", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, password_hint), 0 },
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, hashed_password), JSON_SAFE },
{ "sshAuthorizedKeys", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, ssh_authorized_keys), 0 },
{ "pkcs11EncryptedKey", JSON_VARIANT_ARRAY, dispatch_pkcs11_key, 0, 0 },
{ "fido2HmacSalt", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_salt, 0, 0 },
{},
};
@ -906,66 +1058,67 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) {
static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
static const JsonDispatch per_machine_dispatch_table[] = {
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0, },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0, },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0, },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0, },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "fido2HmacCredential", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
{},
};
@ -1247,84 +1400,85 @@ int user_group_record_mangle(
int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_flags) {
static const JsonDispatch user_dispatch_table[] = {
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 },
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 },
{ "lastPasswordChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(UserRecord, disposition), 0 },
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_change_usec), 0 },
{ "lastPasswordChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 },
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
{ "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 },
{ "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 },
{ "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE },
{ "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE },
{ "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 },
{ "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 },
{ "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 },
{ "notBeforeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 },
{ "notAfterUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 },
{ "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 },
{ "diskSize", JSON_VARIANT_UNSIGNED, json_dispatch_disk_size, offsetof(UserRecord, disk_size), 0 },
{ "diskSizeRelative", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 },
{ "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 },
{ "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 },
{ "tasksMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, tasks_max), 0 },
{ "memoryHigh", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_high), 0 },
{ "memoryMax", JSON_VARIANT_UNSIGNED, json_dispatch_tasks_or_memory_max, offsetof(UserRecord, memory_max), 0 },
{ "cpuWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, cpu_weight), 0 },
{ "ioWeight", JSON_VARIANT_UNSIGNED, json_dispatch_weight, offsetof(UserRecord, io_weight), 0 },
{ "mountNoDevices", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nodev), 0 },
{ "mountNoSuid", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, nosuid), 0 },
{ "mountNoExecute", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, noexec), 0 },
{ "cifsDomain", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_domain), JSON_SAFE },
{ "cifsUserName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_user_name), JSON_SAFE },
{ "cifsService", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, cifs_service), JSON_SAFE },
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, image_path), 0 },
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 },
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 },
{ "memberOf", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(UserRecord, member_of), JSON_RELAX},
{ "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE },
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
{ "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 },
{ "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 },
{ "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 },
{ "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE },
{ "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE },
{ "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 },
{ "luksPbkdfHashAlgorithm", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_hash_algorithm), JSON_SAFE },
{ "luksPbkdfType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_pbkdf_type), JSON_SAFE },
{ "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 },
{ "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 },
{ "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 },
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE },
{ "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 },
{ "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 },
{ "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 },
{ "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 },
{ "stopDelayUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 },
{ "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 },
{ "passwordChangeMinUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 },
{ "passwordChangeMaxUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_max_usec), 0 },
{ "passwordChangeWarnUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_warn_usec), 0 },
{ "passwordChangeInactiveUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, password_change_inactive_usec), 0 },
{ "passwordChangeNow", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, password_change_now), 0 },
{ "pkcs11TokenUri", JSON_VARIANT_ARRAY, dispatch_pkcs11_uri_array, offsetof(UserRecord, pkcs11_token_uri), 0 },
{ "fido2HmacCredential", JSON_VARIANT_ARRAY, dispatch_fido2_hmac_credential_array, 0, 0 },
{ "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
{ "secret", JSON_VARIANT_OBJECT, dispatch_secret, 0, 0 },
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
/* Ignore the perMachine, binding, status stuff here, and process it later, so that it overrides whatever is set above */
{ "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
{ "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
/* Ignore 'signature', we check it with explicit accessors instead */
{ "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{ "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
{},
};
@ -1684,6 +1838,9 @@ bool user_record_can_authenticate(UserRecord *h) {
if (h->n_pkcs11_encrypted_key > 0)
return true;
if (h->n_fido2_hmac_salt > 0)
return true;
return !strv_isempty(h->hashed_password);
}

View File

@ -189,6 +189,23 @@ typedef struct Pkcs11EncryptedKey {
char *hashed_password;
} Pkcs11EncryptedKey;
typedef struct Fido2HmacCredential {
void *id;
size_t size;
} Fido2HmacCredential;
typedef struct Fido2HmacSalt {
/* The FIDO2 Cridential ID to use */
Fido2HmacCredential credential;
/* The FIDO2 salt value */
void *salt;
size_t salt_size;
/* What to test the hashed salt value against, usualy UNIX password hash here. */
char *hashed_password;
} Fido2HmacSalt;
typedef struct UserRecord {
/* The following three fields are not part of the JSON record */
unsigned n_ref;
@ -239,7 +256,7 @@ typedef struct UserRecord {
char **hashed_password;
char **ssh_authorized_keys;
char **password;
char **pkcs11_pin;
char **token_pin;
char *cifs_domain;
char *cifs_user_name;
@ -309,6 +326,12 @@ typedef struct UserRecord {
size_t n_pkcs11_encrypted_key;
int pkcs11_protected_authentication_path_permitted;
Fido2HmacCredential *fido2_hmac_credential;
size_t n_fido2_hmac_credential;
Fido2HmacSalt *fido2_hmac_salt;
size_t n_fido2_hmac_salt;
int fido2_user_presence_permitted;
JsonVariant *json;
} UserRecord;

View File

@ -89,7 +89,7 @@ static void test_keymaps(void) {
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
static void dump_special_glyphs(void) {
assert_cc(SPECIAL_GLYPH_LOCK_AND_KEY + 1 == _SPECIAL_GLYPH_MAX);
assert_cc(SPECIAL_GLYPH_TOUCH + 1 == _SPECIAL_GLYPH_MAX);
log_info("/* %s */", __func__);
@ -116,6 +116,7 @@ static void dump_special_glyphs(void) {
dump_glyph(SPECIAL_GLYPH_UNHAPPY_SMILEY);
dump_glyph(SPECIAL_GLYPH_DEPRESSED_SMILEY);
dump_glyph(SPECIAL_GLYPH_LOCK_AND_KEY);
dump_glyph(SPECIAL_GLYPH_TOUCH);
}
int main(int argc, char *argv[]) {

View File

@ -410,6 +410,85 @@ static void test_system_tasks_max_scale(void) {
assert_se(system_tasks_max_scale(UINT64_MAX/4, UINT64_MAX) == UINT64_MAX);
}
static void test_foreach_pointer(void) {
int a, b, c, *i;
size_t k = 0;
FOREACH_POINTER(i, &a, &b, &c) {
switch (k) {
case 0:
assert_se(i == &a);
break;
case 1:
assert_se(i == &b);
break;
case 2:
assert_se(i == &c);
break;
default:
assert_not_reached("unexpected index");
break;
}
k++;
}
assert(k == 3);
FOREACH_POINTER(i, &b) {
assert(k == 3);
assert(i == &b);
k = 4;
}
assert(k == 4);
FOREACH_POINTER(i, NULL, &c, NULL, &b, NULL, &a, NULL) {
switch (k) {
case 4:
assert_se(i == NULL);
break;
case 5:
assert_se(i == &c);
break;
case 6:
assert_se(i == NULL);
break;
case 7:
assert_se(i == &b);
break;
case 8:
assert_se(i == NULL);
break;
case 9:
assert_se(i == &a);
break;
case 10:
assert_se(i == NULL);
break;
default:
assert_not_reached("unexpected index");
break;
}
k++;
}
assert(k == 11);
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_INFO);
@ -428,6 +507,7 @@ int main(int argc, char *argv[]) {
test_physical_memory_scale();
test_system_tasks_max();
test_system_tasks_max_scale();
test_foreach_pointer();
return 0;
}