]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
Merge pull request #25168 from valentindavid/valentindavid/umount-move-recursive...
[thirdparty/systemd.git] / src / cryptsetup / cryptsetup-tokens / cryptsetup-token-systemd-tpm2.c
CommitLineData
d1ae38d8
OK
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <errno.h>
4#include <libcryptsetup.h>
5
6#include "cryptsetup-token.h"
7#include "cryptsetup-token-util.h"
8#include "hexdecoct.h"
1f895ada 9#include "json.h"
d1ae38d8
OK
10#include "luks2-tpm2.h"
11#include "memory-util.h"
1f895ada 12#include "strv.h"
d1ae38d8
OK
13#include "tpm2-util.h"
14#include "version.h"
15
16#define TOKEN_NAME "systemd-tpm2"
17#define TOKEN_VERSION_MAJOR "1"
18#define TOKEN_VERSION_MINOR "0"
19
20/* for libcryptsetup debug purpose */
21_public_ const char *cryptsetup_token_version(void) {
22
23 return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")";
24}
25
26static int log_debug_open_error(struct crypt_device *cd, int r) {
b6fd88a5
LP
27 if (r == -EAGAIN)
28 return crypt_log_debug_errno(cd, r, "TPM2 device not found.");
29 if (r == -ENXIO)
30 return crypt_log_debug_errno(cd, r, "No matching TPM2 token data found.");
d1ae38d8
OK
31
32 return crypt_log_debug_errno(cd, r, TOKEN_NAME " open failed: %m.");
33}
34
35ba2b4f 35_public_ int cryptsetup_token_open_pin(
d1ae38d8
OK
36 struct crypt_device *cd, /* is always LUKS2 context */
37 int token /* is always >= 0 */,
35ba2b4f
JW
38 const char *pin,
39 size_t pin_size,
75a9681e
LP
40 char **ret_password, /* freed by cryptsetup_token_buffer_free */
41 size_t *ret_password_len,
d1ae38d8
OK
42 void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
43
75a9681e 44 _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
aae6eb96
WR
45 _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
46 size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0;
75a9681e
LP
47 _cleanup_(erase_and_freep) void *decrypted_key = NULL;
48 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
49 uint32_t hash_pcr_mask, pubkey_pcr_mask;
d1ae38d8
OK
50 systemd_tpm2_plugin_params params = {
51 .search_pcr_mask = UINT32_MAX
52 };
75a9681e 53 uint16_t pcr_bank, primary_alg;
5e476b85 54 ssize_t base64_encoded_size;
75a9681e
LP
55 TPM2Flags flags = 0;
56 const char *json;
57 int r;
d1ae38d8 58
d1ae38d8 59 assert(token >= 0);
75a9681e
LP
60 assert(!pin || pin_size > 0);
61 assert(ret_password);
62 assert(ret_password_len);
d1ae38d8
OK
63
64 /* This must not fail at this moment (internal error) */
65 r = crypt_token_json_get(cd, token, &json);
66 assert(token == r);
67 assert(json);
68
35ba2b4f
JW
69 r = crypt_normalize_pin(pin, pin_size, &pin_string);
70 if (r < 0)
71 return crypt_log_debug_errno(cd, r, "Can not normalize PIN: %m");
72
d1ae38d8
OK
73 if (usrptr)
74 params = *(systemd_tpm2_plugin_params *)usrptr;
75
75a9681e 76 r = json_parse(json, 0, &v, NULL, NULL);
d1ae38d8 77 if (r < 0)
75a9681e
LP
78 return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m");
79
80 r = tpm2_parse_luks2_json(
81 v,
82 NULL,
83 &hash_pcr_mask,
84 &pcr_bank,
85 &pubkey,
86 &pubkey_size,
87 &pubkey_pcr_mask,
88 &primary_alg,
89 &blob,
90 &blob_size,
91 &policy_hash,
92 &policy_hash_size,
aae6eb96
WR
93 &salt,
94 &salt_size,
75a9681e 95 &flags);
d1ae38d8
OK
96 if (r < 0)
97 return log_debug_open_error(cd, r);
98
75a9681e
LP
99 if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask)
100 return crypt_log_debug_errno(cd, ENXIO, "PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", hash_pcr_mask, params.search_pcr_mask);
d1ae38d8
OK
101
102 r = acquire_luks2_key(
75a9681e
LP
103 params.device,
104 hash_pcr_mask,
b98855d9 105 pcr_bank,
75a9681e
LP
106 pubkey, pubkey_size,
107 pubkey_pcr_mask,
108 params.signature_path,
109 pin_string,
2b92a672 110 primary_alg,
d1ae38d8
OK
111 blob,
112 blob_size,
113 policy_hash,
114 policy_hash_size,
aae6eb96
WR
115 salt,
116 salt_size,
1f895ada 117 flags,
d1ae38d8
OK
118 &decrypted_key,
119 &decrypted_key_size);
120 if (r < 0)
121 return log_debug_open_error(cd, r);
122
123 /* Before using this key as passphrase we base64 encode it, for compat with homed */
5e476b85
LP
124 base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
125 if (base64_encoded_size < 0)
126 return log_debug_open_error(cd, base64_encoded_size);
d1ae38d8 127
bdbb61f6 128 /* free'd automatically by libcryptsetup */
75a9681e 129 *ret_password = TAKE_PTR(base64_encoded);
5e476b85 130 *ret_password_len = base64_encoded_size;
d1ae38d8
OK
131
132 return 0;
133}
134
35ba2b4f
JW
135/*
136 * This function is called from within following libcryptsetup calls
137 * provided conditions further below are met:
138 *
139 * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-tpm2'):
140 *
141 * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device
142 * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY
143 * and token is assigned to at least single keyslot).
144 *
145 * - if plugin defines validate function (see cryptsetup_token_validate below) it must have
146 * passed the check (aka return 0)
147 */
148_public_ int cryptsetup_token_open(
149 struct crypt_device *cd, /* is always LUKS2 context */
150 int token /* is always >= 0 */,
75a9681e
LP
151 char **ret_password, /* freed by cryptsetup_token_buffer_free */
152 size_t *ret_password_len,
35ba2b4f
JW
153 void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
154
75a9681e 155 return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr);
35ba2b4f
JW
156}
157
d1ae38d8
OK
158/*
159 * libcryptsetup callback for memory deallocation of 'password' parameter passed in
160 * any crypt_token_open_* plugin function
161 */
162_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) {
163 erase_and_free(buffer);
164}
165
166/*
167 * prints systemd-tpm2 token content in crypt_dump().
168 * 'type' and 'keyslots' fields are printed by libcryptsetup
169 */
170_public_ void cryptsetup_token_dump(
171 struct crypt_device *cd /* is always LUKS2 context */,
172 const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
173
75a9681e 174 _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
aae6eb96 175 _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
75a9681e 176 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
aae6eb96 177 size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
75a9681e 178 uint32_t hash_pcr_mask, pubkey_pcr_mask;
2b92a672 179 uint16_t pcr_bank, primary_alg;
75a9681e
LP
180 TPM2Flags flags = 0;
181 int r;
d1ae38d8
OK
182
183 assert(json);
184
75a9681e
LP
185 r = json_parse(json, 0, &v, NULL, NULL);
186 if (r < 0)
187 return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m");
188
189 r = tpm2_parse_luks2_json(
190 v,
191 NULL,
192 &hash_pcr_mask,
193 &pcr_bank,
194 &pubkey,
195 &pubkey_size,
196 &pubkey_pcr_mask,
197 &primary_alg,
198 &blob,
199 &blob_size,
200 &policy_hash,
201 &policy_hash_size,
aae6eb96
WR
202 &salt,
203 &salt_size,
75a9681e
LP
204 &flags);
205 if (r < 0)
206 return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
207
208 r = pcr_mask_to_string(hash_pcr_mask, &hash_pcrs_str);
d1ae38d8 209 if (r < 0)
75a9681e 210 return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
d1ae38d8 211
75a9681e
LP
212 r = pcr_mask_to_string(pubkey_pcr_mask, &pubkey_pcrs_str);
213 if (r < 0)
214 return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
d1ae38d8 215
75a9681e 216 r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
d1ae38d8
OK
217 if (r < 0)
218 return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
219
75a9681e 220 r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str);
d1ae38d8
OK
221 if (r < 0)
222 return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
223
75a9681e 224 r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str);
d1ae38d8
OK
225 if (r < 0)
226 return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
227
75a9681e
LP
228 crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
229 crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
230 crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
231 crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
232 crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
233 crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
dfba4518 234 crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
75a9681e 235 crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
aae6eb96 236 crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt));
d1ae38d8
OK
237}
238
239/*
240 * Note:
241 * If plugin is available in library path, it's called in before following libcryptsetup calls:
242 *
243 * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour
244 */
245_public_ int cryptsetup_token_validate(
246 struct crypt_device *cd, /* is always LUKS2 context */
247 const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) {
248
249 int r;
250 JsonVariant *w, *e;
251 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
252
253 assert(json);
254
255 r = json_parse(json, 0, &v, NULL, NULL);
256 if (r < 0)
257 return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m");
258
259 w = json_variant_by_key(v, "tpm2-pcrs");
260 if (!w || !json_variant_is_array(w)) {
261 crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-pcrs' field.");
262 return 1;
263 }
264
265 JSON_VARIANT_ARRAY_FOREACH(e, w) {
718ca772 266 uint64_t u;
d1ae38d8
OK
267
268 if (!json_variant_is_number(e)) {
269 crypt_log_debug(cd, "TPM2 PCR is not a number.");
270 return 1;
271 }
272
273 u = json_variant_unsigned(e);
274 if (u >= TPM2_PCRS_MAX) {
275 crypt_log_debug(cd, "TPM2 PCR number out of range.");
276 return 1;
277 }
278 }
279
2b92a672
LP
280 /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded
281 * to SHA256. */
38a0aec6
OK
282 w = json_variant_by_key(v, "tpm2-pcr-bank");
283 if (w) {
284 /* The PCR bank field is optional */
285
286 if (!json_variant_is_string(w)) {
287 crypt_log_debug(cd, "TPM2 PCR bank is not a string.");
288 return 1;
289 }
290
291 if (tpm2_pcr_bank_from_string(json_variant_string(w)) < 0) {
292 crypt_log_debug(cd, "TPM2 PCR bank invalid or not supported: %s.", json_variant_string(w));
293 return 1;
294 }
295 }
296
2b92a672
LP
297 /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before
298 * the algorithm was hardcoded to ECC. */
299 w = json_variant_by_key(v, "tpm2-primary-alg");
300 if (w) {
301 /* The primary key algorithm is optional */
302
303 if (!json_variant_is_string(w)) {
304 crypt_log_debug(cd, "TPM2 primary key algorithm is not a string.");
305 return 1;
306 }
307
308 if (tpm2_primary_alg_from_string(json_variant_string(w)) < 0) {
309 crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w));
310 return 1;
311 }
312 }
313
d1ae38d8
OK
314 w = json_variant_by_key(v, "tpm2-blob");
315 if (!w || !json_variant_is_string(w)) {
316 crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-blob' field.");
317 return 1;
318 }
319
320 r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL);
321 if (r < 0)
322 return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
323
324 w = json_variant_by_key(v, "tpm2-policy-hash");
325 if (!w || !json_variant_is_string(w)) {
326 crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-policy-hash' field.");
327 return 1;
328 }
329
330 r = unhexmem(json_variant_string(w), SIZE_MAX, NULL, NULL);
331 if (r < 0)
332 return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
333
1f895ada
GG
334 w = json_variant_by_key(v, "tpm2-pin");
335 if (w) {
336 if (!json_variant_is_boolean(w)) {
337 crypt_log_debug(cd, "TPM2 PIN policy is not a boolean.");
338 return 1;
339 }
340 }
341
d1ae38d8
OK
342 return 0;
343}