1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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"
21 static int add_fido2_credential_id(
26 _cleanup_(json_variant_unrefp
) JsonVariant
*w
= NULL
;
27 _cleanup_strv_free_
char **l
= NULL
;
28 _cleanup_free_
char *escaped
= NULL
;
35 escaped_size
= base64mem(cid
, cid_size
, &escaped
);
37 return log_error_errno(escaped_size
, "Failed to base64 encode FIDO2 credential ID: %m");
39 w
= json_variant_ref(json_variant_by_key(*v
, "fido2HmacCredential"));
41 r
= json_variant_strv(w
, &l
);
43 return log_error_errno(r
, "Failed to parse FIDO2 credential ID list: %m");
45 if (strv_contains(l
, escaped
))
49 r
= strv_extend(&l
, escaped
);
53 w
= json_variant_unref(w
);
54 r
= json_variant_new_array_strv(&w
, l
);
56 return log_error_errno(r
, "Failed to create FIDO2 credential ID JSON: %m");
58 r
= json_variant_set_field(v
, "fido2HmacCredential", w
);
60 return log_error_errno(r
, "Failed to update FIDO2 credential ID: %m");
65 static int add_fido2_salt(
69 const void *fido2_salt
,
70 size_t fido2_salt_size
,
73 Fido2EnrollFlags lock_with
) {
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
;
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");
86 r
= hash_password(base64_encoded
, &hashed
);
88 return log_error_errno(errno_or_else(EINVAL
), "Failed to UNIX hash secret key: %m");
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
)))));
99 return log_error_errno(r
, "Failed to build FIDO2 salt JSON key object: %m");
101 w
= json_variant_ref(json_variant_by_key(*v
, "privileged"));
102 l
= json_variant_ref(json_variant_by_key(w
, "fido2HmacSalt"));
104 r
= json_variant_append_array(&l
, e
);
106 return log_error_errno(r
, "Failed append FIDO2 salt: %m");
108 r
= json_variant_set_field(&w
, "fido2HmacSalt", l
);
110 return log_error_errno(r
, "Failed to set FDO2 salt: %m");
112 r
= json_variant_set_field(v
, "privileged", w
);
114 return log_error_errno(r
, "Failed to update privileged field: %m");
120 int identity_add_fido2_parameters(
123 Fido2EnrollFlags lock_with
,
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
;
138 un
= json_variant_by_key(*v
, "userName");
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");
146 realm
= json_variant_by_key(*v
, "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");
152 fido_un
= strjoina(json_variant_string(un
), json_variant_string(realm
));
154 fido_un
= json_variant_string(un
);
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");
161 r
= fido2_generate_hmac_hash(
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",
174 &secret
, &secret_size
,
180 r
= add_fido2_credential_id(
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
202 r
= identity_add_token_pin(v
, used_pin
);
208 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
),
209 "FIDO2 tokens not supported on this build.");