]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/home/homectl-fido2.c
tree-wide: fix return value handling of base64mem()
[thirdparty/systemd.git] / src / home / homectl-fido2.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #if HAVE_LIBFIDO2
4 #include <fido.h>
5 #endif
6
7 #include "ask-password-api.h"
8 #include "errno-util.h"
9 #include "format-table.h"
10 #include "hexdecoct.h"
11 #include "homectl-fido2.h"
12 #include "homectl-pkcs11.h"
13 #include "libcrypt-util.h"
14 #include "libfido2-util.h"
15 #include "locale-util.h"
16 #include "memory-util.h"
17 #include "random-util.h"
18 #include "strv.h"
19
20 #if HAVE_LIBFIDO2
21 static int add_fido2_credential_id(
22 JsonVariant **v,
23 const void *cid,
24 size_t cid_size) {
25
26 _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
27 _cleanup_strv_free_ char **l = NULL;
28 _cleanup_free_ char *escaped = NULL;
29 ssize_t escaped_size;
30 int r;
31
32 assert(v);
33 assert(cid);
34
35 escaped_size = base64mem(cid, cid_size, &escaped);
36 if (escaped_size < 0)
37 return log_error_errno(escaped_size, "Failed to base64 encode FIDO2 credential ID: %m");
38
39 w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential"));
40 if (w) {
41 r = json_variant_strv(w, &l);
42 if (r < 0)
43 return log_error_errno(r, "Failed to parse FIDO2 credential ID list: %m");
44
45 if (strv_contains(l, escaped))
46 return 0;
47 }
48
49 r = strv_extend(&l, escaped);
50 if (r < 0)
51 return log_oom();
52
53 w = json_variant_unref(w);
54 r = json_variant_new_array_strv(&w, l);
55 if (r < 0)
56 return log_error_errno(r, "Failed to create FIDO2 credential ID JSON: %m");
57
58 r = json_variant_set_field(v, "fido2HmacCredential", w);
59 if (r < 0)
60 return log_error_errno(r, "Failed to update FIDO2 credential ID: %m");
61
62 return 0;
63 }
64
65 static int add_fido2_salt(
66 JsonVariant **v,
67 const void *cid,
68 size_t cid_size,
69 const void *fido2_salt,
70 size_t fido2_salt_size,
71 const void *secret,
72 size_t secret_size,
73 Fido2EnrollFlags lock_with) {
74
75 _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
76 _cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL;
77 ssize_t base64_encoded_size;
78 int r;
79
80 /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
81 * expect a NUL terminated string, and we use a binary key */
82 base64_encoded_size = base64mem(secret, secret_size, &base64_encoded);
83 if (base64_encoded_size < 0)
84 return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m");
85
86 r = hash_password(base64_encoded, &hashed);
87 if (r < 0)
88 return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
89
90 r = json_build(&e, JSON_BUILD_OBJECT(
91 JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
92 JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
93 JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed)),
94 JSON_BUILD_PAIR("up", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))),
95 JSON_BUILD_PAIR("uv", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))),
96 JSON_BUILD_PAIR("clientPin", JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN)))));
97
98 if (r < 0)
99 return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");
100
101 w = json_variant_ref(json_variant_by_key(*v, "privileged"));
102 l = json_variant_ref(json_variant_by_key(w, "fido2HmacSalt"));
103
104 r = json_variant_append_array(&l, e);
105 if (r < 0)
106 return log_error_errno(r, "Failed append FIDO2 salt: %m");
107
108 r = json_variant_set_field(&w, "fido2HmacSalt", l);
109 if (r < 0)
110 return log_error_errno(r, "Failed to set FDO2 salt: %m");
111
112 r = json_variant_set_field(v, "privileged", w);
113 if (r < 0)
114 return log_error_errno(r, "Failed to update privileged field: %m");
115
116 return 0;
117 }
118 #endif
119
120 int identity_add_fido2_parameters(
121 JsonVariant **v,
122 const char *device,
123 Fido2EnrollFlags lock_with,
124 int cred_alg) {
125
126 #if HAVE_LIBFIDO2
127 JsonVariant *un, *realm, *rn;
128 _cleanup_(erase_and_freep) void *secret = NULL, *salt = NULL;
129 _cleanup_(erase_and_freep) char *used_pin = NULL;
130 size_t cid_size, salt_size, secret_size;
131 _cleanup_free_ void *cid = NULL;
132 const char *fido_un;
133 int r;
134
135 assert(v);
136 assert(device);
137
138 un = json_variant_by_key(*v, "userName");
139 if (!un)
140 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
141 "userName field of user record is missing");
142 if (!json_variant_is_string(un))
143 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
144 "userName field of user record is not a string");
145
146 realm = json_variant_by_key(*v, "realm");
147 if (realm) {
148 if (!json_variant_is_string(realm))
149 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
150 "realm field of user record is not a string");
151
152 fido_un = strjoina(json_variant_string(un), json_variant_string(realm));
153 } else
154 fido_un = json_variant_string(un);
155
156 rn = json_variant_by_key(*v, "realName");
157 if (rn && !json_variant_is_string(rn))
158 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
159 "realName field of user record is not a string");
160
161 r = fido2_generate_hmac_hash(
162 device,
163 /* rp_id= */ "io.systemd.home",
164 /* rp_name= */ "Home Directory",
165 /* user_id= */ fido_un, strlen(fido_un), /* We pass the user ID and name as the same */
166 /* user_name= */ fido_un,
167 /* user_display_name= */ rn ? json_variant_string(rn) : NULL,
168 /* user_icon_name= */ NULL,
169 /* askpw_icon_name= */ "user-home",
170 lock_with,
171 cred_alg,
172 &cid, &cid_size,
173 &salt, &salt_size,
174 &secret, &secret_size,
175 &used_pin,
176 &lock_with);
177 if (r < 0)
178 return r;
179
180 r = add_fido2_credential_id(
181 v,
182 cid,
183 cid_size);
184 if (r < 0)
185 return r;
186
187 r = add_fido2_salt(
188 v,
189 cid,
190 cid_size,
191 salt,
192 salt_size,
193 secret,
194 secret_size,
195 lock_with);
196 if (r < 0)
197 return r;
198
199 /* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
200 * can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
201 * fscrypt. */
202 r = identity_add_token_pin(v, used_pin);
203 if (r < 0)
204 return r;
205
206 return 0;
207 #else
208 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
209 "FIDO2 tokens not supported on this build.");
210 #endif
211 }