]>
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 | ||
ea2a57be | 23 | return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" PROJECT_VERSION_FULL " (" GIT_VERSION ")"; |
d1ae38d8 OK |
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; |
d37c312b | 45 | _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {}; |
8d042bc4 | 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); |
5cef6b53 | 59 | assert(pin || pin_size == 0); |
75a9681e LP |
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, |
d37c312b | 91 | &pcrlock_nv, |
75a9681e | 92 | &flags); |
d1ae38d8 OK |
93 | if (r < 0) |
94 | return log_debug_open_error(cd, r); | |
95 | ||
75a9681e LP |
96 | if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask) |
97 | 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 |
98 | |
99 | r = acquire_luks2_key( | |
75a9681e LP |
100 | params.device, |
101 | hash_pcr_mask, | |
b98855d9 | 102 | pcr_bank, |
8d042bc4 | 103 | &pubkey, |
75a9681e LP |
104 | pubkey_pcr_mask, |
105 | params.signature_path, | |
106 | pin_string, | |
404aea78 | 107 | params.pcrlock_path, |
2b92a672 | 108 | primary_alg, |
8d042bc4 LP |
109 | &blob, |
110 | &policy_hash, | |
111 | &salt, | |
112 | &srk, | |
d37c312b | 113 | &pcrlock_nv, |
1f895ada | 114 | flags, |
8d042bc4 | 115 | &decrypted_key); |
d1ae38d8 OK |
116 | if (r < 0) |
117 | return log_debug_open_error(cd, r); | |
118 | ||
119 | /* Before using this key as passphrase we base64 encode it, for compat with homed */ | |
8d042bc4 | 120 | base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded); |
5e476b85 LP |
121 | if (base64_encoded_size < 0) |
122 | return log_debug_open_error(cd, base64_encoded_size); | |
d1ae38d8 | 123 | |
bdbb61f6 | 124 | /* free'd automatically by libcryptsetup */ |
75a9681e | 125 | *ret_password = TAKE_PTR(base64_encoded); |
5e476b85 | 126 | *ret_password_len = base64_encoded_size; |
d1ae38d8 OK |
127 | |
128 | return 0; | |
129 | } | |
130 | ||
35ba2b4f JW |
131 | /* |
132 | * This function is called from within following libcryptsetup calls | |
133 | * provided conditions further below are met: | |
134 | * | |
135 | * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-tpm2'): | |
136 | * | |
137 | * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device | |
138 | * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY | |
139 | * and token is assigned to at least single keyslot). | |
140 | * | |
141 | * - if plugin defines validate function (see cryptsetup_token_validate below) it must have | |
142 | * passed the check (aka return 0) | |
143 | */ | |
144 | _public_ int cryptsetup_token_open( | |
145 | struct crypt_device *cd, /* is always LUKS2 context */ | |
146 | int token /* is always >= 0 */, | |
75a9681e LP |
147 | char **ret_password, /* freed by cryptsetup_token_buffer_free */ |
148 | size_t *ret_password_len, | |
35ba2b4f JW |
149 | void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { |
150 | ||
75a9681e | 151 | return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr); |
35ba2b4f JW |
152 | } |
153 | ||
d1ae38d8 OK |
154 | /* |
155 | * libcryptsetup callback for memory deallocation of 'password' parameter passed in | |
156 | * any crypt_token_open_* plugin function | |
157 | */ | |
158 | _public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { | |
159 | erase_and_free(buffer); | |
160 | } | |
161 | ||
162 | /* | |
163 | * prints systemd-tpm2 token content in crypt_dump(). | |
164 | * 'type' and 'keyslots' fields are printed by libcryptsetup | |
165 | */ | |
166 | _public_ void cryptsetup_token_dump( | |
167 | struct crypt_device *cd /* is always LUKS2 context */, | |
168 | const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { | |
169 | ||
75a9681e | 170 | _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL; |
d37c312b | 171 | _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {}; |
75a9681e | 172 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
75a9681e | 173 | uint32_t hash_pcr_mask, pubkey_pcr_mask; |
2b92a672 | 174 | uint16_t pcr_bank, primary_alg; |
75a9681e LP |
175 | TPM2Flags flags = 0; |
176 | int r; | |
d1ae38d8 OK |
177 | |
178 | assert(json); | |
179 | ||
75a9681e LP |
180 | r = json_parse(json, 0, &v, NULL, NULL); |
181 | if (r < 0) | |
182 | return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); | |
183 | ||
184 | r = tpm2_parse_luks2_json( | |
185 | v, | |
186 | NULL, | |
187 | &hash_pcr_mask, | |
188 | &pcr_bank, | |
189 | &pubkey, | |
75a9681e LP |
190 | &pubkey_pcr_mask, |
191 | &primary_alg, | |
192 | &blob, | |
75a9681e | 193 | &policy_hash, |
aae6eb96 | 194 | &salt, |
8d042bc4 | 195 | &srk, |
d37c312b | 196 | &pcrlock_nv, |
75a9681e LP |
197 | &flags); |
198 | if (r < 0) | |
199 | return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); | |
200 | ||
c69bd0ab DS |
201 | hash_pcrs_str = tpm2_pcr_mask_to_string(hash_pcr_mask); |
202 | if (!hash_pcrs_str) | |
203 | return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); | |
d1ae38d8 | 204 | |
c69bd0ab DS |
205 | pubkey_pcrs_str = tpm2_pcr_mask_to_string(pubkey_pcr_mask); |
206 | if (!pubkey_pcrs_str) | |
207 | return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); | |
d1ae38d8 | 208 | |
8d042bc4 | 209 | r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str); |
d1ae38d8 | 210 | if (r < 0) |
cee60fc3 | 211 | return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); |
d1ae38d8 | 212 | |
8d042bc4 | 213 | r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str); |
d1ae38d8 | 214 | if (r < 0) |
cee60fc3 | 215 | return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); |
d1ae38d8 | 216 | |
8d042bc4 | 217 | r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str); |
d1ae38d8 | 218 | if (r < 0) |
cee60fc3 | 219 | return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); |
d1ae38d8 | 220 | |
75a9681e | 221 | crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str)); |
7bfe0a48 | 222 | crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank))); |
75a9681e LP |
223 | crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str); |
224 | crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str)); | |
7bfe0a48 | 225 | crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); |
75a9681e | 226 | crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); |
dfba4518 | 227 | crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); |
75a9681e | 228 | crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); |
404aea78 | 229 | crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); |
8d042bc4 LP |
230 | crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt))); |
231 | crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk))); | |
d37c312b | 232 | crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv))); |
d1ae38d8 OK |
233 | } |
234 | ||
235 | /* | |
236 | * Note: | |
237 | * If plugin is available in library path, it's called in before following libcryptsetup calls: | |
238 | * | |
239 | * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour | |
240 | */ | |
241 | _public_ int cryptsetup_token_validate( | |
242 | struct crypt_device *cd, /* is always LUKS2 context */ | |
243 | const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) { | |
244 | ||
245 | int r; | |
246 | JsonVariant *w, *e; | |
247 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
248 | ||
249 | assert(json); | |
250 | ||
251 | r = json_parse(json, 0, &v, NULL, NULL); | |
252 | if (r < 0) | |
253 | return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m"); | |
254 | ||
255 | w = json_variant_by_key(v, "tpm2-pcrs"); | |
256 | if (!w || !json_variant_is_array(w)) { | |
257 | crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-pcrs' field."); | |
258 | return 1; | |
259 | } | |
260 | ||
261 | JSON_VARIANT_ARRAY_FOREACH(e, w) { | |
718ca772 | 262 | uint64_t u; |
d1ae38d8 OK |
263 | |
264 | if (!json_variant_is_number(e)) { | |
265 | crypt_log_debug(cd, "TPM2 PCR is not a number."); | |
266 | return 1; | |
267 | } | |
268 | ||
269 | u = json_variant_unsigned(e); | |
323eb480 | 270 | if (!TPM2_PCR_INDEX_VALID(u)) { |
d1ae38d8 OK |
271 | crypt_log_debug(cd, "TPM2 PCR number out of range."); |
272 | return 1; | |
273 | } | |
274 | } | |
275 | ||
2b92a672 LP |
276 | /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded |
277 | * to SHA256. */ | |
38a0aec6 OK |
278 | w = json_variant_by_key(v, "tpm2-pcr-bank"); |
279 | if (w) { | |
280 | /* The PCR bank field is optional */ | |
281 | ||
282 | if (!json_variant_is_string(w)) { | |
283 | crypt_log_debug(cd, "TPM2 PCR bank is not a string."); | |
284 | return 1; | |
285 | } | |
286 | ||
7bfe0a48 | 287 | if (tpm2_hash_alg_from_string(json_variant_string(w)) < 0) { |
38a0aec6 OK |
288 | crypt_log_debug(cd, "TPM2 PCR bank invalid or not supported: %s.", json_variant_string(w)); |
289 | return 1; | |
290 | } | |
291 | } | |
292 | ||
2b92a672 LP |
293 | /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before |
294 | * the algorithm was hardcoded to ECC. */ | |
295 | w = json_variant_by_key(v, "tpm2-primary-alg"); | |
296 | if (w) { | |
297 | /* The primary key algorithm is optional */ | |
298 | ||
299 | if (!json_variant_is_string(w)) { | |
300 | crypt_log_debug(cd, "TPM2 primary key algorithm is not a string."); | |
301 | return 1; | |
302 | } | |
303 | ||
7bfe0a48 | 304 | if (tpm2_asym_alg_from_string(json_variant_string(w)) < 0) { |
2b92a672 LP |
305 | crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); |
306 | return 1; | |
307 | } | |
308 | } | |
309 | ||
d1ae38d8 OK |
310 | w = json_variant_by_key(v, "tpm2-blob"); |
311 | if (!w || !json_variant_is_string(w)) { | |
312 | crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-blob' field."); | |
313 | return 1; | |
314 | } | |
315 | ||
bdd2036e | 316 | r = unbase64mem(json_variant_string(w), NULL, NULL); |
d1ae38d8 OK |
317 | if (r < 0) |
318 | return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); | |
319 | ||
320 | w = json_variant_by_key(v, "tpm2-policy-hash"); | |
321 | if (!w || !json_variant_is_string(w)) { | |
322 | crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-policy-hash' field."); | |
323 | return 1; | |
324 | } | |
325 | ||
bdd2036e | 326 | r = unhexmem(json_variant_string(w), NULL, NULL); |
d1ae38d8 OK |
327 | if (r < 0) |
328 | return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); | |
329 | ||
1f895ada GG |
330 | w = json_variant_by_key(v, "tpm2-pin"); |
331 | if (w) { | |
332 | if (!json_variant_is_boolean(w)) { | |
333 | crypt_log_debug(cd, "TPM2 PIN policy is not a boolean."); | |
334 | return 1; | |
335 | } | |
336 | } | |
337 | ||
d1ae38d8 OK |
338 | return 0; |
339 | } |