]>
Commit | Line | Data |
---|---|---|
2bc5c425 LP |
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, | |
26 | void **ret_decrypted_key, | |
27 | size_t *ret_decrypted_key_size) { | |
28 | ||
29 | AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; | |
30 | _cleanup_strv_free_erase_ char **pins = NULL; | |
31 | _cleanup_free_ void *loaded_salt = NULL; | |
32 | const char *salt; | |
33 | size_t salt_size; | |
34 | char *e; | |
35 | int r; | |
36 | ||
37 | assert(cid); | |
38 | assert(key_file || key_data); | |
39 | ||
40 | if (key_data) { | |
41 | salt = key_data; | |
42 | salt_size = key_data_size; | |
43 | } else { | |
44 | _cleanup_free_ char *bindname = NULL; | |
45 | ||
46 | /* If we read the salt via AF_UNIX, make this client recognizable */ | |
47 | if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0) | |
48 | return log_oom(); | |
49 | ||
50 | r = read_full_file_full( | |
51 | AT_FDCWD, key_file, | |
52 | key_file_offset == 0 ? UINT64_MAX : key_file_offset, | |
53 | key_file_size == 0 ? SIZE_MAX : key_file_size, | |
54 | READ_FULL_FILE_CONNECT_SOCKET, | |
55 | bindname, | |
56 | (char**) &loaded_salt, &salt_size); | |
57 | if (r < 0) | |
58 | return r; | |
59 | ||
60 | salt = loaded_salt; | |
61 | } | |
62 | ||
63 | e = getenv("PIN"); | |
64 | if (e) { | |
65 | pins = strv_new(e); | |
66 | if (!pins) | |
67 | return log_oom(); | |
68 | ||
69 | string_erase(e); | |
70 | if (unsetenv("PIN") < 0) | |
71 | return log_error_errno(errno, "Failed to unset $PIN: %m"); | |
72 | } | |
73 | ||
74 | for (;;) { | |
75 | r = fido2_use_hmac_hash( | |
76 | device, | |
77 | rp_id ?: "io.systemd.cryptsetup", | |
78 | salt, salt_size, | |
79 | cid, cid_size, | |
80 | pins, | |
81 | /* up= */ true, | |
82 | ret_decrypted_key, | |
83 | ret_decrypted_key_size); | |
84 | if (!IN_SET(r, | |
85 | -ENOANO, /* needs pin */ | |
86 | -ENOLCK)) /* pin incorrect */ | |
87 | return r; | |
88 | ||
89 | pins = strv_free_erase(pins); | |
90 | ||
8806bb4b | 91 | r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); |
2bc5c425 | 92 | if (r < 0) |
45861042 | 93 | return log_error_errno(r, "Failed to ask for user password: %m"); |
2bc5c425 LP |
94 | |
95 | flags &= ~ASK_PASSWORD_ACCEPT_CACHED; | |
96 | } | |
97 | } | |
98 | ||
99 | int find_fido2_auto_data( | |
100 | struct crypt_device *cd, | |
101 | char **ret_rp_id, | |
102 | void **ret_salt, | |
103 | size_t *ret_salt_size, | |
104 | void **ret_cid, | |
105 | size_t *ret_cid_size, | |
106 | int *ret_keyslot) { | |
107 | ||
108 | _cleanup_free_ void *cid = NULL, *salt = NULL; | |
109 | size_t cid_size = 0, salt_size = 0; | |
110 | _cleanup_free_ char *rp = NULL; | |
111 | int r, keyslot = -1; | |
112 | ||
113 | assert(cd); | |
114 | assert(ret_salt); | |
115 | assert(ret_salt_size); | |
116 | assert(ret_cid); | |
117 | assert(ret_cid_size); | |
118 | assert(ret_keyslot); | |
119 | ||
120 | /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ | |
121 | ||
3c2c8e62 | 122 | for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) { |
2bc5c425 LP |
123 | _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; |
124 | JsonVariant *w; | |
125 | ||
126 | r = cryptsetup_get_token_as_json(cd, token, "systemd-fido2", &v); | |
127 | if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) | |
128 | continue; | |
129 | if (r < 0) | |
130 | return log_error_errno(r, "Failed to read JSON token data off disk: %m"); | |
131 | ||
132 | if (cid) | |
133 | return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), | |
134 | "Multiple FIDO2 tokens enrolled, cannot automatically determine token."); | |
135 | ||
136 | w = json_variant_by_key(v, "fido2-credential"); | |
137 | if (!w || !json_variant_is_string(w)) | |
138 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
139 | "FIDO2 token data lacks 'fido2-credential' field."); | |
140 | ||
f5fbe71d | 141 | r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); |
2bc5c425 LP |
142 | if (r < 0) |
143 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
144 | "Invalid base64 data in 'fido2-credential' field."); | |
145 | ||
146 | w = json_variant_by_key(v, "fido2-salt"); | |
147 | if (!w || !json_variant_is_string(w)) | |
148 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
149 | "FIDO2 token data lacks 'fido2-salt' field."); | |
150 | ||
151 | assert(!salt); | |
152 | assert(salt_size == 0); | |
f5fbe71d | 153 | r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); |
2bc5c425 LP |
154 | if (r < 0) |
155 | return log_error_errno(r, "Failed to decode base64 encoded salt."); | |
156 | ||
157 | assert(keyslot < 0); | |
158 | keyslot = cryptsetup_get_keyslot_from_token(v); | |
159 | if (keyslot < 0) | |
160 | return log_error_errno(keyslot, "Failed to extract keyslot index from FIDO2 JSON data: %m"); | |
161 | ||
162 | w = json_variant_by_key(v, "fido2-rp"); | |
163 | if (w) { | |
164 | /* The "rp" field is optional. */ | |
165 | ||
166 | if (!json_variant_is_string(w)) | |
167 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
168 | "FIDO2 token data's 'fido2-rp' field is not a string."); | |
169 | ||
170 | assert(!rp); | |
171 | rp = strdup(json_variant_string(w)); | |
172 | if (!rp) | |
173 | return log_oom(); | |
174 | } | |
175 | } | |
176 | ||
177 | if (!cid) | |
178 | return log_error_errno(SYNTHETIC_ERRNO(ENXIO), | |
179 | "No valid FIDO2 token data found."); | |
180 | ||
181 | log_info("Automatically discovered security FIDO2 token unlocks volume."); | |
182 | ||
183 | *ret_rp_id = TAKE_PTR(rp); | |
184 | *ret_cid = TAKE_PTR(cid); | |
185 | *ret_cid_size = cid_size; | |
186 | *ret_salt = TAKE_PTR(salt); | |
187 | *ret_salt_size = salt_size; | |
188 | *ret_keyslot = keyslot; | |
189 | return 0; | |
190 | } |