mirror of
https://github.com/systemd/systemd.git
synced 2025-01-10 05:18:17 +03:00
cryptenroll: add new "systemd-cryptenroll" tool for enrolling FIDO2+PKCS#11 security tokens
This commit is contained in:
parent
2bc5c425e6
commit
8710a6818e
30
meson.build
30
meson.build
@ -2420,6 +2420,36 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : systemgeneratordir)
|
||||
|
||||
systemd_cryptenroll_sources = files('''
|
||||
src/cryptenroll/cryptenroll-fido2.h
|
||||
src/cryptenroll/cryptenroll-password.c
|
||||
src/cryptenroll/cryptenroll-password.h
|
||||
src/cryptenroll/cryptenroll-pkcs11.h
|
||||
src/cryptenroll/cryptenroll-recovery.c
|
||||
src/cryptenroll/cryptenroll-recovery.h
|
||||
src/cryptenroll/cryptenroll.c
|
||||
'''.split())
|
||||
|
||||
if conf.get('HAVE_P11KIT') == 1 and conf.get('HAVE_OPENSSL') == 1
|
||||
systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-pkcs11.c')
|
||||
endif
|
||||
|
||||
if conf.get('HAVE_LIBFIDO2') == 1
|
||||
systemd_cryptenroll_sources += files('src/cryptenroll/cryptenroll-fido2.c')
|
||||
endif
|
||||
|
||||
executable(
|
||||
'systemd-cryptenroll',
|
||||
systemd_cryptenroll_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [libcryptsetup,
|
||||
libopenssl,
|
||||
libp11kit],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : bindir)
|
||||
endif
|
||||
|
||||
if conf.get('HAVE_SYSV_COMPAT') == 1
|
||||
|
88
src/cryptenroll/cryptenroll-fido2.c
Normal file
88
src/cryptenroll/cryptenroll-fido2.c
Normal file
@ -0,0 +1,88 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "cryptenroll-fido2.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "json.h"
|
||||
#include "libfido2-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "random-util.h"
|
||||
|
||||
int enroll_fido2(
|
||||
struct crypt_device *cd,
|
||||
const void *volume_key,
|
||||
size_t volume_key_size,
|
||||
const char *device) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL;
|
||||
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_free_ char *keyslot_as_string = NULL;
|
||||
size_t cid_size, salt_size, secret_size;
|
||||
_cleanup_free_ void *cid = NULL;
|
||||
const char *node, *un;
|
||||
int r, keyslot;
|
||||
|
||||
assert_se(cd);
|
||||
assert_se(volume_key);
|
||||
assert_se(volume_key_size > 0);
|
||||
assert_se(device);
|
||||
|
||||
assert_se(node = crypt_get_device_name(cd));
|
||||
|
||||
un = strempty(crypt_get_uuid(cd));
|
||||
|
||||
r = fido2_generate_hmac_hash(
|
||||
device,
|
||||
/* rp_id= */ "io.systemd.cryptsetup",
|
||||
/* rp_name= */ "Encrypted Volume",
|
||||
/* user_id= */ un, strlen(un), /* We pass the user ID and name as the same: the disk's UUID if we have it */
|
||||
/* user_name= */ un,
|
||||
/* user_display_name= */ node,
|
||||
/* user_icon_name= */ NULL,
|
||||
/* askpw_icon_name= */ "drive-harddisk",
|
||||
&cid, &cid_size,
|
||||
&salt, &salt_size,
|
||||
&secret, &secret_size,
|
||||
NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Before we use the secret, we base64 encode it, for compat with homed, and to make it easier to type in manually */
|
||||
r = base64mem(secret, secret_size, &base64_encoded);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to base64 encode secret key: %m");
|
||||
|
||||
r = cryptsetup_set_minimal_pbkdf(cd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
|
||||
|
||||
keyslot = crypt_keyslot_add_by_volume_key(
|
||||
cd,
|
||||
CRYPT_ANY_SLOT,
|
||||
volume_key,
|
||||
volume_key_size,
|
||||
base64_encoded,
|
||||
strlen(base64_encoded));
|
||||
if (keyslot < 0)
|
||||
return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node);
|
||||
|
||||
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = json_build(&v,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-fido2")),
|
||||
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
|
||||
JSON_BUILD_PAIR("fido2-credential", JSON_BUILD_BASE64(cid, cid_size)),
|
||||
JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_BASE64(salt, salt_size)),
|
||||
JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_STRING("io.systemd.cryptsetup"))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
|
||||
|
||||
r = cryptsetup_add_token_json(cd, v);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m");
|
||||
|
||||
log_info("New FIDO2 token enrolled as key slot %i.", keyslot);
|
||||
return keyslot;
|
||||
}
|
16
src/cryptenroll/cryptenroll-fido2.h
Normal file
16
src/cryptenroll/cryptenroll-fido2.h
Normal file
@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "cryptsetup-util.h"
|
||||
#include "log.h"
|
||||
|
||||
#if HAVE_LIBFIDO2
|
||||
int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device);
|
||||
#else
|
||||
static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device) {
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"FIDO2 key enrollment not supported.");
|
||||
}
|
||||
#endif
|
105
src/cryptenroll/cryptenroll-password.c
Normal file
105
src/cryptenroll/cryptenroll-password.c
Normal file
@ -0,0 +1,105 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "ask-password-api.h"
|
||||
#include "cryptenroll-password.h"
|
||||
#include "escape.h"
|
||||
#include "memory-util.h"
|
||||
#include "pwquality-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int enroll_password(
|
||||
struct crypt_device *cd,
|
||||
const void *volume_key,
|
||||
size_t volume_key_size) {
|
||||
|
||||
_cleanup_(erase_and_freep) char *new_password = NULL;
|
||||
_cleanup_free_ char *error = NULL;
|
||||
const char *node;
|
||||
int r, keyslot;
|
||||
char *e;
|
||||
|
||||
assert_se(node = crypt_get_device_name(cd));
|
||||
|
||||
e = getenv("NEWPASSWORD");
|
||||
if (e) {
|
||||
|
||||
new_password = strdup(e);
|
||||
if (!new_password)
|
||||
return log_oom();
|
||||
|
||||
string_erase(e);
|
||||
assert_se(unsetenv("NEWPASSWORD") == 0);
|
||||
|
||||
} else {
|
||||
_cleanup_free_ char *disk_path = NULL;
|
||||
unsigned i = 5;
|
||||
const char *id;
|
||||
|
||||
assert_se(node = crypt_get_device_name(cd));
|
||||
|
||||
(void) suggest_passwords();
|
||||
|
||||
disk_path = cescape(node);
|
||||
if (!disk_path)
|
||||
return log_oom();
|
||||
|
||||
id = strjoina("cryptsetup:", disk_path);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_strv_free_erase_ char **passwords = NULL, **passwords2 = NULL;
|
||||
_cleanup_free_ char *question = NULL;
|
||||
|
||||
if (--i == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
|
||||
"Too many attempts, giving up:");
|
||||
|
||||
question = strjoin("Please enter new passphrase for disk ", node, ":");
|
||||
if (!question)
|
||||
return log_oom();
|
||||
|
||||
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to query password: %m");
|
||||
|
||||
assert(strv_length(passwords) == 1);
|
||||
|
||||
free(question);
|
||||
question = strjoin("Please enter new passphrase for disk ", node, " (repeat):");
|
||||
if (!question)
|
||||
return log_oom();
|
||||
|
||||
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords2);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to query password: %m");
|
||||
|
||||
assert(strv_length(passwords2) == 1);
|
||||
|
||||
if (strv_equal(passwords, passwords2)) {
|
||||
new_password = passwords2[0];
|
||||
passwords2 = mfree(passwords2);
|
||||
break;
|
||||
}
|
||||
|
||||
log_error("Password didn't match, try again.");
|
||||
}
|
||||
}
|
||||
|
||||
r = quality_check_password(new_password, NULL, &error);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check password for quality: %m");
|
||||
if (r == 0)
|
||||
log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", error);
|
||||
|
||||
keyslot = crypt_keyslot_add_by_volume_key(
|
||||
cd,
|
||||
CRYPT_ANY_SLOT,
|
||||
volume_key,
|
||||
volume_key_size,
|
||||
new_password,
|
||||
strlen(new_password));
|
||||
if (keyslot < 0)
|
||||
return log_error_errno(keyslot, "Failed to add new password to %s: %m", node);
|
||||
|
||||
log_info("New password enrolled as key slot %i.", keyslot);
|
||||
return keyslot;
|
||||
}
|
8
src/cryptenroll/cryptenroll-password.h
Normal file
8
src/cryptenroll/cryptenroll-password.h
Normal file
@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "cryptsetup-util.h"
|
||||
|
||||
int enroll_password(struct crypt_device *cd, const void *volume_key, size_t volume_key_size);
|
99
src/cryptenroll/cryptenroll-pkcs11.c
Normal file
99
src/cryptenroll/cryptenroll-pkcs11.c
Normal file
@ -0,0 +1,99 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "cryptenroll-pkcs11.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "json.h"
|
||||
#include "memory-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "random-util.h"
|
||||
|
||||
int enroll_pkcs11(
|
||||
struct crypt_device *cd,
|
||||
const void *volume_key,
|
||||
size_t volume_key_size,
|
||||
const char *uri) {
|
||||
|
||||
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
|
||||
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_free_ char *keyslot_as_string = NULL;
|
||||
size_t decrypted_key_size, encrypted_key_size;
|
||||
_cleanup_free_ void *encrypted_key = NULL;
|
||||
_cleanup_(X509_freep) X509 *cert = NULL;
|
||||
const char *node;
|
||||
EVP_PKEY *pkey;
|
||||
int keyslot, r;
|
||||
|
||||
assert_se(cd);
|
||||
assert_se(volume_key);
|
||||
assert_se(volume_key_size > 0);
|
||||
assert_se(uri);
|
||||
|
||||
assert_se(node = crypt_get_device_name(cd));
|
||||
|
||||
r = pkcs11_acquire_certificate(uri, "volume enrollment operation", "drive-harddisk", &cert, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
pkey = X509_get0_pubkey(cert);
|
||||
if (!pkey)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate.");
|
||||
|
||||
r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine RSA public key size.");
|
||||
|
||||
log_debug("Generating %zu bytes random key.", decrypted_key_size);
|
||||
|
||||
decrypted_key = malloc(decrypted_key_size);
|
||||
if (!decrypted_key)
|
||||
return log_oom();
|
||||
|
||||
r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate random key: %m");
|
||||
|
||||
r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to encrypt key: %m");
|
||||
|
||||
/* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by
|
||||
* keyboard, if that might ever end up being necessary.) */
|
||||
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to base64 encode secret key: %m");
|
||||
|
||||
r = cryptsetup_set_minimal_pbkdf(cd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
|
||||
|
||||
keyslot = crypt_keyslot_add_by_volume_key(
|
||||
cd,
|
||||
CRYPT_ANY_SLOT,
|
||||
volume_key,
|
||||
volume_key_size,
|
||||
base64_encoded,
|
||||
strlen(base64_encoded));
|
||||
if (keyslot < 0)
|
||||
return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node);
|
||||
|
||||
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
|
||||
return log_oom();
|
||||
|
||||
r = json_build(&v,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-pkcs11")),
|
||||
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
|
||||
JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)),
|
||||
JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m");
|
||||
|
||||
r = cryptsetup_add_token_json(cd, v);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add PKCS#11 JSON token to LUKS2 header: %m");
|
||||
|
||||
log_info("New PKCS#11 token enrolled as key slot %i.", keyslot);
|
||||
return keyslot;
|
||||
}
|
16
src/cryptenroll/cryptenroll-pkcs11.h
Normal file
16
src/cryptenroll/cryptenroll-pkcs11.h
Normal file
@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "cryptsetup-util.h"
|
||||
#include "log.h"
|
||||
|
||||
#if HAVE_P11KIT && HAVE_OPENSSL
|
||||
int enroll_pkcs11(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *uri);
|
||||
#else
|
||||
static inline int enroll_pkcs11(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *uri) {
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"PKCS#11 key enrollment not supported.");
|
||||
}
|
||||
#endif
|
101
src/cryptenroll/cryptenroll-recovery.c
Normal file
101
src/cryptenroll/cryptenroll-recovery.c
Normal file
@ -0,0 +1,101 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "cryptenroll-recovery.h"
|
||||
#include "json.h"
|
||||
#include "locale-util.h"
|
||||
#include "memory-util.h"
|
||||
#include "qrcode-util.h"
|
||||
#include "recovery-key.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
int enroll_recovery(
|
||||
struct crypt_device *cd,
|
||||
const void *volume_key,
|
||||
size_t volume_key_size) {
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(erase_and_freep) char *password = NULL;
|
||||
_cleanup_free_ char *keyslot_as_string = NULL;
|
||||
int keyslot, r, q;
|
||||
const char *node;
|
||||
|
||||
assert_se(cd);
|
||||
assert_se(volume_key);
|
||||
assert_se(volume_key_size > 0);
|
||||
|
||||
assert_se(node = crypt_get_device_name(cd));
|
||||
|
||||
r = make_recovery_key(&password);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate recovery key: %m");
|
||||
|
||||
r = cryptsetup_set_minimal_pbkdf(cd);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set minimal PBKDF: %m");
|
||||
|
||||
keyslot = crypt_keyslot_add_by_volume_key(
|
||||
cd,
|
||||
CRYPT_ANY_SLOT,
|
||||
volume_key,
|
||||
volume_key_size,
|
||||
password,
|
||||
strlen(password));
|
||||
if (keyslot < 0)
|
||||
return log_error_errno(keyslot, "Failed to add new recovery key to %s: %m", node);
|
||||
|
||||
fflush(stdout);
|
||||
fprintf(stderr,
|
||||
"A secret recovery key has been generated for this volume:\n\n"
|
||||
" %s%s%s",
|
||||
emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "",
|
||||
emoji_enabled() ? " " : "",
|
||||
ansi_highlight());
|
||||
fflush(stderr);
|
||||
|
||||
fputs(password, stdout);
|
||||
fflush(stdout);
|
||||
|
||||
fputs(ansi_normal(), stderr);
|
||||
fflush(stderr);
|
||||
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
|
||||
fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
|
||||
"regain access to the volume if the other configured access credentials have\n"
|
||||
"been lost or forgotten. The recovery key may be entered in place of a password\n"
|
||||
"whenever authentication is requested.\n", stderr);
|
||||
fflush(stderr);
|
||||
|
||||
(void) print_qrcode(stderr, "You may optionally scan the recovery key off screen", password);
|
||||
|
||||
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) {
|
||||
r = log_oom();
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
r = json_build(&v,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-recovery")),
|
||||
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string)))));
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to prepare recovery key JSON token object: %m");
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
r = cryptsetup_add_token_json(cd, v);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to add recovery JSON token to LUKS2 header: %m");
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
log_info("New recovery key enrolled as key slot %i.", keyslot);
|
||||
return keyslot;
|
||||
|
||||
rollback:
|
||||
q = crypt_keyslot_destroy(cd, keyslot);
|
||||
if (q < 0)
|
||||
log_debug_errno(q, "Unable to remove key slot we just added again, can't rollback, sorry: %m");
|
||||
|
||||
return r;
|
||||
}
|
8
src/cryptenroll/cryptenroll-recovery.h
Normal file
8
src/cryptenroll/cryptenroll-recovery.h
Normal file
@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "cryptsetup-util.h"
|
||||
|
||||
int enroll_recovery(struct crypt_device *cd, const void *volume_key, size_t volume_key_size);
|
358
src/cryptenroll/cryptenroll.c
Normal file
358
src/cryptenroll/cryptenroll.c
Normal file
@ -0,0 +1,358 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "ask-password-api.h"
|
||||
#include "cryptenroll-fido2.h"
|
||||
#include "cryptenroll-password.h"
|
||||
#include "cryptenroll-pkcs11.h"
|
||||
#include "cryptenroll-recovery.h"
|
||||
#include "cryptsetup-util.h"
|
||||
#include "escape.h"
|
||||
#include "libfido2-util.h"
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
typedef enum EnrollType {
|
||||
ENROLL_PASSWORD,
|
||||
ENROLL_RECOVERY,
|
||||
ENROLL_PKCS11,
|
||||
ENROLL_FIDO2,
|
||||
_ENROLL_TYPE_MAX,
|
||||
_ENROLL_TYPE_INVALID = -1,
|
||||
} EnrollType;
|
||||
|
||||
static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
|
||||
static char *arg_pkcs11_token_uri = NULL;
|
||||
static char *arg_fido2_device = NULL;
|
||||
static char *arg_tpm2_device = NULL;
|
||||
static char *arg_node = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-cryptenroll", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...] BLOCK-DEVICE\n"
|
||||
"\n%sEnroll a security token or authentication credential to a LUKS volume.%s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --password Enroll a user-supplied password\n"
|
||||
" --recovery-key Enroll a recovery key\n"
|
||||
" --pkcs11-token-uri=URI\n"
|
||||
" Specify PKCS#11 security token URI\n"
|
||||
" --fido2-device=PATH\n"
|
||||
" Enroll a FIDO2-HMAC security token\n"
|
||||
"\nSee the %s for details.\n"
|
||||
, program_invocation_short_name
|
||||
, ansi_highlight(), ansi_normal()
|
||||
, link
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_PASSWORD,
|
||||
ARG_RECOVERY_KEY,
|
||||
ARG_PKCS11_TOKEN_URI,
|
||||
ARG_FIDO2_DEVICE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "password", no_argument, NULL, ARG_PASSWORD },
|
||||
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
|
||||
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
|
||||
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_PASSWORD:
|
||||
if (arg_enroll_type >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
arg_enroll_type = ENROLL_PASSWORD;
|
||||
break;
|
||||
|
||||
case ARG_RECOVERY_KEY:
|
||||
if (arg_enroll_type >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
arg_enroll_type = ENROLL_RECOVERY;
|
||||
break;
|
||||
|
||||
case ARG_PKCS11_TOKEN_URI: {
|
||||
_cleanup_free_ char *uri = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
return pkcs11_list_tokens();
|
||||
|
||||
if (arg_enroll_type >= 0 || arg_pkcs11_token_uri)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
if (streq(optarg, "auto")) {
|
||||
r = pkcs11_find_token_auto(&uri);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
if (!pkcs11_uri_valid(optarg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
|
||||
|
||||
uri = strdup(optarg);
|
||||
if (!uri)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
arg_enroll_type = ENROLL_PKCS11;
|
||||
arg_pkcs11_token_uri = TAKE_PTR(uri);
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_FIDO2_DEVICE: {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
return fido2_list_devices();
|
||||
|
||||
if (arg_enroll_type >= 0 || arg_fido2_device)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
if (streq(optarg, "auto")) {
|
||||
r = fido2_find_device_auto(&device);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
device = strdup(optarg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
arg_enroll_type = ENROLL_FIDO2;
|
||||
arg_fido2_device = TAKE_PTR(device);
|
||||
break;
|
||||
}
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unhandled option");
|
||||
}
|
||||
}
|
||||
|
||||
if (arg_enroll_type < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"No operation specified, refusing.");
|
||||
|
||||
if (optind >= argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"No block device node specified, refusing.");
|
||||
|
||||
if (argc > optind+1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Too many arguments, refusing.");
|
||||
|
||||
r = parse_path_argument_and_warn(argv[optind], false, &arg_node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int prepare_luks(
|
||||
struct crypt_device **ret_cd,
|
||||
void **ret_volume_key,
|
||||
size_t *ret_volume_key_size) {
|
||||
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
_cleanup_(erase_and_freep) void *vk = NULL;
|
||||
char *e = NULL;
|
||||
size_t vks;
|
||||
int r;
|
||||
|
||||
assert(ret_cd);
|
||||
assert(!ret_volume_key == !ret_volume_key_size);
|
||||
|
||||
r = crypt_init(&cd, arg_node);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
|
||||
|
||||
cryptsetup_enable_logging(cd);
|
||||
|
||||
r = crypt_load(cd, CRYPT_LUKS2, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load LUKS2 superblock: %m");
|
||||
|
||||
if (!ret_volume_key) {
|
||||
*ret_cd = TAKE_PTR(cd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = crypt_get_volume_key_size(cd);
|
||||
if (r <= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
|
||||
vks = (size_t) r;
|
||||
|
||||
vk = malloc(vks);
|
||||
if (!vk)
|
||||
return log_oom();
|
||||
|
||||
e = getenv("PASSWORD");
|
||||
if (e) {
|
||||
_cleanup_(erase_and_freep) char *password = NULL;
|
||||
|
||||
password = strdup(e);
|
||||
if (!password)
|
||||
return log_oom();
|
||||
|
||||
string_erase(e);
|
||||
assert_se(unsetenv("PASSWORD") >= 0);
|
||||
|
||||
r = crypt_volume_key_get(
|
||||
cd,
|
||||
CRYPT_ANY_SLOT,
|
||||
vk,
|
||||
&vks,
|
||||
password,
|
||||
strlen(password));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Password from environent variable $PASSWORD did not work.");
|
||||
} else {
|
||||
AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED;
|
||||
_cleanup_free_ char *question = NULL, *disk_path = NULL;
|
||||
unsigned i = 5;
|
||||
const char *id;
|
||||
|
||||
question = strjoin("Please enter current passphrase for disk ", arg_node, ":");
|
||||
if (!question)
|
||||
return log_oom();
|
||||
|
||||
disk_path = cescape(arg_node);
|
||||
if (!disk_path)
|
||||
return log_oom();
|
||||
|
||||
id = strjoina("cryptsetup:", disk_path);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_strv_free_erase_ char **passwords = NULL;
|
||||
char **p;
|
||||
|
||||
if (--i == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY),
|
||||
"Too many attempts, giving up:");
|
||||
|
||||
r = ask_password_auto(
|
||||
question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY,
|
||||
ask_password_flags,
|
||||
&passwords);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to query password: %m");
|
||||
|
||||
r = -EPERM;
|
||||
STRV_FOREACH(p, passwords) {
|
||||
r = crypt_volume_key_get(
|
||||
cd,
|
||||
CRYPT_ANY_SLOT,
|
||||
vk,
|
||||
&vks,
|
||||
*p,
|
||||
strlen(*p));
|
||||
if (r >= 0)
|
||||
break;
|
||||
}
|
||||
if (r >= 0)
|
||||
break;
|
||||
|
||||
log_error_errno(r, "Password not correct, please try again.");
|
||||
ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
|
||||
}
|
||||
}
|
||||
|
||||
*ret_cd = TAKE_PTR(cd);
|
||||
*ret_volume_key = TAKE_PTR(vk);
|
||||
*ret_volume_key_size = vks;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
_cleanup_(erase_and_freep) void *vk = NULL;
|
||||
size_t vks;
|
||||
int r;
|
||||
|
||||
log_show_color(true);
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
r = prepare_luks(&cd, &vk, &vks);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
switch (arg_enroll_type) {
|
||||
|
||||
case ENROLL_PASSWORD:
|
||||
r = enroll_password(cd, vk, vks);
|
||||
break;
|
||||
|
||||
case ENROLL_RECOVERY:
|
||||
r = enroll_recovery(cd, vk, vks);
|
||||
break;
|
||||
|
||||
case ENROLL_PKCS11:
|
||||
r = enroll_pkcs11(cd, vk, vks, arg_pkcs11_token_uri);
|
||||
break;
|
||||
|
||||
case ENROLL_FIDO2:
|
||||
r = enroll_fido2(cd, vk, vks, arg_fido2_device);
|
||||
break;
|
||||
|
||||
default:
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet.");
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
Loading…
Reference in New Issue
Block a user