]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cryptenroll/cryptenroll-tpm2.c
ask-password: rework how we pass request meta info when asking passwords
[thirdparty/systemd.git] / src / cryptenroll / cryptenroll-tpm2.c
CommitLineData
5e521624
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include "alloc-util.h"
6c7a1681 4#include "ask-password-api.h"
5e521624 5#include "cryptenroll-tpm2.h"
631cf7f0 6#include "cryptsetup-tpm2.h"
6c7a1681 7#include "env-util.h"
631cf7f0 8#include "errno-util.h"
f0f4fcae 9#include "fileio.h"
5e521624
LP
10#include "hexdecoct.h"
11#include "json.h"
631cf7f0 12#include "log.h"
5e521624 13#include "memory-util.h"
aae6eb96
WR
14#include "random-util.h"
15#include "sha256.h"
5e521624
LP
16#include "tpm2-util.h"
17
18static int search_policy_hash(
19 struct crypt_device *cd,
20 const void *hash,
21 size_t hash_size) {
22
23 int r;
24
25 assert(cd);
26 assert(hash || hash_size == 0);
27
28 if (hash_size == 0)
29 return 0;
30
b3a9d980 31 for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
5e521624
LP
32 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
33 _cleanup_free_ void *thash = NULL;
34 size_t thash_size = 0;
35 int keyslot;
36 JsonVariant *w;
37
38 r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
39 if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
40 continue;
41 if (r < 0)
42 return log_error_errno(r, "Failed to read JSON token data off disk: %m");
43
44 keyslot = cryptsetup_get_keyslot_from_token(v);
1641c2b1
LP
45 if (keyslot < 0) {
46 /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
47 * us, but by the LUKS2 spec */
48 log_warning_errno(keyslot, "Failed to determine keyslot of JSON token %i, skipping: %m", token);
49 continue;
50 }
5e521624
LP
51
52 w = json_variant_by_key(v, "tpm2-policy-hash");
53 if (!w || !json_variant_is_string(w))
54 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
55 "TPM2 token data lacks 'tpm2-policy-hash' field.");
56
bdd2036e 57 r = unhexmem(json_variant_string(w), &thash, &thash_size);
5e521624
LP
58 if (r < 0)
59 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
60 "Invalid base64 data in 'tpm2-policy-hash' field.");
61
62 if (memcmp_nn(hash, hash_size, thash, thash_size) == 0)
63 return keyslot; /* Found entry with same hash. */
64 }
65
66 return -ENOENT; /* Not found */
67}
68
6c7a1681 69static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
a3b46c6b 70 _cleanup_(erase_and_freep) char *pin_str = NULL;
6c7a1681 71 TPM2Flags flags = 0;
a3b46c6b 72 int r;
6c7a1681
GG
73
74 assert(ret_pin_str);
75 assert(ret_flags);
76
77 r = getenv_steal_erase("NEWPIN", &pin_str);
78 if (r < 0)
79 return log_error_errno(r, "Failed to acquire PIN from environment: %m");
80 if (r > 0)
81 flags |= TPM2_FLAGS_USE_PIN;
82 else {
83 for (size_t i = 5;; i--) {
84 _cleanup_strv_free_erase_ char **pin = NULL, **pin2 = NULL;
85
86 if (i <= 0)
87 return log_error_errno(
88 SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up.");
89
d08fd4c3
LP
90 AskPasswordRequest req = {
91 .message = "Please enter TPM2 PIN:",
92 .icon = "drive-harddisk",
93 .keyring = "tpm2-pin",
94 .credential = "cryptenroll.tpm2-pin",
95 };
96
6c7a1681
GG
97 pin = strv_free_erase(pin);
98 r = ask_password_auto(
d08fd4c3
LP
99 &req,
100 /* until= */ USEC_INFINITY,
101 /* flags= */ 0,
6c7a1681
GG
102 &pin);
103 if (r < 0)
104 return log_error_errno(r, "Failed to ask for user pin: %m");
105 assert(strv_length(pin) == 1);
106
d08fd4c3
LP
107 req.message = "Please enter TPM2 PIN (repeat):";
108
6c7a1681 109 r = ask_password_auto(
d08fd4c3 110 &req,
6c7a1681 111 USEC_INFINITY,
d08fd4c3 112 /* flags= */ 0,
6c7a1681
GG
113 &pin2);
114 if (r < 0)
115 return log_error_errno(r, "Failed to ask for user pin: %m");
116 assert(strv_length(pin) == 1);
117
118 if (strv_equal(pin, pin2)) {
119 pin_str = strdup(*pin);
120 if (!pin_str)
121 return log_oom();
122 flags |= TPM2_FLAGS_USE_PIN;
123 break;
124 }
125
126 log_error("PINs didn't match, please try again!");
127 }
128 }
129
130 *ret_flags = flags;
131 *ret_pin_str = TAKE_PTR(pin_str);
132
133 return 0;
134}
135
631cf7f0
GAP
136int load_volume_key_tpm2(
137 struct crypt_device *cd,
138 const char *cd_node,
139 const char *device,
140 void *ret_vk,
141 size_t *ret_vks) {
142
143 _cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
144 _cleanup_(erase_and_freep) char *passphrase = NULL;
145 ssize_t passphrase_size;
146 int r;
147
148 assert_se(cd);
149 assert_se(cd_node);
150 assert_se(ret_vk);
151 assert_se(ret_vks);
152
153 bool found_some = false;
154 int token = 0; /* first token to look at */
155
156 for (;;) {
157 _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
158 _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
159 uint32_t hash_pcr_mask, pubkey_pcr_mask;
160 uint16_t pcr_bank, primary_alg;
161 TPM2Flags tpm2_flags;
162 int keyslot;
163
164 r = find_tpm2_auto_data(
165 cd,
166 UINT32_MAX,
167 token,
168 &hash_pcr_mask,
169 &pcr_bank,
170 &pubkey,
171 &pubkey_pcr_mask,
172 &primary_alg,
173 &blob,
174 &policy_hash,
175 &salt,
176 &srk,
177 &pcrlock_nv,
178 &tpm2_flags,
179 &keyslot,
180 &token);
181 if (r == -ENXIO)
182 return log_full_errno(LOG_NOTICE,
183 SYNTHETIC_ERRNO(EAGAIN),
184 found_some
185 ? "No TPM2 metadata matching the current system state found in LUKS2 header."
186 : "No TPM2 metadata enrolled in LUKS2 header.");
187 if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
188 /* TPM2 support not compiled in? */
189 return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available.");
190 if (r < 0)
191 return r;
192
193 found_some = true;
194
195 r = acquire_tpm2_key(
196 cd_node,
197 device,
198 hash_pcr_mask,
199 pcr_bank,
200 &pubkey,
201 pubkey_pcr_mask,
202 /* signature_path= */ NULL,
203 /* pcrlock_path= */ NULL,
204 primary_alg,
205 /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
206 &blob,
207 &policy_hash,
208 &salt,
209 &srk,
210 &pcrlock_nv,
211 tpm2_flags,
212 /* until= */ 0,
213 /* headless= */ false,
214 /* ask_password_flags */ false,
215 &decrypted_key);
216 if (IN_SET(r, -EACCES, -ENOLCK))
217 return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed");
218 if (r != -EPERM)
219 break;
220
221 token++; /* try a different token next time */
222 }
223
224 if (r < 0)
225 return log_error_errno(r, "Unlocking via TPM2 device failed: %m");
226
227 passphrase_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &passphrase);
228 if (passphrase_size < 0)
229 return log_oom();
230
231 r = crypt_volume_key_get(
232 cd,
233 CRYPT_ANY_SLOT,
234 ret_vk,
235 ret_vks,
236 passphrase,
237 passphrase_size);
238 if (r < 0)
239 return log_error_errno(r, "Unlocking via TPM2 device failed: %m");
240
241 return r;
242}
243
5e521624
LP
244int enroll_tpm2(struct crypt_device *cd,
245 const void *volume_key,
246 size_t volume_key_size,
247 const char *device,
382bfd90 248 uint32_t seal_key_handle,
c3a2a681 249 const char *device_key,
9e437994
DS
250 Tpm2PCRValue *hash_pcr_values,
251 size_t n_hash_pcr_values,
f0f4fcae
LP
252 const char *pubkey_path,
253 uint32_t pubkey_pcr_mask,
254 const char *signature_path,
404aea78 255 bool use_pin,
47ec2c8a
GAP
256 const char *pcrlock_path,
257 int *ret_slot_to_wipe) {
5e521624 258
f0f4fcae 259 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
5e521624 260 _cleanup_(erase_and_freep) char *base64_encoded = NULL;
8d042bc4
LP
261 _cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {};
262 _cleanup_(iovec_done_erase) struct iovec secret = {};
5e521624 263 const char *node;
6c7a1681 264 _cleanup_(erase_and_freep) char *pin_str = NULL;
5e476b85 265 ssize_t base64_encoded_size;
47ec2c8a 266 int r, keyslot, slot_to_wipe = -1;
6c7a1681 267 TPM2Flags flags = 0;
aae6eb96
WR
268 uint8_t binary_salt[SHA256_DIGEST_SIZE] = {};
269 /*
270 * erase the salt, we'd rather attempt to not have this in a coredump
271 * as an attacker would have all the parameters but pin used to create
272 * the session key. This problem goes away when we move to a trusted
273 * primary key, aka the SRK.
274 */
275 CLEANUP_ERASE(binary_salt);
5e521624
LP
276
277 assert(cd);
278 assert(volume_key);
279 assert(volume_key_size > 0);
cc1a78d5 280 assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values));
f0f4fcae 281 assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
47ec2c8a 282 assert(ret_slot_to_wipe);
5e521624
LP
283
284 assert_se(node = crypt_get_device_name(cd));
285
6c7a1681
GG
286 if (use_pin) {
287 r = get_pin(&pin_str, &flags);
288 if (r < 0)
289 return r;
aae6eb96
WR
290
291 r = crypto_random_bytes(binary_salt, sizeof(binary_salt));
292 if (r < 0)
293 return log_error_errno(r, "Failed to acquire random salt: %m");
294
295 uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
296 CLEANUP_ERASE(salted_pin);
297 r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), binary_salt, sizeof(binary_salt), salted_pin);
298 if (r < 0)
299 return log_error_errno(r, "Failed to perform PBKDF2: %m");
300
301 pin_str = erase_and_free(pin_str);
302 /* re-stringify pin_str */
303 base64_encoded_size = base64mem(salted_pin, sizeof(salted_pin), &pin_str);
304 if (base64_encoded_size < 0)
305 return log_error_errno(base64_encoded_size, "Failed to base64 encode salted pin: %m");
6c7a1681
GG
306 }
307
a4e9f3d3 308 TPM2B_PUBLIC public = {};
8d042bc4 309 r = tpm2_load_pcr_public_key(pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
f0f4fcae
LP
310 if (r < 0) {
311 if (pubkey_path || signature_path || r != -ENOENT)
cb7aabf1 312 return log_error_errno(r, "Failed to read TPM PCR public key: %m");
f0f4fcae
LP
313
314 log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
315 pubkey_pcr_mask = 0;
a4e9f3d3 316 } else {
8d042bc4 317 r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
645063d1 318 if (r < 0)
a4e9f3d3
LP
319 return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
320
321 if (signature_path) {
322 /* Also try to load the signature JSON object, to verify that our enrollment will work.
323 * This is optional however, skip it if it's not explicitly provided. */
324
325 r = tpm2_load_pcr_signature(signature_path, &signature_json);
326 if (r < 0)
327 return log_debug_errno(r, "Failed to read TPM PCR signature: %m");
328 }
f0f4fcae
LP
329 }
330
c3a2a681
DS
331 bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values);
332
404aea78
LP
333 _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {};
334 if (pcrlock_path) {
335 r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
336 if (r < 0)
337 return r;
338
c3a2a681 339 any_pcr_value_specified = true;
404aea78
LP
340 flags |= TPM2_FLAGS_USE_PCRLOCK;
341 }
342
9e437994 343 _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
c3a2a681
DS
344 TPM2B_PUBLIC device_key_public = {};
345 if (device_key) {
a8d8d34b 346 r = tpm2_load_public_key_file(device_key, &device_key_public);
c3a2a681 347 if (r < 0)
a8d8d34b 348 return r;
c3a2a681
DS
349
350 if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values))
351 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
352 "Must provide all PCR values when using TPM2 device key.");
353 } else {
354 r = tpm2_context_new(device, &tpm2_context);
355 if (r < 0)
356 return log_error_errno(r, "Failed to create TPM2 context: %m");
357
358 if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) {
359 r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values);
360 if (r < 0)
361 return log_error_errno(r, "Could not read pcr values: %m");
362 }
363 }
9e437994
DS
364
365 uint16_t hash_pcr_bank = 0;
366 uint32_t hash_pcr_mask = 0;
367 if (n_hash_pcr_values > 0) {
368 size_t hash_count;
369 r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count);
370 if (r < 0)
371 return log_error_errno(r, "Could not get hash count: %m");
372
373 if (hash_count > 1)
374 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected.");
375
376 hash_pcr_bank = hash_pcr_values[0].hash;
377 r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask);
378 if (r < 0)
379 return log_error_errno(r, "Could not get hash mask: %m");
380 }
381
9e437994
DS
382 TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
383 r = tpm2_calculate_sealing_policy(
384 hash_pcr_values,
385 n_hash_pcr_values,
8d042bc4 386 iovec_is_set(&pubkey) ? &public : NULL,
9e437994 387 use_pin,
404aea78 388 pcrlock_path ? &pcrlock_policy : NULL,
9e437994
DS
389 &policy);
390 if (r < 0)
391 return r;
392
c3a2a681
DS
393 if (device_key)
394 r = tpm2_calculate_seal(
395 seal_key_handle,
396 &device_key_public,
397 /* attributes= */ NULL,
8d042bc4 398 /* secret= */ NULL,
c3a2a681
DS
399 &policy,
400 pin_str,
8d042bc4
LP
401 &secret,
402 &blob,
403 &srk);
c3a2a681
DS
404 else
405 r = tpm2_seal(tpm2_context,
406 seal_key_handle,
407 &policy,
408 pin_str,
8d042bc4
LP
409 &secret,
410 &blob,
c3a2a681 411 /* ret_primary_alg= */ NULL,
8d042bc4 412 &srk);
5e521624 413 if (r < 0)
f9a0ee75 414 return log_error_errno(r, "Failed to seal to TPM2: %m");
5e521624
LP
415
416 /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */
9e437994 417 r = search_policy_hash(cd, policy.buffer, policy.size);
5e521624
LP
418 if (r == -ENOENT)
419 log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
420 else if (r < 0)
421 return r;
47ec2c8a
GAP
422 else if (use_pin) {
423 log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN.");
424 slot_to_wipe = r;
425 } else {
5e521624 426 log_info("This PCR set is already enrolled, executing no operation.");
47ec2c8a 427 *ret_slot_to_wipe = slot_to_wipe;
5e521624
LP
428 return r; /* return existing keyslot, so that wiping won't kill it */
429 }
430
c3a2a681 431 /* If possible, verify the sealed data object. */
8d042bc4
LP
432 if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) {
433 _cleanup_(iovec_done_erase) struct iovec secret2 = {};
f0f4fcae
LP
434
435 log_debug("Unsealing for verification...");
db7fdf15 436 r = tpm2_unseal(tpm2_context,
f0f4fcae 437 hash_pcr_mask,
9e437994 438 hash_pcr_bank,
8d042bc4 439 &pubkey,
f0f4fcae
LP
440 pubkey_pcr_mask,
441 signature_json,
442 pin_str,
404aea78 443 pcrlock_path ? &pcrlock_policy : NULL,
9e437994 444 /* primary_alg= */ 0,
8d042bc4
LP
445 &blob,
446 &IOVEC_MAKE(policy.buffer, policy.size),
447 &srk,
448 &secret2);
f0f4fcae 449 if (r < 0)
f9a0ee75 450 return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
5e521624 451
8d042bc4 452 if (iovec_memcmp(&secret, &secret2) != 0)
f0f4fcae
LP
453 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
454 }
5e521624
LP
455
456 /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
8d042bc4 457 base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded);
5e476b85
LP
458 if (base64_encoded_size < 0)
459 return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
5e521624
LP
460
461 r = cryptsetup_set_minimal_pbkdf(cd);
462 if (r < 0)
463 return log_error_errno(r, "Failed to set minimal PBKDF: %m");
464
465 keyslot = crypt_keyslot_add_by_volume_key(
466 cd,
467 CRYPT_ANY_SLOT,
468 volume_key,
469 volume_key_size,
470 base64_encoded,
5e476b85 471 base64_encoded_size);
5e521624
LP
472 if (keyslot < 0)
473 return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
474
f0f4fcae
LP
475 r = tpm2_make_luks2_json(
476 keyslot,
477 hash_pcr_mask,
9e437994 478 hash_pcr_bank,
8d042bc4 479 &pubkey,
f0f4fcae 480 pubkey_pcr_mask,
9e437994 481 /* primary_alg= */ 0,
8d042bc4
LP
482 &blob,
483 &IOVEC_MAKE(policy.buffer, policy.size),
484 use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
485 &srk,
d37c312b 486 pcrlock_path ? &pcrlock_policy.nv_handle : NULL,
f0f4fcae
LP
487 flags,
488 &v);
5e521624
LP
489 if (r < 0)
490 return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
491
492 r = cryptsetup_add_token_json(cd, v);
493 if (r < 0)
494 return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
495
496 log_info("New TPM2 token enrolled as key slot %i.", keyslot);
47ec2c8a
GAP
497
498 *ret_slot_to_wipe = slot_to_wipe;
5e521624
LP
499 return keyslot;
500}