]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/home/homectl-recovery-key.c
networkctl: use and print full hardware address
[thirdparty/systemd.git] / src / home / homectl-recovery-key.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #if HAVE_QRENCODE
4 #include <qrencode.h>
5 #include "qrcode-util.h"
6 #endif
7
8 #include "dlfcn-util.h"
9 #include "errno-util.h"
10 #include "homectl-recovery-key.h"
11 #include "libcrypt-util.h"
12 #include "locale-util.h"
13 #include "memory-util.h"
14 #include "modhex.h"
15 #include "random-util.h"
16 #include "strv.h"
17 #include "terminal-util.h"
18
19 static int make_recovery_key(char **ret) {
20 _cleanup_(erase_and_freep) char *formatted = NULL;
21 _cleanup_(erase_and_freep) uint8_t *key = NULL;
22 int r;
23
24 assert(ret);
25
26 key = new(uint8_t, MODHEX_RAW_LENGTH);
27 if (!key)
28 return log_oom();
29
30 r = genuine_random_bytes(key, MODHEX_RAW_LENGTH, RANDOM_BLOCK);
31 if (r < 0)
32 return log_error_errno(r, "Failed to gather entropy for recovery key: %m");
33
34 /* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */
35 formatted = new(char, MODHEX_FORMATTED_LENGTH);
36 if (!formatted)
37 return log_oom();
38
39 for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) {
40 formatted[j++] = modhex_alphabet[key[i] >> 4];
41 formatted[j++] = modhex_alphabet[key[i] & 0xF];
42
43 if (i % 4 == 3)
44 formatted[j++] = '-';
45 }
46
47 formatted[MODHEX_FORMATTED_LENGTH-1] = 0;
48
49 *ret = TAKE_PTR(formatted);
50 return 0;
51 }
52
53 static int add_privileged(JsonVariant **v, const char *hashed) {
54 _cleanup_(json_variant_unrefp) JsonVariant *e = NULL, *w = NULL, *l = NULL;
55 int r;
56
57 assert(v);
58 assert(hashed);
59
60 r = json_build(&e, JSON_BUILD_OBJECT(
61 JSON_BUILD_PAIR("type", JSON_BUILD_STRING("modhex64")),
62 JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed))));
63 if (r < 0)
64 return log_error_errno(r, "Failed to build recover key JSON object: %m");
65
66 json_variant_sensitive(e);
67
68 w = json_variant_ref(json_variant_by_key(*v, "privileged"));
69 l = json_variant_ref(json_variant_by_key(w, "recoveryKey"));
70
71 r = json_variant_append_array(&l, e);
72 if (r < 0)
73 return log_error_errno(r, "Failed append recovery key: %m");
74
75 r = json_variant_set_field(&w, "recoveryKey", l);
76 if (r < 0)
77 return log_error_errno(r, "Failed to set recovery key array: %m");
78
79 r = json_variant_set_field(v, "privileged", w);
80 if (r < 0)
81 return log_error_errno(r, "Failed to update privileged field: %m");
82
83 return 0;
84 }
85
86 static int add_public(JsonVariant **v) {
87 _cleanup_strv_free_ char **types = NULL;
88 int r;
89
90 assert(v);
91
92 r = json_variant_strv(json_variant_by_key(*v, "recoveryKeyType"), &types);
93 if (r < 0)
94 return log_error_errno(r, "Failed to parse recovery key type list: %m");
95
96 r = strv_extend(&types, "modhex64");
97 if (r < 0)
98 return log_oom();
99
100 r = json_variant_set_field_strv(v, "recoveryKeyType", types);
101 if (r < 0)
102 return log_error_errno(r, "Failed to update recovery key types: %m");
103
104 return 0;
105 }
106
107 static int add_secret(JsonVariant **v, const char *password) {
108 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
109 _cleanup_(strv_free_erasep) char **passwords = NULL;
110 int r;
111
112 assert(v);
113 assert(password);
114
115 w = json_variant_ref(json_variant_by_key(*v, "secret"));
116 l = json_variant_ref(json_variant_by_key(w, "password"));
117
118 r = json_variant_strv(l, &passwords);
119 if (r < 0)
120 return log_error_errno(r, "Failed to convert password array: %m");
121
122 r = strv_extend(&passwords, password);
123 if (r < 0)
124 return log_oom();
125
126 r = json_variant_new_array_strv(&l, passwords);
127 if (r < 0)
128 return log_error_errno(r, "Failed to allocate new password array JSON: %m");
129
130 json_variant_sensitive(l);
131
132 r = json_variant_set_field(&w, "password", l);
133 if (r < 0)
134 return log_error_errno(r, "Failed to update password field: %m");
135
136 r = json_variant_set_field(v, "secret", w);
137 if (r < 0)
138 return log_error_errno(r, "Failed to update secret object: %m");
139
140 return 0;
141 }
142
143 static int print_qr_code(const char *secret) {
144 #if HAVE_QRENCODE
145 QRcode* (*sym_QRcode_encodeString)(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive);
146 void (*sym_QRcode_free)(QRcode *qrcode);
147 _cleanup_(dlclosep) void *dl = NULL;
148 QRcode* qr;
149 int r;
150
151 /* If this is not an UTF-8 system or ANSI colors aren't supported/disabled don't print any QR
152 * codes */
153 if (!is_locale_utf8() || !colors_enabled())
154 return -EOPNOTSUPP;
155
156 dl = dlopen("libqrencode.so.4", RTLD_LAZY);
157 if (!dl)
158 return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
159 "QRCODE support is not installed: %s", dlerror());
160
161 r = dlsym_many_and_warn(
162 dl,
163 LOG_DEBUG,
164 &sym_QRcode_encodeString, "QRcode_encodeString",
165 &sym_QRcode_free, "QRcode_free",
166 NULL);
167 if (r < 0)
168 return r;
169
170 qr = sym_QRcode_encodeString(secret, 0, QR_ECLEVEL_L, QR_MODE_8, 0);
171 if (!qr)
172 return -ENOMEM;
173
174 fprintf(stderr, "\nYou may optionally scan the recovery key off screen:\n\n");
175
176 write_qrcode(stderr, qr);
177
178 fputc('\n', stderr);
179
180 sym_QRcode_free(qr);
181 #endif
182 return 0;
183 }
184
185 int identity_add_recovery_key(JsonVariant **v) {
186 _cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL;
187 int r;
188
189 assert(v);
190
191 /* First, let's generate a secret key */
192 r = make_recovery_key(&password);
193 if (r < 0)
194 return r;
195
196 /* Let's UNIX hash it */
197 r = hash_password(password, &hashed);
198 if (r < 0)
199 return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
200
201 /* Let's now add the "privileged" version of the recovery key */
202 r = add_privileged(v, hashed);
203 if (r < 0)
204 return r;
205
206 /* Let's then add the public information about the recovery key */
207 r = add_public(v);
208 if (r < 0)
209 return r;
210
211 /* Finally, let's add the new key to the secret part, too */
212 r = add_secret(v, password);
213 if (r < 0)
214 return r;
215
216 /* We output the key itself with a trailing newline to stdout and the decoration around it to stderr
217 * instead. */
218
219 fflush(stdout);
220 fprintf(stderr,
221 "A secret recovery key has been generated for this account:\n\n"
222 " %s%s%s",
223 emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "",
224 emoji_enabled() ? " " : "",
225 ansi_highlight());
226 fflush(stderr);
227
228 fputs(password, stdout);
229 fflush(stdout);
230
231 fputs(ansi_normal(), stderr);
232 fflush(stderr);
233
234 fputc('\n', stdout);
235 fflush(stdout);
236
237 fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n"
238 "regain access to the account if the other configured access credentials have\n"
239 "been lost or forgotten. The recovery key may be entered in place of a password\n"
240 "whenever authentication is requested.\n", stderr);
241 fflush(stderr);
242
243 print_qr_code(password);
244
245 return 0;
246 }