]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
80c41552 | 2 | |
80c41552 LP |
3 | #include "errno-util.h" |
4 | #include "homectl-recovery-key.h" | |
5 | #include "libcrypt-util.h" | |
6 | #include "locale-util.h" | |
7 | #include "memory-util.h" | |
8 | #include "modhex.h" | |
f1b82359 | 9 | #include "qrcode-util.h" |
80c41552 LP |
10 | #include "random-util.h" |
11 | #include "strv.h" | |
12 | #include "terminal-util.h" | |
13 | ||
14 | static int make_recovery_key(char **ret) { | |
15 | _cleanup_(erase_and_freep) char *formatted = NULL; | |
16 | _cleanup_(erase_and_freep) uint8_t *key = NULL; | |
17 | int r; | |
18 | ||
19 | assert(ret); | |
20 | ||
21 | key = new(uint8_t, MODHEX_RAW_LENGTH); | |
22 | if (!key) | |
23 | return log_oom(); | |
24 | ||
25 | r = genuine_random_bytes(key, MODHEX_RAW_LENGTH, RANDOM_BLOCK); | |
26 | if (r < 0) | |
27 | return log_error_errno(r, "Failed to gather entropy for recovery key: %m"); | |
28 | ||
29 | /* Let's now format it as 64 modhex chars, and after each 8 chars insert a dash */ | |
30 | formatted = new(char, MODHEX_FORMATTED_LENGTH); | |
31 | if (!formatted) | |
32 | return log_oom(); | |
33 | ||
34 | for (size_t i = 0, j = 0; i < MODHEX_RAW_LENGTH; i++) { | |
35 | formatted[j++] = modhex_alphabet[key[i] >> 4]; | |
36 | formatted[j++] = modhex_alphabet[key[i] & 0xF]; | |
37 | ||
38 | if (i % 4 == 3) | |
39 | formatted[j++] = '-'; | |
40 | } | |
41 | ||
42 | formatted[MODHEX_FORMATTED_LENGTH-1] = 0; | |
43 | ||
44 | *ret = TAKE_PTR(formatted); | |
45 | return 0; | |
46 | } | |
47 | ||
48 | static int add_privileged(JsonVariant **v, const char *hashed) { | |
49 | _cleanup_(json_variant_unrefp) JsonVariant *e = NULL, *w = NULL, *l = NULL; | |
50 | int r; | |
51 | ||
52 | assert(v); | |
53 | assert(hashed); | |
54 | ||
55 | r = json_build(&e, JSON_BUILD_OBJECT( | |
56 | JSON_BUILD_PAIR("type", JSON_BUILD_STRING("modhex64")), | |
57 | JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed)))); | |
58 | if (r < 0) | |
59 | return log_error_errno(r, "Failed to build recover key JSON object: %m"); | |
60 | ||
61 | json_variant_sensitive(e); | |
62 | ||
63 | w = json_variant_ref(json_variant_by_key(*v, "privileged")); | |
64 | l = json_variant_ref(json_variant_by_key(w, "recoveryKey")); | |
65 | ||
66 | r = json_variant_append_array(&l, e); | |
67 | if (r < 0) | |
68 | return log_error_errno(r, "Failed append recovery key: %m"); | |
69 | ||
70 | r = json_variant_set_field(&w, "recoveryKey", l); | |
71 | if (r < 0) | |
72 | return log_error_errno(r, "Failed to set recovery key array: %m"); | |
73 | ||
74 | r = json_variant_set_field(v, "privileged", w); | |
75 | if (r < 0) | |
76 | return log_error_errno(r, "Failed to update privileged field: %m"); | |
77 | ||
78 | return 0; | |
79 | } | |
80 | ||
81 | static int add_public(JsonVariant **v) { | |
82 | _cleanup_strv_free_ char **types = NULL; | |
83 | int r; | |
84 | ||
85 | assert(v); | |
86 | ||
87 | r = json_variant_strv(json_variant_by_key(*v, "recoveryKeyType"), &types); | |
88 | if (r < 0) | |
89 | return log_error_errno(r, "Failed to parse recovery key type list: %m"); | |
90 | ||
91 | r = strv_extend(&types, "modhex64"); | |
92 | if (r < 0) | |
93 | return log_oom(); | |
94 | ||
95 | r = json_variant_set_field_strv(v, "recoveryKeyType", types); | |
96 | if (r < 0) | |
97 | return log_error_errno(r, "Failed to update recovery key types: %m"); | |
98 | ||
99 | return 0; | |
100 | } | |
101 | ||
102 | static int add_secret(JsonVariant **v, const char *password) { | |
103 | _cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL; | |
104 | _cleanup_(strv_free_erasep) char **passwords = NULL; | |
105 | int r; | |
106 | ||
107 | assert(v); | |
108 | assert(password); | |
109 | ||
110 | w = json_variant_ref(json_variant_by_key(*v, "secret")); | |
111 | l = json_variant_ref(json_variant_by_key(w, "password")); | |
112 | ||
113 | r = json_variant_strv(l, &passwords); | |
114 | if (r < 0) | |
115 | return log_error_errno(r, "Failed to convert password array: %m"); | |
116 | ||
117 | r = strv_extend(&passwords, password); | |
118 | if (r < 0) | |
119 | return log_oom(); | |
120 | ||
121 | r = json_variant_new_array_strv(&l, passwords); | |
122 | if (r < 0) | |
123 | return log_error_errno(r, "Failed to allocate new password array JSON: %m"); | |
124 | ||
125 | json_variant_sensitive(l); | |
126 | ||
127 | r = json_variant_set_field(&w, "password", l); | |
128 | if (r < 0) | |
129 | return log_error_errno(r, "Failed to update password field: %m"); | |
130 | ||
131 | r = json_variant_set_field(v, "secret", w); | |
132 | if (r < 0) | |
133 | return log_error_errno(r, "Failed to update secret object: %m"); | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
80c41552 | 138 | int identity_add_recovery_key(JsonVariant **v) { |
0e98d17e | 139 | _cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL; |
80c41552 LP |
140 | int r; |
141 | ||
142 | assert(v); | |
143 | ||
144 | /* First, let's generate a secret key */ | |
145 | r = make_recovery_key(&password); | |
146 | if (r < 0) | |
147 | return r; | |
148 | ||
149 | /* Let's UNIX hash it */ | |
0e98d17e | 150 | r = hash_password(password, &hashed); |
80c41552 | 151 | if (r < 0) |
80c41552 LP |
152 | return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m"); |
153 | ||
154 | /* Let's now add the "privileged" version of the recovery key */ | |
0e98d17e | 155 | r = add_privileged(v, hashed); |
80c41552 LP |
156 | if (r < 0) |
157 | return r; | |
158 | ||
159 | /* Let's then add the public information about the recovery key */ | |
160 | r = add_public(v); | |
161 | if (r < 0) | |
162 | return r; | |
163 | ||
164 | /* Finally, let's add the new key to the secret part, too */ | |
165 | r = add_secret(v, password); | |
166 | if (r < 0) | |
167 | return r; | |
168 | ||
169 | /* We output the key itself with a trailing newline to stdout and the decoration around it to stderr | |
170 | * instead. */ | |
171 | ||
172 | fflush(stdout); | |
173 | fprintf(stderr, | |
174 | "A secret recovery key has been generated for this account:\n\n" | |
175 | " %s%s%s", | |
176 | emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "", | |
177 | emoji_enabled() ? " " : "", | |
178 | ansi_highlight()); | |
179 | fflush(stderr); | |
180 | ||
181 | fputs(password, stdout); | |
182 | fflush(stdout); | |
183 | ||
184 | fputs(ansi_normal(), stderr); | |
185 | fflush(stderr); | |
186 | ||
187 | fputc('\n', stdout); | |
188 | fflush(stdout); | |
189 | ||
190 | fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n" | |
191 | "regain access to the account if the other configured access credentials have\n" | |
192 | "been lost or forgotten. The recovery key may be entered in place of a password\n" | |
193 | "whenever authentication is requested.\n", stderr); | |
194 | fflush(stderr); | |
195 | ||
f1b82359 | 196 | (void) print_qrcode(stderr, "You may optionally scan the recovery key off screen", password); |
80c41552 LP |
197 | |
198 | return 0; | |
199 | } |