1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-25 06:03:40 +03:00

Merge pull request #25268 from PeterCxy/fido2-preflight

libfido2-util: Perform pre-flight check for credentials in token
This commit is contained in:
Luca Boccassi 2022-11-12 15:51:47 +01:00 committed by GitHub
commit 0d217f6349
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -194,6 +194,157 @@ static int verify_features(
return 0;
}
static int fido2_assert_set_basic_properties(
fido_assert_t *a,
const char *rp_id,
const void *cid,
size_t cid_size) {
int r;
assert(a);
assert(rp_id);
assert(cid);
assert(cid_size > 0);
r = sym_fido_assert_set_rp(a, rp_id);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
r = sym_fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
return 0;
}
static int fido2_common_assert_error_handle(int r) {
switch (r) {
case FIDO_OK:
return 0;
case FIDO_ERR_NO_CREDENTIALS:
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
"Wrong security token; needed credentials not present on token.");
case FIDO_ERR_PIN_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
"Security token requires PIN.");
case FIDO_ERR_PIN_AUTH_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"PIN of security token is blocked, please remove/reinsert token.");
#ifdef FIDO_ERR_UV_BLOCKED
case FIDO_ERR_UV_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"Verification of security token is blocked, please remove/reinsert token.");
#endif
case FIDO_ERR_PIN_INVALID:
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
"PIN of security token incorrect.");
case FIDO_ERR_UP_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"User presence required.");
case FIDO_ERR_ACTION_TIMEOUT:
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
default:
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", sym_fido_strerr(r));
}
}
static int fido2_is_cred_in_specific_token(
const char *path,
const char *rp_id,
const void *cid,
size_t cid_size,
char **pins,
Fido2EnrollFlags flags) {
assert(path);
assert(rp_id);
assert(cid);
assert(cid_size);
_cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL;
_cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL;
bool has_up = false, has_uv = false;
int r;
d = sym_fido_dev_new();
if (!d)
return log_oom();
r = sym_fido_dev_open(d, path);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to open FIDO2 device %s: %s", path, sym_fido_strerr(r));
r = verify_features(d, path, LOG_ERR, NULL, NULL, &has_up, &has_uv);
if (r < 0)
return r;
a = sym_fido_assert_new();
if (!a)
return log_oom();
r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size);
if (r < 0)
return r;
/* According to CTAP 2.1 specification, to do pre-flight we need to set up option to false
* with optionally pinUvAuthParam in assertion[1]. But for authenticator that doesn't support
* user presence, once up option is present, the authenticator may return CTAP2_ERR_UNSUPPORTED_OPTION[2].
* So we simplely omit the option in that case.
* Reference:
* 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight
* 2: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion (in step 5)
*/
if (has_up)
r = sym_fido_assert_set_up(a, FIDO_OPT_FALSE);
else
r = sym_fido_assert_set_up(a, FIDO_OPT_OMIT);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set assertion user presence: %s", sym_fido_strerr(r));
/* According to CTAP 2.1 specification, if the credential is marked as userVerificationRequired,
* we may pass pinUvAuthParam parameter or set uv option to true in order to check whether the
* credential is in the token. Here we choose to use PIN (pinUvAuthParam) to achieve this.
* If we don't do that, the authenticator will remove the credential from the applicable
* credentials list, hence CTAP2_ERR_NO_CREDENTIALS error will be returned.
* Please see
* https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#sctn-getAssert-authnr-alg
* step 7.4 for detailed information.
*/
if (FLAGS_SET(flags, FIDO2ENROLL_UV) && has_uv) {
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);
switch (r) {
case FIDO_OK:
return true;
case FIDO_ERR_NO_CREDENTIALS:
return false;
default:
return fido2_common_assert_error_handle(r);
}
}
static int fido2_use_hmac_hash_specific_token(
const char *path,
const char *rp_id,
@ -263,20 +414,9 @@ static int fido2_use_hmac_hash_specific_token(
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_rp(a, rp_id);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
r = sym_fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size);
if (r < 0)
return r;
log_info("Asking FIDO2 token for authentication.");
@ -403,36 +543,9 @@ static int fido2_use_hmac_hash_specific_token(
required |= FIDO2ENROLL_PIN;
}
switch (r) {
case FIDO_OK:
break;
case FIDO_ERR_NO_CREDENTIALS:
return log_error_errno(SYNTHETIC_ERRNO(EBADSLT),
"Wrong security token; needed credentials not present on token.");
case FIDO_ERR_PIN_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(ENOANO),
"Security token requires PIN.");
case FIDO_ERR_PIN_AUTH_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"PIN of security token is blocked, please remove/reinsert token.");
#ifdef FIDO_ERR_UV_BLOCKED
case FIDO_ERR_UV_BLOCKED:
return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD),
"Verification of security token is blocked, please remove/reinsert token.");
#endif
case FIDO_ERR_PIN_INVALID:
return log_error_errno(SYNTHETIC_ERRNO(ENOLCK),
"PIN of security token incorrect.");
case FIDO_ERR_UP_REQUIRED:
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE),
"User presence required.");
case FIDO_ERR_ACTION_TIMEOUT:
return log_error_errno(SYNTHETIC_ERRNO(ENOSTR),
"Token action timeout. (User didn't interact with token quickly enough.)");
default:
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to ask token for assertion: %s", sym_fido_strerr(r));
}
r = fido2_common_assert_error_handle(r);
if (r < 0)
return r;
hmac = sym_fido_assert_hmac_secret_ptr(a, 0);
if (!hmac)
@ -519,6 +632,30 @@ int fido2_use_hmac_hash(
goto finish;
}
r = fido2_is_cred_in_specific_token(path, rp_id, cid, cid_size, pins, required);
/* We handle PIN related errors here since we have used PIN in the check.
* If PIN error happens, we can avoid pin retries decreased the second time. */
if (IN_SET(r, -ENOANO, /* → pin required */
-ENOLCK, /* → pin incorrect */
-EOWNERDEAD)) { /* → uv blocked */
/* If it's not the last device, try next one */
if (i < found - 1)
continue;
/* -EOWNERDEAD is returned when uv is blocked. Map it to EAGAIN so users can reinsert
* the token and try again. */
if (r == -EOWNERDEAD)
r = -EAGAIN;
goto finish;
} else if (r == -ENODEV) /* not a FIDO2 device or lacking HMAC-SECRET extension */
continue;
else if (r < 0)
log_error_errno(r, "Failed to determine whether the credential is in the token, trying anyway: %m");
else if (r == 0) {
log_debug("The credential is not in the token %s, skipping.", path);
continue;
}
r = fido2_use_hmac_hash_specific_token(path, rp_id, salt, salt_size, cid, cid_size, pins, required, ret_hmac, ret_hmac_size);
if (!IN_SET(r,
-EBADSLT, /* device doesn't understand our credential hash */
@ -762,20 +899,9 @@ int fido2_generate_hmac_hash(
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_rp(a, rp_id);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion ID: %s", sym_fido_strerr(r));
r = sym_fido_assert_set_clientdata_hash(a, (const unsigned char[32]) {}, 32);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to set FIDO2 assertion client data hash: %s", sym_fido_strerr(r));
r = sym_fido_assert_allow_cred(a, cid, cid_size);
if (r != FIDO_OK)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Failed to add FIDO2 assertion credential ID: %s", sym_fido_strerr(r));
r = fido2_assert_set_basic_properties(a, rp_id, cid, cid_size);
if (r < 0)
return r;
log_info("Generating secret key on FIDO2 security token.");