Commit 5034e855 authored by Nina Satragno's avatar Nina Satragno Committed by Commit Bot

[webauthn] Send permissions RPID with UvToken

Implement sending the permissions RPID [1] to authenticators when
getting a PinUvAuthToken using UV.

[1] https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#permissions-rpid

Fixed: 1090361
Change-Id: I4ac942ea787b60f3bc69b21da84c05bd29ff5801
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2225294
Commit-Queue: Nina Satragno <nsatragno@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#775303}
parent 26d14d14
......@@ -36,6 +36,7 @@ void FidoAuthenticator::GetUvRetries(
}
void FidoAuthenticator::GetUvToken(
base::Optional<std::string> rp_id,
FidoAuthenticator::GetTokenCallback callback) {
NOTREACHED();
}
......
......@@ -100,7 +100,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
// GetUvToken uses internal user verification to request a PinUvAuthToken from
// an authenticator. It is only valid to call this method if |Options|
// indicates that the authenticator supports UV tokens.
virtual void GetUvToken(GetTokenCallback callback);
// |rp_id| must be set if the PinUvAuthToken will be used for MakeCredential
// or GetAssertion.
virtual void GetUvToken(base::Optional<std::string> rp_id,
GetTokenCallback callback);
// SetPIN sets a new PIN on a device that does not currently have one. The
// length of |pin| must respect |pin::kMinLength| and |pin::kMaxLength|. It is
// only valid to call this method if |Options| indicates that the
......
......@@ -251,7 +251,6 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential(
if (Options()->user_verification_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured) {
// TODO(crbug.com/1056317): implement inline bioenrollment.
return device_support == ClientPinAvailability::kSupportedAndPinSet &&
can_collect_pin
? MakeCredentialPINDisposition::kUsePINForFallback
......@@ -706,13 +705,15 @@ void FidoDeviceAuthenticator::GetUvRetries(GetRetriesCallback callback) {
base::BindOnce(&pin::RetriesResponse::ParseUvRetries));
}
void FidoDeviceAuthenticator::GetUvToken(GetTokenCallback callback) {
GetEphemeralKey(
base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken,
weak_factory_.GetWeakPtr(), std::move(callback)));
void FidoDeviceAuthenticator::GetUvToken(base::Optional<std::string> rp_id,
GetTokenCallback callback) {
GetEphemeralKey(base::BindOnce(
&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken,
weak_factory_.GetWeakPtr(), std::move(rp_id), std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken(
base::Optional<std::string> rp_id,
GetTokenCallback callback,
CtapDeviceResponseCode status,
base::Optional<pin::KeyAgreementResponse> key) {
......@@ -723,7 +724,7 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken(
DCHECK(key);
pin::UvTokenRequest request(*key);
pin::UvTokenRequest request(*key, std::move(rp_id));
std::array<uint8_t, 32> shared_key = request.shared_key();
RunOperation<pin::UvTokenRequest, pin::TokenResponse>(
std::move(request), std::move(callback),
......
......@@ -47,7 +47,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
void GetPinRetries(GetRetriesCallback callback) override;
void GetPINToken(std::string pin, GetTokenCallback callback) override;
void GetUvRetries(GetRetriesCallback callback) override;
void GetUvToken(GetTokenCallback callback) override;
void GetUvToken(base::Optional<std::string> rp_id,
GetTokenCallback callback) override;
void SetPIN(const std::string& pin,
SetPINCallback callback) override;
void ChangePIN(const std::string& old_pin,
......@@ -139,6 +140,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
CtapDeviceResponseCode status,
base::Optional<pin::KeyAgreementResponse> key);
void OnHaveEphemeralKeyForUvToken(
base::Optional<std::string> rp_id,
GetTokenCallback callback,
CtapDeviceResponseCode status,
base::Optional<pin::KeyAgreementResponse> key);
......
......@@ -344,6 +344,7 @@ void GetAssertionRequestHandler::DispatchRequest(
FIDO_LOG(DEBUG) << "Getting UV token from "
<< authenticator->GetDisplayName();
authenticator->GetUvToken(
request_.rp_id,
base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken,
weak_factory_.GetWeakPtr(), authenticator));
return;
......@@ -727,6 +728,7 @@ void GetAssertionRequestHandler::OnUvRetriesResponse(
}
observer()->OnRetryUserVerification(response->retries);
authenticator_->GetUvToken(
request_.rp_id,
base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken,
weak_factory_.GetWeakPtr(), authenticator_));
}
......
......@@ -413,6 +413,7 @@ void MakeCredentialRequestHandler::DispatchRequest(
FidoTransportProtocol::kInternal) {
if (authenticator->Options()->supports_uv_token) {
authenticator->GetUvToken(
request_.rp.id,
base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken,
weak_factory_.GetWeakPtr(), authenticator));
return;
......@@ -818,6 +819,7 @@ void MakeCredentialRequestHandler::OnUvRetriesResponse(
}
observer()->OnRetryUserVerification(response->retries);
authenticator_->GetUvToken(
request_.rp.id,
base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken,
weak_factory_.GetWeakPtr(), authenticator_));
}
......
......@@ -508,8 +508,9 @@ AsCTAPRequestValuePair(const PinTokenRequest& request) {
});
}
UvTokenRequest::UvTokenRequest(const KeyAgreementResponse& peer_key)
: TokenRequest(peer_key) {}
UvTokenRequest::UvTokenRequest(const KeyAgreementResponse& peer_key,
base::Optional<std::string> rp_id)
: TokenRequest(peer_key), rp_id_(rp_id) {}
UvTokenRequest::~UvTokenRequest() = default;
......@@ -524,6 +525,10 @@ AsCTAPRequestValuePair(const UvTokenRequest& request) {
map->emplace(static_cast<int>(RequestKey::kPermissions),
static_cast<uint8_t>(Permissions::kMakeCredential) |
static_cast<uint8_t>(Permissions::kGetAssertion));
if (request.rp_id_) {
map->emplace(static_cast<int>(RequestKey::kPermissionsRPID),
*request.rp_id_);
}
});
}
......
......@@ -172,13 +172,17 @@ class PinTokenRequest : public TokenRequest {
class UvTokenRequest : public TokenRequest {
public:
explicit UvTokenRequest(const KeyAgreementResponse& peer_key);
UvTokenRequest(const KeyAgreementResponse& peer_key,
base::Optional<std::string> rp_id);
UvTokenRequest(UvTokenRequest&&);
UvTokenRequest(const UvTokenRequest&) = delete;
virtual ~UvTokenRequest();
friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>>
AsCTAPRequestValuePair(const UvTokenRequest&);
private:
base::Optional<std::string> rp_id_;
};
// TokenResponse represents the response to a pin-token request. In order to
......
......@@ -61,6 +61,14 @@ std::vector<uint8_t> ConstructResponse(CtapDeviceResponseCode response_code,
return response;
}
// Returns true if the |permissions| parameter requires an explicit permissions
// RPID.
bool PermissionsRequireRPID(uint8_t permissions) {
return permissions &
static_cast<uint8_t>(pin::Permissions::kMakeCredential) ||
permissions & static_cast<uint8_t>(pin::Permissions::kGetAssertion);
}
void ReturnCtap2Response(
FidoDevice::DeviceCallback cb,
CtapDeviceResponseCode response_code,
......@@ -605,6 +613,7 @@ base::Optional<CtapDeviceResponseCode>
VirtualCtap2Device::CheckUserVerification(
bool is_make_credential,
const AuthenticatorSupportedOptions& options,
const std::string& rp_id,
const base::Optional<std::vector<uint8_t>>& pin_auth,
const base::Optional<uint8_t>& pin_protocol,
base::span<const uint8_t> pin_token,
......@@ -658,9 +667,11 @@ VirtualCtap2Device::CheckUserVerification(
return CtapDeviceResponseCode::kCtap2ErrInvalidOption;
}
// Step 4.
// "If authenticator is protected by some form of user verification:"
bool uv = false;
if (can_do_uv) {
// "If the request is passed with "uv" option, use built-in user
// verification method and verify the user."
if (user_verification == UserVerificationRequirement::kRequired) {
if (options.user_verification_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
......@@ -680,17 +691,38 @@ VirtualCtap2Device::CheckUserVerification(
}
}
// "If pinUvAuthParam parameter is present and pinUvAuthProtocol is 1".
if (pin_auth && (options.client_pin_availability ==
AuthenticatorSupportedOptions::ClientPinAvailability::
kSupportedAndPinSet ||
options.supports_uv_token)) {
DCHECK(pin_protocol && *pin_protocol == 1);
// "Verify that the pinUvAuthToken has the {mc,ga} permission, if not,
// return CTAP2_ERR_PIN_AUTH_INVALID."
auto permission = is_make_credential ? pin::Permissions::kMakeCredential
: pin::Permissions::kGetAssertion;
if (mutable_state()->pin_uv_token_permissions &
static_cast<uint8_t>(permission) &&
CheckPINToken(pin_token, *pin_auth, client_data_hash)) {
if (!(mutable_state()->pin_uv_token_permissions &
static_cast<uint8_t>(permission))) {
return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
}
// "If the pinUvAuthToken has a permissions RPID associated and it
// does not match the RPID in this request, return
// CTAP2_ERR_PIN_AUTH_INVALID."
if (mutable_state()->pin_uv_token_rpid &&
mutable_state()->pin_uv_token_rpid != rp_id) {
return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
}
// "If the pinUvAuthToken does not have a permissions RPID associated,
// associate the request RPID as permissions RPID."
if (!mutable_state()->pin_uv_token_rpid) {
mutable_state()->pin_uv_token_rpid = rp_id;
}
// Verify pinUvAuthParam.
if (CheckPINToken(pin_token, *pin_auth, client_data_hash)) {
uv = true;
} else {
return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
......@@ -728,7 +760,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
bool user_verified;
const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification(
true /* is makeCredential */, options, request.pin_auth,
true /* is makeCredential */, options, request.rp.id, request.pin_auth,
request.pin_protocol, mutable_state()->pin_token,
request.client_data_hash, request.user_verification, &user_verified);
if (uv_error != CtapDeviceResponseCode::kSuccess) {
......@@ -966,7 +998,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
bool user_verified;
const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification(
false /* not makeCredential */, options, request.pin_auth,
false /* not makeCredential */, options, request.rp_id, request.pin_auth,
request.pin_protocol, mutable_state()->pin_token,
request.client_data_hash, request.user_verification, &user_verified);
if (uv_error != CtapDeviceResponseCode::kSuccess) {
......@@ -1323,6 +1355,13 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
}
if (request_map.find(cbor::Value(static_cast<int>(
pin::RequestKey::kPermissions))) != request_map.end() ||
request_map.find(cbor::Value(static_cast<int>(
pin::RequestKey::kPermissionsRPID))) != request_map.end()) {
return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
}
uint8_t shared_key[SHA256_DIGEST_LENGTH];
if (!mutable_state()->ecdh_key) {
// kGetKeyAgreement should have been called first.
......@@ -1344,6 +1383,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
mutable_state()->pin_uv_token_permissions =
static_cast<uint8_t>(pin::Permissions::kMakeCredential) |
static_cast<uint8_t>(pin::Permissions::kGetAssertion);
mutable_state()->pin_uv_token_rpid = base::nullopt;
response_map.emplace(
static_cast<int>(pin::ResponseKey::kPINToken),
......@@ -1369,6 +1409,20 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
}
const auto permissions_rpid_it = request_map.find(
cbor::Value(static_cast<int>(pin::RequestKey::kPermissionsRPID)));
base::Optional<std::string> permissions_rpid;
if (permissions_rpid_it == request_map.end() &&
PermissionsRequireRPID(permissions)) {
return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
}
if (permissions_rpid_it != request_map.end()) {
if (!permissions_rpid_it->second.is_string()) {
return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
}
permissions_rpid = permissions_rpid_it->second.GetString();
}
if (device_info_->options.user_verification_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedButNotConfigured) {
......@@ -1401,6 +1455,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
mutable_state()->pin_retries = kMaxPinRetries;
mutable_state()->uv_retries = kMaxUvRetries;
mutable_state()->pin_uv_token_permissions = permissions;
mutable_state()->pin_uv_token_rpid = permissions_rpid;
response_map.emplace(
static_cast<int>(pin::ResponseKey::kPINToken),
......@@ -1408,6 +1463,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
break;
}
// TODO(crbug.com/1087419): implement
// getPinUvAuthTokenUsingPinWithPermissions.
default:
return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
}
......@@ -1696,6 +1753,8 @@ CtapDeviceResponseCode VirtualCtap2Device::OnBioEnrollment(
using SubCmd = BioEnrollmentSubCommand;
switch (*cmd) {
// TODO(crbug.com/1090415): some of these commands should be checking
// PinUvAuthToken.
case SubCmd::kGetFingerprintSensorInfo:
response_map.emplace(
static_cast<int>(BioEnrollmentResponseKey::kModality),
......
......@@ -157,6 +157,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
base::Optional<CtapDeviceResponseCode> CheckUserVerification(
bool is_make_credential,
const AuthenticatorSupportedOptions& options,
const std::string& rp_id,
const base::Optional<std::vector<uint8_t>>& pin_auth,
const base::Optional<uint8_t>& pin_protocol,
base::span<const uint8_t> pin_token,
......
......@@ -166,8 +166,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
// itself.
uint8_t pin_token[32];
// The permissions parameter for |pin_token|.
// TODO(nsatragno): implement permissions RPID.
uint8_t pin_uv_token_permissions = 0;
// The permissions RPID for |pin_token|.
base::Optional<std::string> pin_uv_token_rpid;
// Number of internal UV retries remaining.
int uv_retries = kMaxUvRetries;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment