cryptenroll-varlink: Allow to display recovery-key before enrolling (#43687)
Closes https://github.com/systemd/systemd/issues/43676
This commit is contained in:
@@ -29,9 +29,15 @@ int enroll_recovery(
|
||||
|
||||
assert_se(node = sym_crypt_get_device_name(cd));
|
||||
|
||||
if (c->recovery_key) {
|
||||
password = memdup_suffix0(c->recovery_key, c->recovery_key_size);
|
||||
if (!password)
|
||||
return log_oom();
|
||||
} else {
|
||||
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)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "json-util.h"
|
||||
#include "libfido2-util.h"
|
||||
#include "path-util.h"
|
||||
#include "recovery-key.h"
|
||||
#include "string-util.h"
|
||||
#include "varlink-io.systemd.CryptEnroll.h"
|
||||
#include "varlink-util.h"
|
||||
@@ -47,6 +48,7 @@ typedef struct MethodEnrollParameters {
|
||||
char *unlock_fido2_device;
|
||||
char *unlock_tpm2_device;
|
||||
char *password;
|
||||
char *recovery_key;
|
||||
char *fido2_device;
|
||||
char *fido2_pin;
|
||||
int fido2_with_client_pin;
|
||||
@@ -65,6 +67,7 @@ static void method_enroll_parameters_done(MethodEnrollParameters *p) {
|
||||
free(p->unlock_fido2_device);
|
||||
free(p->unlock_tpm2_device);
|
||||
erase_and_free(p->password);
|
||||
erase_and_free(p->recovery_key);
|
||||
free(p->fido2_device);
|
||||
erase_and_free(p->fido2_pin);
|
||||
sd_json_variant_unref(p->wipe_slots);
|
||||
@@ -163,6 +166,7 @@ static int vl_method_enroll(
|
||||
{ "unlockFido2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_fido2_device), SD_JSON_NULLABLE },
|
||||
{ "unlockTpm2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_tpm2_device), SD_JSON_NULLABLE },
|
||||
{ "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, password), SD_JSON_NULLABLE },
|
||||
{ "recoveryKey", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, recovery_key), SD_JSON_NULLABLE },
|
||||
{ "fido2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, fido2_device), SD_JSON_NULLABLE },
|
||||
{ "fido2Pin", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, fido2_pin), SD_JSON_NULLABLE },
|
||||
{ "fido2WithClientPin", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_client_pin), SD_JSON_NULLABLE },
|
||||
@@ -288,6 +292,20 @@ static int vl_method_enroll(
|
||||
break;
|
||||
|
||||
case ENROLL_RECOVERY:
|
||||
if (p.recovery_key) {
|
||||
_cleanup_(erase_and_freep) char *mangled = NULL;
|
||||
|
||||
r = normalize_recovery_key(p.recovery_key, &mangled);
|
||||
if (r == -EINVAL) /* Not properly formatted, rejecting */
|
||||
return sd_varlink_error_invalid_parameter_name(link, "recoveryKey");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to normalize recovery key: %m");
|
||||
if (!streq(p.recovery_key, mangled))
|
||||
return sd_varlink_error_invalid_parameter_name(link, "recoveryKey");
|
||||
|
||||
c.recovery_key = TAKE_PTR(p.recovery_key);
|
||||
c.recovery_key_size = strlen(c.recovery_key);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_FIDO2:
|
||||
@@ -450,6 +468,36 @@ static int vl_method_list_slots(
|
||||
return sd_varlink_reply(link, NULL);
|
||||
}
|
||||
|
||||
static int vl_method_make_recovery_key(
|
||||
sd_varlink *link,
|
||||
sd_json_variant *parameters,
|
||||
sd_varlink_method_flags_t flags,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_(erase_and_freep) char *recovery_key = NULL;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *rk = NULL;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(parameters);
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = make_recovery_key(&recovery_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_json_variant_new_string(&rk, recovery_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
sd_json_variant_sensitive(rk);
|
||||
|
||||
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("recoveryKey", rk));
|
||||
}
|
||||
|
||||
int cryptenroll_varlink_server(void) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
|
||||
_cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
|
||||
@@ -469,7 +517,8 @@ int cryptenroll_varlink_server(void) {
|
||||
r = sd_varlink_server_bind_method_many(
|
||||
varlink_server,
|
||||
"io.systemd.CryptEnroll.Enroll", vl_method_enroll,
|
||||
"io.systemd.CryptEnroll.ListSlots", vl_method_list_slots);
|
||||
"io.systemd.CryptEnroll.ListSlots", vl_method_list_slots,
|
||||
"io.systemd.CryptEnroll.MakeRecoveryKey", vl_method_make_recovery_key);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind Varlink methods: %m");
|
||||
|
||||
|
||||
@@ -176,6 +176,7 @@ void enroll_context_done(EnrollContext *c) {
|
||||
c->unlock_tpm2_device = mfree(c->unlock_tpm2_device);
|
||||
c->unlock_password = erase_and_free(c->unlock_password);
|
||||
c->passphrase = erase_and_free(c->passphrase);
|
||||
c->recovery_key = erase_and_free(c->recovery_key);
|
||||
c->fido2_device = mfree(c->fido2_device);
|
||||
c->fido2_salt_file = mfree(c->fido2_salt_file);
|
||||
c->fido2_pin = erase_and_free(c->fido2_pin);
|
||||
|
||||
@@ -66,6 +66,11 @@ typedef struct EnrollContext {
|
||||
char *passphrase;
|
||||
size_t passphrase_size;
|
||||
|
||||
/* New recovery key to enroll (mechanism == recovery). When NULL the helpers fall back to
|
||||
* generate a new recovery key. */
|
||||
char *recovery_key;
|
||||
size_t recovery_key_size;
|
||||
|
||||
/* FIDO2 */
|
||||
char *fido2_device;
|
||||
char *fido2_salt_file;
|
||||
|
||||
@@ -42,6 +42,8 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("The passphrase to enroll, when mechanism is 'password'. Contains key material."),
|
||||
SD_VARLINK_DEFINE_INPUT(password, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("A recovery key to enroll, when mechanism is 'recovery'. The key must be in the normalized form and have high entropy. Use MakeRecoveryKey() to generate the recovery key. Contains key material."),
|
||||
SD_VARLINK_DEFINE_INPUT(recoveryKey, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
|
||||
SD_VARLINK_FIELD_COMMENT("Path to the FIDO2 device to enroll, when mechanism is 'fido2'. Leave empty to automatically discover a suitable device."),
|
||||
SD_VARLINK_DEFINE_INPUT(fido2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
@@ -65,7 +67,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
SD_VARLINK_DEFINE_OUTPUT(slot, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The keyslots wiped as part of this call, if any."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(wipedSlots, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("The generated recovery key, when mechanism is 'recoveryKey'. Contains key material."),
|
||||
SD_VARLINK_FIELD_COMMENT("The generated recovery key or the provided recovery key, when mechanism is 'recovery'. Contains key material."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(recoveryKey, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Progress indicator, only sent on intermediate replies when 'more' was set. Currently the only value is 'touch', signalling that the FIDO2 token is waiting for a touch. Unset on the terminating reply."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(state, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
|
||||
@@ -80,6 +82,11 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
|
||||
SD_VARLINK_FIELD_COMMENT("The type of the keyslot."),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, EnrollMechanism, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
MakeRecoveryKey,
|
||||
SD_VARLINK_FIELD_COMMENT("The generated recovery key."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(recoveryKey, SD_VARLINK_STRING, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(VolumeUnderForeignManagement);
|
||||
static SD_VARLINK_DEFINE_ERROR(PasswordRequired);
|
||||
static SD_VARLINK_DEFINE_ERROR(PasswordIncorrect);
|
||||
@@ -96,6 +103,8 @@ SD_VARLINK_DEFINE_INTERFACE(
|
||||
&vl_method_Enroll,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Enumerate the keyslots currently enrolled in a LUKS2 volume, one per reply. Must be called with the 'more' flag."),
|
||||
&vl_method_ListSlots,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Generate a recovery key that can be enrolled using the 'recovery' mechanism in Enroll()."),
|
||||
&vl_method_MakeRecoveryKey,
|
||||
SD_VARLINK_SYMBOL_COMMENT("The volume is managed by another subsystem (e.g. systemd-homed) and may not be enrolled into directly."),
|
||||
&vl_error_VolumeUnderForeignManagement,
|
||||
SD_VARLINK_SYMBOL_COMMENT("A password is required to unlock the volume, but none was provided."),
|
||||
|
||||
@@ -113,6 +113,20 @@ cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-uran
|
||||
varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
|
||||
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"recovery\",\"unlockKeyFile\":\"/tmp/password\"}" | grep recoveryKey >/dev/null
|
||||
|
||||
# Enroll a recovery key generated via MakeRecoveryKey(), unlocking via key file (cf. systemd-cryptenroll --unlock-key-file= --recovery-key)
|
||||
RECOVERY_KEY=$(varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.MakeRecoveryKey "{}" | jq -e -r '.recoveryKey')
|
||||
|
||||
RESULT_RECOVERY_KEY=$(varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
|
||||
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"recovery\",\"recoveryKey\":\"$RECOVERY_KEY\",\"unlockKeyFile\":\"/tmp/password\"}" | jq -e -r '.recoveryKey')
|
||||
test "$RECOVERY_KEY" = "$RESULT_RECOVERY_KEY"
|
||||
|
||||
# Ensure that the enrolled recovery key works
|
||||
echo -n "$RECOVERY_KEY" | cryptsetup open --type=luks2 --test-passphrase --key-file=- "$VL_IMAGE"
|
||||
|
||||
# Fail to enroll a malformed recovery key, unlocking via key file (cf. systemd-cryptenroll --unlock-key-file= --recovery-key)
|
||||
(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
|
||||
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"recovery\",\"recoveryKey\":\"SOME_RANDOM_KEY\",\"unlockKeyFile\":\"/tmp/password\"}" | grep recoveryKey >/dev/null)
|
||||
|
||||
# Enroll a password, unlocking via key file (cf. NEWPASSWORD=… systemd-cryptenroll --unlock-key-file= --password)
|
||||
varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
|
||||
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFile\":\"/tmp/password\",\"password\":\"varlinkpassword\"}"
|
||||
@@ -121,9 +135,10 @@ varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
|
||||
varlinkctl --push-fd=3 call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \
|
||||
"{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFileDescriptor\":0,\"password\":\"fdpassword\"}" 3</tmp/password
|
||||
|
||||
# List enrolled slots (must be called with 'more'); we should see the password and recovery slots
|
||||
varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"recovery"' >/dev/null
|
||||
varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"password"' >/dev/null
|
||||
# List enrolled slots (must be called with 'more'); we should see the password and the two recovery slots we enrolled
|
||||
SLOTS=$(varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}")
|
||||
test "$(grep -c '"type":"recovery"' <<<"$SLOTS")" -eq 2
|
||||
grep '"type":"password"' <<<"$SLOTS" >/dev/null
|
||||
|
||||
# Enroll combined with a wipe of the recovery key slot (cf. systemd-cryptenroll --wipe-slot=recovery --password).
|
||||
# The recovery key slot just got wiped, so it should be reported back in the (non-empty) wipedSlots output.
|
||||
|
||||
Reference in New Issue
Block a user