200 lines
6.3 KiB
C
200 lines
6.3 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "errno-util.h"
|
|
#include "homectl-recovery-key.h"
|
|
#include "libcrypt-util.h"
|
|
#include "locale-util.h"
|
|
#include "memory-util.h"
|
|
#include "modhex.h"
|
|
#include "qrcode-util.h"
|
|
#include "random-util.h"
|
|
#include "strv.h"
|
|
#include "terminal-util.h"
|
|
|
|
static int make_recovery_key(char **ret) {
|
|
_cleanup_(erase_and_freep) char *formatted = NULL;
|
|
_cleanup_(erase_and_freep) uint8_t *key = NULL;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
key = new(uint8_t, MODHEX_RAW_LENGTH);
|
|
if (!key)
|
|
return log_oom();
|
|
|
|
r = genuine_random_bytes(key, MODHEX_RAW_LENGTH, RANDOM_BLOCK);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to gather entropy for recovery key: %m");
|
|
|
|
/* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */
|
|
formatted = new(char, MODHEX_FORMATTED_LENGTH);
|
|
if (!formatted)
|
|
return log_oom();
|
|
|
|
for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) {
|
|
formatted[j++] = modhex_alphabet[key[i] >> 4];
|
|
formatted[j++] = modhex_alphabet[key[i] & 0xF];
|
|
|
|
if (i % 4 == 3)
|
|
formatted[j++] = '-';
|
|
}
|
|
|
|
formatted[MODHEX_FORMATTED_LENGTH-1] = 0;
|
|
|
|
*ret = TAKE_PTR(formatted);
|
|
return 0;
|
|
}
|
|
|
|
static int add_privileged(JsonVariant **v, const char *hashed) {
|
|
_cleanup_(json_variant_unrefp) JsonVariant *e = NULL, *w = NULL, *l = NULL;
|
|
int r;
|
|
|
|
assert(v);
|
|
assert(hashed);
|
|
|
|
r = json_build(&e, JSON_BUILD_OBJECT(
|
|
JSON_BUILD_PAIR("type", JSON_BUILD_STRING("modhex64")),
|
|
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed))));
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to build recover key JSON object: %m");
|
|
|
|
json_variant_sensitive(e);
|
|
|
|
w = json_variant_ref(json_variant_by_key(*v, "privileged"));
|
|
l = json_variant_ref(json_variant_by_key(w, "recoveryKey"));
|
|
|
|
r = json_variant_append_array(&l, e);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed append recovery key: %m");
|
|
|
|
r = json_variant_set_field(&w, "recoveryKey", l);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to set recovery key array: %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_public(JsonVariant **v) {
|
|
_cleanup_strv_free_ char **types = NULL;
|
|
int r;
|
|
|
|
assert(v);
|
|
|
|
r = json_variant_strv(json_variant_by_key(*v, "recoveryKeyType"), &types);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to parse recovery key type list: %m");
|
|
|
|
r = strv_extend(&types, "modhex64");
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
r = json_variant_set_field_strv(v, "recoveryKeyType", types);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to update recovery key types: %m");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int add_secret(JsonVariant **v, const char *password) {
|
|
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
|
|
_cleanup_(strv_free_erasep) char **passwords = NULL;
|
|
int r;
|
|
|
|
assert(v);
|
|
assert(password);
|
|
|
|
w = json_variant_ref(json_variant_by_key(*v, "secret"));
|
|
l = json_variant_ref(json_variant_by_key(w, "password"));
|
|
|
|
r = json_variant_strv(l, &passwords);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to convert password array: %m");
|
|
|
|
r = strv_extend(&passwords, password);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
r = json_variant_new_array_strv(&l, passwords);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to allocate new password array JSON: %m");
|
|
|
|
json_variant_sensitive(l);
|
|
|
|
r = json_variant_set_field(&w, "password", l);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to update password 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 0;
|
|
}
|
|
|
|
int identity_add_recovery_key(JsonVariant **v) {
|
|
_cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL;
|
|
int r;
|
|
|
|
assert(v);
|
|
|
|
/* First, let's generate a secret key */
|
|
r = make_recovery_key(&password);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Let's UNIX hash it */
|
|
r = hash_password(password, &hashed);
|
|
if (r < 0)
|
|
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
|
|
|
|
/* Let's now add the "privileged" version of the recovery key */
|
|
r = add_privileged(v, hashed);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Let's then add the public information about the recovery key */
|
|
r = add_public(v);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Finally, let's add the new key to the secret part, too */
|
|
r = add_secret(v, password);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* We output the key itself with a trailing newline to stdout and the decoration around it to stderr
|
|
* instead. */
|
|
|
|
fflush(stdout);
|
|
fprintf(stderr,
|
|
"A secret recovery key has been generated for this account:\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 account 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);
|
|
|
|
return 0;
|
|
}
|