From 8007d018749cd1038c755d86335e4fd89cd8f2ef Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 30 Aug 2021 11:22:46 -0700 Subject: [PATCH] feat: add support for the U2F Web API (#30438) * feat: add support for the U2F Web API * chore: fix lint * chore: fix tests * build: disable src caching * Revert "build: disable src caching" This reverts commit c4c8a60fc435a10788475ec171399a55ac2dd674. * chore: update per feedback * chore: consistent code removal --- electron_resources.grd | 1 + filenames.gni | 2 + patches/chromium/.patches | 1 + ...dows_in_cryptotoken_webrequestsender.patch | 48 +++ shell/browser/electron_browser_client.cc | 12 + shell/browser/electron_browser_client.h | 4 + shell/browser/extensions/api/BUILD.gn | 1 + .../cryptotoken_private_api.cc | 311 ++++++++++++++++++ .../cryptotoken_private_api.h | 86 +++++ shell/browser/extensions/api/tabs/tabs_api.cc | 2 + .../extensions/electron_extension_system.cc | 32 +- shell/common/extensions/api/BUILD.gn | 2 + .../common/extensions/api/_api_features.json | 6 +- .../extensions/api/_permission_features.json | 8 + .../extensions/api/cryptotoken_private.idl | 63 ++++ .../electron_extensions_api_provider.cc | 1 + spec-main/extensions-spec.ts | 2 +- 17 files changed, 579 insertions(+), 3 deletions(-) create mode 100644 patches/chromium/chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch create mode 100644 shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc create mode 100644 shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h create mode 100644 shell/common/extensions/api/cryptotoken_private.idl diff --git a/electron_resources.grd b/electron_resources.grd index ae47cedd42..08eb971b39 100644 --- a/electron_resources.grd +++ b/electron_resources.grd @@ -19,6 +19,7 @@ + diff --git a/filenames.gni b/filenames.gni index f9647547e0..d7f93a307b 100644 --- a/filenames.gni +++ b/filenames.gni @@ -677,6 +677,8 @@ filenames = { lib_sources_extensions = [ "shell/browser/extensions/api/i18n/i18n_api.cc", "shell/browser/extensions/api/i18n/i18n_api.h", + "shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc", + "shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h", "shell/browser/extensions/api/management/electron_management_api_delegate.cc", "shell/browser/extensions/api/management/electron_management_api_delegate.h", "shell/browser/extensions/api/resources_private/resources_private_api.cc", diff --git a/patches/chromium/.patches b/patches/chromium/.patches index a764e98755..d7134ea4fa 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -107,3 +107,4 @@ disable_use_lld_for_macos.patch fix_media_key_usage_with_globalshortcuts.patch feat_expose_raw_response_headers_from_urlloader.patch revert_roll_clang_llvmorg-14-init-1002-gb5e470aa-1.patch +chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch diff --git a/patches/chromium/chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch b/patches/chromium/chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch new file mode 100644 index 0000000000..a26f886be4 --- /dev/null +++ b/patches/chromium/chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samuel Attard +Date: Fri, 6 Aug 2021 03:36:57 -0700 +Subject: chore: do not use chrome.windows in cryptotoken webrequestsender + +Electron does not support chrome.windows which this method depends on. It also does not need to, the method's goal is to determine if the webContents that triggered the cryptotoken request is still "active". In Chrome this means active tab in the tab strip === this tab + the window owning that tab strip is focused. + +In Electron that can be simplified to webContents.isFocused() which maps to "is the webContents view focused and is the owning window the key window". We map tab.active to that IsFocused() value and we can remove the chrome.windows logic here. + +This can't be upstreamed but the patch is minimal. + +diff --git a/chrome/browser/resources/cryptotoken/webrequestsender.js b/chrome/browser/resources/cryptotoken/webrequestsender.js +index 734abbbf3132d245c2c39bbe9b7780acbea196b0..adff416286eaa10a099be83aaf07e56ec323fe3d 100644 +--- a/chrome/browser/resources/cryptotoken/webrequestsender.js ++++ b/chrome/browser/resources/cryptotoken/webrequestsender.js +@@ -134,10 +134,11 @@ function tabInForeground(tabId) { + reject(); + return; + } +- if (!chrome.windows || !chrome.windows.get) { +- reject(); +- return; +- } ++ // Electron does not support chrome.windows ++ // if (!chrome.windows || !chrome.windows.get) { ++ // reject(); ++ // return; ++ // } + chrome.tabs.get(tabId, function(tab) { + if (chrome.runtime.lastError) { + resolve(false); +@@ -147,9 +148,13 @@ function tabInForeground(tabId) { + resolve(false); + return; + } +- chrome.windows.get(tab.windowId, function(aWindow) { +- resolve(aWindow && aWindow.focused); +- }); ++ // tab.active in Electron maps to the "focused" state of the view ++ // which is only true when both the webContents and the window are ++ // focused. ++ resolve(true); ++ // chrome.windows.get(tab.windowId, function(aWindow) { ++ // resolve(aWindow && aWindow.focused); ++ // }); + }); + }); + } diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index fca85afec5..81958c0bef 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -142,6 +142,7 @@ #include "extensions/browser/api/mime_handler_private/mime_handler_private.h" #include "extensions/browser/api/web_request/web_request_api.h" #include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/browser/event_router.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_message_filter.h" #include "extensions/browser/extension_navigation_throttle.h" @@ -163,6 +164,7 @@ #include "shell/browser/extensions/electron_extension_message_filter.h" #include "shell/browser/extensions/electron_extension_system.h" #include "shell/browser/extensions/electron_extension_web_contents_observer.h" +#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h" #endif #if BUILDFLAG(ENABLE_PLUGINS) @@ -1551,6 +1553,16 @@ void BindBeforeUnloadControl( } #endif +void ElectronBrowserClient::ExposeInterfacesToRenderer( + service_manager::BinderRegistry* registry, + blink::AssociatedInterfaceRegistry* associated_registry, + content::RenderProcessHost* render_process_host) { +#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) + associated_registry->AddInterface(base::BindRepeating( + &extensions::EventRouter::BindForRenderer, render_process_host->GetID())); +#endif +} + void ElectronBrowserClient::RegisterBrowserInterfaceBindersForFrame( content::RenderFrameHost* render_frame_host, mojo::BinderMapWithContext* map) { diff --git a/shell/browser/electron_browser_client.h b/shell/browser/electron_browser_client.h index f14339cc1a..bf9de42746 100644 --- a/shell/browser/electron_browser_client.h +++ b/shell/browser/electron_browser_client.h @@ -69,6 +69,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient, void BindHostReceiverForRenderer( content::RenderProcessHost* render_process_host, mojo::GenericPendingReceiver receiver) override; + void ExposeInterfacesToRenderer( + service_manager::BinderRegistry* registry, + blink::AssociatedInterfaceRegistry* associated_registry, + content::RenderProcessHost* render_process_host) override; void RegisterBrowserInterfaceBindersForFrame( content::RenderFrameHost* render_frame_host, mojo::BinderMapWithContext* map) override; diff --git a/shell/browser/extensions/api/BUILD.gn b/shell/browser/extensions/api/BUILD.gn index 0765454c8f..c253b1ea0c 100644 --- a/shell/browser/extensions/api/BUILD.gn +++ b/shell/browser/extensions/api/BUILD.gn @@ -10,6 +10,7 @@ assert(enable_extensions, function_registration("api_registration") { sources = [ + "//electron/shell/common/extensions/api/cryptotoken_private.idl", "//electron/shell/common/extensions/api/extension.json", "//electron/shell/common/extensions/api/i18n.json", "//electron/shell/common/extensions/api/resources_private.idl", diff --git a/shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc b/shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc new file mode 100644 index 0000000000..2a21e0a4c9 --- /dev/null +++ b/shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc @@ -0,0 +1,311 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h" + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +// #include "chrome/browser/extensions/extension_tab_util.h" +// #include "chrome/browser/permissions/attestation_permission_request.h" +// #include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_features.h" +#include "chrome/common/pref_names.h" +// #include +// "components/page_load_metrics/browser/metrics_web_contents_observer.h" +// #include "components/permissions/permission_request_manager.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents.h" +#include "crypto/sha2.h" +#include "device/fido/filter.h" +#include "extensions/browser/extension_api_frame_id_map.h" +#include "extensions/common/error_utils.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "shell/browser/api/electron_api_web_contents.h" +#include "url/origin.h" + +#if defined(OS_WIN) +#include "device/fido/features.h" +#include "device/fido/win/webauthn_api.h" +#endif // defined(OS_WIN) + +namespace extensions { + +namespace api { + +namespace { + +const char kGoogleDotCom[] = "google.com"; +constexpr const char* kGoogleGstaticAppIds[] = { + "https://www.gstatic.com/securitykey/origins.json", + "https://www.gstatic.com/securitykey/a/google.com/origins.json"}; + +// ContainsAppIdByHash returns true iff the SHA-256 hash of one of the +// elements of |list| equals |hash|. +bool ContainsAppIdByHash(const base::ListValue& list, + const std::vector& hash) { + if (hash.size() != crypto::kSHA256Length) { + return false; + } + + for (const auto& i : list.GetList()) { + const std::string& s = i.GetString(); + if (s.find('/') == std::string::npos) { + // No slashes mean that this is a webauthn RP ID, not a U2F AppID. + continue; + } + + if (crypto::SHA256HashString(s).compare( + 0, crypto::kSHA256Length, + reinterpret_cast(hash.data()), + crypto::kSHA256Length) == 0) { + return true; + } + } + + return false; +} + +content::RenderFrameHost* RenderFrameHostForTabAndFrameId( + content::BrowserContext* const browser_context, + const int tab_id, + const int frame_id) { + auto* contents = electron::api::WebContents::FromID(tab_id); + if (!contents || !contents->web_contents()) { + return nullptr; + } + return ExtensionApiFrameIdMap::GetRenderFrameHostById( + contents->web_contents(), frame_id); +} + +} // namespace + +// void CryptotokenRegisterProfilePrefs( +// user_prefs::PrefRegistrySyncable* registry) { +// registry->RegisterListPref(prefs::kSecurityKeyPermitAttestation); +// } + +CryptotokenPrivateCanOriginAssertAppIdFunction:: + CryptotokenPrivateCanOriginAssertAppIdFunction() = default; + +ExtensionFunction::ResponseAction +CryptotokenPrivateCanOriginAssertAppIdFunction::Run() { + std::unique_ptr params = + cryptotoken_private::CanOriginAssertAppId::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params); + + const GURL origin_url(params->security_origin); + if (!origin_url.is_valid()) { + return RespondNow(Error(extensions::ErrorUtils::FormatErrorMessage( + "Security origin * is not a valid URL", params->security_origin))); + } + const GURL app_id_url(params->app_id_url); + if (!app_id_url.is_valid()) { + return RespondNow(Error(extensions::ErrorUtils::FormatErrorMessage( + "appId * is not a valid URL", params->app_id_url))); + } + + if (origin_url == app_id_url) { + return RespondNow(OneArgument(base::Value(true))); + } + + // Fetch the eTLD+1 of both. + const std::string origin_etldp1 = + net::registry_controlled_domains::GetDomainAndRegistry( + origin_url, + net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); + if (origin_etldp1.empty()) { + return RespondNow(Error(extensions::ErrorUtils::FormatErrorMessage( + "Could not find an eTLD for origin *", params->security_origin))); + } + const std::string app_id_etldp1 = + net::registry_controlled_domains::GetDomainAndRegistry( + app_id_url, + net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); + if (app_id_etldp1.empty()) { + return RespondNow(Error(extensions::ErrorUtils::FormatErrorMessage( + "Could not find an eTLD for appId *", params->app_id_url))); + } + if (origin_etldp1 == app_id_etldp1) { + return RespondNow(OneArgument(base::Value(true))); + } + // For legacy purposes, allow google.com origins to assert certain + // gstatic.com appIds. + // TODO(juanlang): remove when legacy constraints are removed. + if (origin_etldp1 == kGoogleDotCom) { + for (const char* id : kGoogleGstaticAppIds) { + if (params->app_id_url == id) + return RespondNow(OneArgument(base::Value(true))); + } + } + return RespondNow(OneArgument(base::Value(false))); +} + +CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction:: + CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction() {} + +ExtensionFunction::ResponseAction +CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction::Run() { + std::unique_ptr + params( + cryptotoken_private::IsAppIdHashInEnterpriseContext::Params::Create( + *args_)); + EXTENSION_FUNCTION_VALIDATE(params); + +#if 0 + Profile* const profile = Profile::FromBrowserContext(browser_context()); + const PrefService* const prefs = profile->GetPrefs(); + const base::ListValue* const permit_attestation = + prefs->GetList(prefs::kSecurityKeyPermitAttestation); +#endif + const base::ListValue permit_attestation; + + return RespondNow(ArgumentList( + cryptotoken_private::IsAppIdHashInEnterpriseContext::Results::Create( + ContainsAppIdByHash(permit_attestation, params->app_id_hash)))); +} + +CryptotokenPrivateCanAppIdGetAttestationFunction:: + CryptotokenPrivateCanAppIdGetAttestationFunction() {} + +ExtensionFunction::ResponseAction +CryptotokenPrivateCanAppIdGetAttestationFunction::Run() { + std::unique_ptr params = + cryptotoken_private::CanAppIdGetAttestation::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params); + + return RespondNow(Error("API not supported in Electron")); +#if 0 + const GURL origin_url(params->options.origin); + if (!origin_url.is_valid()) { + return RespondNow(Error(extensions::ErrorUtils::FormatErrorMessage( + "Security origin * is not a valid URL", params->options.origin))); + } + const url::Origin origin(url::Origin::Create(origin_url)); + + const std::string& app_id = params->options.app_id; + + // If the appId is permitted by the enterprise policy then no permission + // prompt is shown. + // Profile* const profile = Profile::FromBrowserContext(browser_context()); + // const PrefService* const prefs = profile->GetPrefs(); + // const base::ListValue* const permit_attestation = + // prefs->GetList(prefs::kSecurityKeyPermitAttestation); + + // for (const auto& entry : permit_attestation->GetList()) { + // if (entry.GetString() == app_id) + // return RespondNow(OneArgument(base::Value(true))); + // } + + // If the origin is blocked, reject attestation. + if (device::fido_filter::Evaluate( + device::fido_filter::Operation::MAKE_CREDENTIAL, origin.Serialize(), + /*device=*/absl::nullopt, /*id=*/absl::nullopt) == + device::fido_filter::Action::NO_ATTESTATION) { + return RespondNow(OneArgument(base::Value(false))); + } + + // If prompting is disabled, allow attestation because that is the historical + // behavior. + if (!base::FeatureList::IsEnabled( + ::features::kSecurityKeyAttestationPrompt)) { + return RespondNow(OneArgument(base::Value(true))); + } + +#if defined(OS_WIN) + // If the request was handled by the Windows WebAuthn API on a version of + // Windows that shows an attestation permission prompt, don't show another + // one. + // + // Note that this does not account for the possibility of the + // WinWebAuthnApi having been disabled by a FidoDiscoveryFactory override, + // which may be done in tests or via the Virtual Authenticator WebDriver + // API. + if (base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi) && + device::WinWebAuthnApi::GetDefault()->IsAvailable() && + device::WinWebAuthnApi::GetDefault()->Version() >= + WEBAUTHN_API_VERSION_2) { + return RespondNow(OneArgument(base::Value(true))); + } +#endif // defined(OS_WIN) + + // Otherwise, show a permission prompt and pass the user's decision back. + const GURL app_id_url(app_id); + EXTENSION_FUNCTION_VALIDATE(app_id_url.is_valid()); + + auto* contents = electron::api::WebContents::FromID(params->options.tab_id); + if (!contents || !contents->web_contents()) { + return RespondNow(Error("cannot find specified tab")); + } + + // permissions::PermissionRequestManager* permission_request_manager = + // permissions::PermissionRequestManager::FromWebContents(web_contents); + // nullptr; + // if (!permission_request_manager) { + return RespondNow(Error("no PermissionRequestManager")); + // } + + // // The created AttestationPermissionRequest deletes itself once complete. + // permission_request_manager->AddRequest( + // web_contents->GetMainFrame(), // Extension API targets a particular + // tab, + // // so select the current main frame to + // // handle the request. + // NewAttestationPermissionRequest( + // origin, + // base::BindOnce( + // &CryptotokenPrivateCanAppIdGetAttestationFunction::Complete, + // this))); + // return RespondLater(); +#endif +} + +void CryptotokenPrivateCanAppIdGetAttestationFunction::Complete(bool result) { + Respond(OneArgument(base::Value(result))); +} + +ExtensionFunction::ResponseAction +CryptotokenPrivateRecordRegisterRequestFunction::Run() { + auto params = + cryptotoken_private::RecordRegisterRequest::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params); + + content::RenderFrameHost* frame = RenderFrameHostForTabAndFrameId( + browser_context(), params->tab_id, params->frame_id); + if (!frame) { + return RespondNow(Error("cannot find specified tab or frame")); + } + + // page_load_metrics::MetricsWebContentsObserver::RecordFeatureUsage( + // frame, blink::mojom::WebFeature::kU2FCryptotokenRegister); + return RespondNow(NoArguments()); +} + +ExtensionFunction::ResponseAction +CryptotokenPrivateRecordSignRequestFunction::Run() { + auto params = cryptotoken_private::RecordSignRequest::Params::Create(*args_); + EXTENSION_FUNCTION_VALIDATE(params); + + content::RenderFrameHost* frame = RenderFrameHostForTabAndFrameId( + browser_context(), params->tab_id, params->frame_id); + if (!frame) { + return RespondNow(Error("cannot find specified tab or frame")); + } + + // page_load_metrics::MetricsWebContentsObserver::RecordFeatureUsage( + // frame, blink::mojom::WebFeature::kU2FCryptotokenSign); + return RespondNow(NoArguments()); +} + +} // namespace api +} // namespace extensions diff --git a/shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h b/shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h new file mode 100644 index 0000000000..c57b4779fa --- /dev/null +++ b/shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_BROWSER_EXTENSIONS_API_CRYPTOTOKEN_PRIVATE_CRYPTOTOKEN_PRIVATE_API_H_ +#define SHELL_BROWSER_EXTENSIONS_API_CRYPTOTOKEN_PRIVATE_CRYPTOTOKEN_PRIVATE_API_H_ + +#include "chrome/common/extensions/api/cryptotoken_private.h" +#include "extensions/browser/extension_function.h" + +namespace user_prefs { +class PrefRegistrySyncable; +} + +// Implementations for chrome.cryptotokenPrivate API functions. + +namespace extensions { +namespace api { + +// void CryptotokenRegisterProfilePrefs( +// user_prefs::PrefRegistrySyncable* registry); + +class CryptotokenPrivateCanOriginAssertAppIdFunction + : public ExtensionFunction { + public: + CryptotokenPrivateCanOriginAssertAppIdFunction(); + DECLARE_EXTENSION_FUNCTION("cryptotokenPrivate.canOriginAssertAppId", + CRYPTOTOKENPRIVATE_CANORIGINASSERTAPPID) + protected: + ~CryptotokenPrivateCanOriginAssertAppIdFunction() override {} + ResponseAction Run() override; +}; + +class CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction + : public ExtensionFunction { + public: + CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction(); + DECLARE_EXTENSION_FUNCTION( + "cryptotokenPrivate.isAppIdHashInEnterpriseContext", + CRYPTOTOKENPRIVATE_ISAPPIDHASHINENTERPRISECONTEXT) + + protected: + ~CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction() override {} + ResponseAction Run() override; +}; + +class CryptotokenPrivateCanAppIdGetAttestationFunction + : public ExtensionFunction { + public: + CryptotokenPrivateCanAppIdGetAttestationFunction(); + DECLARE_EXTENSION_FUNCTION("cryptotokenPrivate.canAppIdGetAttestation", + CRYPTOTOKENPRIVATE_CANAPPIDGETATTESTATION) + + protected: + ~CryptotokenPrivateCanAppIdGetAttestationFunction() override {} + ResponseAction Run() override; + void Complete(bool result); +}; + +class CryptotokenPrivateRecordRegisterRequestFunction + : public ExtensionFunction { + public: + CryptotokenPrivateRecordRegisterRequestFunction() = default; + DECLARE_EXTENSION_FUNCTION("cryptotokenPrivate.recordRegisterRequest", + CRYPTOTOKENPRIVATE_RECORDREGISTERREQUEST) + + protected: + ~CryptotokenPrivateRecordRegisterRequestFunction() override = default; + ResponseAction Run() override; +}; + +class CryptotokenPrivateRecordSignRequestFunction : public ExtensionFunction { + public: + CryptotokenPrivateRecordSignRequestFunction() = default; + DECLARE_EXTENSION_FUNCTION("cryptotokenPrivate.recordSignRequest", + CRYPTOTOKENPRIVATE_RECORDSIGNREQUEST) + + protected: + ~CryptotokenPrivateRecordSignRequestFunction() override = default; + ResponseAction Run() override; +}; + +} // namespace api +} // namespace extensions + +#endif // SHELL_BROWSER_EXTENSIONS_API_CRYPTOTOKEN_PRIVATE_CRYPTOTOKEN_PRIVATE_API_H_ diff --git a/shell/browser/extensions/api/tabs/tabs_api.cc b/shell/browser/extensions/api/tabs/tabs_api.cc index 6a69e44c03..8fa5783e33 100644 --- a/shell/browser/extensions/api/tabs/tabs_api.cc +++ b/shell/browser/extensions/api/tabs/tabs_api.cc @@ -200,6 +200,8 @@ ExtensionFunction::ResponseAction TabsGetFunction::Run() { tab.url = std::make_unique( contents->web_contents()->GetLastCommittedURL().spec()); + tab.active = contents->IsFocused(); + return RespondNow(ArgumentList(tabs::Get::Results::Create(std::move(tab)))); } diff --git a/shell/browser/extensions/electron_extension_system.cc b/shell/browser/extensions/electron_extension_system.cc index f597b21206..8242024592 100644 --- a/shell/browser/extensions/electron_extension_system.cc +++ b/shell/browser/extensions/electron_extension_system.cc @@ -22,6 +22,7 @@ #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "electron/buildflags/buildflags.h" +#include "electron/grit/electron_resources.h" #include "extensions/browser/api/app_runtime/app_runtime_api.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/info_map.h" @@ -36,6 +37,7 @@ #include "extensions/common/constants.h" #include "extensions/common/file_util.h" #include "shell/browser/extensions/electron_extension_loader.h" +#include "ui/base/resource/resource_bundle.h" #if BUILDFLAG(ENABLE_PDF_VIEWER) #include "chrome/browser/pdf/pdf_extension_util.h" // nogncheck @@ -46,6 +48,18 @@ using content::BrowserThread; namespace extensions { +namespace { + +std::string GetCryptoTokenManifest() { + std::string manifest_contents( + ui::ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_CRYPTOTOKEN_MANIFEST)); + + return manifest_contents; +} + +} // namespace + ElectronExtensionSystem::ElectronExtensionSystem( BrowserContext* browser_context) : browser_context_(browser_context), @@ -109,8 +123,8 @@ std::unique_ptr ParseManifest( } void ElectronExtensionSystem::LoadComponentExtensions() { -#if BUILDFLAG(ENABLE_PDF_VIEWER) std::string utf8_error; +#if BUILDFLAG(ENABLE_PDF_VIEWER) std::string pdf_manifest_string = pdf_extension_util::GetManifest(); std::unique_ptr pdf_manifest = ParseManifest(pdf_manifest_string); @@ -125,6 +139,22 @@ void ElectronExtensionSystem::LoadComponentExtensions() { extension_loader_->registrar()->AddExtension(pdf_extension); } #endif + + std::string cryptotoken_manifest_string = GetCryptoTokenManifest(); + std::unique_ptr cryptotoken_manifest = + ParseManifest(cryptotoken_manifest_string); + DCHECK(cryptotoken_manifest); + if (cryptotoken_manifest) { + base::FilePath root_directory; + CHECK(base::PathService::Get(chrome::DIR_RESOURCES, &root_directory)); + root_directory = root_directory.Append(FILE_PATH_LITERAL("cryptotoken")); + scoped_refptr cryptotoken_extension = + extensions::Extension::Create( + root_directory, extensions::mojom::ManifestLocation::kComponent, + *cryptotoken_manifest, extensions::Extension::REQUIRE_KEY, + &utf8_error); + extension_loader_->registrar()->AddExtension(cryptotoken_extension); + } } ExtensionService* ElectronExtensionSystem::extension_service() { diff --git a/shell/common/extensions/api/BUILD.gn b/shell/common/extensions/api/BUILD.gn index c8512a5761..b3d6568411 100644 --- a/shell/common/extensions/api/BUILD.gn +++ b/shell/common/extensions/api/BUILD.gn @@ -36,6 +36,7 @@ group("extensions_features") { generated_json_strings("generated_api_json_strings") { sources = [ + "cryptotoken_private.idl", "extension.json", "i18n.json", "resources_private.idl", @@ -54,6 +55,7 @@ generated_json_strings("generated_api_json_strings") { generated_types("generated_api_types") { sources = [ + "cryptotoken_private.idl", "i18n.json", "resources_private.idl", "tabs.json", diff --git a/shell/common/extensions/api/_api_features.json b/shell/common/extensions/api/_api_features.json index 6a8d18fa85..bb14d9ea30 100644 --- a/shell/common/extensions/api/_api_features.json +++ b/shell/common/extensions/api/_api_features.json @@ -37,5 +37,9 @@ "matches": [ "chrome://print/*" ] - }] + }], + "cryptotokenPrivate": { + "dependencies": ["permission:cryptotokenPrivate"], + "contexts": ["blessed_extension"] + } } diff --git a/shell/common/extensions/api/_permission_features.json b/shell/common/extensions/api/_permission_features.json index ecd8f074f6..0c44f17bdc 100644 --- a/shell/common/extensions/api/_permission_features.json +++ b/shell/common/extensions/api/_permission_features.json @@ -11,5 +11,13 @@ "extension_types": [ "extension" ] + }, + "cryptotokenPrivate": { + "channel": "stable", + "extension_types": ["extension"], + "location": "component", + "allowlist": [ + "E24F1786D842E91E74C27929B0B3715A4689A473" // Cryptotoken + ] } } \ No newline at end of file diff --git a/shell/common/extensions/api/cryptotoken_private.idl b/shell/common/extensions/api/cryptotoken_private.idl new file mode 100644 index 0000000000..bd761504f2 --- /dev/null +++ b/shell/common/extensions/api/cryptotoken_private.idl @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// chrome.cryptotokenPrivate API that provides hooks to Chrome to +// be used by cryptotoken component extension. +//

In the context of this API, an AppId is roughly an origin and is formally +// defined in +// +// the FIDO spec

+namespace cryptotokenPrivate { + + callback BooleanCallback = void(boolean result); + callback VoidCallback = void(); + + dictionary CanAppIdGetAttestationOptions { + // The AppId (see definition, above) that was used in the registration + // request and which has been authenticated by |canOriginAssertAppId|. + DOMString appId; + // The origin of the caller. + DOMString origin; + // Identifies the tab in which the registration is occuring so that any + // permissions prompt is correctly located. + long tabId; + }; + + interface Functions { + // Checks whether the origin is allowed to assert the appId, according to + // the same origin policy defined at + // http://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/ + // fido-appid-and-facets-ps-20141009.html + // |securityOrigin| is the origin as seen by the extension, and |appIdUrl| + // is the appId being asserted by the origin. + static void canOriginAssertAppId(DOMString securityOrigin, + DOMString appIdUrl, + BooleanCallback callback); + + // Checks whether the given appId is specified in the + // SecurityKeyPermitAttestation policy. This causes a signal to be sent to + // the token that informs it that an individually-identifying attestation + // certificate may be used. Without that signal, the token is required to + // use its batch attestation certificate. + static void isAppIdHashInEnterpriseContext(ArrayBuffer appIdHash, + BooleanCallback callback); + + // Checks whether the given appId may receive attestation data that + // identifies the token. If not, the attestation from the token must be + // substituted with a randomly generated certificate since webauthn and U2F + // require that some attestation be provided. + static void canAppIdGetAttestation(CanAppIdGetAttestationOptions options, + BooleanCallback callback); + + // Increments the WebFeature::kU2FCryptotokenRegister UseCounter for the + // main frame associated with |tabId|. + static void recordRegisterRequest(long tabId, long frameId, + optional VoidCallback callback); + + // Increments the WebFeature::kU2FCryptotokenSign UseCounter for the + // main frame associated with |tabId|. + static void recordSignRequest(long tabId, long frameId, + optional VoidCallback callback); + }; +}; diff --git a/shell/common/extensions/electron_extensions_api_provider.cc b/shell/common/extensions/electron_extensions_api_provider.cc index 18309c03de..de8f0c4a86 100644 --- a/shell/common/extensions/electron_extensions_api_provider.cc +++ b/shell/common/extensions/electron_extensions_api_provider.cc @@ -35,6 +35,7 @@ constexpr APIPermissionInfo::InitInfo permissions_to_register[] = { {mojom::APIPermissionID::kResourcesPrivate, "resourcesPrivate", APIPermissionInfo::kFlagCannotBeOptional}, {mojom::APIPermissionID::kManagement, "management"}, + {mojom::APIPermissionID::kCryptotokenPrivate, "cryptotokenPrivate"}, }; base::span GetPermissionInfos() { return base::make_span(permissions_to_register); diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 0018234449..d3b62f9aa7 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -153,7 +153,7 @@ describe('chrome extensions', () => { const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const [, loadedExtension] = await loadedPromise; const [, readyExtension] = await emittedUntil(customSession, 'extension-ready', (event: Event, extension: Extension) => { - return extension.name !== 'Chromium PDF Viewer'; + return extension.name !== 'Chromium PDF Viewer' && extension.name !== 'CryptoTokenExtension'; }); expect(loadedExtension).to.deep.equal(extension);