feat: Implement password delegate for NSS (#41205)
* feat: Implement password delegate for NSS (#41188) Introduce an app event client-certificate-request-password. It allows the user to display a UI to prompt for the password. An alternative would have been to implement a class similar to CryptoModulePasswordDialogView, to provide the UI. This might have been simpler for the user, comparing to letting them implement the UI. But it seems like electron does not have an i18n framework, so it's not possible to provide a locale aware UI. * fix lint:markdown error * address review comments * use a trampoline handler in JS. The api exposed is now app.setClientCertRequestPasswordHandler * use properties on the Event object instead of positional parameters * remove ChromeNSSCryptoModuleDelegate::OnPassword in favor of args->GetNext(&password_) * address review comments second round - backslash escape parametrized TypeScript - rename hostName param to hostname - use base::ScopedAllowBaseSyncPrimitivesForTesting - and then, rename ChromeNSSCryptoModuleDelegate to ElectronNSSCryptoModuleDelegate * Update docs/api/app.md Co-authored-by: Sam Maddock <samuel.maddock@gmail.com> * Update docs/api/app.md Co-authored-by: Erick Zhao <erick@hotmail.ca> --------- Co-authored-by: Arno Renevier <arnaud@switchboard.app> Co-authored-by: Sam Maddock <samuel.maddock@gmail.com> Co-authored-by: Erick Zhao <erick@hotmail.ca>
This commit is contained in:
parent
c210ae9b33
commit
81bdba67ec
7 changed files with 173 additions and 1 deletions
|
@ -1489,6 +1489,38 @@ This method can only be called after app is ready.
|
|||
|
||||
Returns `Promise<string>` - Resolves with the proxy information for `url` that will be used when attempting to make requests using [Net](net.md) in the [utility process](../glossary.md#utility-process).
|
||||
|
||||
### `app.setClientCertRequestPasswordHandler(handler)` _Linux_
|
||||
|
||||
* `handler` Function\<Promise\<string\>\>
|
||||
* `clientCertRequestParams` Object
|
||||
* `hostname` string - the hostname of the site requiring a client certificate
|
||||
* `tokenName` string - the token (or slot) name of the cryptographic device
|
||||
* `isRetry` boolean - whether there have been previous failed attempts at prompting the password
|
||||
|
||||
Returns `Promise<string>` - Resolves with the password
|
||||
|
||||
The handler is called when a password is needed to unlock a client certificate for
|
||||
`hostname`.
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
async function passwordPromptUI (text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// display UI to prompt user for password
|
||||
// ...
|
||||
// ...
|
||||
resolve('the password')
|
||||
})
|
||||
}
|
||||
|
||||
app.setClientCertRequestPasswordHandler(async ({ hostname, tokenName, isRetry }) => {
|
||||
const text = `Please sign in to ${tokenName} to authenticate to ${hostname} with your certificate`
|
||||
const password = await passwordPromptUI(text)
|
||||
return password
|
||||
})
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
### `app.accessibilitySupportEnabled` _macOS_ _Windows_
|
||||
|
|
|
@ -362,6 +362,8 @@ filenames = {
|
|||
"shell/browser/electron_browser_context.h",
|
||||
"shell/browser/electron_browser_main_parts.cc",
|
||||
"shell/browser/electron_browser_main_parts.h",
|
||||
"shell/browser/electron_crypto_module_delegate_nss.cc",
|
||||
"shell/browser/electron_crypto_module_delegate_nss.h",
|
||||
"shell/browser/electron_download_manager_delegate.cc",
|
||||
"shell/browser/electron_download_manager_delegate.h",
|
||||
"shell/browser/electron_gpu_client.cc",
|
||||
|
|
|
@ -111,3 +111,19 @@ for (const name of events) {
|
|||
webContents.emit(name, event, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
app._clientCertRequestPasswordHandler = null;
|
||||
app.setClientCertRequestPasswordHandler = function (handler: (params: Electron.ClientCertRequestParams) => Promise<string>) {
|
||||
app._clientCertRequestPasswordHandler = handler;
|
||||
};
|
||||
|
||||
app.on('-client-certificate-request-password', async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
|
||||
event.preventDefault();
|
||||
const { hostname, tokenName, isRetry } = event;
|
||||
if (!app._clientCertRequestPasswordHandler) {
|
||||
callback('');
|
||||
return;
|
||||
}
|
||||
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
|
||||
callback(password);
|
||||
});
|
||||
|
|
|
@ -128,6 +128,7 @@
|
|||
|
||||
#if BUILDFLAG(USE_NSS_CERTS)
|
||||
#include "net/ssl/client_cert_store_nss.h"
|
||||
#include "shell/browser/electron_crypto_module_delegate_nss.h"
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
#include "net/ssl/client_cert_store_win.h"
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
|
@ -783,7 +784,11 @@ ElectronBrowserClient::CreateClientCertStore(
|
|||
content::BrowserContext* browser_context) {
|
||||
#if BUILDFLAG(USE_NSS_CERTS)
|
||||
return std::make_unique<net::ClientCertStoreNSS>(
|
||||
net::ClientCertStoreNSS::PasswordDelegateFactory());
|
||||
base::BindRepeating([](const net::HostPortPair& server) {
|
||||
crypto::CryptoModuleBlockingPasswordDelegate* delegate =
|
||||
new ElectronNSSCryptoModuleDelegate(server);
|
||||
return delegate;
|
||||
}));
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
return std::make_unique<net::ClientCertStoreWin>();
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
|
|
72
shell/browser/electron_crypto_module_delegate_nss.cc
Normal file
72
shell/browser/electron_crypto_module_delegate_nss.cc
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2024 Switchboard
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/electron_crypto_module_delegate_nss.h"
|
||||
|
||||
#include "crypto/nss_crypto_module_delegate.h"
|
||||
#include "shell/browser/api/electron_api_app.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_helper/callback.h"
|
||||
#include "shell/common/v8_value_serializer.h"
|
||||
|
||||
ElectronNSSCryptoModuleDelegate::ElectronNSSCryptoModuleDelegate(
|
||||
const net::HostPortPair& server)
|
||||
: server_(server),
|
||||
event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
|
||||
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
|
||||
|
||||
ElectronNSSCryptoModuleDelegate::~ElectronNSSCryptoModuleDelegate() = default;
|
||||
|
||||
std::string ElectronNSSCryptoModuleDelegate::RequestPassword(
|
||||
const std::string& token_name,
|
||||
bool retry,
|
||||
bool* cancelled) {
|
||||
DCHECK(!event_.IsSignaled());
|
||||
event_.Reset();
|
||||
|
||||
if (content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
&ElectronNSSCryptoModuleDelegate::RequestPasswordOnUIThread, this,
|
||||
token_name, retry))) {
|
||||
base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
|
||||
event_.Wait();
|
||||
}
|
||||
*cancelled = cancelled_;
|
||||
return password_;
|
||||
}
|
||||
|
||||
void ElectronNSSCryptoModuleDelegate::RequestPasswordOnUIThread(
|
||||
const std::string& token_name,
|
||||
bool retry) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
gin::Handle<gin_helper::internal::Event> event =
|
||||
gin_helper::internal::Event::New(isolate);
|
||||
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();
|
||||
gin_helper::Dictionary dict(isolate, event_object);
|
||||
dict.Set("hostname", server_.host());
|
||||
dict.Set("tokenName", token_name);
|
||||
dict.Set("isRetry", retry);
|
||||
|
||||
electron::api::App::Get()->EmitWithoutEvent(
|
||||
"-client-certificate-request-password", event_object,
|
||||
base::BindOnce(&ElectronNSSCryptoModuleDelegate::OnPassword, this));
|
||||
|
||||
if (!event->GetDefaultPrevented()) {
|
||||
password_ = "";
|
||||
cancelled_ = true;
|
||||
event_.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronNSSCryptoModuleDelegate::OnPassword(gin::Arguments* args) {
|
||||
args->GetNext(&password_);
|
||||
cancelled_ = password_.empty();
|
||||
event_.Signal();
|
||||
}
|
43
shell/browser/electron_crypto_module_delegate_nss.h
Normal file
43
shell/browser/electron_crypto_module_delegate_nss.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2024 Switchboard
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_CRYPTO_MODULE_DELEGATE_NSS_H_
|
||||
#define ELECTRON_SHELL_CRYPTO_MODULE_DELEGATE_NSS_H_
|
||||
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "crypto/nss_crypto_module_delegate.h"
|
||||
#include "net/base/host_port_pair.h"
|
||||
|
||||
namespace gin {
|
||||
class Arguments;
|
||||
}
|
||||
|
||||
class ElectronNSSCryptoModuleDelegate
|
||||
: public crypto::CryptoModuleBlockingPasswordDelegate {
|
||||
public:
|
||||
explicit ElectronNSSCryptoModuleDelegate(const net::HostPortPair& server);
|
||||
ElectronNSSCryptoModuleDelegate(const ElectronNSSCryptoModuleDelegate&) =
|
||||
delete;
|
||||
ElectronNSSCryptoModuleDelegate& operator=(
|
||||
const ElectronNSSCryptoModuleDelegate&) = delete;
|
||||
|
||||
std::string RequestPassword(const std::string& token_name,
|
||||
bool retry,
|
||||
bool* cancelled) override;
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<ElectronNSSCryptoModuleDelegate>;
|
||||
~ElectronNSSCryptoModuleDelegate() override;
|
||||
|
||||
void RequestPasswordOnUIThread(const std::string& token_name, bool retry);
|
||||
void OnPassword(gin::Arguments* args);
|
||||
|
||||
net::HostPortPair server_;
|
||||
base::WaitableEvent event_;
|
||||
std::string password_;
|
||||
bool cancelled_ = false;
|
||||
};
|
||||
|
||||
#endif // ELECTRON_SHELL_CRYPTO_MODULE_DELEGATE_NSS_H_
|
2
typings/internal-electron.d.ts
vendored
2
typings/internal-electron.d.ts
vendored
|
@ -17,6 +17,8 @@ declare namespace Electron {
|
|||
setVersion(version: string): void;
|
||||
setDesktopName(name: string): void;
|
||||
setAppPath(path: string | null): void;
|
||||
_clientCertRequestPasswordHandler: ((params: ClientCertRequestParams) => Promise<string>) | null;
|
||||
on(event: '-client-certificate-request-password', listener: (event: Event<ClientCertRequestParams>, callback: (password: string) => void) => Promise<void>): this;
|
||||
}
|
||||
|
||||
interface AutoUpdater {
|
||||
|
|
Loading…
Reference in a new issue