]>
Commit | Line | Data |
---|---|---|
3a6ce677 BR |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include "ask-password-api.h" | |
4 | #include "cryptsetup-fido2.h" | |
5 | #include "fileio.h" | |
6 | #include "hexdecoct.h" | |
7 | #include "json.h" | |
8 | #include "libfido2-util.h" | |
9 | #include "parse-util.h" | |
10 | #include "random-util.h" | |
11 | #include "strv.h" | |
12 | ||
13 | int acquire_fido2_key( | |
14 | const char *volume_name, | |
15 | const char *friendly_name, | |
16 | const char *device, | |
17 | const char *rp_id, | |
18 | const void *cid, | |
19 | size_t cid_size, | |
20 | const char *key_file, | |
21 | size_t key_file_size, | |
22 | uint64_t key_file_offset, | |
23 | const void *key_data, | |
24 | size_t key_data_size, | |
25 | usec_t until, | |
8b3d4ff0 MB |
26 | bool headless, |
27 | Fido2EnrollFlags required, | |
3a6ce677 | 28 | void **ret_decrypted_key, |
8b3d4ff0 MB |
29 | size_t *ret_decrypted_key_size, |
30 | AskPasswordFlags ask_password_flags) { | |
3a6ce677 | 31 | |
3a6ce677 BR |
32 | _cleanup_strv_free_erase_ char **pins = NULL; |
33 | _cleanup_free_ void *loaded_salt = NULL; | |
34 | const char *salt; | |
35 | size_t salt_size; | |
36 | char *e; | |
37 | int r; | |
38 | ||
8b3d4ff0 MB |
39 | ask_password_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; |
40 | ||
3a6ce677 BR |
41 | assert(cid); |
42 | assert(key_file || key_data); | |
43 | ||
44 | if (key_data) { | |
45 | salt = key_data; | |
46 | salt_size = key_data_size; | |
47 | } else { | |
48 | _cleanup_free_ char *bindname = NULL; | |
49 | ||
50 | /* If we read the salt via AF_UNIX, make this client recognizable */ | |
51 | if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0) | |
52 | return log_oom(); | |
53 | ||
54 | r = read_full_file_full( | |
55 | AT_FDCWD, key_file, | |
56 | key_file_offset == 0 ? UINT64_MAX : key_file_offset, | |
57 | key_file_size == 0 ? SIZE_MAX : key_file_size, | |
58 | READ_FULL_FILE_CONNECT_SOCKET, | |
59 | bindname, | |
60 | (char**) &loaded_salt, &salt_size); | |
61 | if (r < 0) | |
62 | return r; | |
63 | ||
64 | salt = loaded_salt; | |
65 | } | |
66 | ||
67 | e = getenv("PIN"); | |
68 | if (e) { | |
69 | pins = strv_new(e); | |
70 | if (!pins) | |
71 | return log_oom(); | |
72 | ||
73 | string_erase(e); | |
74 | if (unsetenv("PIN") < 0) | |
75 | return log_error_errno(errno, "Failed to unset $PIN: %m"); | |
76 | } | |
77 | ||
78 | for (;;) { | |
8b3d4ff0 MB |
79 | if (!FLAGS_SET(required, FIDO2ENROLL_PIN) || pins) { |
80 | r = fido2_use_hmac_hash( | |
81 | device, | |
82 | rp_id ?: "io.systemd.cryptsetup", | |
83 | salt, salt_size, | |
84 | cid, cid_size, | |
85 | pins, | |
86 | required, | |
87 | ret_decrypted_key, | |
88 | ret_decrypted_key_size); | |
89 | if (!IN_SET(r, | |
90 | -ENOANO, /* needs pin */ | |
91 | -ENOLCK)) /* pin incorrect */ | |
92 | return r; | |
93 | } | |
3a6ce677 BR |
94 | |
95 | pins = strv_free_erase(pins); | |
96 | ||
8b3d4ff0 MB |
97 | if (headless) |
98 | return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); | |
99 | ||
100 | r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, ask_password_flags, &pins); | |
3a6ce677 BR |
101 | if (r < 0) |
102 | return log_error_errno(r, "Failed to ask for user password: %m"); | |
103 | ||
8b3d4ff0 | 104 | ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; |
3a6ce677 BR |
105 | } |
106 | } | |
107 | ||
108 | int find_fido2_auto_data( | |
109 | struct crypt_device *cd, | |
110 | char **ret_rp_id, | |
111 | void **ret_salt, | |
112 | size_t *ret_salt_size, | |
113 | void **ret_cid, | |
114 | size_t *ret_cid_size, | |
8b3d4ff0 MB |
115 | int *ret_keyslot, |
116 | Fido2EnrollFlags *ret_required) { | |
3a6ce677 BR |
117 | |
118 | _cleanup_free_ void *cid = NULL, *salt = NULL; | |
119 | size_t cid_size = 0, salt_size = 0; | |
120 | _cleanup_free_ char *rp = NULL; | |
121 | int r, keyslot = -1; | |
8b3d4ff0 | 122 | Fido2EnrollFlags required = 0; |
3a6ce677 BR |
123 | |
124 | assert(cd); | |
125 | assert(ret_salt); | |
126 | assert(ret_salt_size); | |
127 | assert(ret_cid); | |
128 | assert(ret_cid_size); | |
129 | assert(ret_keyslot); | |
8b3d4ff0 | 130 | assert(ret_required); |
3a6ce677 BR |
131 | |
132 | /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ | |
133 | ||
134 | for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) { | |
135 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; | |
136 | JsonVariant *w; | |
137 | ||
138 | r = cryptsetup_get_token_as_json(cd, token, "systemd-fido2", &v); | |
139 | if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) | |
140 | continue; | |
141 | if (r < 0) | |
142 | return log_error_errno(r, "Failed to read JSON token data off disk: %m"); | |
143 | ||
144 | if (cid) | |
145 | return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), | |
146 | "Multiple FIDO2 tokens enrolled, cannot automatically determine token."); | |
147 | ||
148 | w = json_variant_by_key(v, "fido2-credential"); | |
149 | if (!w || !json_variant_is_string(w)) | |
150 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
151 | "FIDO2 token data lacks 'fido2-credential' field."); | |
152 | ||
153 | r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); | |
154 | if (r < 0) | |
155 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
156 | "Invalid base64 data in 'fido2-credential' field."); | |
157 | ||
158 | w = json_variant_by_key(v, "fido2-salt"); | |
159 | if (!w || !json_variant_is_string(w)) | |
160 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
161 | "FIDO2 token data lacks 'fido2-salt' field."); | |
162 | ||
163 | assert(!salt); | |
164 | assert(salt_size == 0); | |
165 | r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); | |
166 | if (r < 0) | |
167 | return log_error_errno(r, "Failed to decode base64 encoded salt."); | |
168 | ||
169 | assert(keyslot < 0); | |
170 | keyslot = cryptsetup_get_keyslot_from_token(v); | |
171 | if (keyslot < 0) | |
172 | return log_error_errno(keyslot, "Failed to extract keyslot index from FIDO2 JSON data: %m"); | |
173 | ||
174 | w = json_variant_by_key(v, "fido2-rp"); | |
175 | if (w) { | |
176 | /* The "rp" field is optional. */ | |
177 | ||
178 | if (!json_variant_is_string(w)) | |
179 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
180 | "FIDO2 token data's 'fido2-rp' field is not a string."); | |
181 | ||
182 | assert(!rp); | |
183 | rp = strdup(json_variant_string(w)); | |
184 | if (!rp) | |
185 | return log_oom(); | |
186 | } | |
8b3d4ff0 MB |
187 | |
188 | w = json_variant_by_key(v, "fido2-clientPin-required"); | |
189 | if (w) { | |
190 | /* The "fido2-clientPin-required" field is optional. */ | |
191 | ||
192 | if (!json_variant_is_boolean(w)) | |
193 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
194 | "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); | |
195 | ||
196 | SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); | |
197 | } else | |
198 | required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */ | |
199 | ||
200 | w = json_variant_by_key(v, "fido2-up-required"); | |
201 | if (w) { | |
202 | /* The "fido2-up-required" field is optional. */ | |
203 | ||
204 | if (!json_variant_is_boolean(w)) | |
205 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
206 | "FIDO2 token data's 'fido2-up-required' field is not a boolean."); | |
207 | ||
208 | SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); | |
209 | } else | |
210 | required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */ | |
211 | ||
212 | w = json_variant_by_key(v, "fido2-uv-required"); | |
213 | if (w) { | |
214 | /* The "fido2-uv-required" field is optional. */ | |
215 | ||
216 | if (!json_variant_is_boolean(w)) | |
217 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
218 | "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); | |
219 | ||
220 | SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); | |
221 | } else | |
222 | required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */ | |
3a6ce677 BR |
223 | } |
224 | ||
225 | if (!cid) | |
226 | return log_error_errno(SYNTHETIC_ERRNO(ENXIO), | |
227 | "No valid FIDO2 token data found."); | |
228 | ||
229 | log_info("Automatically discovered security FIDO2 token unlocks volume."); | |
230 | ||
231 | *ret_rp_id = TAKE_PTR(rp); | |
232 | *ret_cid = TAKE_PTR(cid); | |
233 | *ret_cid_size = cid_size; | |
234 | *ret_salt = TAKE_PTR(salt); | |
235 | *ret_salt_size = salt_size; | |
236 | *ret_keyslot = keyslot; | |
8b3d4ff0 | 237 | *ret_required = required; |
3a6ce677 BR |
238 | return 0; |
239 | } |