size_t cid_size = 0, salt_size = 0;
_cleanup_free_ char *rp = NULL;
int r, keyslot = -1;
- /* For backward compatibility, require pin and presence by default */
- Fido2EnrollFlags required = FIDO2ENROLL_PIN | FIDO2ENROLL_UP;
+ Fido2EnrollFlags required = 0;
assert(cd);
assert(ret_salt);
"FIDO2 token data's 'fido2-clientPin-required' field is not a boolean.");
SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w));
- }
+ } else
+ required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */
w = json_variant_by_key(v, "fido2-up-required");
if (w) {
"FIDO2 token data's 'fido2-up-required' field is not a boolean.");
SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w));
- }
+ } else
+ required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */
w = json_variant_by_key(v, "fido2-uv-required");
if (w) {
"FIDO2 token data's 'fido2-uv-required' field is not a boolean.");
SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w));
- }
+ } else
+ required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */
}
if (!cid)
log_info("User presence required to unlock.");
}
- if (has_uv) {
+ if (has_uv && !FLAGS_SET(required, FIDO2ENROLL_UV_OMIT)) {
r = sym_fido_assert_set_uv(a, FLAGS_SET(required, FIDO2ENROLL_UV) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
log_info("User verification required to unlock.");
}
- if (FLAGS_SET(required, FIDO2ENROLL_PIN)) {
- char **i;
+ for (;;) {
+ bool retry_with_up = false, retry_with_pin = false;
- /* OK, we need a pin, try with all pins in turn */
- if (strv_isempty(pins))
- r = FIDO_ERR_PIN_REQUIRED;
- else
- STRV_FOREACH(i, pins) {
- r = sym_fido_dev_get_assert(d, a, *i);
- if (r != FIDO_ERR_PIN_INVALID)
- break;
+ if (FLAGS_SET(required, FIDO2ENROLL_PIN)) {
+ char **i;
+
+ /* OK, we need a pin, try with all pins in turn */
+ if (strv_isempty(pins))
+ r = FIDO_ERR_PIN_REQUIRED;
+ else
+ STRV_FOREACH(i, pins) {
+ r = sym_fido_dev_get_assert(d, a, *i);
+ if (r != FIDO_ERR_PIN_INVALID)
+ break;
+ }
+
+ } else
+ r = sym_fido_dev_get_assert(d, a, NULL);
+
+ /* In some conditions, where a PIN or UP is required we might accept that. Let's check the
+ * conditions and if so try immediately again. */
+
+ switch (r) {
+
+ case FIDO_ERR_UP_REQUIRED:
+ /* So the token asked for "up". Try to turn it on, for compat with systemd 248 and try again. */
+
+ if (!has_up)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Token asks for user presence check but doesn't advertise 'up' feature.");
+
+ if (FLAGS_SET(required, FIDO2ENROLL_UP))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Token asks for user presence check but was already enabled.");
+
+ if (FLAGS_SET(required, FIDO2ENROLL_UP_IF_NEEDED)) {
+ log_info("User presence required to unlock.");
+ retry_with_up = true;
+ }
+
+ break;
+
+ case FIDO_ERR_UNSUPPORTED_OPTION:
+ /* AuthenTrend ATKey.Pro returns this instead of FIDO_ERR_UP_REQUIRED, let's handle
+ * it gracefully (also see below.) */
+
+ if (has_up && (required & (FIDO2ENROLL_UP|FIDO2ENROLL_UP_IF_NEEDED)) == FIDO2ENROLL_UP_IF_NEEDED) {
+ log_notice("Got unsupported option error when when user presence test is turned off. Trying with user presence test turned on.");
+ retry_with_up = true;
}
- } else
- r = sym_fido_dev_get_assert(d, a, NULL);
+
+ break;
+
+ case FIDO_ERR_PIN_REQUIRED:
+ /* A pin was requested. Maybe supply one, if we are configured to do so on request */
+
+ if (!has_client_pin)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Token asks for PIN but doesn't advertise 'clientPin' feature.");
+
+ if (FLAGS_SET(required, FIDO2ENROLL_PIN) && !strv_isempty(pins))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Token asks for PIN but one was already supplied.");
+
+ if ((required & (FIDO2ENROLL_PIN|FIDO2ENROLL_PIN_IF_NEEDED)) == FIDO2ENROLL_PIN_IF_NEEDED) {
+ /* If a PIN so far wasn't specified but is requested by the device, and
+ * FIDO2ENROLL_PIN_IF_NEEDED is set, then provide it */
+ log_debug("Retrying to create credential with PIN.");
+ retry_with_pin = true;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (!retry_with_up && !retry_with_pin)
+ break;
+
+ if (retry_with_up) {
+ r = sym_fido_assert_set_up(a, FIDO_OPT_TRUE);
+ if (r != FIDO_OK)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to enable FIDO2 user presence test: %s", sym_fido_strerr(r));
+
+ required |= FIDO2ENROLL_UP;
+ }
+
+ if (retry_with_pin)
+ required |= FIDO2ENROLL_PIN;
+ }
switch (r) {
case FIDO_OK: