]>
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; |
8d042bc4 LP |
45 | _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}; |
46 | _cleanup_(iovec_done_erase) struct iovec decrypted_key = {}; | |
75a9681e LP |
47 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
48 | uint32_t hash_pcr_mask, pubkey_pcr_mask; | |
d1ae38d8 OK |
49 | systemd_tpm2_plugin_params params = { |
50 | .search_pcr_mask = UINT32_MAX | |
51 | }; | |
75a9681e | 52 | uint16_t pcr_bank, primary_alg; |
5e476b85 | 53 | ssize_t base64_encoded_size; |
75a9681e LP |
54 | TPM2Flags flags = 0; |
55 | const char *json; | |
56 | int r; | |
d1ae38d8 | 57 | |
d1ae38d8 | 58 | assert(token >= 0); |
75a9681e LP |
59 | assert(!pin || pin_size > 0); |
60 | assert(ret_password); | |
61 | assert(ret_password_len); | |
d1ae38d8 OK |
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 | ||
35ba2b4f JW |
68 | r = crypt_normalize_pin(pin, pin_size, &pin_string); |
69 | if (r < 0) | |
cee60fc3 | 70 | return crypt_log_debug_errno(cd, r, "Cannot normalize PIN: %m"); |
35ba2b4f | 71 | |
d1ae38d8 OK |
72 | if (usrptr) |
73 | params = *(systemd_tpm2_plugin_params *)usrptr; | |
74 | ||
75a9681e | 75 | r = json_parse(json, 0, &v, NULL, NULL); |
d1ae38d8 | 76 | if (r < 0) |
75a9681e LP |
77 | return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); |
78 | ||
79 | r = tpm2_parse_luks2_json( | |
80 | v, | |
8d042bc4 | 81 | /* ret_keyslot= */ NULL, |
75a9681e LP |
82 | &hash_pcr_mask, |
83 | &pcr_bank, | |
84 | &pubkey, | |
75a9681e LP |
85 | &pubkey_pcr_mask, |
86 | &primary_alg, | |
87 | &blob, | |
75a9681e | 88 | &policy_hash, |
aae6eb96 | 89 | &salt, |
8d042bc4 | 90 | &srk, |
75a9681e | 91 | &flags); |
d1ae38d8 OK |
92 | if (r < 0) |
93 | return log_debug_open_error(cd, r); | |
94 | ||
75a9681e LP |
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); | |
d1ae38d8 OK |
97 | |
98 | r = acquire_luks2_key( | |
75a9681e LP |
99 | params.device, |
100 | hash_pcr_mask, | |
b98855d9 | 101 | pcr_bank, |
8d042bc4 | 102 | &pubkey, |
75a9681e LP |
103 | pubkey_pcr_mask, |
104 | params.signature_path, | |
105 | pin_string, | |
404aea78 | 106 | params.pcrlock_path, |
2b92a672 | 107 | primary_alg, |
8d042bc4 LP |
108 | &blob, |
109 | &policy_hash, | |
110 | &salt, | |
111 | &srk, | |
1f895ada | 112 | flags, |
8d042bc4 | 113 | &decrypted_key); |
d1ae38d8 OK |
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 */ | |
8d042bc4 | 118 | base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded); |
5e476b85 LP |
119 | if (base64_encoded_size < 0) |
120 | return log_debug_open_error(cd, base64_encoded_size); | |
d1ae38d8 | 121 | |
bdbb61f6 | 122 | /* free'd automatically by libcryptsetup */ |
75a9681e | 123 | *ret_password = TAKE_PTR(base64_encoded); |
5e476b85 | 124 | *ret_password_len = base64_encoded_size; |
d1ae38d8 OK |
125 | |
126 | return 0; | |
127 | } | |
128 | ||
35ba2b4f JW |
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 */, | |
75a9681e LP |
145 | char **ret_password, /* freed by cryptsetup_token_buffer_free */ |
146 | size_t *ret_password_len, | |
35ba2b4f JW |
147 | void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { |
148 | ||
75a9681e | 149 | return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr); |
35ba2b4f JW |
150 | } |
151 | ||
d1ae38d8 OK |
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 | ||
75a9681e | 168 | _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL; |
8d042bc4 | 169 | _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}; |
75a9681e | 170 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
75a9681e | 171 | uint32_t hash_pcr_mask, pubkey_pcr_mask; |
2b92a672 | 172 | uint16_t pcr_bank, primary_alg; |
75a9681e LP |
173 | TPM2Flags flags = 0; |
174 | int r; | |
d1ae38d8 OK |
175 | |
176 | assert(json); | |
177 | ||
75a9681e LP |
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, | |
75a9681e LP |
188 | &pubkey_pcr_mask, |
189 | &primary_alg, | |
190 | &blob, | |
75a9681e | 191 | &policy_hash, |
aae6eb96 | 192 | &salt, |
8d042bc4 | 193 | &srk, |
75a9681e LP |
194 | &flags); |
195 | if (r < 0) | |
196 | return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); | |
197 | ||
c69bd0ab DS |
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"); | |
d1ae38d8 | 201 | |
c69bd0ab DS |
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"); | |
d1ae38d8 | 205 | |
8d042bc4 | 206 | r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str); |
d1ae38d8 | 207 | if (r < 0) |
cee60fc3 | 208 | return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); |
d1ae38d8 | 209 | |
8d042bc4 | 210 | r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str); |
d1ae38d8 | 211 | if (r < 0) |
cee60fc3 | 212 | return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); |
d1ae38d8 | 213 | |
8d042bc4 | 214 | r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str); |
d1ae38d8 | 215 | if (r < 0) |
cee60fc3 | 216 | return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); |
d1ae38d8 | 217 | |
75a9681e | 218 | crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str)); |
7bfe0a48 | 219 | crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank))); |
75a9681e LP |
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)); | |
7bfe0a48 | 222 | crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); |
75a9681e | 223 | crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); |
dfba4518 | 224 | crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); |
75a9681e | 225 | crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); |
404aea78 | 226 | crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); |
8d042bc4 LP |
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))); | |
d1ae38d8 OK |
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) { | |
718ca772 | 258 | uint64_t u; |
d1ae38d8 OK |
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); | |
323eb480 | 266 | if (!TPM2_PCR_INDEX_VALID(u)) { |
d1ae38d8 OK |
267 | crypt_log_debug(cd, "TPM2 PCR number out of range."); |
268 | return 1; | |
269 | } | |
270 | } | |
271 | ||
2b92a672 LP |
272 | /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded |
273 | * to SHA256. */ | |
38a0aec6 OK |
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 | ||
7bfe0a48 | 283 | if (tpm2_hash_alg_from_string(json_variant_string(w)) < 0) { |
38a0aec6 OK |
284 | crypt_log_debug(cd, "TPM2 PCR bank invalid or not supported: %s.", json_variant_string(w)); |
285 | return 1; | |
286 | } | |
287 | } | |
288 | ||
2b92a672 LP |
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 | ||
7bfe0a48 | 300 | if (tpm2_asym_alg_from_string(json_variant_string(w)) < 0) { |
2b92a672 LP |
301 | crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); |
302 | return 1; | |
303 | } | |
304 | } | |
305 | ||
d1ae38d8 OK |
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 | ||
bdd2036e | 312 | r = unbase64mem(json_variant_string(w), NULL, NULL); |
d1ae38d8 OK |
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 | ||
bdd2036e | 322 | r = unhexmem(json_variant_string(w), NULL, NULL); |
d1ae38d8 OK |
323 | if (r < 0) |
324 | return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); | |
325 | ||
1f895ada GG |
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 | ||
d1ae38d8 OK |
334 | return 0; |
335 | } |