mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #15442 from poettering/fido2
add fido2 authentication support to homed
This commit is contained in:
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/homed: also support FIDO2 HMAC password logic for unlocking
|
||||
devices. (see: https://github.com/mjec/fido2-hmac-secret)
|
||||
* cryptsetup: support FIDO2 tokens for deriving keys (i.e. do what homed can do
|
||||
also in plain cryptsetup)
|
||||
|
||||
* systemd-gpt-auto should probably set x-systemd.growfs on the mounts it
|
||||
creates
|
||||
|
||||
@@ -546,6 +546,11 @@ below). It's undefined how precise the URI is: during log-in it is tested
|
||||
against all plugged in security tokens and if there's exactly one matching
|
||||
private key found with it it is used.
|
||||
|
||||
`fido2HmacCredential` → An array of strings, each with a Base64-encoded FIDO2
|
||||
credential ID that shell be used for authentication with FIDO2 devices that
|
||||
implement the `hmac-secret` extension. The salt to pass to the FIDO2 device is
|
||||
found in `fido2HmacSalt`.
|
||||
|
||||
`privileged` → An object, which contains the fields of the `privileged` section
|
||||
of the user record, see below.
|
||||
|
||||
@@ -594,7 +599,7 @@ as the lines in the traditional `~/.ssh/authorized_key` file.
|
||||
|
||||
`pkcs11EncryptedKey` → An array of objects. Each element of the array should be
|
||||
an object consisting of three string fields: `uri` shall contain a PKCS#11
|
||||
security token URI, `data` shall contain a Base64 encoded encrypted key and
|
||||
security token URI, `data` shall contain a Base64-encoded encrypted key and
|
||||
`hashedPassword` shall contain a UNIX password hash to test the key
|
||||
against. Authenticating with a security token against this account shall work
|
||||
as follows: the encrypted secret key is converted from its Base64
|
||||
@@ -602,13 +607,29 @@ representation into binary, then decrypted with the PKCS#11 `C_Decrypt()`
|
||||
function of the PKCS#11 module referenced by the specified URI, using the
|
||||
private key found on the same token. The resulting decrypted key is then
|
||||
Base64-encoded and tested against the specified UNIX hashed password. The
|
||||
Base64-enceded decrypted key may also be used to unlock further resources
|
||||
Base64-encoded decrypted key may also be used to unlock further resources
|
||||
during log-in, for example the LUKS or `fscrypt` storage backend. It is
|
||||
generally recommended that for each entry in `pkcs11EncryptedKey` there's also
|
||||
a matching one in `pkcs11TokenUri` and vice versa, with the same URI, appearing
|
||||
in the same order, but this should not be required by applications processing
|
||||
user records.
|
||||
|
||||
`fido2HmacSalt` → An array of objects, implementing authentication support with
|
||||
FIDO2 devices that implement the `hmac-secret` extension. Each element of the
|
||||
array should be an object consisting of three string fields: `credential`,
|
||||
`salt`, `hashedPassword`. The first two shall contain Base64-encoded binary
|
||||
data: the FIDO2 credential ID and the salt value to pass to the FIDO2
|
||||
device. During authentication this salt along with the credential ID is sent to
|
||||
the FIDO2 token, which will HMAC hash the salt with its internal secret key and
|
||||
return the result. This resulting binary key should then be Base64-encoded and
|
||||
used as string password for the further layers of the stack. The
|
||||
`hashedPassword` field of the `fido2HmacSalt` field shall be a UNIX password
|
||||
hash to test this derived secret key against for authentication. It is
|
||||
generally recommended that for each entry in `fido2HmacSalt` there's also a
|
||||
matching one in `fido2HmacCredential`, and vice versa, with the same credential
|
||||
ID, appearing in the same order, but this should not be required by
|
||||
applications processing user recrods.
|
||||
|
||||
## Fields in the `perMachine` section
|
||||
|
||||
As mentioned, the `perMachine` section contains settings that shall apply to
|
||||
@@ -652,13 +673,13 @@ that may be used in this section are identical to the equally named ones in the
|
||||
`mountNoDevices`, `mountNoSuid`, `mountNoExecute`, `cifsDomain`,
|
||||
`cifsUserName`, `cifsService`, `imagePath`, `uid`, `gid`, `memberOf`,
|
||||
`fileSystemType`, `partitionUuid`, `luksUuid`, `fileSystemUuid`, `luksDiscard`,
|
||||
`luksOfflineDiscard`, `luksOfflineDiscard`, `luksCipher`, `luksCipherMode`,
|
||||
`luksVolumeKeySize`, `luksPbkdfHashAlgorithm`, `luksPbkdfType`,
|
||||
`luksPbkdfTimeCostUSec`, `luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`,
|
||||
`rateLimitIntervalUSec`, `rateLimitBurst`, `enforcePasswordPolicy`,
|
||||
`autoLogin`, `stopDelayUSec`, `killProcesses`, `passwordChangeMinUSec`,
|
||||
`passwordChangeMaxUSec`, `passwordChangeWarnUSec`,
|
||||
`passwordChangeInactiveUSec`, `passwordChangeNow`, `pkcs11TokenUri`.
|
||||
`luksOfflineDiscard`, `luksCipher`, `luksCipherMode`, `luksVolumeKeySize`,
|
||||
`luksPbkdfHashAlgorithm`, `luksPbkdfType`, `luksPbkdfTimeCostUSec`,
|
||||
`luksPbkdfMemoryCost`, `luksPbkdfParallelThreads`, `rateLimitIntervalUSec`,
|
||||
`rateLimitBurst`, `enforcePasswordPolicy`, `autoLogin`, `stopDelayUSec`,
|
||||
`killProcesses`, `passwordChangeMinUSec`, `passwordChangeMaxUSec`,
|
||||
`passwordChangeWarnUSec`, `passwordChangeInactiveUSec`, `passwordChangeNow`,
|
||||
`pkcs11TokenUri`, `fido2HmacCredential`.
|
||||
|
||||
## Fields in the `binding` section
|
||||
|
||||
@@ -810,7 +831,7 @@ public key.
|
||||
The `signature` field in the top-level user record object is an array of
|
||||
objects. Each object encapsulates one signature and has two fields: `data` and
|
||||
`key` (both are strings). The `data` field contains the actual signature,
|
||||
encoded in base64, the `key` field contains a copy of the public key whose
|
||||
encoded in Base64, the `key` field contains a copy of the public key whose
|
||||
private key was used to make the signature, in PEM format. Currently only
|
||||
signatures with Ed25519 keys are defined.
|
||||
|
||||
@@ -864,13 +885,20 @@ The `secret` field of the top-level user record contains the following fields:
|
||||
|
||||
`password` → an array of strings, each containing a plain text password.
|
||||
|
||||
`pkcs11Pin` → an array of strings, each containing a plain text PIN, suitable
|
||||
for unlocking PKCS#11 security tokens that require that.
|
||||
`tokenPin` → an array of strings, each containing a plain text PIN, suitable
|
||||
for unlocking security tokens that require that. (The field `pkcs11Pin` should
|
||||
be considered a compatibility alias for this field, and merged with `tokenPin`
|
||||
in case both are set.)
|
||||
|
||||
`pkcs11ProtectedAuthenticationPathPermitted` → a boolean. If set to true allows
|
||||
the receiver to use the PKCS#11 "protected authentication path" (i.e. a
|
||||
physical button/touch element on the security token) for authenticating the
|
||||
user. If false or unset authentication this way shall not be attempted.
|
||||
user. If false or unset, authentication this way shall not be attempted.
|
||||
|
||||
`fido2UserPresencePermitted` → a boolean. If set to true allows the receiver to
|
||||
use the FIDO2 "user presence" flag. This is similar to the concept of
|
||||
`pkcs11ProtectedAuthenticationPathPermitted`, but exposes the FIDO2 concept
|
||||
behind it. If false or unset authentication this way shall not be attempted.
|
||||
|
||||
## Mapping to `struct passwd` and `struct spwd`
|
||||
|
||||
|
||||
@@ -332,7 +332,49 @@
|
||||
then generated, encrypted with the public key of the X.509 certificate, and stored as part of the
|
||||
user record. At login time it is decrypted with the PKCS#11 module and then used to unlock the
|
||||
account and associated resources. See below for an example how to set up authentication with security
|
||||
token.</para></listitem>
|
||||
token.</para>
|
||||
|
||||
<para>Instead of a valid PKCS#11 URI, the special strings <literal>list</literal> and
|
||||
<literal>auto</literal> may be specified. If <literal>list</literal> is passed, a brief table of
|
||||
suitable, currently plugged in PKCS#11 hardware tokens is shown, along with their URIs. If
|
||||
<literal>auto</literal> is passed, a suitable PKCS#11 hardware token is automatically selected (this
|
||||
operation will fail if there isn't exactly one suitable token discovered). The latter is a useful
|
||||
shortcut for the most common case where a single PKCS#11 hardware token is plugged in.</para>
|
||||
|
||||
<para>Note that many hardware security tokens implement both PKCS#11/PIV and FIDO2 with the
|
||||
<literal>hmac-secret</literal> extension (for example: the YubiKey 5 series), as supported with the
|
||||
<option>--fido2-device=</option> option below. Both mechanisms are similarly powerful, though FIDO2
|
||||
is the more modern technology. PKCS#11/PIV tokens have the benefit of being recognizable before
|
||||
authentication and hence can be used for implying the user identity to use for logging in, which
|
||||
FIDO2 does not allow. PKCS#11/PIV devices generally require initialization (i.e. storing a
|
||||
private/public key pair on them, see example below) before they can be used; FIDO2 security tokens
|
||||
generally do not required that, and work out of the box.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--fido2-device=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>Takes a path to a Linux <literal>hidraw</literal> device
|
||||
(e.g. <filename>/dev/hidraw1</filename>), referring to a FIDO2 security token implementing the
|
||||
<literal>hmac-secret</literal> extension, that shall be able to unlock the user account. If used, a
|
||||
random salt value is generated on the host, which is passed to the FIDO2 device, which calculates a
|
||||
HMAC hash of it, keyed by its internal secret key. The result is then used as key for unlocking the
|
||||
user account. The random salt is included in the user record, so that whenever authentication is
|
||||
needed it can be passed again to the FIDO2 token, to retrieve the actual key.</para>
|
||||
|
||||
<para>Instead of a valid path to a FIDO2 <literal>hidraw</literal> device the special strings
|
||||
<literal>list</literal> and <literal>auto</literal> may be specified. If <literal>list</literal> is
|
||||
passed, a brief table of suitable discovered FIDO2 devices is shown. If <literal>auto</literal> is
|
||||
passed, a suitable FIDO2 token is automatically selected, if exactly one is discovered. The latter is
|
||||
a useful shortcut for the most common case where a single FIDO2 hardware token is plugged in.</para>
|
||||
|
||||
<para>Note that FIDO2 devices suitable for this option must implement the
|
||||
<literal>hmac-secret</literal> extension. Most current devices (such as the YubiKey 5 series) do. If
|
||||
the extension is not implemented the device cannot be used for unlocking home directories.</para>
|
||||
|
||||
<para>Note that many hardware security tokens implement both FIDO2 and PKCS#11/PIV (and thus may be
|
||||
used with either <option>--fido2-device=</option> or <option>--pkcs11-token-uri=</option>), for a
|
||||
discussion see above.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
@@ -810,7 +852,7 @@
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Set up authentication with a YubiKey security token:</title>
|
||||
<title>Set up authentication with a YubiKey security token using PKCS#11/PIV:</title>
|
||||
|
||||
<programlisting># Clear the Yubikey from any old keys (careful!)
|
||||
ykman piv reset
|
||||
@@ -821,16 +863,18 @@ ykman piv generate-key -a RSA2048 9d pubkey.pem
|
||||
# Create a self-signed certificate from this public key, and store it on the device.
|
||||
ykman piv generate-certificate --subject "Knobelei" 9d pubkey.pem
|
||||
|
||||
# We don't need the publibc key on disk anymore
|
||||
# We don't need the public key on disk anymore
|
||||
rm pubkey.pem
|
||||
|
||||
# Check if the newly create key on the Yubikey shows up as token in PKCS#11. Have a look at the output, and
|
||||
# copy the resulting token URI to the clipboard.
|
||||
p11tool --list-tokens
|
||||
# Allow the security token to unlock the account of user 'lafcadio'.
|
||||
homectl update lafcadio --pkcs11-token-uri=auto</programlisting>
|
||||
</example>
|
||||
|
||||
# Allow the security token referenced by the determined PKCS#11 URI to unlock the account of user
|
||||
# 'lafcadio'. (Replace the '…' by the URI from the clipboard.)
|
||||
homectl update lafcadio --pkcs11-token-uri=…</programlisting>
|
||||
<example>
|
||||
<title>Set up authentication with a FIDO2 security token:</title>
|
||||
|
||||
<programlisting># Allow a FIDO2 security token to unlock the account of user 'nihilbaxter'.
|
||||
homectl update nihilbaxter --fido2-device=auto</programlisting>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
|
||||
16
meson.build
16
meson.build
@@ -1153,6 +1153,17 @@ else
|
||||
endif
|
||||
conf.set10('HAVE_P11KIT', have)
|
||||
|
||||
want_libfido2 = get_option('libfido2')
|
||||
if want_libfido2 != 'false' and not skip_deps
|
||||
libfido2 = dependency('libfido2',
|
||||
required : want_libfido2 == 'true')
|
||||
have = libfido2.found()
|
||||
else
|
||||
have = false
|
||||
libfido2 = []
|
||||
endif
|
||||
conf.set10('HAVE_LIBFIDO2', have)
|
||||
|
||||
want_elfutils = get_option('elfutils')
|
||||
if want_elfutils != 'false' and not skip_deps
|
||||
libdw = dependency('libdw',
|
||||
@@ -2146,7 +2157,8 @@ if conf.get('ENABLE_HOMED') == 1
|
||||
libcrypt,
|
||||
libopenssl,
|
||||
libfdisk,
|
||||
libp11kit],
|
||||
libp11kit,
|
||||
libfido2],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
@@ -2173,6 +2185,7 @@ if conf.get('ENABLE_HOMED') == 1
|
||||
libcrypt,
|
||||
libopenssl,
|
||||
libp11kit,
|
||||
libfido2,
|
||||
libpwquality],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
@@ -3575,6 +3588,7 @@ foreach tuple : [
|
||||
['pwquality'],
|
||||
['libfdisk'],
|
||||
['p11kit'],
|
||||
['libfido2'],
|
||||
['AUDIT'],
|
||||
['IMA'],
|
||||
['AppArmor'],
|
||||
|
||||
@@ -312,6 +312,8 @@ option('openssl', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'openssl support')
|
||||
option('p11kit', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'p11kit support')
|
||||
option('libfido2', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'FIDO2 support')
|
||||
option('elfutils', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
description : 'elfutils support')
|
||||
option('zlib', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||
|
||||
@@ -373,7 +373,8 @@ const char *special_glyph(SpecialGlyph code) {
|
||||
[SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY] = ":-(",
|
||||
[SPECIAL_GLYPH_UNHAPPY_SMILEY] = ":-{",
|
||||
[SPECIAL_GLYPH_DEPRESSED_SMILEY] = ":-[",
|
||||
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,"
|
||||
[SPECIAL_GLYPH_LOCK_AND_KEY] = "o-,",
|
||||
[SPECIAL_GLYPH_TOUCH] = "O=", /* Yeah, not very convincing, can you do it better? */
|
||||
},
|
||||
|
||||
/* UTF-8 */
|
||||
@@ -415,6 +416,9 @@ const char *special_glyph(SpecialGlyph code) {
|
||||
|
||||
/* This emoji is a single character cell glyph in Unicode, and three in ASCII */
|
||||
[SPECIAL_GLYPH_LOCK_AND_KEY] = "\360\237\224\220", /* 🔐 (actually called: CLOSED LOCK WITH KEY) */
|
||||
|
||||
/* This emoji is a single character cell glyph in Unicode, and two in ASCII */
|
||||
[SPECIAL_GLYPH_TOUCH] = "\360\237\221\206", /* 👆 (actually called: BACKHAND INDEX POINTING UP */
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ typedef enum {
|
||||
SPECIAL_GLYPH_UNHAPPY_SMILEY,
|
||||
SPECIAL_GLYPH_DEPRESSED_SMILEY,
|
||||
SPECIAL_GLYPH_LOCK_AND_KEY,
|
||||
_SPECIAL_GLYPH_MAX
|
||||
SPECIAL_GLYPH_TOUCH,
|
||||
_SPECIAL_GLYPH_MAX,
|
||||
} SpecialGlyph;
|
||||
|
||||
const char *special_glyph(SpecialGlyph code) _const_;
|
||||
|
||||
@@ -538,6 +538,12 @@ static inline int __coverity_check_and_return__(int condition) {
|
||||
(y) = (_t); \
|
||||
} while (false)
|
||||
|
||||
/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses (void*) -1 as internal marker for EOL. */
|
||||
#define FOREACH_POINTER(p, x, ...) \
|
||||
for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, (void*) -1 }; \
|
||||
p != (typeof(p)) (void*) -1; \
|
||||
p = *(++_l))
|
||||
|
||||
/* Define C11 thread_local attribute even on older gcc compiler
|
||||
* version */
|
||||
#ifndef thread_local
|
||||
|
||||
539
src/home/homectl-fido2.c
Normal file
539
src/home/homectl-fido2.c
Normal file
File diff suppressed because it is too large
Load Diff
10
src/home/homectl-fido2.h
Normal file
10
src/home/homectl-fido2.h
Normal file
@@ -0,0 +1,10 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "json.h"
|
||||
|
||||
int identity_add_fido2_parameters(JsonVariant **v, const char *device);
|
||||
|
||||
int list_fido2_devices(void);
|
||||
|
||||
int find_fido2_auto(char **ret);
|
||||
480
src/home/homectl-pkcs11.c
Normal file
480
src/home/homectl-pkcs11.c
Normal file
@@ -0,0 +1,480 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "errno-util.h"
|
||||
#include "format-table.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "homectl-pkcs11.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "random-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
struct pkcs11_callback_data {
|
||||
char *pin_used;
|
||||
X509 *cert;
|
||||
};
|
||||
|
||||
static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
|
||||
erase_and_free(data->pin_used);
|
||||
X509_free(data->cert);
|
||||
}
|
||||
|
||||
#if HAVE_P11KIT
|
||||
static int pkcs11_callback(
|
||||
CK_FUNCTION_LIST *m,
|
||||
CK_SESSION_HANDLE session,
|
||||
CK_SLOT_ID slot_id,
|
||||
const CK_SLOT_INFO *slot_info,
|
||||
const CK_TOKEN_INFO *token_info,
|
||||
P11KitUri *uri,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(erase_and_freep) char *pin_used = NULL;
|
||||
struct pkcs11_callback_data *data = userdata;
|
||||
CK_OBJECT_HANDLE object;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(slot_info);
|
||||
assert(token_info);
|
||||
assert(uri);
|
||||
assert(data);
|
||||
|
||||
/* Called for every token matching our URI */
|
||||
|
||||
r = pkcs11_token_login(m, session, slot_id, token_info, "home directory operation", "user-home", "pkcs11-pin", UINT64_MAX, &pin_used);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Let's read some random data off the token and write it to the kernel pool before we generate our
|
||||
* random key from it. This way we can claim the quality of the RNG is at least as good as the
|
||||
* kernel's and the token's pool */
|
||||
(void) pkcs11_token_acquire_rng(m, session);
|
||||
|
||||
data->pin_used = TAKE_PTR(pin_used);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int acquire_pkcs11_certificate(
|
||||
const char *uri,
|
||||
X509 **ret_cert,
|
||||
char **ret_pin_used) {
|
||||
|
||||
#if HAVE_P11KIT
|
||||
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {};
|
||||
int r;
|
||||
|
||||
r = pkcs11_find_token(uri, pkcs11_callback, &data);
|
||||
if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
|
||||
return log_error_errno(ENXIO, "Specified PKCS#11 token with URI '%s' not found.", uri);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret_cert = TAKE_PTR(data.cert);
|
||||
*ret_pin_used = TAKE_PTR(data.pin_used);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int encrypt_bytes(
|
||||
EVP_PKEY *pkey,
|
||||
const void *decrypted_key,
|
||||
size_t decrypted_key_size,
|
||||
void **ret_encrypt_key,
|
||||
size_t *ret_encrypt_key_size) {
|
||||
|
||||
_cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
|
||||
_cleanup_free_ void *b = NULL;
|
||||
size_t l;
|
||||
|
||||
ctx = EVP_PKEY_CTX_new(pkey, NULL);
|
||||
if (!ctx)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context");
|
||||
|
||||
if (EVP_PKEY_encrypt_init(ctx) <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context");
|
||||
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding");
|
||||
|
||||
if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
|
||||
|
||||
b = malloc(l);
|
||||
if (!b)
|
||||
return log_oom();
|
||||
|
||||
if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
|
||||
|
||||
*ret_encrypt_key = TAKE_PTR(b);
|
||||
*ret_encrypt_key_size = l;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_pkcs11_encrypted_key(
|
||||
JsonVariant **v,
|
||||
const char *uri,
|
||||
const void *encrypted_key, size_t encrypted_key_size,
|
||||
const void *decrypted_key, size_t decrypted_key_size) {
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
|
||||
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
|
||||
_cleanup_free_ char *salt = NULL;
|
||||
struct crypt_data cd = {};
|
||||
char *k;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
assert(uri);
|
||||
assert(encrypted_key);
|
||||
assert(encrypted_key_size > 0);
|
||||
assert(decrypted_key);
|
||||
assert(decrypted_key_size > 0);
|
||||
|
||||
r = make_salt(&salt);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate salt: %m");
|
||||
|
||||
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
|
||||
* expect a NUL terminated string, and we use a binary key */
|
||||
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to base64 encode secret key: %m");
|
||||
|
||||
errno = 0;
|
||||
k = crypt_r(base64_encoded, salt, &cd);
|
||||
if (!k)
|
||||
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
|
||||
|
||||
r = json_build(&e, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
|
||||
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
|
||||
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
|
||||
|
||||
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
|
||||
l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey"));
|
||||
|
||||
r = json_variant_append_array(&l, e);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m");
|
||||
|
||||
r = json_variant_set_field(&w, "pkcs11EncryptedKey", l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %m");
|
||||
|
||||
r = json_variant_set_field(v, "privileged", w);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update privileged field: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_pkcs11_token_uri(JsonVariant **v, const char *uri) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
assert(uri);
|
||||
|
||||
w = json_variant_ref(json_variant_by_key(*v, "pkcs11TokenUri"));
|
||||
if (w) {
|
||||
r = json_variant_strv(w, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse PKCS#11 token list: %m");
|
||||
|
||||
if (strv_contains(l, uri))
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = strv_extend(&l, uri);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
w = json_variant_unref(w);
|
||||
r = json_variant_new_array_strv(&w, l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create PKCS#11 token URI JSON: %m");
|
||||
|
||||
r = json_variant_set_field(v, "pkcs11TokenUri", w);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update PKCS#11 token URI list: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int identity_add_token_pin(JsonVariant **v, const char *pin) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
|
||||
_cleanup_(strv_free_erasep) char **pins = NULL;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
|
||||
if (isempty(pin))
|
||||
return 0;
|
||||
|
||||
w = json_variant_ref(json_variant_by_key(*v, "secret"));
|
||||
l = json_variant_ref(json_variant_by_key(w, "tokenPin"));
|
||||
|
||||
r = json_variant_strv(l, &pins);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to convert PIN array: %m");
|
||||
|
||||
if (strv_find(pins, pin))
|
||||
return 0;
|
||||
|
||||
r = strv_extend(&pins, pin);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
strv_uniq(pins);
|
||||
|
||||
l = json_variant_unref(l);
|
||||
|
||||
r = json_variant_new_array_strv(&l, pins);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate new PIN array JSON: %m");
|
||||
|
||||
json_variant_sensitive(l);
|
||||
|
||||
r = json_variant_set_field(&w, "tokenPin", l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update PIN field: %m");
|
||||
|
||||
r = json_variant_set_field(v, "secret", w);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update secret object: %m");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) {
|
||||
_cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL;
|
||||
_cleanup_(erase_and_freep) char *pin = NULL;
|
||||
size_t decrypted_key_size, encrypted_key_size;
|
||||
_cleanup_(X509_freep) X509 *cert = NULL;
|
||||
EVP_PKEY *pkey;
|
||||
RSA *rsa;
|
||||
int bits;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
|
||||
r = acquire_pkcs11_certificate(uri, &cert, &pin);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pkey = X509_get0_pubkey(cert);
|
||||
if (!pkey)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
|
||||
|
||||
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
|
||||
|
||||
rsa = EVP_PKEY_get0_RSA(pkey);
|
||||
if (!rsa)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire RSA public key from X.509 certificate.");
|
||||
|
||||
bits = RSA_bits(rsa);
|
||||
log_debug("Bits in RSA key: %i", bits);
|
||||
|
||||
/* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
|
||||
* generate a random key half the size of the RSA length */
|
||||
decrypted_key_size = bits / 8 / 2;
|
||||
|
||||
if (decrypted_key_size < 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
|
||||
|
||||
log_debug("Generating %zu bytes random key.", decrypted_key_size);
|
||||
|
||||
decrypted_key = malloc(decrypted_key_size);
|
||||
if (!decrypted_key)
|
||||
return log_oom();
|
||||
|
||||
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate random key: %m");
|
||||
|
||||
r = encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to encrypt key: %m");
|
||||
|
||||
/* Add the token URI to the public part of the record. */
|
||||
r = add_pkcs11_token_uri(v, uri);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Include the encrypted version of the random key we just generated in the privileged part of the record */
|
||||
r = add_pkcs11_encrypted_key(
|
||||
v,
|
||||
uri,
|
||||
encrypted_key, encrypted_key_size,
|
||||
decrypted_key, decrypted_key_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
|
||||
* can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
|
||||
* fscrypt. */
|
||||
r = identity_add_token_pin(v, pin);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_P11KIT
|
||||
static int list_callback(
|
||||
CK_FUNCTION_LIST *m,
|
||||
CK_SESSION_HANDLE session,
|
||||
CK_SLOT_ID slot_id,
|
||||
const CK_SLOT_INFO *slot_info,
|
||||
const CK_TOKEN_INFO *token_info,
|
||||
P11KitUri *uri,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_free_ char *token_uri_string = NULL, *token_label = NULL, *token_manufacturer_id = NULL, *token_model = NULL;
|
||||
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
|
||||
Table *t = userdata;
|
||||
int uri_result, r;
|
||||
|
||||
assert(slot_info);
|
||||
assert(token_info);
|
||||
|
||||
/* We only care about hardware devices here with a token inserted. Let's filter everything else
|
||||
* out. (Note that the user can explicitly specify non-hardware tokens if they like, but during
|
||||
* enumeration we'll filter those, since software tokens are typically the system certificate store
|
||||
* and such, and it's typically not what people want to bind their home directories to.) */
|
||||
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
|
||||
return -EAGAIN;
|
||||
|
||||
token_label = pkcs11_token_label(token_info);
|
||||
if (!token_label)
|
||||
return log_oom();
|
||||
|
||||
token_manufacturer_id = pkcs11_token_manufacturer_id(token_info);
|
||||
if (!token_manufacturer_id)
|
||||
return log_oom();
|
||||
|
||||
token_model = pkcs11_token_model(token_info);
|
||||
if (!token_model)
|
||||
return log_oom();
|
||||
|
||||
token_uri = uri_from_token_info(token_info);
|
||||
if (!token_uri)
|
||||
return log_oom();
|
||||
|
||||
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, &token_uri_string);
|
||||
if (uri_result != P11_KIT_URI_OK)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
|
||||
|
||||
r = table_add_many(
|
||||
t,
|
||||
TABLE_STRING, token_uri_string,
|
||||
TABLE_STRING, token_label,
|
||||
TABLE_STRING, token_manufacturer_id,
|
||||
TABLE_STRING, token_model);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
|
||||
return -EAGAIN; /* keep scanning */
|
||||
}
|
||||
#endif
|
||||
|
||||
int list_pkcs11_tokens(void) {
|
||||
#if HAVE_P11KIT
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
int r;
|
||||
|
||||
t = table_new("uri", "label", "manufacturer", "model");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
r = pkcs11_find_token(NULL, list_callback, t);
|
||||
if (r < 0 && r != -EAGAIN)
|
||||
return r;
|
||||
|
||||
if (table_get_rows(t) <= 1) {
|
||||
log_info("No suitable PKCS#11 tokens found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = table_print(t, stdout);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to show device table: %m");
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_P11KIT
|
||||
static int auto_callback(
|
||||
CK_FUNCTION_LIST *m,
|
||||
CK_SESSION_HANDLE session,
|
||||
CK_SLOT_ID slot_id,
|
||||
const CK_SLOT_INFO *slot_info,
|
||||
const CK_TOKEN_INFO *token_info,
|
||||
P11KitUri *uri,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(p11_kit_uri_freep) P11KitUri *token_uri = NULL;
|
||||
char **t = userdata;
|
||||
int uri_result;
|
||||
|
||||
assert(slot_info);
|
||||
assert(token_info);
|
||||
|
||||
if (!FLAGS_SET(token_info->flags, CKF_HW_SLOT|CKF_TOKEN_PRESENT))
|
||||
return -EAGAIN;
|
||||
|
||||
if (*t)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
|
||||
"More than one suitable PKCS#11 token found.");
|
||||
|
||||
token_uri = uri_from_token_info(token_info);
|
||||
if (!token_uri)
|
||||
return log_oom();
|
||||
|
||||
uri_result = p11_kit_uri_format(token_uri, P11_KIT_URI_FOR_ANY, t);
|
||||
if (uri_result != P11_KIT_URI_OK)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result));
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int find_pkcs11_token_auto(char **ret) {
|
||||
#if HAVE_P11KIT
|
||||
int r;
|
||||
|
||||
r = pkcs11_find_token(NULL, auto_callback, ret);
|
||||
if (r == -EAGAIN)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No suitable PKCS#11 tokens found.");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
|
||||
#endif
|
||||
}
|
||||
11
src/home/homectl-pkcs11.h
Normal file
11
src/home/homectl-pkcs11.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "json.h"
|
||||
|
||||
int identity_add_token_pin(JsonVariant **v, const char *pin);
|
||||
|
||||
int identity_add_pkcs11_key_data(JsonVariant **v, const char *token_uri);
|
||||
|
||||
int list_pkcs11_tokens(void);
|
||||
int find_pkcs11_token_auto(char **ret);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -457,6 +457,10 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
|
||||
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required.");
|
||||
case -ERFKILL:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, "Security token requires protected authentication path.");
|
||||
case -EMEDIUMTYPE:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_USER_PRESENCE_NEEDED, "Security token requires user presence.");
|
||||
case -ENOSTR:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_ACTION_TIMEOUT, "Token action timeout. (User was supposed to verify presence or similar, by interacting with the token, and didn't do that in time.)");
|
||||
case -EOWNERDEAD:
|
||||
return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_LOCKED, "PIN of security token locked.");
|
||||
case -ENOLCK:
|
||||
@@ -1357,7 +1361,13 @@ static int user_record_extend_with_binding(UserRecord *hr, UserRecord *with_bind
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int home_update_internal(Home *h, const char *verb, UserRecord *hr, UserRecord *secret, sd_bus_error *error) {
|
||||
static int home_update_internal(
|
||||
Home *h,
|
||||
const char *verb,
|
||||
UserRecord *hr,
|
||||
UserRecord *secret,
|
||||
sd_bus_error *error) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
|
||||
int r, c;
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ int home_prepare_cifs(
|
||||
|
||||
int home_activate_cifs(
|
||||
UserRecord *h,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
PasswordCache *cache,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
|
||||
@@ -120,7 +120,7 @@ int home_activate_cifs(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
|
||||
r = home_refresh(h, &setup, NULL, cache, NULL, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
|
||||
int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
|
||||
|
||||
int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
int home_activate_cifs(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
|
||||
|
||||
int home_create_cifs(UserRecord *h, UserRecord **ret_home);
|
||||
|
||||
@@ -26,7 +26,7 @@ int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *set
|
||||
|
||||
int home_activate_directory(
|
||||
UserRecord *h,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
PasswordCache *cache,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
|
||||
@@ -44,11 +44,11 @@ int home_activate_directory(
|
||||
assert_se(hdo = user_record_home_directory(h));
|
||||
hd = strdupa(hdo);
|
||||
|
||||
r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
|
||||
r = home_prepare(h, false, cache, &setup, &header_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, NULL, &new_home);
|
||||
r = home_refresh(h, &setup, header_home, cache, NULL, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -193,7 +193,7 @@ int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) {
|
||||
int home_resize_directory(
|
||||
UserRecord *h,
|
||||
bool already_activated,
|
||||
char ***pkcs11_decrypted_passwords,
|
||||
PasswordCache *cache,
|
||||
HomeSetup *setup,
|
||||
UserRecord **ret_home) {
|
||||
|
||||
@@ -205,11 +205,11 @@ int home_resize_directory(
|
||||
assert(ret_home);
|
||||
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
|
||||
|
||||
r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
|
||||
r = home_prepare(h, already_activated, cache, setup, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
|
||||
r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
#include "user-record.h"
|
||||
|
||||
int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup);
|
||||
int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
|
||||
int home_activate_directory(UserRecord *h, PasswordCache *cache, UserRecord **ret_home);
|
||||
int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
|
||||
int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
|
||||
int home_resize_directory(UserRecord *h, bool already_activated, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
|
||||
|
||||
197
src/home/homework-fido2.c
Normal file
197
src/home/homework-fido2.c
Normal file
@@ -0,0 +1,197 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <fido.h>
|
||||
|
||||
#include "hexdecoct.h"
|
||||
#include "homework-fido2.h"
|
||||
#include "strv.h"
|
||||
|
||||
static int fido2_use_specific_token(
|
||||
const char *path,
|
||||
UserRecord *h,
|
||||
UserRecord *secret,
|
||||
const Fido2HmacSalt *salt,
|
||||
char **ret) {
|
||||
|
||||
_cleanup_(fido_cbor_info_free) fido_cbor_info_t *di = NULL;
|
||||
_cleanup_(fido_assert_free) fido_assert_t *a = NULL;
|
||||
_cleanup_(fido_dev_free) fido_dev_t *d = NULL;
|
||||
bool found_extension = false;
|
||||
size_t n, hmac_size;
|
||||
const void *hmac;
|
||||
char **e;
|
||||
int r;
|
||||
|
||||
d = fido_dev_new();
|
||||
if (!d)
|
||||
return log_oom();
|
||||
|
||||
r = fido_dev_open(d, path);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to open FIDO2 device %s: %s", path, fido_strerr(r));
|
||||
|
||||
if (!fido_dev_is_fido2(d))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
|
||||
"Specified device %s is not a FIDO2 device.", path);
|
||||
|
||||
di = fido_cbor_info_new();
|
||||
if (!di)
|
||||
return log_oom();
|
||||
|
||||
r = fido_dev_get_cbor_info(d, di);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to get CBOR device info for %s: %s", path, fido_strerr(r));
|
||||
|
||||
e = fido_cbor_info_extensions_ptr(di);
|
||||
n = fido_cbor_info_extensions_len(di);
|
||||
|
||||
for (size_t i = 0; i < n; i++)
|
||||
if (streq(e[i], "hmac-secret")) {
|
||||
found_extension = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found_extension)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
|
||||
"Specified device %s is a FIDO2 device, but does not support the required HMAC-SECRET extension.", path);
|
||||
|
||||
a = fido_assert_new();
|
||||
if (!a)
|
||||
return log_oom();
|
||||
|
||||
r = fido_assert_set_extensions(a, FIDO_EXT_HMAC_SECRET);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", fido_strerr(r));
|
||||
|
||||
r = fido_assert_set_hmac_salt(a, salt->salt, salt->salt_size);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to set salt on FIDO2 assertion: %s", fido_strerr(r));
|
||||
|
||||
r = fido_assert_set_rp(a, "io.systemd.home");
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to set FIDO2 assertion ID: %s", fido_strerr(r));
|
||||
|
||||
r = fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to set FIDO2 assertion client data hash: %s", fido_strerr(r));
|
||||
|
||||
r = fido_assert_allow_cred(a, salt->credential.id, salt->credential.size);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to add FIDO2 assertion credential ID: %s", fido_strerr(r));
|
||||
|
||||
r = fido_assert_set_up(a, h->fido2_user_presence_permitted <= 0 ? FIDO_OPT_FALSE : FIDO_OPT_TRUE);
|
||||
if (r != FIDO_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to set FIDO2 assertion user presence: %s", fido_strerr(r));
|
||||
|
||||
log_info("Asking FIDO2 token for authentication.");
|
||||
|
||||
r = fido_dev_get_assert(d, a, NULL); /* try without pin first */
|
||||
if (r == FIDO_ERR_PIN_REQUIRED) {
|
||||
char **i;
|
||||
|
||||
/* OK, we needed a pin, try with all pins in turn */
|
||||
STRV_FOREACH(i, secret->token_pin) {
|
||||
r = fido_dev_get_assert(d, a, *i);
|
||||
if (r != FIDO_ERR_PIN_INVALID)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (r) {
|
||||
case FIDO_OK:
|
||||
break;
|
||||
case FIDO_ERR_NO_CREDENTIALS:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
|
||||
"Wrong security token; needed credentials not present on token.");
|
||||
case FIDO_ERR_PIN_REQUIRED:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
|
||||
"Security token requires PIN.");
|
||||
case FIDO_ERR_PIN_AUTH_BLOCKED:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
|
||||
"PIN of security token is blocked, please remove/reinsert token.");
|
||||
case FIDO_ERR_PIN_INVALID:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
|
||||
"PIN of security token incorrect.");
|
||||
case FIDO_ERR_UP_REQUIRED:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
|
||||
"User presence required.");
|
||||
case FIDO_ERR_ACTION_TIMEOUT:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
|
||||
"Token action timeout. (User didn't interact with token quickly enough.)");
|
||||
default:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to ask token for assertion: %s", fido_strerr(r));
|
||||
}
|
||||
|
||||
hmac = fido_assert_hmac_secret_ptr(a, 0);
|
||||
if (!hmac)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve HMAC secret.");
|
||||
|
||||
hmac_size = fido_assert_hmac_secret_len(a, 0);
|
||||
|
||||
r = base64mem(hmac, hmac_size, ret);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to base64 encode HMAC secret: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret) {
|
||||
size_t allocated = 64, found = 0;
|
||||
fido_dev_info_t *di = NULL;
|
||||
int r;
|
||||
|
||||
di = fido_dev_info_new(allocated);
|
||||
if (!di)
|
||||
return log_oom();
|
||||
|
||||
r = fido_dev_info_manifest(di, allocated, &found);
|
||||
if (r == FIDO_ERR_INTERNAL) {
|
||||
/* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */
|
||||
r = log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got FIDO_ERR_INTERNAL, assuming no devices.");
|
||||
goto finish;
|
||||
}
|
||||
if (r != FIDO_OK) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", fido_strerr(r));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < found; i++) {
|
||||
const fido_dev_info_t *entry;
|
||||
const char *path;
|
||||
|
||||
entry = fido_dev_info_ptr(di, i);
|
||||
if (!entry) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to get device information for FIDO device %zu.", i);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
path = fido_dev_info_path(entry);
|
||||
if (!path) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Failed to query FIDO device path.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = fido2_use_specific_token(path, h, secret, salt, ret);
|
||||
if (!IN_SET(r,
|
||||
-EBADSLT, /* device doesn't understand our credential hash */
|
||||
-ENODEV /* device is not a FIDO2 device with HMAC-SECRET */))
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = -EAGAIN;
|
||||
|
||||
finish:
|
||||
fido_dev_info_free(&di, allocated);
|
||||
return r;
|
||||
}
|
||||
6
src/home/homework-fido2.h
Normal file
6
src/home/homework-fido2.h
Normal file
@@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
int fido2_use_token(UserRecord *h, UserRecord *secret, const Fido2HmacSalt *salt, char **ret);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user