// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "device/fido/win/type_conversions.h"
#include <algorithm>
#include <optional>
#include <string>
#include <vector>
#include "base/containers/fixed_flat_map.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/cbor/reader.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/authenticator_make_credential_response.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "device/fido/get_assertion_request_handler.h"
#include "device/fido/make_credential_request_handler.h"
#include "device/fido/opaque_attestation_statement.h"
#include "third_party/microsoft_webauthn/webauthn.h"
namespace device {
namespace {
std::optional<std::vector<uint8_t>> HMACSecretOutputs(
const WEBAUTHN_HMAC_SECRET_SALT& salt) {
constexpr size_t kOutputLength = 32;
if (salt.cbFirst != kOutputLength ||
(salt.cbSecond != 0 && salt.cbSecond != kOutputLength)) {
FIDO_LOG(ERROR) << "Incorrect HMAC output lengths: " << salt.cbFirst << " "
<< salt.cbSecond;
return std::nullopt;
}
std::vector<uint8_t> ret;
ret.insert(ret.end(), salt.pbFirst, salt.pbFirst + salt.cbFirst);
if (salt.cbSecond == kOutputLength) {
ret.insert(ret.end(), salt.pbSecond, salt.pbSecond + salt.cbSecond);
}
return ret;
}
} // namespace
std::optional<FidoTransportProtocol> FromWinTransportsMask(
const DWORD transport) {
switch (transport) {
case WEBAUTHN_CTAP_TRANSPORT_USB:
return FidoTransportProtocol::kUsbHumanInterfaceDevice;
case WEBAUTHN_CTAP_TRANSPORT_NFC:
return FidoTransportProtocol::kNearFieldCommunication;
case WEBAUTHN_CTAP_TRANSPORT_BLE:
return FidoTransportProtocol::kBluetoothLowEnergy;
case WEBAUTHN_CTAP_TRANSPORT_INTERNAL:
return FidoTransportProtocol::kInternal;
case WEBAUTHN_CTAP_TRANSPORT_HYBRID:
return FidoTransportProtocol::kHybrid;
default:
// Ignore _TEST and possibly future others.
return std::nullopt;
}
}
uint32_t ToWinTransportsMask(
const base::flat_set<FidoTransportProtocol>& transports) {
uint32_t result = 0;
for (const FidoTransportProtocol transport : transports) {
switch (transport) {
case FidoTransportProtocol::kUsbHumanInterfaceDevice:
result |= WEBAUTHN_CTAP_TRANSPORT_USB;
break;
case FidoTransportProtocol::kNearFieldCommunication:
result |= WEBAUTHN_CTAP_TRANSPORT_NFC;
break;
case FidoTransportProtocol::kBluetoothLowEnergy:
result |= WEBAUTHN_CTAP_TRANSPORT_BLE;
break;
case FidoTransportProtocol::kInternal:
result |= WEBAUTHN_CTAP_TRANSPORT_INTERNAL;
break;
case FidoTransportProtocol::kHybrid:
result |= WEBAUTHN_CTAP_TRANSPORT_HYBRID;
break;
case FidoTransportProtocol::kAndroidAccessory:
// AOA is unsupported by the Windows API.
break;
}
}
return result;
}
std::optional<AuthenticatorMakeCredentialResponse>
ToAuthenticatorMakeCredentialResponse(
const WEBAUTHN_CREDENTIAL_ATTESTATION& credential_attestation) {
auto authenticator_data = AuthenticatorData::DecodeAuthenticatorData(
base::span<const uint8_t>(credential_attestation.pbAuthenticatorData,
credential_attestation.cbAuthenticatorData));
if (!authenticator_data) {
DLOG(ERROR) << "DecodeAuthenticatorData failed: "
<< base::HexEncode(credential_attestation.pbAuthenticatorData,
credential_attestation.cbAuthenticatorData);
return std::nullopt;
}
std::optional<cbor::Value> cbor_attestation_statement = cbor::Reader::Read(
base::span<const uint8_t>(credential_attestation.pbAttestation,
credential_attestation.cbAttestation));
if (!cbor_attestation_statement || !cbor_attestation_statement->is_map()) {
DLOG(ERROR) << "CBOR decoding attestation statement failed: "
<< base::HexEncode(credential_attestation.pbAttestation,
credential_attestation.cbAttestation);
return std::nullopt;
}
std::optional<FidoTransportProtocol> transport_used;
if (credential_attestation.dwVersion >=
WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3) {
// dwUsedTransport should have exactly one of the
// WEBAUTHN_CTAP_TRANSPORT_* values set.
transport_used =
FromWinTransportsMask(credential_attestation.dwUsedTransport);
}
AuthenticatorMakeCredentialResponse ret(
transport_used,
AttestationObject(
std::move(*authenticator_data),
std::make_unique<OpaqueAttestationStatement>(
base::WideToUTF8(credential_attestation.pwszFormatType),
std::move(*cbor_attestation_statement))));
if (transport_used == FidoTransportProtocol::kInternal) {
// Windows platform credentials can't be used from other devices, so we can
// fill in the authenticator supported transports.
ret.transports = {*transport_used};
}
if (credential_attestation.dwVersion >=
WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4) {
ret.enterprise_attestation_returned = credential_attestation.bEpAtt;
ret.is_resident_key = credential_attestation.bResidentKey;
if (credential_attestation.bLargeBlobSupported) {
ret.large_blob_type = LargeBlobSupportType::kKey;
}
}
if (credential_attestation.dwVersion >=
WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5) {
ret.prf_enabled = credential_attestation.bPrfEnabled;
}
return ret;
}
std::optional<AuthenticatorGetAssertionResponse>
ToAuthenticatorGetAssertionResponse(
const WEBAUTHN_ASSERTION& assertion,
const CtapGetAssertionOptions& request_options) {
auto authenticator_data =
AuthenticatorData::DecodeAuthenticatorData(base::span<const uint8_t>(
assertion.pbAuthenticatorData, assertion.cbAuthenticatorData));
if (!authenticator_data) {
DLOG(ERROR) << "DecodeAuthenticatorData failed: "
<< base::HexEncode(assertion.pbAuthenticatorData,
assertion.cbAuthenticatorData);
return std::nullopt;
}
std::optional<FidoTransportProtocol> transport_used =
assertion.dwVersion >= WEBAUTHN_ASSERTION_VERSION_4
? FromWinTransportsMask(assertion.dwUsedTransport)
: std::nullopt;
AuthenticatorGetAssertionResponse response(
std::move(*authenticator_data),
std::vector<uint8_t>(assertion.pbSignature,
assertion.pbSignature + assertion.cbSignature),
transport_used);
response.credential = PublicKeyCredentialDescriptor(
CredentialType::kPublicKey,
std::vector<uint8_t>(
assertion.Credential.pbId,
assertion.Credential.pbId + assertion.Credential.cbId));
if (assertion.cbUserId > 0) {
response.user_entity = PublicKeyCredentialUserEntity(std::vector<uint8_t>(
assertion.pbUserId, assertion.pbUserId + assertion.cbUserId));
}
if (assertion.dwVersion >= WEBAUTHN_ASSERTION_VERSION_2 &&
assertion.dwCredLargeBlobStatus ==
WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS) {
if (request_options.large_blob_read) {
response.large_blob = std::vector<uint8_t>(
assertion.pbCredLargeBlob,
assertion.pbCredLargeBlob + assertion.cbCredLargeBlob);
} else if (request_options.large_blob_write) {
response.large_blob_written = true;
}
}
if (assertion.dwVersion >= WEBAUTHN_ASSERTION_VERSION_3 &&
assertion.pHmacSecret) {
response.hmac_secret = HMACSecretOutputs(*assertion.pHmacSecret);
}
return response;
}
uint32_t ToWinUserVerificationRequirement(
UserVerificationRequirement user_verification_requirement) {
switch (user_verification_requirement) {
case UserVerificationRequirement::kRequired:
return WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
case UserVerificationRequirement::kPreferred:
return WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
case UserVerificationRequirement::kDiscouraged:
return WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
}
NOTREACHED_IN_MIGRATION();
return WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
}
uint32_t ToWinAuthenticatorAttachment(
AuthenticatorAttachment authenticator_attachment) {
switch (authenticator_attachment) {
case AuthenticatorAttachment::kAny:
return WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
case AuthenticatorAttachment::kPlatform:
return WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM;
case AuthenticatorAttachment::kCrossPlatform:
return WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
}
NOTREACHED_IN_MIGRATION();
return WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
}
std::vector<WEBAUTHN_CREDENTIAL> ToWinCredentialVector(
const std::vector<PublicKeyCredentialDescriptor>* credentials) {
std::vector<WEBAUTHN_CREDENTIAL> result;
for (const auto& credential : *credentials) {
if (credential.credential_type != CredentialType::kPublicKey) {
continue;
}
result.push_back(WEBAUTHN_CREDENTIAL{
WEBAUTHN_CREDENTIAL_CURRENT_VERSION,
base::checked_cast<DWORD>(credential.id.size()),
const_cast<unsigned char*>(credential.id.data()),
WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY,
});
}
return result;
}
std::vector<WEBAUTHN_CREDENTIAL_EX> ToWinCredentialExVector(
const std::vector<PublicKeyCredentialDescriptor>* credentials) {
std::vector<WEBAUTHN_CREDENTIAL_EX> result;
for (const auto& credential : *credentials) {
if (credential.credential_type != CredentialType::kPublicKey) {
continue;
}
result.push_back(
WEBAUTHN_CREDENTIAL_EX{WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION,
base::checked_cast<DWORD>(credential.id.size()),
const_cast<unsigned char*>(credential.id.data()),
WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY,
ToWinTransportsMask(credential.transports)});
}
return result;
}
uint32_t ToWinLargeBlobSupport(LargeBlobSupport large_blob_support) {
switch (large_blob_support) {
case LargeBlobSupport::kNotRequested:
return WEBAUTHN_LARGE_BLOB_SUPPORT_NONE;
case LargeBlobSupport::kPreferred:
return WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED;
case LargeBlobSupport::kRequired:
return WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED;
}
}
COMPONENT_EXPORT(DEVICE_FIDO)
MakeCredentialStatus WinErrorNameToMakeCredentialStatus(
std::u16string_view error_name) {
// See WebAuthNGetErrorName in <webauthn.h> for these string literals.
constexpr auto kResponseCodeMap =
base::MakeFixedFlatMap<std::u16string_view, MakeCredentialStatus>({
{u"Success", MakeCredentialStatus::kSuccess},
{u"InvalidStateError",
MakeCredentialStatus::kUserConsentButCredentialExcluded},
{u"ConstraintError",
MakeCredentialStatus::kAuthenticatorResponseInvalid},
{u"NotSupportedError",
MakeCredentialStatus::kAuthenticatorResponseInvalid},
{u"NotAllowedError", MakeCredentialStatus::kWinNotAllowedError},
{u"UnknownError",
MakeCredentialStatus::kAuthenticatorResponseInvalid},
});
const auto it = kResponseCodeMap.find(error_name);
if (it == kResponseCodeMap.end()) {
FIDO_LOG(ERROR) << "Unexpected error name: " << error_name;
return MakeCredentialStatus::kAuthenticatorResponseInvalid;
}
return it->second;
}
GetAssertionStatus WinErrorNameToGetAssertionStatus(
std::u16string_view error_name) {
// See WebAuthNGetErrorName in <webauthn.h> for these string literals.
//
// "NotAllowedError" indicates the user cancelled, there was no matching
// credential, or a timeout. Other errors indicate that either the
// request was rejected or there was an error processing it.
constexpr auto kResponseCodeMap = base::MakeFixedFlatMap<std::u16string_view,
GetAssertionStatus>({
{u"Success", GetAssertionStatus::kSuccess},
{u"InvalidStateError", GetAssertionStatus::kAuthenticatorResponseInvalid},
{u"ConstraintError", GetAssertionStatus ::kAuthenticatorResponseInvalid},
{u"NotSupportedError", GetAssertionStatus::kAuthenticatorResponseInvalid},
{u"NotAllowedError", GetAssertionStatus::kWinNotAllowedError},
{u"UnknownError", GetAssertionStatus::kAuthenticatorResponseInvalid},
});
const auto it = kResponseCodeMap.find(error_name);
if (it == kResponseCodeMap.end()) {
FIDO_LOG(ERROR) << "Unexpected error name: " << error_name;
return GetAssertionStatus::kAuthenticatorResponseInvalid;
}
return it->second;
}
uint32_t ToWinAttestationConveyancePreference(
const AttestationConveyancePreference& value,
int api_version) {
switch (value) {
case AttestationConveyancePreference::kNone:
return WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
case AttestationConveyancePreference::kIndirect:
return WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
case AttestationConveyancePreference::kDirect:
return WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
case AttestationConveyancePreference::kEnterpriseIfRPListedOnAuthenticator:
case AttestationConveyancePreference::kEnterpriseApprovedByBrowser:
// Enterprise attestation is supported in API version 3.
return api_version >= 3
? WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT
: WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
}
NOTREACHED_IN_MIGRATION();
return WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE;
}
std::vector<DiscoverableCredentialMetadata>
WinCredentialDetailsListToCredentialMetadata(
const WEBAUTHN_CREDENTIAL_DETAILS_LIST& credentials) {
std::vector<DiscoverableCredentialMetadata> result;
for (size_t i = 0; i < credentials.cCredentialDetails; ++i) {
WEBAUTHN_CREDENTIAL_DETAILS* credential =
credentials.ppCredentialDetails[i];
WEBAUTHN_USER_ENTITY_INFORMATION* user = credential->pUserInformation;
WEBAUTHN_RP_ENTITY_INFORMATION* rp = credential->pRpInformation;
DiscoverableCredentialMetadata metadata(
AuthenticatorType::kWinNative, base::WideToUTF8(rp->pwszId),
std::vector<uint8_t>(
credential->pbCredentialID,
credential->pbCredentialID + credential->cbCredentialID),
PublicKeyCredentialUserEntity(
std::vector<uint8_t>(user->pbId, user->pbId + user->cbId),
user->pwszName
? std::make_optional(base::WideToUTF8(user->pwszName))
: std::nullopt,
user->pwszDisplayName
? std::make_optional(base::WideToUTF8(user->pwszDisplayName))
: std::nullopt));
metadata.system_created = !credential->bRemovable;
result.push_back(std::move(metadata));
}
return result;
}
} // namespace device