Compare commits
No commits in common. "071be2fa9fe3fbc870a69afe6d44818d8980eb6c" and "a68da222571789414b86d6bfa67aa7d38c63b19a" have entirely different histories.
071be2fa9f
...
a68da22257
15
NEWS
15
NEWS
|
@ -147,9 +147,6 @@ 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.
|
||||
|
@ -181,12 +178,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 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 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 [DHCPv4] section gained a new setting UseGateway=
|
||||
which may be used to turn off use of the gateway information provided
|
||||
|
|
4
TODO
4
TODO
|
@ -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: support FIDO2 tokens for deriving keys (i.e. do what homed can do
|
||||
also in plain cryptsetup)
|
||||
* cryptsetup/homed: also support FIDO2 HMAC password logic for unlocking
|
||||
devices. (see: https://github.com/mjec/fido2-hmac-secret)
|
||||
|
||||
* systemd-gpt-auto should probably set x-systemd.growfs on the mounts it
|
||||
creates
|
||||
|
|
|
@ -546,11 +546,6 @@ 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.
|
||||
|
||||
|
@ -599,7 +594,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
|
||||
|
@ -607,29 +602,13 @@ 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-encoded decrypted key may also be used to unlock further resources
|
||||
Base64-enceded 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
|
||||
|
@ -673,13 +652,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`, `luksCipher`, `luksCipherMode`, `luksVolumeKeySize`,
|
||||
`luksPbkdfHashAlgorithm`, `luksPbkdfType`, `luksPbkdfTimeCostUSec`,
|
||||
`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `rateLimitIntervalUSec`,
|
||||
`rateLimitBurst`, `enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`,
|
||||
`killProcesses`, `passwordChangeMinUSec`, `passwordChangeMaxUSec`,
|
||||
`passwordChangeWarnUSec`, `passwordChangeInactiveUSec`, `passwordChangeNow`,
|
||||
`pkcs11TokenUri`, `fido2HmacCredential`.
|
||||
`luksOfflineDiscard`, `luksOfflineDiscard`, `luksCipher`, `luksCipherMode`,
|
||||
`luksVolumeKeySize`, `luksPbkdfHashAlgorithm`, `luksPbkdfType`,
|
||||
`luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`,
|
||||
`rateLimitIntervalUSec`, `rateLimitBurst`, `enforcePasswordPolicy`,
|
||||
`autoLogin`, `stopDelayUSec`, `killProcesses`, `passwordChangeMinUSec`,
|
||||
`passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
|
||||
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`.
|
||||
|
||||
## Fields in the `binding` section
|
||||
|
||||
|
@ -831,7 +810,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.
|
||||
|
||||
|
@ -885,20 +864,13 @@ The `secret` field of the top-level user record contains the following fields:
|
|||
|
||||
`password` → an array of strings, each containing a plain text password.
|
||||
|
||||
`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.)
|
||||
`pkcs11Pin` → an array of strings, each containing a plain text PIN, suitable
|
||||
for unlocking PKCS#11 security tokens that require that.
|
||||
|
||||
`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.
|
||||
|
||||
`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.
|
||||
user. If false or unset authentication this way shall not be attempted.
|
||||
|
||||
## Mapping to `struct passwd` and `struct spwd`
|
||||
|
||||
|
|
|
@ -332,49 +332,7 @@
|
|||
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>
|
||||
|
||||
<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>
|
||||
token.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
@ -852,7 +810,7 @@
|
|||
</example>
|
||||
|
||||
<example>
|
||||
<title>Set up authentication with a YubiKey security token using PKCS#11/PIV:</title>
|
||||
<title>Set up authentication with a YubiKey security token:</title>
|
||||
|
||||
<programlisting># Clear the Yubikey from any old keys (careful!)
|
||||
ykman piv reset
|
||||
|
@ -863,18 +821,16 @@ 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 public key on disk anymore
|
||||
# We don't need the publibc key on disk anymore
|
||||
rm pubkey.pem
|
||||
|
||||
# Allow the security token to unlock the account of user 'lafcadio'.
|
||||
homectl update lafcadio --pkcs11-token-uri=auto</programlisting>
|
||||
</example>
|
||||
# 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
|
||||
|
||||
<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>
|
||||
# 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>
|
||||
</refsect1>
|
||||
|
||||
|
|
16
meson.build
16
meson.build
|
@ -1153,17 +1153,6 @@ 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',
|
||||
|
@ -2157,8 +2146,7 @@ if conf.get('ENABLE_HOMED') == 1
|
|||
libcrypt,
|
||||
libopenssl,
|
||||
libfdisk,
|
||||
libp11kit,
|
||||
libfido2],
|
||||
libp11kit],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
@ -2185,7 +2173,6 @@ if conf.get('ENABLE_HOMED') == 1
|
|||
libcrypt,
|
||||
libopenssl,
|
||||
libp11kit,
|
||||
libfido2,
|
||||
libpwquality],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
|
@ -3588,7 +3575,6 @@ foreach tuple : [
|
|||
['pwquality'],
|
||||
['libfdisk'],
|
||||
['p11kit'],
|
||||
['libfido2'],
|
||||
['AUDIT'],
|
||||
['IMA'],
|
||||
['AppArmor'],
|
||||
|
|
|
@ -312,8 +312,6 @@ 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'],
|
||||
|
|
|
@ -373,8 +373,7 @@ 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_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */
|
||||
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,"
|
||||
},
|
||||
|
||||
/* UTF-8 */
|
||||
|
@ -416,9 +415,6 @@ 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 */
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -65,8 +65,7 @@ typedef enum {
|
|||
SPECIAL_GLYPH_UNHAPPY_SMILEY,
|
||||
SPECIAL_GLYPH_DEPRESSED_SMILEY,
|
||||
SPECIAL_GLYPH_LOCK_AND_KEY,
|
||||
SPECIAL_GLYPH_TOUCH,
|
||||
_SPECIAL_GLYPH_MAX,
|
||||
_SPECIAL_GLYPH_MAX
|
||||
} SpecialGlyph;
|
||||
|
||||
const char *special_glyph(SpecialGlyph code) _const_;
|
||||
|
|
|
@ -538,12 +538,6 @@ 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
|
||||
|
|
|
@ -2320,7 +2320,6 @@ 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,
|
||||
|
|
|
@ -1,539 +0,0 @@
|
|||
/* 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
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
/* 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);
|
|
@ -1,480 +0,0 @@
|
|||
/* 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
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/* 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);
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ask-password-api.h"
|
||||
#include "bus-common-errors.h"
|
||||
#include "bus-error.h"
|
||||
|
@ -14,12 +15,15 @@
|
|||
#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 "homectl-fido2.h"
|
||||
#include "homectl-pkcs11.h"
|
||||
#include "libcrypt-util.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"
|
||||
|
@ -27,6 +31,7 @@
|
|||
#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"
|
||||
|
@ -51,7 +56,6 @@ 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;
|
||||
|
@ -69,7 +73,6 @@ 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
|
||||
|
@ -80,8 +83,7 @@ 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_fido2_device);
|
||||
!strv_isempty(arg_pkcs11_token_uri);
|
||||
}
|
||||
|
||||
static int acquire_bus(sd_bus **bus) {
|
||||
|
@ -234,7 +236,7 @@ static int acquire_existing_password(const char *user_name, UserRecord *hr, bool
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int acquire_token_pin(const char *user_name, UserRecord *hr) {
|
||||
static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
|
||||
_cleanup_(strv_free_erasep) char **pin = NULL;
|
||||
_cleanup_free_ char *question = NULL;
|
||||
char *e;
|
||||
|
@ -245,9 +247,9 @@ static int acquire_token_pin(const char *user_name, UserRecord *hr) {
|
|||
|
||||
e = getenv("PIN");
|
||||
if (e) {
|
||||
r = user_record_set_token_pin(hr, STRV_MAKE(e), false);
|
||||
r = user_record_set_pkcs11_pin(hr, STRV_MAKE(e), false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to store token PIN: %m");
|
||||
return log_error_errno(r, "Failed to store PKCS#11 PIN: %m");
|
||||
|
||||
string_erase(e);
|
||||
|
||||
|
@ -261,11 +263,11 @@ static int acquire_token_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, "token-pin", USEC_INFINITY, 0, &pin);
|
||||
r = ask_password_auto(question, "user-home", NULL, "pkcs11-pin", USEC_INFINITY, 0, &pin);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire security token PIN: %m");
|
||||
|
||||
r = user_record_set_token_pin(hr, pin, false);
|
||||
r = user_record_set_pkcs11_pin(hr, pin, false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to store security token PIN: %m");
|
||||
|
||||
|
@ -313,38 +315,26 @@ static int handle_generic_user_record_error(
|
|||
|
||||
} else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
|
||||
|
||||
r = acquire_token_pin(user_name, hr);
|
||||
r = acquire_pkcs11_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("%s%sPlease authenticate physically on security token.",
|
||||
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "",
|
||||
emoji_enabled() ? " " : "");
|
||||
log_notice("Please authenticate physically on security token.");
|
||||
|
||||
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 it first. (Hint: Removal and re-insertion might suffice.)");
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock security token PIN first.");
|
||||
|
||||
else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
|
||||
|
||||
log_notice("Security token PIN incorrect, please try again.");
|
||||
|
||||
r = acquire_token_pin(user_name, hr);
|
||||
r = acquire_pkcs11_pin(user_name, hr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -352,7 +342,7 @@ static int handle_generic_user_record_error(
|
|||
|
||||
log_notice("Security token PIN incorrect, please try again (only a few tries left!).");
|
||||
|
||||
r = acquire_token_pin(user_name, hr);
|
||||
r = acquire_pkcs11_pin(user_name, hr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -360,7 +350,7 @@ static int handle_generic_user_record_error(
|
|||
|
||||
log_notice("Security token PIN incorrect, please try again (only one try left!).");
|
||||
|
||||
r = acquire_token_pin(user_name, hr);
|
||||
r = acquire_pkcs11_pin(user_name, hr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
|
@ -899,6 +889,336 @@ 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;
|
||||
|
@ -926,13 +1246,7 @@ static int acquire_new_home_record(UserRecord **ret) {
|
|||
return r;
|
||||
|
||||
STRV_FOREACH(i, arg_pkcs11_token_uri) {
|
||||
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);
|
||||
r = add_pkcs11_key_data(&v, *i);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
@ -1109,7 +1423,7 @@ static int create_home(int argc, char *argv[], void *userdata) {
|
|||
|
||||
r = json_variant_format(hr->json, 0, &formatted);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format user record: %m");
|
||||
return r;
|
||||
|
||||
r = bus_message_new_method_call(bus, &m, bus_home_mgr, "CreateHome");
|
||||
if (r < 0)
|
||||
|
@ -1123,9 +1437,13 @@ 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)) {
|
||||
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));
|
||||
|
||||
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.)");
|
||||
} else
|
||||
break; /* done */
|
||||
|
||||
r = user_record_set_hashed_password(hr, original_hashed_passwords);
|
||||
if (r < 0)
|
||||
|
@ -1138,13 +1456,6 @@ static int create_home(int argc, char *argv[], void *userdata) {
|
|||
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 */
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1255,20 +1566,14 @@ static int acquire_updated_home_record(
|
|||
return r;
|
||||
|
||||
STRV_FOREACH(i, arg_pkcs11_token_uri) {
|
||||
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);
|
||||
r = add_pkcs11_key_data(&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_fido2_device, !arg_identity);
|
||||
r = update_last_change(&json, !!arg_pkcs11_token_uri, !arg_identity);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1287,26 +1592,6 @@ 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;
|
||||
|
@ -1335,12 +1620,6 @@ 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;
|
||||
|
@ -1352,7 +1631,7 @@ static int update_home(int argc, char *argv[], void *userdata) {
|
|||
|
||||
r = json_variant_format(hr->json, 0, &formatted);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format user record: %m");
|
||||
return r;
|
||||
|
||||
(void) sd_bus_message_sensitive(m);
|
||||
|
||||
|
@ -1376,16 +1655,13 @@ 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);
|
||||
|
@ -1412,16 +1688,13 @@ 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);
|
||||
|
@ -1459,8 +1732,6 @@ 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.");
|
||||
|
||||
|
@ -1911,8 +2182,6 @@ 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"
|
||||
|
@ -2059,7 +2328,6 @@ 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,
|
||||
};
|
||||
|
@ -2137,7 +2405,6 @@ 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 },
|
||||
{}
|
||||
|
@ -3098,9 +3365,6 @@ 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);
|
||||
|
@ -3113,19 +3377,10 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (streq(optarg, "auto")) {
|
||||
_cleanup_free_ char *found = NULL;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -3133,41 +3388,6 @@ 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;
|
||||
|
@ -3238,7 +3458,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!strv_isempty(arg_pkcs11_token_uri) || !strv_isempty(arg_fido2_device))
|
||||
if (!strv_isempty(arg_pkcs11_token_uri))
|
||||
arg_and_change_password = true;
|
||||
|
||||
if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX)
|
||||
|
|
|
@ -457,10 +457,6 @@ 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:
|
||||
|
@ -1361,13 +1357,7 @@ 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;
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ int home_prepare_cifs(
|
|||
|
||||
int home_activate_cifs(
|
||||
UserRecord *h,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
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, cache, NULL, &new_home);
|
||||
r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@
|
|||
|
||||
int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
|
||||
|
||||
int home_activate_cifs(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
|
||||
int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
|
||||
int home_create_cifs(UserRecord *h, UserRecord **ret_home);
|
||||
|
|
|
@ -26,7 +26,7 @@ int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *set
|
|||
|
||||
int home_activate_directory(
|
||||
UserRecord *h,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
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, cache, &setup, &header_home);
|
||||
r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_refresh(h, &setup, header_home, cache, NULL, &new_home);
|
||||
r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, 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,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
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, cache, setup, NULL);
|
||||
r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
|
||||
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
|
|
@ -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, PasswordCache *cache, UserRecord **ret_home);
|
||||
int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
|
||||
int home_resize_directory(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
|
||||
int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
/* 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;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/* 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);
|
|
@ -208,7 +208,7 @@ static int fscrypt_slot_try_many(
|
|||
}
|
||||
|
||||
static int fscrypt_setup(
|
||||
const PasswordCache *cache,
|
||||
char **pkcs11_decrypted_passwords,
|
||||
char **password,
|
||||
HomeSetup *setup,
|
||||
void **ret_volume_key,
|
||||
|
@ -230,7 +230,6 @@ 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 */
|
||||
|
@ -257,17 +256,19 @@ static int fscrypt_setup(
|
|||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
|
||||
|
||||
r = -ENOANO;
|
||||
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, password) {
|
||||
r = fscrypt_slot_try_many(
|
||||
list,
|
||||
pkcs11_decrypted_passwords,
|
||||
salt, salt_size,
|
||||
encrypted, encrypted_size,
|
||||
setup->fscrypt_key_descriptor,
|
||||
ret_volume_key, ret_volume_key_size);
|
||||
if (r == -ENOANO)
|
||||
r = fscrypt_slot_try_many(
|
||||
password,
|
||||
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;
|
||||
|
@ -281,7 +282,7 @@ static int fscrypt_setup(
|
|||
int home_prepare_fscrypt(
|
||||
UserRecord *h,
|
||||
bool already_activated,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
HomeSetup *setup) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *volume_key = NULL;
|
||||
|
@ -313,7 +314,7 @@ int home_prepare_fscrypt(
|
|||
memcpy(setup->fscrypt_key_descriptor, policy.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
|
||||
|
||||
r = fscrypt_setup(
|
||||
cache,
|
||||
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
|
||||
h->password,
|
||||
setup,
|
||||
&volume_key,
|
||||
|
@ -583,7 +584,7 @@ int home_create_fscrypt(
|
|||
int home_passwd_fscrypt(
|
||||
UserRecord *h,
|
||||
HomeSetup *setup,
|
||||
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
|
||||
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
|
||||
char **effective_passwords /* new passwords */) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *volume_key = NULL;
|
||||
|
@ -599,7 +600,7 @@ int home_passwd_fscrypt(
|
|||
assert(setup);
|
||||
|
||||
r = fscrypt_setup(
|
||||
cache,
|
||||
pkcs11_decrypted_passwords,
|
||||
h->password,
|
||||
setup,
|
||||
&volume_key,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "homework.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int home_prepare_fscrypt(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup);
|
||||
int home_prepare_fscrypt(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup);
|
||||
int home_create_fscrypt(UserRecord *h, char **effective_passwords, UserRecord **ret_home);
|
||||
|
||||
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);
|
||||
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
|
||||
|
|
|
@ -216,7 +216,7 @@ static int luks_setup(
|
|||
const char *cipher_mode,
|
||||
uint64_t volume_key_size,
|
||||
char **passwords,
|
||||
const PasswordCache *cache,
|
||||
char **pkcs11_decrypted_passwords,
|
||||
bool discard,
|
||||
struct crypt_device **ret,
|
||||
sd_id128_t *ret_found_uuid,
|
||||
|
@ -227,7 +227,6 @@ static int luks_setup(
|
|||
_cleanup_(erase_and_freep) void *vk = NULL;
|
||||
sd_id128_t p;
|
||||
size_t vks;
|
||||
char **list;
|
||||
int r;
|
||||
|
||||
assert(node);
|
||||
|
@ -279,14 +278,12 @@ static int luks_setup(
|
|||
if (!vk)
|
||||
return log_oom();
|
||||
|
||||
r = -ENOKEY;
|
||||
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
|
||||
r = luks_try_passwords(cd, list, vk, &vks);
|
||||
if (r != -ENOKEY)
|
||||
break;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
|
||||
|
||||
|
@ -315,7 +312,7 @@ static int luks_setup(
|
|||
static int luks_open(
|
||||
const char *dm_name,
|
||||
char **passwords,
|
||||
PasswordCache *cache,
|
||||
char **pkcs11_decrypted_passwords,
|
||||
struct crypt_device **ret,
|
||||
sd_id128_t *ret_found_uuid,
|
||||
void **ret_volume_key,
|
||||
|
@ -324,7 +321,6 @@ 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;
|
||||
|
||||
|
@ -365,14 +361,12 @@ static int luks_open(
|
|||
if (!vk)
|
||||
return log_oom();
|
||||
|
||||
r = -ENOKEY;
|
||||
FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, passwords) {
|
||||
r = luks_try_passwords(cd, list, vk, &vks);
|
||||
if (r != -ENOKEY)
|
||||
break;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
|
||||
|
||||
|
@ -628,7 +622,7 @@ static int luks_validate_home_record(
|
|||
struct crypt_device *cd,
|
||||
UserRecord *h,
|
||||
const void *volume_key,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
UserRecord **ret_luks_home_record) {
|
||||
|
||||
int r, token;
|
||||
|
@ -733,7 +727,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, cache, /* strict_verify= */ true);
|
||||
r = user_record_authenticate(lhr, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
assert(r > 0); /* Insist that a password was verified */
|
||||
|
@ -988,7 +982,7 @@ int home_prepare_luks(
|
|||
UserRecord *h,
|
||||
bool already_activated,
|
||||
const char *force_image_path,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
HomeSetup *setup,
|
||||
UserRecord **ret_luks_home) {
|
||||
|
||||
|
@ -1016,7 +1010,7 @@ int home_prepare_luks(
|
|||
|
||||
r = luks_open(setup->dm_name,
|
||||
h->password,
|
||||
cache,
|
||||
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
|
||||
&cd,
|
||||
&found_luks_uuid,
|
||||
&volume_key,
|
||||
|
@ -1024,7 +1018,7 @@ int home_prepare_luks(
|
|||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
|
||||
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1139,7 +1133,7 @@ int home_prepare_luks(
|
|||
h->luks_cipher_mode,
|
||||
h->luks_volume_key_size,
|
||||
h->password,
|
||||
cache,
|
||||
pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
|
||||
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
|
||||
&cd,
|
||||
&found_luks_uuid,
|
||||
|
@ -1150,7 +1144,7 @@ int home_prepare_luks(
|
|||
|
||||
dm_activated = true;
|
||||
|
||||
r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
|
||||
r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
|
@ -1224,7 +1218,7 @@ static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, stru
|
|||
|
||||
int home_activate_luks(
|
||||
UserRecord *h,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *luks_home_record = NULL;
|
||||
|
@ -1256,7 +1250,7 @@ int home_activate_luks(
|
|||
h,
|
||||
false,
|
||||
NULL,
|
||||
cache,
|
||||
pkcs11_decrypted_passwords,
|
||||
&setup,
|
||||
&luks_home_record);
|
||||
if (r < 0)
|
||||
|
@ -1274,7 +1268,7 @@ int home_activate_luks(
|
|||
h,
|
||||
&setup,
|
||||
luks_home_record,
|
||||
cache,
|
||||
pkcs11_decrypted_passwords,
|
||||
&sfs,
|
||||
&new_home);
|
||||
if (r < 0)
|
||||
|
@ -1470,7 +1464,7 @@ static int luks_format(
|
|||
const char *dm_name,
|
||||
sd_id128_t uuid,
|
||||
const char *label,
|
||||
const PasswordCache *cache,
|
||||
char **pkcs11_decrypted_passwords,
|
||||
char **effective_passwords,
|
||||
bool discard,
|
||||
UserRecord *hr,
|
||||
|
@ -1539,8 +1533,7 @@ static int luks_format(
|
|||
|
||||
STRV_FOREACH(pp, effective_passwords) {
|
||||
|
||||
if (strv_contains(cache->pkcs11_passwords, *pp) ||
|
||||
strv_contains(cache->fido2_passwords, *pp)) {
|
||||
if (strv_contains(pkcs11_decrypted_passwords, *pp)) {
|
||||
log_debug("Using minimal PBKDF for slot %i", slot);
|
||||
r = crypt_set_pbkdf_type(cd, &minimal_pbkdf);
|
||||
} else {
|
||||
|
@ -1865,7 +1858,7 @@ static int home_truncate(
|
|||
|
||||
int home_create_luks(
|
||||
UserRecord *h,
|
||||
PasswordCache *cache,
|
||||
char **pkcs11_decrypted_passwords,
|
||||
char **effective_passwords,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
|
@ -2062,7 +2055,7 @@ int home_create_luks(
|
|||
dm_name,
|
||||
luks_uuid,
|
||||
user_record_user_name_and_realm(h),
|
||||
cache,
|
||||
pkcs11_decrypted_passwords,
|
||||
effective_passwords,
|
||||
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
|
||||
h,
|
||||
|
@ -2568,7 +2561,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,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
HomeSetup *setup,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
|
@ -2654,11 +2647,11 @@ int home_resize_luks(
|
|||
}
|
||||
}
|
||||
|
||||
r = home_prepare_luks(h, already_activated, whole_disk, cache, setup, &header_home);
|
||||
r = home_prepare_luks(h, already_activated, whole_disk, pkcs11_decrypted_passwords, 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, cache, &embedded_home, &new_home);
|
||||
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -2862,14 +2855,13 @@ int home_resize_luks(
|
|||
int home_passwd_luks(
|
||||
UserRecord *h,
|
||||
HomeSetup *setup,
|
||||
PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */
|
||||
char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 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);
|
||||
|
@ -2894,14 +2886,12 @@ int home_passwd_luks(
|
|||
if (!volume_key)
|
||||
return log_oom();
|
||||
|
||||
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;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
|
||||
|
||||
|
@ -2921,8 +2911,7 @@ int home_passwd_luks(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (strv_contains(cache->pkcs11_passwords, effective_passwords[i]) ||
|
||||
strv_contains(cache->fido2_passwords, effective_passwords[i])) {
|
||||
if (strv_find(pkcs11_decrypted_passwords, effective_passwords[i])) {
|
||||
log_debug("Using minimal PBKDF for slot %zu", i);
|
||||
r = crypt_set_pbkdf_type(setup->crypt_device, &minimal_pbkdf);
|
||||
} else {
|
||||
|
@ -3019,10 +3008,9 @@ static int luks_try_resume(
|
|||
return -ENOKEY;
|
||||
}
|
||||
|
||||
int home_unlock_luks(UserRecord *h, PasswordCache *cache) {
|
||||
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords) {
|
||||
_cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
char **list;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
@ -3038,14 +3026,12 @@ int home_unlock_luks(UserRecord *h, PasswordCache *cache) {
|
|||
log_info("Discovered used LUKS device %s.", dm_node);
|
||||
crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
|
||||
|
||||
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;
|
||||
}
|
||||
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.");
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to resume LUKS superblock: %m");
|
||||
|
||||
|
|
|
@ -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, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_luks_home);
|
||||
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_activate_luks(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
|
||||
int home_activate_luks(UserRecord *h, char ***pkcs11_decrypted_passwords, 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, PasswordCache *cache, char **effective_passwords, UserRecord **ret_home);
|
||||
int home_create_luks(UserRecord *h, char **pkcs11_decrypted_passwords, char **effective_passwords, UserRecord **ret_home);
|
||||
|
||||
int home_validate_update_luks(UserRecord *h, HomeSetup *setup);
|
||||
|
||||
int home_resize_luks(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
|
||||
int home_resize_luks(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
|
||||
|
||||
int home_passwd_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache, char **effective_passwords);
|
||||
int home_passwd_luks(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
|
||||
|
||||
int home_lock_luks(UserRecord *h);
|
||||
int home_unlock_luks(UserRecord *h, PasswordCache *cache);
|
||||
int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords);
|
||||
|
||||
static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
|
||||
int k;
|
||||
|
|
|
@ -62,10 +62,10 @@ int pkcs11_callback(
|
|||
goto decrypt;
|
||||
}
|
||||
|
||||
if (strv_isempty(data->secret->token_pin))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security token requires PIN.");
|
||||
if (strv_isempty(data->secret->pkcs11_pin))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security Token requires PIN.");
|
||||
|
||||
STRV_FOREACH(i, data->secret->token_pin) {
|
||||
STRV_FOREACH(i, data->secret->pkcs11_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);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#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"
|
||||
|
@ -22,6 +21,7 @@
|
|||
#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,22 +32,14 @@
|
|||
/* 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,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
bool strict_verify) {
|
||||
|
||||
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;
|
||||
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;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
@ -55,14 +47,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 or FIDO2 token is around and can unlock the record.
|
||||
* configured PKCS#11 token is around and can unlock the record.
|
||||
*
|
||||
* 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. */
|
||||
* 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. */
|
||||
|
||||
/* First, let's see if the supplied plain-text passwords work? */
|
||||
r = user_record_test_secret(h, secret);
|
||||
|
@ -78,38 +70,7 @@ int user_record_authenticate(
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Second, test cached PKCS#11 passwords */
|
||||
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
|
||||
char **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) {
|
||||
log_info("Previously acquired PKCS#11 password unlocks user record.");
|
||||
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 */
|
||||
/* Second, 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 = {
|
||||
|
@ -117,6 +78,18 @@ int user_record_authenticate(
|
|||
.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);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
|
||||
if (r > 0) {
|
||||
log_info("Previously acquired PKCS#11 password unlocks user record.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
|
||||
switch (r) {
|
||||
|
@ -153,56 +126,7 @@ int user_record_authenticate(
|
|||
|
||||
log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
|
||||
|
||||
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);
|
||||
r = strv_extend(pkcs11_decrypted_passwords, data.decrypted_password);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
|
@ -223,12 +147,8 @@ 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)
|
||||
|
@ -236,11 +156,10 @@ int user_record_authenticate(
|
|||
if (need_password)
|
||||
return -ENOKEY;
|
||||
|
||||
/* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords were supplied, we cannot
|
||||
* authenticate this reasonably */
|
||||
/* Hmm, this means neither PCKS#11 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/FIDO2 tokens defined, cannot authenticate user record, refusing.");
|
||||
"No hashed passwords and no PKCS#11 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
|
||||
|
@ -311,7 +230,7 @@ int home_setup_undo(HomeSetup *setup) {
|
|||
int home_prepare(
|
||||
UserRecord *h,
|
||||
bool already_activated,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
HomeSetup *setup,
|
||||
UserRecord **ret_header_home) {
|
||||
|
||||
|
@ -330,7 +249,7 @@ int home_prepare(
|
|||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
return home_prepare_luks(h, already_activated, NULL, cache, setup, ret_header_home);
|
||||
return home_prepare_luks(h, already_activated, NULL, pkcs11_decrypted_passwords, setup, ret_header_home);
|
||||
|
||||
case USER_SUBVOLUME:
|
||||
case USER_DIRECTORY:
|
||||
|
@ -338,7 +257,7 @@ int home_prepare(
|
|||
break;
|
||||
|
||||
case USER_FSCRYPT:
|
||||
r = home_prepare_fscrypt(h, already_activated, cache, setup);
|
||||
r = home_prepare_fscrypt(h, already_activated, pkcs11_decrypted_passwords, setup);
|
||||
break;
|
||||
|
||||
case USER_CIFS:
|
||||
|
@ -468,7 +387,7 @@ int home_load_embedded_identity(
|
|||
int root_fd,
|
||||
UserRecord *header_home,
|
||||
UserReconcileMode mode,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
UserRecord **ret_embedded_home,
|
||||
UserRecord **ret_new_home) {
|
||||
|
||||
|
@ -495,7 +414,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, cache, /* strict_verify= */ true);
|
||||
r = user_record_authenticate(embedded_home, h, pkcs11_decrypted_passwords, /* strict_verify= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
assert(r > 0); /* Insist that a password was verified */
|
||||
|
@ -657,7 +576,7 @@ int home_refresh(
|
|||
UserRecord *h,
|
||||
HomeSetup *setup,
|
||||
UserRecord *header_home,
|
||||
PasswordCache *cache,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
struct statfs *ret_statfs,
|
||||
UserRecord **ret_new_home) {
|
||||
|
||||
|
@ -671,7 +590,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, cache, &embedded_home, &new_home);
|
||||
r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -696,7 +615,7 @@ int home_refresh(
|
|||
}
|
||||
|
||||
static int home_activate(UserRecord *h, UserRecord **ret_home) {
|
||||
_cleanup_(password_cache_free) PasswordCache cache = {};
|
||||
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
|
||||
int r;
|
||||
|
||||
|
@ -709,7 +628,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, &cache, /* strict_verify= */ false);
|
||||
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -728,7 +647,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
|
|||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
r = home_activate_luks(h, &cache, &new_home);
|
||||
r = home_activate_luks(h, &pkcs11_decrypted_passwords, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -737,14 +656,14 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
|
|||
case USER_SUBVOLUME:
|
||||
case USER_DIRECTORY:
|
||||
case USER_FSCRYPT:
|
||||
r = home_activate_directory(h, &cache, &new_home);
|
||||
r = home_activate_directory(h, &pkcs11_decrypted_passwords, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case USER_CIFS:
|
||||
r = home_activate_cifs(h, &cache, &new_home);
|
||||
r = home_activate_cifs(h, &pkcs11_decrypted_passwords, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -864,16 +783,15 @@ int home_populate(UserRecord *h, int dir_fd) {
|
|||
|
||||
static int user_record_compile_effective_passwords(
|
||||
UserRecord *h,
|
||||
PasswordCache *cache,
|
||||
char ***ret_effective_passwords) {
|
||||
char ***ret_effective_passwords,
|
||||
char ***ret_pkcs11_decrypted_passwords) {
|
||||
|
||||
_cleanup_(strv_free_erasep) char **effective = NULL;
|
||||
_cleanup_(strv_free_erasep) char **effective = NULL, **pkcs11_passwords = 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
|
||||
|
@ -940,37 +858,11 @@ static int user_record_compile_effective_passwords(
|
|||
return log_oom();
|
||||
}
|
||||
|
||||
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 (ret_pkcs11_decrypted_passwords) {
|
||||
r = strv_extend(&pkcs11_passwords, data.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
|
||||
|
@ -978,6 +870,8 @@ 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;
|
||||
}
|
||||
|
@ -1040,9 +934,8 @@ static int determine_default_storage(UserStorage *ret) {
|
|||
}
|
||||
|
||||
static int home_create(UserRecord *h, UserRecord **ret_home) {
|
||||
_cleanup_(strv_free_erasep) char **effective_passwords = NULL;
|
||||
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_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;
|
||||
|
@ -1054,7 +947,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, &cache, &effective_passwords);
|
||||
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1103,7 +996,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
|
|||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
r = home_create_luks(h, &cache, effective_passwords, &new_home);
|
||||
r = home_create_luks(h, pkcs11_decrypted_passwords, effective_passwords, &new_home);
|
||||
break;
|
||||
|
||||
case USER_DIRECTORY:
|
||||
|
@ -1289,15 +1182,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, &cache, /* strict_verify= */ true);
|
||||
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
assert(r > 0); /* Insist that a password was verified */
|
||||
|
@ -1308,11 +1201,11 @@ static int home_update(UserRecord *h, UserRecord **ret) {
|
|||
|
||||
already_activated = r > 0;
|
||||
|
||||
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
|
||||
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &cache, &embedded_home, &new_home);
|
||||
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1344,7 +1237,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_(password_cache_free) PasswordCache cache = {};
|
||||
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
|
||||
bool already_activated = false;
|
||||
int r;
|
||||
|
||||
|
@ -1354,7 +1247,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, &cache, /* strict_verify= */ true);
|
||||
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
assert(r > 0); /* Insist that a password was verified */
|
||||
|
@ -1368,12 +1261,12 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
|
|||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
return home_resize_luks(h, already_activated, &cache, &setup, ret);
|
||||
return home_resize_luks(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
|
||||
|
||||
case USER_DIRECTORY:
|
||||
case USER_SUBVOLUME:
|
||||
case USER_FSCRYPT:
|
||||
return home_resize_directory(h, already_activated, &cache, &setup, ret);
|
||||
return home_resize_directory(h, already_activated, &pkcs11_decrypted_passwords, &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)));
|
||||
|
@ -1382,9 +1275,8 @@ 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;
|
||||
_cleanup_(strv_free_erasep) char **effective_passwords = NULL, **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;
|
||||
|
||||
|
@ -1394,7 +1286,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, &cache, &effective_passwords);
|
||||
r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1404,24 +1296,24 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
|
|||
|
||||
already_activated = r > 0;
|
||||
|
||||
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
|
||||
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &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, &cache, &embedded_home, &new_home);
|
||||
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
switch (user_record_storage(h)) {
|
||||
|
||||
case USER_LUKS:
|
||||
r = home_passwd_luks(h, &setup, &cache, effective_passwords);
|
||||
r = home_passwd_luks(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case USER_FSCRYPT:
|
||||
r = home_passwd_fscrypt(h, &setup, &cache, effective_passwords);
|
||||
r = home_passwd_fscrypt(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
@ -1459,14 +1351,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_(password_cache_free) PasswordCache cache = {};
|
||||
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
|
||||
bool already_activated = false;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(ret_home);
|
||||
|
||||
r = user_record_authenticate(h, h, &cache, /* strict_verify= */ false);
|
||||
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1476,11 +1368,11 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
|
|||
|
||||
already_activated = r > 0;
|
||||
|
||||
r = home_prepare(h, already_activated, &cache, &setup, &header_home);
|
||||
r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &cache, NULL, &new_home);
|
||||
r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &pkcs11_decrypted_passwords, NULL, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1523,7 +1415,7 @@ static int home_lock(UserRecord *h) {
|
|||
}
|
||||
|
||||
static int home_unlock(UserRecord *h) {
|
||||
_cleanup_(password_cache_free) PasswordCache cache = {};
|
||||
_cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
|
@ -1536,11 +1428,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, &cache, /* strict_verify= */ false);
|
||||
r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords, /* strict_verify= */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_unlock_luks(h, &cache);
|
||||
r = home_unlock_luks(h, &pkcs11_decrypted_passwords);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
@ -1603,12 +1495,10 @@ 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/FIDO2 device found, but PIN is missing to unlock it
|
||||
* ENOANO → suitable PKCS#11 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
|
||||
* 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
|
||||
* EOWNERDEAD → suitable PKCS#11 device found, but its PIN is locked
|
||||
* ENOLCK → suitable PKCS#11 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
|
||||
|
|
|
@ -36,14 +36,6 @@ 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, \
|
||||
|
@ -54,16 +46,16 @@ void password_cache_free(PasswordCache *cache);
|
|||
|
||||
int home_setup_undo(HomeSetup *setup);
|
||||
|
||||
int home_prepare(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_header_home);
|
||||
int home_prepare(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_header_home);
|
||||
|
||||
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_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_populate(UserRecord *h, int dir_fd);
|
||||
|
||||
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_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_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, PasswordCache *cache, bool strict_verify);
|
||||
int user_record_authenticate(UserRecord *h, UserRecord *secret, char ***pkcs11_decrypted_passwords, bool strict_verify);
|
||||
|
||||
int home_sync_and_statfs(int root_fd, struct statfs *ret);
|
||||
|
|
|
@ -14,7 +14,6 @@ systemd_homework_sources = files('''
|
|||
homework-mount.c
|
||||
homework-mount.h
|
||||
homework-pkcs11.h
|
||||
homework-fido2.h
|
||||
homework-quota.c
|
||||
homework-quota.h
|
||||
homework.c
|
||||
|
@ -26,9 +25,6 @@ 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
|
||||
|
@ -69,10 +65,6 @@ 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
|
||||
|
|
|
@ -359,7 +359,7 @@ static int handle_generic_user_record_error(
|
|||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
|
||||
r = user_record_set_pkcs11_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,21 +375,6 @@ 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;
|
||||
|
||||
|
@ -403,7 +388,7 @@ static int handle_generic_user_record_error(
|
|||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
|
||||
r = user_record_set_pkcs11_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;
|
||||
|
@ -422,7 +407,7 @@ static int handle_generic_user_record_error(
|
|||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
|
||||
r = user_record_set_pkcs11_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;
|
||||
|
@ -441,7 +426,7 @@ static int handle_generic_user_record_error(
|
|||
return PAM_AUTHTOK_ERR;
|
||||
}
|
||||
|
||||
r = user_record_set_token_pin(secret, STRV_MAKE(newp), false);
|
||||
r = user_record_set_pkcs11_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;
|
||||
|
|
|
@ -887,7 +887,7 @@ int user_record_set_password(UserRecord *h, char **password, bool prepend) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend) {
|
||||
int user_record_set_pkcs11_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_token_pin(UserRecord *h, char **pin, bool prepend) {
|
|||
if (!e)
|
||||
return -ENOMEM;
|
||||
|
||||
r = strv_extend_strv(&e, h->token_pin, true);
|
||||
r = strv_extend_strv(&e, h->pkcs11_pin, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
strv_uniq(e);
|
||||
|
||||
if (strv_equal(h->token_pin, e))
|
||||
if (strv_equal(h->pkcs11_pin, e))
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
if (strv_equal(h->token_pin, pin))
|
||||
if (strv_equal(h->pkcs11_pin, pin))
|
||||
return 0;
|
||||
|
||||
e = strv_copy(pin);
|
||||
|
@ -922,7 +922,7 @@ int user_record_set_token_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("tokenPin"));
|
||||
r = json_variant_filter(&w, STRV_MAKE("pkcs11Pin"));
|
||||
else {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
|
||||
|
||||
|
@ -932,7 +932,7 @@ int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend) {
|
|||
|
||||
json_variant_sensitive(l);
|
||||
|
||||
r = json_variant_set_field(&w, "tokenPin", l);
|
||||
r = json_variant_set_field(&w, "pkcs11Pin", l);
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
@ -943,7 +943,7 @@ int user_record_set_token_pin(UserRecord *h, char **pin, bool prepend) {
|
|||
if (r < 0)
|
||||
return r;
|
||||
|
||||
strv_free_and_replace(h->token_pin, e);
|
||||
strv_free_and_replace(h->pkcs11_pin, e);
|
||||
|
||||
SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
|
||||
return 0;
|
||||
|
@ -980,34 +980,6 @@ 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;
|
||||
|
@ -1090,22 +1062,12 @@ int user_record_merge_secret(UserRecord *h, UserRecord *secret) {
|
|||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = user_record_set_token_pin(h, secret->token_pin, true);
|
||||
r = user_record_set_pkcs11_pin(h, secret->pkcs11_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);
|
||||
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);
|
||||
r = user_record_set_pkcs11_protected_authentication_path_permitted(h, secret->pkcs11_protected_authentication_path_permitted);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -47,9 +47,8 @@ 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_token_pin(UserRecord *h, char **pin, bool prepend);
|
||||
int user_record_set_pkcs11_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);
|
||||
|
|
|
@ -120,8 +120,6 @@ 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),
|
||||
|
|
|
@ -100,8 +100,6 @@
|
|||
#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"
|
||||
|
|
|
@ -151,28 +151,6 @@ 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,
|
||||
|
@ -187,8 +165,9 @@ 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, r;
|
||||
int uri_result;
|
||||
CK_RV rv;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(token_info);
|
||||
|
@ -232,19 +211,8 @@ int pkcs11_token_login(
|
|||
|
||||
for (unsigned tries = 0; tries < 3; tries++) {
|
||||
_cleanup_strv_free_erase_ char **passwords = NULL;
|
||||
char **i, *e;
|
||||
|
||||
e = getenv("PIN");
|
||||
if (e) {
|
||||
passwords = strv_new(e);
|
||||
if (!passwords)
|
||||
return log_oom();
|
||||
|
||||
string_erase(e);
|
||||
if (unsetenv("PIN") < 0)
|
||||
return log_error_errno(errno, "Failed to unset $PIN: %m");
|
||||
} else {
|
||||
_cleanup_free_ char *text = NULL;
|
||||
char **i, *e;
|
||||
|
||||
if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY))
|
||||
r = asprintf(&text,
|
||||
|
@ -265,6 +233,16 @@ int pkcs11_token_login(
|
|||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
e = getenv("PIN");
|
||||
if (e) {
|
||||
passwords = strv_new(e);
|
||||
if (!passwords)
|
||||
return log_oom();
|
||||
|
||||
string_erase(e);
|
||||
if (unsetenv("PIN") < 0)
|
||||
return log_error_errno(errno, "Failed to unset $PIN: %m");
|
||||
} else {
|
||||
/* 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)
|
||||
|
@ -724,6 +702,7 @@ 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)
|
||||
|
@ -761,6 +740,7 @@ 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. */
|
||||
|
@ -806,7 +786,7 @@ static int slot_process(
|
|||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (search_uri && !p11_kit_uri_match_token_info(search_uri, &token_info)) {
|
||||
if (!p11_kit_uri_match_token_info(search_uri, &token_info)) {
|
||||
log_debug("Found non-matching token with URI %s.", token_uri_string);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
@ -840,6 +820,7 @@ 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
|
||||
|
@ -902,14 +883,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. */
|
||||
|
||||
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)
|
||||
|
|
|
@ -27,8 +27,6 @@ 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);
|
||||
|
||||
|
|
|
@ -472,13 +472,10 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
|||
|
||||
STRV_FOREACH(i, hr->pkcs11_token_uri)
|
||||
printf(i == hr->pkcs11_token_uri ?
|
||||
"PKCS11 Token: %s\n" :
|
||||
" Sec. 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",
|
||||
|
|
|
@ -81,7 +81,6 @@ 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;
|
||||
|
@ -96,22 +95,6 @@ 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;
|
||||
|
@ -137,7 +120,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->token_pin);
|
||||
strv_free_erase(h->pkcs11_pin);
|
||||
|
||||
free(h->cifs_service);
|
||||
free(h->cifs_user_name);
|
||||
|
@ -164,11 +147,6 @@ 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);
|
||||
|
@ -642,10 +620,8 @@ 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 },
|
||||
{ "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 },
|
||||
{ "pkcs11Pin", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(UserRecord, pkcs11_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 },
|
||||
{},
|
||||
};
|
||||
|
||||
|
@ -730,7 +706,7 @@ static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, Json
|
|||
int r;
|
||||
|
||||
if (json_variant_is_null(variant)) {
|
||||
k->data = erase_and_free(k->data);
|
||||
k->data = mfree(k->data);
|
||||
k->size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
@ -790,133 +766,6 @@ 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[] = {
|
||||
|
@ -924,7 +773,6 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa
|
|||
{ "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 },
|
||||
{},
|
||||
};
|
||||
|
||||
|
@ -1118,7 +966,6 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
|
|||
{ "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 },
|
||||
{},
|
||||
};
|
||||
|
||||
|
@ -1467,7 +1314,6 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
|
|||
{ "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 },
|
||||
|
@ -1838,9 +1684,6 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -189,23 +189,6 @@ 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;
|
||||
|
@ -256,7 +239,7 @@ typedef struct UserRecord {
|
|||
char **hashed_password;
|
||||
char **ssh_authorized_keys;
|
||||
char **password;
|
||||
char **token_pin;
|
||||
char **pkcs11_pin;
|
||||
|
||||
char *cifs_domain;
|
||||
char *cifs_user_name;
|
||||
|
@ -326,12 +309,6 @@ 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;
|
||||
|
||||
|
|
|
@ -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_TOUCH + 1 == _SPECIAL_GLYPH_MAX);
|
||||
assert_cc(SPECIAL_GLYPH_LOCK_AND_KEY + 1 == _SPECIAL_GLYPH_MAX);
|
||||
|
||||
log_info("/* %s */", __func__);
|
||||
|
||||
|
@ -116,7 +116,6 @@ 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[]) {
|
||||
|
|
|
@ -410,85 +410,6 @@ 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);
|
||||
|
||||
|
@ -507,7 +428,6 @@ int main(int argc, char *argv[]) {
|
|||
test_physical_memory_scale();
|
||||
test_system_tasks_max();
|
||||
test_system_tasks_max_scale();
|
||||
test_foreach_pointer();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue