]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cryptenroll/cryptenroll-tpm2.c
Merge pull request #26410 from DaanDeMeyer/xattr-symlink
[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"
6c7a1681 6#include "env-util.h"
f0f4fcae 7#include "fileio.h"
5e521624
LP
8#include "hexdecoct.h"
9#include "json.h"
10#include "memory-util.h"
aae6eb96
WR
11#include "random-util.h"
12#include "sha256.h"
5e521624
LP
13#include "tpm2-util.h"
14
15static int search_policy_hash(
16 struct crypt_device *cd,
17 const void *hash,
18 size_t hash_size) {
19
20 int r;
21
22 assert(cd);
23 assert(hash || hash_size == 0);
24
25 if (hash_size == 0)
26 return 0;
27
3c2c8e62 28 for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) {
5e521624
LP
29 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
30 _cleanup_free_ void *thash = NULL;
31 size_t thash_size = 0;
32 int keyslot;
33 JsonVariant *w;
34
35 r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
36 if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
37 continue;
38 if (r < 0)
39 return log_error_errno(r, "Failed to read JSON token data off disk: %m");
40
41 keyslot = cryptsetup_get_keyslot_from_token(v);
1641c2b1
LP
42 if (keyslot < 0) {
43 /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
44 * us, but by the LUKS2 spec */
45 log_warning_errno(keyslot, "Failed to determine keyslot of JSON token %i, skipping: %m", token);
46 continue;
47 }
5e521624
LP
48
49 w = json_variant_by_key(v, "tpm2-policy-hash");
50 if (!w || !json_variant_is_string(w))
51 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
52 "TPM2 token data lacks 'tpm2-policy-hash' field.");
53
f5fbe71d 54 r = unhexmem(json_variant_string(w), SIZE_MAX, &thash, &thash_size);
5e521624
LP
55 if (r < 0)
56 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
57 "Invalid base64 data in 'tpm2-policy-hash' field.");
58
59 if (memcmp_nn(hash, hash_size, thash, thash_size) == 0)
60 return keyslot; /* Found entry with same hash. */
61 }
62
63 return -ENOENT; /* Not found */
64}
65
6c7a1681
GG
66static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
67 _cleanup_free_ char *pin_str = NULL;
68 int r;
69 TPM2Flags flags = 0;
70
71 assert(ret_pin_str);
72 assert(ret_flags);
73
74 r = getenv_steal_erase("NEWPIN", &pin_str);
75 if (r < 0)
76 return log_error_errno(r, "Failed to acquire PIN from environment: %m");
77 if (r > 0)
78 flags |= TPM2_FLAGS_USE_PIN;
79 else {
80 for (size_t i = 5;; i--) {
81 _cleanup_strv_free_erase_ char **pin = NULL, **pin2 = NULL;
82
83 if (i <= 0)
84 return log_error_errno(
85 SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up.");
86
87 pin = strv_free_erase(pin);
88 r = ask_password_auto(
89 "Please enter TPM2 PIN:",
90 "drive-harddisk",
91 NULL,
92 "tpm2-pin",
93 "cryptenroll.tpm2-pin",
94 USEC_INFINITY,
95 0,
96 &pin);
97 if (r < 0)
98 return log_error_errno(r, "Failed to ask for user pin: %m");
99 assert(strv_length(pin) == 1);
100
101 r = ask_password_auto(
102 "Please enter TPM2 PIN (repeat):",
103 "drive-harddisk",
104 NULL,
105 "tpm2-pin",
106 "cryptenroll.tpm2-pin",
107 USEC_INFINITY,
108 0,
109 &pin2);
110 if (r < 0)
111 return log_error_errno(r, "Failed to ask for user pin: %m");
112 assert(strv_length(pin) == 1);
113
114 if (strv_equal(pin, pin2)) {
115 pin_str = strdup(*pin);
116 if (!pin_str)
117 return log_oom();
118 flags |= TPM2_FLAGS_USE_PIN;
119 break;
120 }
121
122 log_error("PINs didn't match, please try again!");
123 }
124 }
125
126 *ret_flags = flags;
127 *ret_pin_str = TAKE_PTR(pin_str);
128
129 return 0;
130}
131
5e521624
LP
132int enroll_tpm2(struct crypt_device *cd,
133 const void *volume_key,
134 size_t volume_key_size,
135 const char *device,
d9b5841d 136 uint32_t hash_pcr_mask,
f0f4fcae
LP
137 const char *pubkey_path,
138 uint32_t pubkey_pcr_mask,
139 const char *signature_path,
6c7a1681 140 bool use_pin) {
5e521624 141
f0f4fcae
LP
142 _cleanup_(erase_and_freep) void *secret = NULL;
143 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
5e521624 144 _cleanup_(erase_and_freep) char *base64_encoded = NULL;
f0f4fcae
LP
145 size_t secret_size, blob_size, hash_size, pubkey_size = 0;
146 _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL;
2b92a672 147 uint16_t pcr_bank, primary_alg;
5e521624 148 const char *node;
6c7a1681 149 _cleanup_(erase_and_freep) char *pin_str = NULL;
5e476b85 150 ssize_t base64_encoded_size;
5e521624 151 int r, keyslot;
6c7a1681 152 TPM2Flags flags = 0;
aae6eb96
WR
153 uint8_t binary_salt[SHA256_DIGEST_SIZE] = {};
154 /*
155 * erase the salt, we'd rather attempt to not have this in a coredump
156 * as an attacker would have all the parameters but pin used to create
157 * the session key. This problem goes away when we move to a trusted
158 * primary key, aka the SRK.
159 */
160 CLEANUP_ERASE(binary_salt);
5e521624
LP
161
162 assert(cd);
163 assert(volume_key);
164 assert(volume_key_size > 0);
d9b5841d 165 assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
f0f4fcae 166 assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
5e521624
LP
167
168 assert_se(node = crypt_get_device_name(cd));
169
6c7a1681
GG
170 if (use_pin) {
171 r = get_pin(&pin_str, &flags);
172 if (r < 0)
173 return r;
aae6eb96
WR
174
175 r = crypto_random_bytes(binary_salt, sizeof(binary_salt));
176 if (r < 0)
177 return log_error_errno(r, "Failed to acquire random salt: %m");
178
179 uint8_t salted_pin[SHA256_DIGEST_SIZE] = {};
180 CLEANUP_ERASE(salted_pin);
181 r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), binary_salt, sizeof(binary_salt), salted_pin);
182 if (r < 0)
183 return log_error_errno(r, "Failed to perform PBKDF2: %m");
184
185 pin_str = erase_and_free(pin_str);
186 /* re-stringify pin_str */
187 base64_encoded_size = base64mem(salted_pin, sizeof(salted_pin), &pin_str);
188 if (base64_encoded_size < 0)
189 return log_error_errno(base64_encoded_size, "Failed to base64 encode salted pin: %m");
6c7a1681
GG
190 }
191
f0f4fcae
LP
192 r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size);
193 if (r < 0) {
194 if (pubkey_path || signature_path || r != -ENOENT)
195 return log_error_errno(r, "Failed read TPM PCR public key: %m");
196
197 log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
198 pubkey_pcr_mask = 0;
b0fc23fa
LB
199 } else if (signature_path) {
200 /* Also try to load the signature JSON object, to verify that our enrollment will work.
201 * This is optional however, skip it if it's not explicitly provided. */
f0f4fcae
LP
202
203 r = tpm2_load_pcr_signature(signature_path, &signature_json);
645063d1
YW
204 if (r < 0)
205 return log_debug_errno(r, "Failed to read TPM PCR signature: %m");
f0f4fcae
LP
206 }
207
d9b5841d
LP
208 r = tpm2_seal(device,
209 hash_pcr_mask,
f0f4fcae
LP
210 pubkey, pubkey_size,
211 pubkey_pcr_mask,
d9b5841d
LP
212 pin_str,
213 &secret, &secret_size,
214 &blob, &blob_size,
215 &hash, &hash_size,
216 &pcr_bank,
217 &primary_alg);
5e521624
LP
218 if (r < 0)
219 return r;
220
221 /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */
222 r = search_policy_hash(cd, hash, hash_size);
223 if (r == -ENOENT)
224 log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
225 else if (r < 0)
226 return r;
227 else {
228 log_info("This PCR set is already enrolled, executing no operation.");
229 return r; /* return existing keyslot, so that wiping won't kill it */
230 }
231
0b75493d 232 /* Quick verification that everything is in order, we are not in a hurry after all. */
f0f4fcae
LP
233 if (!pubkey || signature_json) {
234 _cleanup_(erase_and_freep) void *secret2 = NULL;
235 size_t secret2_size;
236
237 log_debug("Unsealing for verification...");
238 r = tpm2_unseal(device,
239 hash_pcr_mask,
240 pcr_bank,
241 pubkey, pubkey_size,
242 pubkey_pcr_mask,
243 signature_json,
244 pin_str,
245 primary_alg,
246 blob, blob_size,
247 hash, hash_size,
248 &secret2, &secret2_size);
249 if (r < 0)
250 return r;
5e521624 251
f0f4fcae
LP
252 if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
253 return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
254 }
5e521624
LP
255
256 /* 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. */
5e476b85
LP
257 base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
258 if (base64_encoded_size < 0)
259 return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
5e521624
LP
260
261 r = cryptsetup_set_minimal_pbkdf(cd);
262 if (r < 0)
263 return log_error_errno(r, "Failed to set minimal PBKDF: %m");
264
265 keyslot = crypt_keyslot_add_by_volume_key(
266 cd,
267 CRYPT_ANY_SLOT,
268 volume_key,
269 volume_key_size,
270 base64_encoded,
5e476b85 271 base64_encoded_size);
5e521624
LP
272 if (keyslot < 0)
273 return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
274
f0f4fcae
LP
275 r = tpm2_make_luks2_json(
276 keyslot,
277 hash_pcr_mask,
278 pcr_bank,
279 pubkey, pubkey_size,
280 pubkey_pcr_mask,
281 primary_alg,
282 blob, blob_size,
283 hash, hash_size,
aae6eb96
WR
284 use_pin ? binary_salt : NULL,
285 use_pin ? sizeof(binary_salt) : 0,
f0f4fcae
LP
286 flags,
287 &v);
5e521624
LP
288 if (r < 0)
289 return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
290
291 r = cryptsetup_add_token_json(cd, v);
292 if (r < 0)
293 return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m");
294
295 log_info("New TPM2 token enrolled as key slot %i.", keyslot);
296 return keyslot;
297}