]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/cryptsetup/cryptsetup-fido2.c
Merge pull request #18886 from anitazha/shutdownconsole
[thirdparty/systemd.git] / src / cryptsetup / cryptsetup-fido2.c
CommitLineData
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
13int 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
91 r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", until, flags, &pins);
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
99int 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}