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