]>
Commit | Line | Data |
---|---|---|
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 | ||
26 | static 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 | } |