[webauthn] Use software keys when TPM unavailable
Use the Microsoft Software Key Storage Provider to back enclave unexportable keys on Windows when the machine doesn't have a TPM. This allows those machines to use Google Password Manager passkeys. This feature is behind the disabled-by-default WebAuthenticationMicrosoftSoftwareUnexportableKeyProvider flag. Bug: 398125798 Change-Id: Ie38e810dd5655072be824b32a8b4ba606dbd1897 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6297628 Reviewed-by: David Benjamin <davidben@chromium.org> Commit-Queue: Nina Satragno <nsatragno@chromium.org> Reviewed-by: Ken Buchanan <kenrb@chromium.org> Cr-Commit-Position: refs/heads/main@{#1425207}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
43316e2717
commit
7db4fffc81
chrome/browser/webauthn
crypto
device/fido
@ -11,6 +11,7 @@
|
||||
#include "build/chromeos_buildflags.h"
|
||||
#include "crypto/unexportable_key.h"
|
||||
#include "crypto/user_verifying_key.h"
|
||||
#include "device/fido/enclave/constants.h"
|
||||
#include "device/fido/features.h"
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
@ -49,7 +50,17 @@ GetWebAuthnUnexportableKeyProvider() {
|
||||
config.keychain_access_group =
|
||||
EnclaveManager::kEnclaveKeysKeychainAccessGroup;
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
return crypto::GetUnexportableKeyProvider(std::move(config));
|
||||
std::unique_ptr<crypto::UnexportableKeyProvider> provider =
|
||||
crypto::GetUnexportableKeyProvider(std::move(config));
|
||||
if ((!provider || provider->SelectAlgorithm(
|
||||
device::enclave::kSigningAlgorithms) == std::nullopt) &&
|
||||
base::FeatureList::IsEnabled(
|
||||
device::kWebAuthnMicrosoftSoftwareUnexportableKeyProvider)) {
|
||||
// On Windows, if there is no TPM support, use the Microsoft Software Key
|
||||
// Storage Provider instead.
|
||||
provider = crypto::GetMicrosoftSoftwareUnexportableKeyProvider();
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
std::unique_ptr<crypto::UserVerifyingKeyProvider>
|
||||
|
@ -24,6 +24,8 @@ bool UnexportableSigningKey::IsHardwareBacked() const {
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
std::unique_ptr<UnexportableKeyProvider> GetUnexportableKeyProviderWin();
|
||||
std::unique_ptr<UnexportableKeyProvider>
|
||||
GetMicrosoftSoftwareUnexportableKeyProviderWin();
|
||||
std::unique_ptr<VirtualUnexportableKeyProvider>
|
||||
GetVirtualUnexportableKeyProviderWin();
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
@ -50,6 +52,15 @@ std::unique_ptr<UnexportableKeyProvider> GetUnexportableKeyProvider(
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<UnexportableKeyProvider>
|
||||
GetMicrosoftSoftwareUnexportableKeyProvider() {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
return GetMicrosoftSoftwareUnexportableKeyProviderWin();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<VirtualUnexportableKeyProvider>
|
||||
GetVirtualUnexportableKeyProvider_DO_NOT_USE_METRICS_ONLY() {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
|
@ -252,6 +252,17 @@ class CRYPTO_EXPORT VirtualUnexportableKeyProvider {
|
||||
CRYPTO_EXPORT std::unique_ptr<UnexportableKeyProvider>
|
||||
GetUnexportableKeyProvider(UnexportableKeyProvider::Config config);
|
||||
|
||||
// GetMicrosoftSoftwareUnexportableKeyProvider returns an
|
||||
// |UnexportableKeyProvider| that is backed by the Microsoft Software Key
|
||||
// Storage Provider. Keys stored in this fashion are available to both the
|
||||
// software that created them, and any software running locally with
|
||||
// administrative privileges.
|
||||
// Microsoft Software keys are less secure than TPM backed keys, so
|
||||
// |GetUnexportableKeyProvider| should be preferred, but they are more widely
|
||||
// available.
|
||||
CRYPTO_EXPORT std::unique_ptr<UnexportableKeyProvider>
|
||||
GetMicrosoftSoftwareUnexportableKeyProvider();
|
||||
|
||||
// GetVirtualUnexportableKeyProvider_DO_NOT_USE_METRICS_ONLY returns a
|
||||
// |VirtualUnexportableKeyProvider| for the current platform, or nullptr if
|
||||
// there isn't one. This should currently only be used for metrics gathering.
|
||||
|
@ -20,6 +20,18 @@
|
||||
|
||||
namespace {
|
||||
|
||||
enum class Provider {
|
||||
kTPM,
|
||||
kMock,
|
||||
kMicrosoftSoftware,
|
||||
};
|
||||
|
||||
const Provider kAllProviders[] = {
|
||||
Provider::kTPM,
|
||||
Provider::kMock,
|
||||
Provider::kMicrosoftSoftware,
|
||||
};
|
||||
|
||||
const crypto::SignatureVerifier::SignatureAlgorithm kAllAlgorithms[] = {
|
||||
crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256,
|
||||
crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256,
|
||||
@ -29,10 +41,21 @@ const crypto::SignatureVerifier::SignatureAlgorithm kAllAlgorithms[] = {
|
||||
constexpr char kTestKeychainAccessGroup[] = "test-keychain-access-group";
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
|
||||
std::string ToString(Provider provider) {
|
||||
switch (provider) {
|
||||
case Provider::kTPM:
|
||||
return "TPM";
|
||||
case Provider::kMock:
|
||||
return "Mock";
|
||||
case Provider::kMicrosoftSoftware:
|
||||
return "Microsoft Software";
|
||||
}
|
||||
}
|
||||
|
||||
class UnexportableKeySigningTest
|
||||
: public testing::TestWithParam<
|
||||
std::tuple<crypto::SignatureVerifier::SignatureAlgorithm,
|
||||
bool,
|
||||
Provider,
|
||||
bool>> {
|
||||
public:
|
||||
void SetUp() override {
|
||||
@ -58,30 +81,30 @@ class UnexportableKeySigningTest
|
||||
INSTANTIATE_TEST_SUITE_P(All,
|
||||
UnexportableKeySigningTest,
|
||||
testing::Combine(testing::ValuesIn(kAllAlgorithms),
|
||||
testing::Bool(),
|
||||
testing::ValuesIn(kAllProviders),
|
||||
testing::Bool()));
|
||||
|
||||
TEST_P(UnexportableKeySigningTest, RoundTrip) {
|
||||
const crypto::SignatureVerifier::SignatureAlgorithm algo =
|
||||
std::get<0>(GetParam());
|
||||
const bool mock_enabled = std::get<1>(GetParam());
|
||||
const Provider provider_type = std::get<1>(GetParam());
|
||||
|
||||
switch (algo) {
|
||||
case crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256:
|
||||
LOG(INFO) << "ECDSA P-256, mock=" << mock_enabled;
|
||||
LOG(INFO) << "ECDSA P-256, provider=" << ToString(provider_type);
|
||||
break;
|
||||
case crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256:
|
||||
LOG(INFO) << "RSA, mock=" << mock_enabled;
|
||||
LOG(INFO) << "RSA, provider=" << ToString(provider_type);
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
|
||||
SCOPED_TRACE(static_cast<int>(algo));
|
||||
SCOPED_TRACE(mock_enabled);
|
||||
SCOPED_TRACE(ToString(provider_type));
|
||||
|
||||
std::optional<crypto::ScopedMockUnexportableKeyProvider> mock;
|
||||
if (mock_enabled) {
|
||||
if (provider_type == Provider::kMock) {
|
||||
mock.emplace();
|
||||
}
|
||||
|
||||
@ -92,8 +115,12 @@ TEST_P(UnexportableKeySigningTest, RoundTrip) {
|
||||
.keychain_access_group = kTestKeychainAccessGroup
|
||||
#endif // BUILDLFAG(IS_MAC)
|
||||
};
|
||||
std::unique_ptr<crypto::UnexportableKeyProvider> provider =
|
||||
crypto::GetUnexportableKeyProvider(std::move(config));
|
||||
std::unique_ptr<crypto::UnexportableKeyProvider> provider;
|
||||
if (provider_type == Provider::kMicrosoftSoftware) {
|
||||
provider = crypto::GetMicrosoftSoftwareUnexportableKeyProvider();
|
||||
} else {
|
||||
provider = crypto::GetUnexportableKeyProvider(std::move(config));
|
||||
}
|
||||
if (!provider) {
|
||||
LOG(INFO) << "Skipping test because of lack of hardware support.";
|
||||
return;
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/base64.h"
|
||||
#include "base/containers/span.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
@ -53,6 +54,23 @@ const char kMetricVirtualOpenKeyError[] = "Crypto.TpmError.VirtualOpenKey";
|
||||
const char kMetricVirtualOpenStorageError[] =
|
||||
"Crypto.TpmError.VirtualOpenStorage";
|
||||
|
||||
enum class ProviderType {
|
||||
// Keys will be backed by a TPM. Requires TPM support.
|
||||
kTPM,
|
||||
|
||||
// Keys will be backed by software. Widely available.
|
||||
kSoftware
|
||||
};
|
||||
|
||||
LPCWSTR GetWindowsIdentifierForProvider(ProviderType type) {
|
||||
switch (type) {
|
||||
case ProviderType::kTPM:
|
||||
return MS_PLATFORM_CRYPTO_PROVIDER;
|
||||
case ProviderType::kSoftware:
|
||||
return MS_KEY_STORAGE_PROVIDER;
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string KeyIdToWindowsLabel(base::span<const uint8_t> key_id) {
|
||||
return u"unexportable-key-" + base::UTF8ToUTF16(base::Base64Encode(key_id));
|
||||
}
|
||||
@ -336,6 +354,46 @@ base::expected<std::vector<uint8_t>, SECURITY_STATUS> SignRSA(
|
||||
return sig;
|
||||
}
|
||||
|
||||
bool LoadWrappedKey(base::span<const uint8_t> wrapped,
|
||||
ScopedNCryptProvider& provider,
|
||||
ProviderType provider_type,
|
||||
ScopedNCryptKey& key) {
|
||||
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
|
||||
if (FAILED(NCryptOpenStorageProvider(
|
||||
ScopedNCryptProvider::Receiver(provider).get(),
|
||||
GetWindowsIdentifierForProvider(provider_type),
|
||||
/*flags=*/0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SECURITY_STATUS import_status = -1;
|
||||
if (base::FeatureList::IsEnabled(features::kLabelWindowsUnexportableKeys)) {
|
||||
// Current versions of Chrome label keys with a random identifier. Attempt
|
||||
// to obtain a handle from the identifier.
|
||||
std::u16string key_label = KeyIdToWindowsLabel(wrapped);
|
||||
import_status =
|
||||
NCryptOpenKey(provider.get(), ScopedNCryptKey::Receiver(key).get(),
|
||||
base::as_wcstr(key_label),
|
||||
/*dwLegacyKeySpec=*/0, /*dwFlags=*/0);
|
||||
}
|
||||
if (FAILED(import_status)) {
|
||||
// Previous versions of Chrome used an undocumented Windows feature to
|
||||
// export a wrapped key. Attempt to obtain a handle from the wrapped key to
|
||||
// continue to support old keys.
|
||||
import_status = NCryptImportKey(
|
||||
provider.get(), /*hImportKey=*/NULL, BCRYPT_OPAQUE_KEY_BLOB,
|
||||
/*pParameterList=*/nullptr, ScopedNCryptKey::Receiver(key).get(),
|
||||
const_cast<PBYTE>(wrapped.data()), wrapped.size(),
|
||||
/*dwFlags=*/NCRYPT_SILENT_FLAG);
|
||||
}
|
||||
if (FAILED(import_status)) {
|
||||
LogTPMOperationError(TPMOperation::kWrappedKeyCreation, import_status,
|
||||
std::nullopt);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ECDSAKey wraps a TPM-stored P-256 ECDSA key.
|
||||
class ECDSAKey : public UnexportableSigningKey {
|
||||
public:
|
||||
@ -420,6 +478,8 @@ class RSAKey : public UnexportableSigningKey {
|
||||
// Provider to expose TPM-backed keys on Windows.
|
||||
class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
public:
|
||||
explicit UnexportableKeyProviderWin(ProviderType provider_type)
|
||||
: provider_type_(provider_type) {}
|
||||
~UnexportableKeyProviderWin() override = default;
|
||||
|
||||
std::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
|
||||
@ -430,7 +490,7 @@ class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
|
||||
if (FAILED(NCryptOpenStorageProvider(
|
||||
ScopedNCryptProvider::Receiver(provider).get(),
|
||||
MS_PLATFORM_CRYPTO_PROVIDER, /*flags=*/0))) {
|
||||
GetWindowsIdentifierForProvider(provider_type_), /*flags=*/0))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
@ -449,7 +509,7 @@ class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
|
||||
if (FAILED(NCryptOpenStorageProvider(
|
||||
ScopedNCryptProvider::Receiver(provider).get(),
|
||||
MS_PLATFORM_CRYPTO_PROVIDER, /*flags=*/0))) {
|
||||
GetWindowsIdentifierForProvider(provider_type_), /*flags=*/0))) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@ -536,7 +596,7 @@ class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
|
||||
ScopedNCryptProvider provider;
|
||||
ScopedNCryptKey key;
|
||||
if (!LoadWrappedTPMKey(wrapped, provider, key)) {
|
||||
if (!LoadWrappedKey(wrapped, provider, provider_type_, key)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -548,13 +608,20 @@ class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
|
||||
// The documentation suggests that |NCRYPT_ALGORITHM_PROPERTY| should return
|
||||
// the original algorithm, i.e. |BCRYPT_ECDSA_P256_ALGORITHM| for ECDSA. But
|
||||
// it actually returns just "ECDSA" for that case.
|
||||
static const wchar_t kECDSA[] = L"ECDSA";
|
||||
static const wchar_t kRSA[] = BCRYPT_RSA_ALGORITHM;
|
||||
// it actually returns just "ECDSA" for keys backed by the TPM.
|
||||
// Note that these intentionally include the NUL terminator, since they're
|
||||
// comparing against a c-style string that happens to be represented as an
|
||||
// std::vector.
|
||||
static constexpr wchar_t kECDSA[] = L"ECDSA";
|
||||
static const base::span<const uint8_t> kECDSA_TPM =
|
||||
base::as_byte_span(kECDSA);
|
||||
static const base::span<const uint8_t> kECDSA_Software =
|
||||
base::as_byte_span(BCRYPT_ECDSA_P256_ALGORITHM);
|
||||
static const base::span<const uint8_t> kRSA =
|
||||
base::as_byte_span(BCRYPT_RSA_ALGORITHM);
|
||||
|
||||
std::optional<std::vector<uint8_t>> spki;
|
||||
if (algo_bytes->size() == sizeof(kECDSA) &&
|
||||
memcmp(algo_bytes->data(), kECDSA, sizeof(kECDSA)) == 0) {
|
||||
if (algo_bytes == kECDSA_Software || algo_bytes == kECDSA_TPM) {
|
||||
spki = GetP256ECDSASPKI(key.get());
|
||||
if (!spki) {
|
||||
return nullptr;
|
||||
@ -562,8 +629,7 @@ class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
return std::make_unique<ECDSAKey>(
|
||||
std::move(key), std::vector<uint8_t>(wrapped.begin(), wrapped.end()),
|
||||
std::move(spki.value()));
|
||||
} else if (algo_bytes->size() == sizeof(kRSA) &&
|
||||
memcmp(algo_bytes->data(), kRSA, sizeof(kRSA)) == 0) {
|
||||
} else if (algo_bytes == kRSA) {
|
||||
spki = GetRSASPKI(key.get());
|
||||
if (!spki) {
|
||||
return nullptr;
|
||||
@ -580,6 +646,9 @@ class UnexportableKeyProviderWin : public UnexportableKeyProvider {
|
||||
// Unexportable keys are stateless on Windows.
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
ProviderType provider_type_;
|
||||
};
|
||||
|
||||
// ECDSASoftwareKey wraps a Credential Guard stored P-256 ECDSA key.
|
||||
@ -801,22 +870,25 @@ class VirtualUnexportableKeyProviderWin
|
||||
const std::optional<std::vector<uint8_t>> algo_bytes =
|
||||
GetKeyProperty(key.get(), NCRYPT_ALGORITHM_PROPERTY);
|
||||
|
||||
// This is the expected behavior, but note it is different from
|
||||
// TPM backed keys.
|
||||
static const wchar_t kECDSA[] = BCRYPT_ECDSA_P256_ALGORITHM;
|
||||
static const wchar_t kRSA[] = BCRYPT_RSA_ALGORITHM;
|
||||
// This is the expected behavior, but note it is different from TPM backed
|
||||
// keys.
|
||||
// Note that these intentionally include the NUL terminator, since they're
|
||||
// comparing against a c-style string that happens to be represented as an
|
||||
// std::vector.
|
||||
static const base::span<const uint8_t> kECDSA_Software =
|
||||
base::as_byte_span(BCRYPT_ECDSA_P256_ALGORITHM);
|
||||
static const base::span<const uint8_t> kRSA =
|
||||
base::as_byte_span(BCRYPT_RSA_ALGORITHM);
|
||||
|
||||
std::optional<std::vector<uint8_t>> spki;
|
||||
if (algo_bytes->size() == sizeof(kECDSA) &&
|
||||
memcmp(algo_bytes->data(), kECDSA, sizeof(kECDSA)) == 0) {
|
||||
if (algo_bytes == kECDSA_Software) {
|
||||
spki = GetP256ECDSASPKI(key.get());
|
||||
if (!spki) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<ECDSASoftwareKey>(std::move(key), name,
|
||||
std::move(spki.value()));
|
||||
} else if (algo_bytes->size() == sizeof(kRSA) &&
|
||||
memcmp(algo_bytes->data(), kRSA, sizeof(kRSA)) == 0) {
|
||||
} else if (algo_bytes == kRSA) {
|
||||
spki = GetRSASPKI(key.get());
|
||||
if (!spki) {
|
||||
return nullptr;
|
||||
@ -834,44 +906,20 @@ class VirtualUnexportableKeyProviderWin
|
||||
bool LoadWrappedTPMKey(base::span<const uint8_t> wrapped,
|
||||
ScopedNCryptProvider& provider,
|
||||
ScopedNCryptKey& key) {
|
||||
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
|
||||
if (FAILED(NCryptOpenStorageProvider(
|
||||
ScopedNCryptProvider::Receiver(provider).get(),
|
||||
MS_PLATFORM_CRYPTO_PROVIDER,
|
||||
/*flags=*/0))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SECURITY_STATUS import_status = -1;
|
||||
if (base::FeatureList::IsEnabled(features::kLabelWindowsUnexportableKeys)) {
|
||||
// Current versions of Chrome label keys with a random identifier. Attempt
|
||||
// to obtain a handle from the identifier.
|
||||
std::u16string key_label = KeyIdToWindowsLabel(wrapped);
|
||||
import_status =
|
||||
NCryptOpenKey(provider.get(), ScopedNCryptKey::Receiver(key).get(),
|
||||
base::as_wcstr(key_label),
|
||||
/*dwLegacyKeySpec=*/0, /*dwFlags=*/0);
|
||||
}
|
||||
if (FAILED(import_status)) {
|
||||
// Previous versions of Chrome used an undocumented Windows feature to
|
||||
// export a wrapped key. Attempt to obtain a handle from the wrapped key to
|
||||
// continue to support old keys.
|
||||
import_status = NCryptImportKey(
|
||||
provider.get(), /*hImportKey=*/NULL, BCRYPT_OPAQUE_KEY_BLOB,
|
||||
/*pParameterList=*/nullptr, ScopedNCryptKey::Receiver(key).get(),
|
||||
const_cast<PBYTE>(wrapped.data()), wrapped.size(),
|
||||
/*dwFlags=*/NCRYPT_SILENT_FLAG);
|
||||
}
|
||||
if (FAILED(import_status)) {
|
||||
LogTPMOperationError(TPMOperation::kWrappedKeyCreation, import_status,
|
||||
std::nullopt);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return LoadWrappedKey(wrapped, provider, ProviderType::kTPM, key);
|
||||
}
|
||||
|
||||
std::unique_ptr<UnexportableKeyProvider> GetUnexportableKeyProviderWin() {
|
||||
return std::make_unique<UnexportableKeyProviderWin>();
|
||||
return std::make_unique<UnexportableKeyProviderWin>(ProviderType::kTPM);
|
||||
}
|
||||
|
||||
std::unique_ptr<UnexportableKeyProvider>
|
||||
GetMicrosoftSoftwareUnexportableKeyProviderWin() {
|
||||
if (!base::FeatureList::IsEnabled(features::kLabelWindowsUnexportableKeys)) {
|
||||
// The software provider requires kLabelWindowsUnexportableKeys to work.
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<UnexportableKeyProviderWin>(ProviderType::kSoftware);
|
||||
}
|
||||
|
||||
std::unique_ptr<VirtualUnexportableKeyProvider>
|
||||
|
@ -167,9 +167,14 @@ BASE_FEATURE(kSyncSecurityDomainBeforePINRenewal,
|
||||
"kWebAuthenticationSyncSecurityDomainBeforePINRenewal",
|
||||
base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
// Net yet enabled by default.
|
||||
// Not yet enabled by default.
|
||||
BASE_FEATURE(kWebAuthnRemoteDesktopAllowedOriginsPolicy,
|
||||
"WebAuthenticationRemoteDesktopAllowedOriginsPolicy",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
// Not yet enabled by default.
|
||||
BASE_FEATURE(kWebAuthnMicrosoftSoftwareUnexportableKeyProvider,
|
||||
"WebAuthenticationMicrosoftSoftwareUnexportableKeyProvider",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
} // namespace device
|
||||
|
@ -140,6 +140,11 @@ BASE_DECLARE_FEATURE(kSyncSecurityDomainBeforePINRenewal);
|
||||
COMPONENT_EXPORT(DEVICE_FIDO)
|
||||
BASE_DECLARE_FEATURE(kWebAuthnRemoteDesktopAllowedOriginsPolicy);
|
||||
|
||||
// Enables using the Microsoft Software Key Storage Provider to store
|
||||
// unexportable keys when a TPM is not available.
|
||||
COMPONENT_EXPORT(DEVICE_FIDO)
|
||||
BASE_DECLARE_FEATURE(kWebAuthnMicrosoftSoftwareUnexportableKeyProvider);
|
||||
|
||||
} // namespace device
|
||||
|
||||
#endif // DEVICE_FIDO_FEATURES_H_
|
||||
|
Reference in New Issue
Block a user