summaryrefslogtreecommitdiff
path: root/chromium/device/fido/virtual_ctap2_device.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/device/fido/virtual_ctap2_device.cc')
-rw-r--r--chromium/device/fido/virtual_ctap2_device.cc261
1 files changed, 199 insertions, 62 deletions
diff --git a/chromium/device/fido/virtual_ctap2_device.cc b/chromium/device/fido/virtual_ctap2_device.cc
index 6cea9e49edd..6a82f0679df 100644
--- a/chromium/device/fido/virtual_ctap2_device.cc
+++ b/chromium/device/fido/virtual_ctap2_device.cc
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
+#include <memory>
#include <string>
#include <utility>
@@ -54,6 +55,17 @@ constexpr std::array<uint8_t, kAaguidLength> kDeviceAaguid = {
{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08}};
+constexpr uint8_t kSupportedPermissionsMask =
+ static_cast<uint8_t>(pin::Permissions::kMakeCredential) |
+ static_cast<uint8_t>(pin::Permissions::kGetAssertion) |
+ static_cast<uint8_t>(pin::Permissions::kCredentialManagement) |
+ static_cast<uint8_t>(pin::Permissions::kBioEnrollment);
+
+struct PinUvAuthTokenPermissions {
+ uint8_t permissions;
+ base::Optional<std::string> rp_id;
+};
+
std::vector<uint8_t> ConstructResponse(CtapDeviceResponseCode response_code,
base::span<const uint8_t> data) {
std::vector<uint8_t> response{base::strict_cast<uint8_t>(response_code)};
@@ -61,6 +73,45 @@ 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);
+}
+
+CtapDeviceResponseCode ExtractPermissions(
+ const cbor::Value::MapValue& request_map,
+ PinUvAuthTokenPermissions& out_permissions) {
+ const auto permissions_it = request_map.find(
+ cbor::Value(static_cast<int>(pin::RequestKey::kPermissions)));
+ if (permissions_it == request_map.end() ||
+ !permissions_it->second.is_unsigned()) {
+ return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
+ }
+ out_permissions.permissions = permissions_it->second.GetUnsigned();
+ if (out_permissions.permissions == 0) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+
+ DCHECK((out_permissions.permissions & ~kSupportedPermissionsMask) == 0);
+
+ const auto permissions_rpid_it = request_map.find(
+ cbor::Value(static_cast<int>(pin::RequestKey::kPermissionsRPID)));
+ if (permissions_rpid_it == request_map.end() &&
+ PermissionsRequireRPID(out_permissions.permissions)) {
+ return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
+ }
+ if (permissions_rpid_it != request_map.end()) {
+ if (!permissions_rpid_it->second.is_string()) {
+ return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
+ }
+ out_permissions.rp_id = permissions_rpid_it->second.GetString();
+ }
+ return CtapDeviceResponseCode::kSuccess;
+}
+
void ReturnCtap2Response(
FidoDevice::DeviceCallback cb,
CtapDeviceResponseCode response_code,
@@ -384,13 +435,8 @@ VirtualCtap2Device::Config& VirtualCtap2Device::Config::operator=(
const Config&) = default;
VirtualCtap2Device::Config::~Config() = default;
-VirtualCtap2Device::VirtualCtap2Device() : VirtualFidoDevice() {
- device_info_ =
- AuthenticatorGetInfoResponse({ProtocolVersion::kCtap2}, kDeviceAaguid);
- device_info_->algorithms = {
- static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256),
- static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256),
- };
+VirtualCtap2Device::VirtualCtap2Device() {
+ Init({ProtocolVersion::kCtap2});
}
VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
@@ -399,10 +445,9 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap2};
if (config.u2f_support) {
versions.emplace_back(ProtocolVersion::kU2f);
- u2f_device_.reset(new VirtualU2fDevice(NewReferenceToState()));
+ u2f_device_ = std::make_unique<VirtualU2fDevice>(NewReferenceToState());
}
- device_info_ =
- AuthenticatorGetInfoResponse(std::move(versions), kDeviceAaguid);
+ Init(std::move(versions));
AuthenticatorSupportedOptions options;
bool options_updated = false;
@@ -427,9 +472,12 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
options.user_verification_availability = AuthenticatorSupportedOptions::
UserVerificationAvailability::kSupportedButNotConfigured;
}
- options.supports_uv_token = config.uv_token_support;
}
+ options.supports_pin_uv_auth_token = config.pin_uv_auth_token_support;
+ DCHECK(!options.supports_pin_uv_auth_token ||
+ base::Contains(config.ctap2_versions, Ctap2Version::kCtap2_1));
+
if (config.resident_key_support) {
options_updated = true;
options.supports_resident_key = true;
@@ -438,6 +486,7 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
if (config.credential_management_support) {
options_updated = true;
options.supports_credential_management = true;
+ options.supports_credential_management_preview = true;
}
if (config.bio_enrollment_support) {
@@ -483,14 +532,22 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
device_info_->options = std::move(options);
}
+ std::vector<std::string> extensions;
+
if (config.cred_protect_support) {
- device_info_->extensions.emplace(
- {std::string(device::kExtensionCredProtect)});
+ extensions.emplace_back(device::kExtensionCredProtect);
+ }
+
+ if (config.hmac_secret_support) {
+ extensions.emplace_back(device::kExtensionHmacSecret);
}
if (config.support_android_client_data_extension) {
- device_info_->extensions.emplace(
- {std::string(device::kExtensionAndroidClientData)});
+ extensions.emplace_back(device::kExtensionAndroidClientData);
+ }
+
+ if (!extensions.empty()) {
+ device_info_->extensions.emplace(std::move(extensions));
}
if (config.max_credential_count_in_list > 0) {
@@ -501,6 +558,11 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
if (config.max_credential_id_length > 0) {
device_info_->max_credential_id_length = config.max_credential_id_length;
}
+
+ if (config.support_invalid_for_testing_algorithm) {
+ device_info_->algorithms.push_back(
+ static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting));
+ }
}
VirtualCtap2Device::~VirtualCtap2Device() = default;
@@ -570,6 +632,7 @@ FidoDevice::CancelToken VirtualCtap2Device::DeviceTransact(
break;
}
case CtapRequestCommand::kAuthenticatorCredentialManagement:
+ case CtapRequestCommand::kAuthenticatorCredentialManagementPreview:
response_code = OnCredentialManagement(request_bytes, &response_data);
break;
case CtapRequestCommand::kAuthenticatorBioEnrollment:
@@ -590,10 +653,21 @@ base::WeakPtr<FidoDevice> VirtualCtap2Device::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
+void VirtualCtap2Device::Init(std::vector<ProtocolVersion> versions) {
+ device_info_ = AuthenticatorGetInfoResponse(
+ std::move(versions), config_.ctap2_versions, kDeviceAaguid);
+ device_info_->algorithms = {
+ static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256),
+ static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA),
+ static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256),
+ };
+}
+
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,
@@ -647,9 +721,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::
@@ -669,11 +745,37 @@ 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)) {
+ options.supports_pin_uv_auth_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))) {
+ 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 {
@@ -712,7 +814,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) {
@@ -756,11 +858,20 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
switch (param.algorithm) {
default:
continue;
- case static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256):
- private_key = FreshP256Key();
+ case static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256):
+ private_key = PrivateKey::FreshP256Key();
+ break;
+ case static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256):
+ private_key = PrivateKey::FreshRSAKey();
break;
- case static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256):
- private_key = FreshRSAKey();
+ case static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA):
+ private_key = PrivateKey::FreshEd25519Key();
+ break;
+ case static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting):
+ if (!config_.support_invalid_for_testing_algorithm) {
+ continue;
+ }
+ private_key = PrivateKey::FreshInvalidForTestingKey();
break;
}
break;
@@ -785,11 +896,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
}
// Our key handles are simple hashes of the public key.
- const auto key_handle = crypto::SHA256Hash(public_key->cose_key_bytes());
+ const auto key_handle = crypto::SHA256Hash(public_key->cose_key_bytes);
base::Optional<cbor::Value> extensions;
cbor::Value::MapValue extensions_map;
if (request.hmac_secret) {
+ if (!config_.hmac_secret_support) {
+ // Should not have been sent. Authenticators will normally ignore unknown
+ // extensions but Chromium should not make this mistake.
+ DLOG(ERROR)
+ << "Rejecting makeCredential due to unexpected hmac_secret extension";
+ return base::nullopt;
+ }
extensions_map.emplace(cbor::Value(kExtensionHmacSecret),
cbor::Value(true));
}
@@ -816,8 +934,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
extensions = cbor::Value(std::move(extensions_map));
}
- auto authenticator_data = ConstructAuthenticatorData(
- rp_id_hash, user_verified, 01ul,
+ AuthenticatorData authenticator_data(
+ rp_id_hash, /*user_present=*/true, user_verified, 01ul,
ConstructAttestedCredentialData(key_handle, std::move(public_key)),
std::move(extensions));
@@ -941,7 +1059,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) {
@@ -1058,9 +1176,9 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
registration.second->private_key->GetPublicKey()));
}
- auto authenticator_data = ConstructAuthenticatorData(
- rp_id_hash, user_verified, registration.second->counter,
- std::move(opt_attested_cred_data),
+ AuthenticatorData authenticator_data(
+ rp_id_hash, /*user_present=*/true, user_verified,
+ registration.second->counter, std::move(opt_attested_cred_data),
extensions ? base::make_optional(extensions->Clone()) : base::nullopt);
base::Optional<std::string> opt_android_client_data_json;
@@ -1158,7 +1276,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
std::vector<uint8_t>* response) {
if (device_info_->options.client_pin_availability ==
AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported &&
- !config_.uv_token_support) {
+ !config_.pin_uv_auth_token_support) {
return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
}
@@ -1287,7 +1405,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
break;
}
- case static_cast<int>(device::pin::Subcommand::kGetPINToken): {
+ case static_cast<int>(device::pin::Subcommand::kGetPINToken):
+ case static_cast<int>(
+ device::pin::Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions): {
+ if (subcommand ==
+ static_cast<int>(device::pin::Subcommand::
+ kGetPinUvAuthTokenUsingPinWithPermissions) &&
+ !config_.pin_uv_auth_token_support) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidCommand;
+ }
const auto encrypted_pin_hash =
GetPINBytestring(request_map, pin::RequestKey::kPINHashEnc);
const auto peer_key =
@@ -1298,6 +1424,31 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
}
+ PinUvAuthTokenPermissions permissions;
+ if (subcommand ==
+ static_cast<int>(device::pin::Subcommand::kGetPINToken)) {
+ 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;
+ }
+ // Set default PinUvAuthToken permissions.
+ permissions.permissions =
+ static_cast<uint8_t>(pin::Permissions::kMakeCredential) |
+ static_cast<uint8_t>(pin::Permissions::kGetAssertion);
+ } else {
+ DCHECK_EQ(
+ subcommand,
+ static_cast<int>(device::pin::Subcommand::
+ kGetPinUvAuthTokenUsingPinWithPermissions));
+ CtapDeviceResponseCode response =
+ ExtractPermissions(request_map, permissions);
+ if (response != CtapDeviceResponseCode::kSuccess) {
+ return response;
+ }
+ }
+
uint8_t shared_key[SHA256_DIGEST_LENGTH];
if (!mutable_state()->ecdh_key) {
// kGetKeyAgreement should have been called first.
@@ -1313,6 +1464,11 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
return err;
};
+ mutable_state()->pin_retries = kMaxPinRetries;
+
+ mutable_state()->pin_uv_token_permissions = permissions.permissions;
+ mutable_state()->pin_uv_token_rpid = permissions.rp_id;
+
response_map.emplace(
static_cast<int>(pin::ResponseKey::kPINToken),
GenerateAndEncryptToken(shared_key, mutable_state()->pin_token));
@@ -1326,6 +1482,13 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
return CtapDeviceResponseCode::kCtap2ErrMissingParameter;
}
+ PinUvAuthTokenPermissions permissions;
+ CtapDeviceResponseCode response =
+ ExtractPermissions(request_map, permissions);
+ if (response != CtapDeviceResponseCode::kSuccess) {
+ return response;
+ }
+
if (device_info_->options.user_verification_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedButNotConfigured) {
@@ -1357,6 +1520,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
mutable_state()->pin_retries = kMaxPinRetries;
mutable_state()->uv_retries = kMaxUvRetries;
+ mutable_state()->pin_uv_token_permissions = permissions.permissions;
+ mutable_state()->pin_uv_token_rpid = permissions.rp_id;
response_map.emplace(
static_cast<int>(pin::ResponseKey::kPINToken),
@@ -1652,6 +1817,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),
@@ -1831,7 +1998,7 @@ void VirtualCtap2Device::InitPendingRegistrations(
registration.first)));
base::Optional<cbor::Value> cose_key = cbor::Reader::Read(
- registration.second.private_key->GetPublicKey()->cose_key_bytes());
+ registration.second.private_key->GetPublicKey()->cose_key_bytes);
response_map.emplace(
static_cast<int>(CredentialManagementResponseKey::kPublicKey),
cose_key->GetMap());
@@ -1874,34 +2041,4 @@ AttestedCredentialData VirtualCtap2Device::ConstructAttestedCredentialData(
fido_parsing_utils::Materialize(key_handle),
std::move(public_key));
}
-
-AuthenticatorData VirtualCtap2Device::ConstructAuthenticatorData(
- base::span<const uint8_t, kRpIdHashLength> rp_id_hash,
- bool user_verified,
- uint32_t current_signature_count,
- base::Optional<AttestedCredentialData> attested_credential_data,
- base::Optional<cbor::Value> extensions) {
- uint8_t flag =
- base::strict_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence);
- if (user_verified) {
- flag |= base::strict_cast<uint8_t>(
- AuthenticatorData::Flag::kTestOfUserVerification);
- }
- if (attested_credential_data)
- flag |= base::strict_cast<uint8_t>(AuthenticatorData::Flag::kAttestation);
- if (extensions) {
- flag |= base::strict_cast<uint8_t>(
- AuthenticatorData::Flag::kExtensionDataIncluded);
- }
-
- std::array<uint8_t, kSignCounterLength> signature_counter;
- signature_counter[0] = (current_signature_count >> 24) & 0xff;
- signature_counter[1] = (current_signature_count >> 16) & 0xff;
- signature_counter[2] = (current_signature_count >> 8) & 0xff;
- signature_counter[3] = (current_signature_count)&0xff;
-
- return AuthenticatorData(rp_id_hash, flag, signature_counter,
- std::move(attested_credential_data),
- std::move(extensions));
-}
} // namespace device