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