]>
Commit | Line | Data |
---|---|---|
18843ecc LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include "alloc-util.h" | |
bea344a1 | 4 | #include "ask-password-api.h" |
18843ecc | 5 | #include "cryptsetup-tpm2.h" |
bea344a1 | 6 | #include "env-util.h" |
18843ecc LP |
7 | #include "fileio.h" |
8 | #include "hexdecoct.h" | |
9 | #include "json.h" | |
10 | #include "parse-util.h" | |
11 | #include "random-util.h" | |
aae6eb96 | 12 | #include "sha256.h" |
18843ecc LP |
13 | #include "tpm2-util.h" |
14 | ||
bea344a1 | 15 | static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) { |
a3b46c6b | 16 | _cleanup_(erase_and_freep) char *pin_str = NULL; |
bea344a1 GG |
17 | _cleanup_strv_free_erase_ char **pin = NULL; |
18 | int r; | |
19 | ||
20 | assert(ret_pin_str); | |
21 | ||
22 | r = getenv_steal_erase("PIN", &pin_str); | |
23 | if (r < 0) | |
24 | return log_error_errno(r, "Failed to acquire PIN from environment: %m"); | |
25 | if (!r) { | |
26 | if (headless) | |
27 | return log_error_errno( | |
28 | SYNTHETIC_ERRNO(ENOPKG), | |
29 | "PIN querying disabled via 'headless' option. " | |
30 | "Use the '$PIN' environment variable."); | |
31 | ||
d08fd4c3 LP |
32 | static const AskPasswordRequest req = { |
33 | .message = "Please enter TPM2 PIN:", | |
34 | .icon = "drive-harddisk", | |
35 | .keyring = "tpm2-pin", | |
36 | .credential = "cryptsetup.tpm2-pin", | |
37 | }; | |
38 | ||
bea344a1 GG |
39 | pin = strv_free_erase(pin); |
40 | r = ask_password_auto( | |
d08fd4c3 | 41 | &req, |
bea344a1 GG |
42 | until, |
43 | ask_password_flags, | |
44 | &pin); | |
45 | if (r < 0) | |
46 | return log_error_errno(r, "Failed to ask for user pin: %m"); | |
47 | assert(strv_length(pin) == 1); | |
48 | ||
49 | pin_str = strdup(pin[0]); | |
50 | if (!pin_str) | |
51 | return log_oom(); | |
52 | } | |
53 | ||
54 | *ret_pin_str = TAKE_PTR(pin_str); | |
55 | ||
56 | return r; | |
57 | } | |
58 | ||
18843ecc LP |
59 | int acquire_tpm2_key( |
60 | const char *volume_name, | |
61 | const char *device, | |
d9b5841d | 62 | uint32_t hash_pcr_mask, |
07697bfe | 63 | uint16_t pcr_bank, |
8d042bc4 | 64 | const struct iovec *pubkey, |
dc63b2c9 LP |
65 | uint32_t pubkey_pcr_mask, |
66 | const char *signature_path, | |
404aea78 | 67 | const char *pcrlock_path, |
2b92a672 | 68 | uint16_t primary_alg, |
18843ecc LP |
69 | const char *key_file, |
70 | size_t key_file_size, | |
71 | uint64_t key_file_offset, | |
8d042bc4 LP |
72 | const struct iovec *key_data, |
73 | const struct iovec *policy_hash, | |
74 | const struct iovec *salt, | |
75 | const struct iovec *srk, | |
d37c312b | 76 | const struct iovec *pcrlock_nv, |
bea344a1 GG |
77 | TPM2Flags flags, |
78 | usec_t until, | |
79 | bool headless, | |
80 | AskPasswordFlags ask_password_flags, | |
8d042bc4 | 81 | struct iovec *ret_decrypted_key) { |
18843ecc | 82 | |
dc63b2c9 | 83 | _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; |
18843ecc LP |
84 | _cleanup_free_ void *loaded_blob = NULL; |
85 | _cleanup_free_ char *auto_device = NULL; | |
8d042bc4 | 86 | struct iovec blob; |
18843ecc LP |
87 | int r; |
88 | ||
8d042bc4 | 89 | assert(iovec_is_valid(salt)); |
504d0acf | 90 | |
18843ecc | 91 | if (!device) { |
f9a0ee75 | 92 | r = tpm2_find_device_auto(&auto_device); |
18843ecc LP |
93 | if (r == -ENODEV) |
94 | return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ | |
95 | if (r < 0) | |
f9a0ee75 | 96 | return log_error_errno(r, "Could not find TPM2 device: %m"); |
18843ecc LP |
97 | |
98 | device = auto_device; | |
99 | } | |
100 | ||
8d042bc4 LP |
101 | if (iovec_is_set(key_data)) |
102 | blob = *key_data; | |
103 | else { | |
18843ecc LP |
104 | _cleanup_free_ char *bindname = NULL; |
105 | ||
106 | /* If we read the salt via AF_UNIX, make this client recognizable */ | |
107 | if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0) | |
108 | return log_oom(); | |
109 | ||
110 | r = read_full_file_full( | |
111 | AT_FDCWD, key_file, | |
112 | key_file_offset == 0 ? UINT64_MAX : key_file_offset, | |
113 | key_file_size == 0 ? SIZE_MAX : key_file_size, | |
114 | READ_FULL_FILE_CONNECT_SOCKET, | |
115 | bindname, | |
8d042bc4 | 116 | (char**) &loaded_blob, &blob.iov_len); |
18843ecc LP |
117 | if (r < 0) |
118 | return r; | |
119 | ||
8d042bc4 | 120 | blob.iov_base = loaded_blob; |
18843ecc LP |
121 | } |
122 | ||
dc63b2c9 LP |
123 | if (pubkey_pcr_mask != 0) { |
124 | r = tpm2_load_pcr_signature(signature_path, &signature_json); | |
125 | if (r < 0) | |
f9a0ee75 | 126 | return log_error_errno(r, "Failed to load pcr signature: %m"); |
dc63b2c9 LP |
127 | } |
128 | ||
404aea78 LP |
129 | _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {}; |
130 | ||
131 | if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) { | |
132 | r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); | |
133 | if (r < 0) | |
134 | return r; | |
d37c312b LP |
135 | if (r == 0) { |
136 | /* Not found? Then search among passed credentials */ | |
137 | r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy); | |
138 | if (r < 0) | |
139 | return r; | |
140 | if (r == 0) | |
141 | return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume."); | |
142 | } | |
404aea78 LP |
143 | } |
144 | ||
db7fdf15 DS |
145 | _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; |
146 | r = tpm2_context_new(device, &tpm2_context); | |
147 | if (r < 0) | |
148 | return log_error_errno(r, "Failed to create TPM2 context: %m"); | |
149 | ||
f9a0ee75 | 150 | if (!(flags & TPM2_FLAGS_USE_PIN)) { |
db7fdf15 | 151 | r = tpm2_unseal(tpm2_context, |
d9b5841d | 152 | hash_pcr_mask, |
bea344a1 | 153 | pcr_bank, |
8d042bc4 | 154 | pubkey, |
dc63b2c9 LP |
155 | pubkey_pcr_mask, |
156 | signature_json, | |
d9b5841d | 157 | /* pin= */ NULL, |
404aea78 | 158 | FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, |
bea344a1 | 159 | primary_alg, |
8d042bc4 | 160 | &blob, |
bea344a1 | 161 | policy_hash, |
8d042bc4 LP |
162 | srk, |
163 | ret_decrypted_key); | |
f9a0ee75 DS |
164 | if (r < 0) |
165 | return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); | |
166 | ||
167 | return r; | |
168 | } | |
bea344a1 GG |
169 | |
170 | for (int i = 5;; i--) { | |
aae6eb96 | 171 | _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; |
bea344a1 GG |
172 | |
173 | if (i <= 0) | |
174 | return -EACCES; | |
175 | ||
176 | r = get_pin(until, ask_password_flags, headless, &pin_str); | |
177 | if (r < 0) | |
178 | return r; | |
179 | ||
8d042bc4 | 180 | if (iovec_is_set(salt)) { |
aae6eb96 WR |
181 | uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; |
182 | CLEANUP_ERASE(salted_pin); | |
183 | ||
8d042bc4 | 184 | r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin); |
aae6eb96 WR |
185 | if (r < 0) |
186 | return log_error_errno(r, "Failed to perform PBKDF2: %m"); | |
187 | ||
188 | r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); | |
189 | if (r < 0) | |
190 | return log_error_errno(r, "Failed to base64 encode salted pin: %m"); | |
191 | } else | |
192 | /* no salting needed, backwards compat with non-salted pins */ | |
193 | b64_salted_pin = TAKE_PTR(pin_str); | |
194 | ||
db7fdf15 | 195 | r = tpm2_unseal(tpm2_context, |
d9b5841d | 196 | hash_pcr_mask, |
bea344a1 | 197 | pcr_bank, |
8d042bc4 | 198 | pubkey, |
dc63b2c9 LP |
199 | pubkey_pcr_mask, |
200 | signature_json, | |
aae6eb96 | 201 | b64_salted_pin, |
404aea78 | 202 | pcrlock_path ? &pcrlock_policy : NULL, |
bea344a1 | 203 | primary_alg, |
8d042bc4 | 204 | &blob, |
bea344a1 | 205 | policy_hash, |
8d042bc4 LP |
206 | srk, |
207 | ret_decrypted_key); | |
f9a0ee75 DS |
208 | if (r < 0) { |
209 | log_error_errno(r, "Failed to unseal secret using TPM2: %m"); | |
210 | ||
211 | /* We get this error in case there is an authentication policy mismatch. This should | |
212 | * not happen, but this avoids confusing behavior, just in case. */ | |
213 | if (!IN_SET(r, -EPERM, -ENOLCK)) | |
214 | continue; | |
215 | } | |
bea344a1 GG |
216 | |
217 | return r; | |
218 | } | |
18843ecc LP |
219 | } |
220 | ||
221 | int find_tpm2_auto_data( | |
222 | struct crypt_device *cd, | |
223 | uint32_t search_pcr_mask, | |
224 | int start_token, | |
dc63b2c9 | 225 | uint32_t *ret_hash_pcr_mask, |
07697bfe | 226 | uint16_t *ret_pcr_bank, |
8d042bc4 | 227 | struct iovec *ret_pubkey, |
dc63b2c9 | 228 | uint32_t *ret_pubkey_pcr_mask, |
2b92a672 | 229 | uint16_t *ret_primary_alg, |
8d042bc4 LP |
230 | struct iovec *ret_blob, |
231 | struct iovec *ret_policy_hash, | |
232 | struct iovec *ret_salt, | |
233 | struct iovec *ret_srk, | |
d37c312b | 234 | struct iovec *ret_pcrlock_nv, |
fdf6c27c | 235 | TPM2Flags *ret_flags, |
18843ecc | 236 | int *ret_keyslot, |
fdf6c27c | 237 | int *ret_token) { |
18843ecc | 238 | |
fdf6c27c | 239 | int r, token; |
18843ecc LP |
240 | |
241 | assert(cd); | |
242 | ||
3c2c8e62 | 243 | for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { |
d37c312b | 244 | _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; |
18843ecc | 245 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
fdf6c27c LP |
246 | uint32_t hash_pcr_mask, pubkey_pcr_mask; |
247 | uint16_t pcr_bank, primary_alg; | |
248 | TPM2Flags flags; | |
249 | int keyslot; | |
18843ecc LP |
250 | |
251 | r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); | |
252 | if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) | |
253 | continue; | |
254 | if (r < 0) | |
255 | return log_error_errno(r, "Failed to read JSON token data off disk: %m"); | |
256 | ||
fdf6c27c LP |
257 | r = tpm2_parse_luks2_json( |
258 | v, | |
259 | &keyslot, | |
260 | &hash_pcr_mask, | |
261 | &pcr_bank, | |
8d042bc4 | 262 | &pubkey, |
fdf6c27c LP |
263 | &pubkey_pcr_mask, |
264 | &primary_alg, | |
8d042bc4 LP |
265 | &blob, |
266 | &policy_hash, | |
267 | &salt, | |
268 | &srk, | |
d37c312b | 269 | &pcrlock_nv, |
fdf6c27c LP |
270 | &flags); |
271 | if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ | |
1641c2b1 | 272 | continue; |
18843ecc | 273 | if (r < 0) |
fdf6c27c LP |
274 | return log_error_errno(r, "Failed to parse TPM2 JSON data: %m"); |
275 | ||
276 | if (search_pcr_mask == UINT32_MAX || | |
277 | search_pcr_mask == hash_pcr_mask) { | |
278 | ||
279 | if (start_token <= 0) | |
280 | log_info("Automatically discovered security TPM2 token unlocks volume."); | |
281 | ||
282 | *ret_hash_pcr_mask = hash_pcr_mask; | |
283 | *ret_pcr_bank = pcr_bank; | |
8d042bc4 | 284 | *ret_pubkey = TAKE_STRUCT(pubkey); |
fdf6c27c LP |
285 | *ret_pubkey_pcr_mask = pubkey_pcr_mask; |
286 | *ret_primary_alg = primary_alg; | |
8d042bc4 LP |
287 | *ret_blob = TAKE_STRUCT(blob); |
288 | *ret_policy_hash = TAKE_STRUCT(policy_hash); | |
289 | *ret_salt = TAKE_STRUCT(salt); | |
fdf6c27c LP |
290 | *ret_keyslot = keyslot; |
291 | *ret_token = token; | |
8d042bc4 | 292 | *ret_srk = TAKE_STRUCT(srk); |
d37c312b | 293 | *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); |
fdf6c27c LP |
294 | *ret_flags = flags; |
295 | return 0; | |
bea344a1 GG |
296 | } |
297 | ||
fdf6c27c | 298 | /* PCR mask doesn't match what is configured, ignore this entry, let's see next */ |
18843ecc LP |
299 | } |
300 | ||
fdf6c27c | 301 | return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found."); |
18843ecc | 302 | } |