feat: add electron.safeStorage encryption API (#30020)

* feat: add SafeStorage api; first commit

* chore: rename files to fit semantically

* chore: add linkedBindings

* chore: fix function signatures

* chore: refactor eisCookieEncryptionEnabled() fuse

* chore: create test file

* chore: add tests and documentation

* chore: add copyright and lint

* chore: add additional tests

* chore: fix constructor

* chore: commit for pair programming

* wip: commit for keeley pairing

* chore: docs change and code cleanup

* chore: add linux import

* chore: add description to documentation

* chore: fixing tests

* chore: modify behaviour to not allow unencrypted strings as decyption input

* fix add patch for enabling default v11 encryption on Linux

* chore: remove file after each test

* chore: fix patch

* chore: remove chromium patch

* chore: add linux specific tests

* chore: fix path

* chore: add checker for linuux file deletion

* chore: add dcheck back

* chore: remove reference to headless mode

* chore: remove tests for linux

* chore: edit commit message

* chore: refactor safeStorage to not be a class

* chore: remove static variable from header

* chore:  spec file remove settimeout

Co-authored-by: VerteDinde <keeleymhammond@gmail.com>
This commit is contained in:
George Xu 2021-08-05 15:12:54 -07:00 committed by GitHub
parent ec6cd0053e
commit bc508c6113
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 393 additions and 46 deletions

View file

@ -0,0 +1,121 @@
// Copyright (c) 2021 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/api/electron_api_safe_storage.h"
#include <string>
#include <vector>
#include "components/os_crypt/os_crypt.h"
#include "shell/browser/browser.h"
#include "shell/common/gin_converters/base_converter.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/node_includes.h"
#include "shell/common/platform_util.h"
namespace electron {
namespace safestorage {
static const char* kEncryptionVersionPrefixV10 = "v10";
static const char* kEncryptionVersionPrefixV11 = "v11";
#if DCHECK_IS_ON()
static bool electron_crypto_ready = false;
void SetElectronCryptoReady(bool ready) {
electron_crypto_ready = ready;
}
#endif
bool IsEncryptionAvailable() {
return OSCrypt::IsEncryptionAvailable();
}
v8::Local<v8::Value> EncryptString(v8::Isolate* isolate,
const std::string& plaintext) {
if (!OSCrypt::IsEncryptionAvailable()) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Error while decrypting the ciphertext provided to "
"safeStorage.decryptString. "
"Encryption is not available.");
return v8::Local<v8::Value>();
}
std::string ciphertext;
bool encrypted = OSCrypt::EncryptString(plaintext, &ciphertext);
if (!encrypted) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Error while encrypting the text provided to "
"safeStorage.encryptString.");
return v8::Local<v8::Value>();
}
return node::Buffer::Copy(isolate, ciphertext.c_str(), ciphertext.size())
.ToLocalChecked();
}
std::string DecryptString(v8::Isolate* isolate, v8::Local<v8::Value> buffer) {
if (!OSCrypt::IsEncryptionAvailable()) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Error while decrypting the ciphertext provided to "
"safeStorage.decryptString. "
"Decryption is not available.");
return "";
}
if (!node::Buffer::HasInstance(buffer)) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Expected the first argument of decryptString() to be a buffer");
return "";
}
// ensures an error is thrown in Mac or Linux on
// decryption failure, rather than failing silently
const char* data = node::Buffer::Data(buffer);
auto size = node::Buffer::Length(buffer);
std::string ciphertext(data, size);
if (ciphertext.empty()) {
return "";
}
if (ciphertext.find(kEncryptionVersionPrefixV10) != 0 &&
ciphertext.find(kEncryptionVersionPrefixV11) != 0) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Error while decrypting the ciphertext provided to "
"safeStorage.decryptString. "
"Ciphertext does not appear to be encrypted.");
return "";
}
std::string plaintext;
bool decrypted = OSCrypt::DecryptString(ciphertext, &plaintext);
if (!decrypted) {
gin_helper::ErrorThrower(isolate).ThrowError(
"Error while decrypting the ciphertext provided to "
"safeStorage.decryptString.");
return "";
}
return plaintext;
}
} // namespace safestorage
} // namespace electron
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("isEncryptionAvailable",
&electron::safestorage::IsEncryptionAvailable);
dict.SetMethod("encryptString", &electron::safestorage::EncryptString);
dict.SetMethod("decryptString", &electron::safestorage::DecryptString);
}
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_safe_storage, Initialize)

View file

@ -0,0 +1,25 @@
// Copyright (c) 2021 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_API_ELECTRON_API_SAFE_STORAGE_H_
#define SHELL_BROWSER_API_ELECTRON_API_SAFE_STORAGE_H_
#include "base/dcheck_is_on.h"
namespace electron {
namespace safestorage {
// Used in a DCHECK to validate that our assumption that the network context
// manager has initialized before app ready holds true. Only used in the
// testing build
#if DCHECK_IS_ON()
void SetElectronCryptoReady(bool ready);
#endif
} // namespace safestorage
} // namespace electron
#endif // SHELL_BROWSER_API_ELECTRON_API_SAFE_STORAGE_H_

View file

@ -103,20 +103,14 @@ void BrowserProcessImpl::PostEarlyInitialization() {
// Only use a persistent prefs store when cookie encryption is enabled as that
// is the only key that needs it
if (electron::fuses::IsCookieEncryptionEnabled()) {
base::FilePath prefs_path;
CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &prefs_path));
prefs_path = prefs_path.Append(FILE_PATH_LITERAL("Local State"));
base::ThreadRestrictions::ScopedAllowIO allow_io;
scoped_refptr<JsonPrefStore> user_pref_store =
base::MakeRefCounted<JsonPrefStore>(prefs_path);
user_pref_store->ReadPrefs();
prefs_factory.set_user_prefs(user_pref_store);
} else {
auto user_pref_store =
base::MakeRefCounted<OverlayUserPrefStore>(new InMemoryPrefStore);
prefs_factory.set_user_prefs(user_pref_store);
}
base::FilePath prefs_path;
CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &prefs_path));
prefs_path = prefs_path.Append(FILE_PATH_LITERAL("Local State"));
base::ThreadRestrictions::ScopedAllowIO allow_io;
scoped_refptr<JsonPrefStore> user_pref_store =
base::MakeRefCounted<JsonPrefStore>(prefs_path);
user_pref_store->ReadPrefs();
prefs_factory.set_user_prefs(user_pref_store);
local_state_ = prefs_factory.Create(std::move(pref_registry));
}

View file

@ -536,13 +536,11 @@ void ElectronBrowserMainParts::PreCreateMainMessageLoopCommon() {
media::SetLocalizedStringProvider(MediaStringProvider);
#if defined(OS_WIN)
if (electron::fuses::IsCookieEncryptionEnabled()) {
auto* local_state = g_browser_process->local_state();
DCHECK(local_state);
auto* local_state = g_browser_process->local_state();
DCHECK(local_state);
bool os_crypt_init = OSCrypt::Init(local_state);
DCHECK(os_crypt_init);
}
bool os_crypt_init = OSCrypt::Init(local_state);
DCHECK(os_crypt_init);
#endif
}

View file

@ -28,6 +28,7 @@
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "shell/browser/api/electron_api_safe_storage.h"
#include "shell/browser/browser.h"
#include "shell/browser/electron_browser_client.h"
#include "shell/common/application_info.h"
@ -39,6 +40,10 @@
#include "components/os_crypt/keychain_password_mac.h"
#endif
#if defined(OS_LINUX)
#include "components/os_crypt/key_storage_config_linux.h"
#endif
namespace {
// The global instance of the SystemNetworkContextmanager.
@ -233,38 +238,56 @@ void SystemNetworkContextManager::OnNetworkServiceCreated(
network_context_.BindNewPipeAndPassReceiver(),
CreateNetworkContextParams());
if (electron::fuses::IsCookieEncryptionEnabled()) {
std::string app_name = electron::Browser::Get()->GetName();
std::string app_name = electron::Browser::Get()->GetName();
#if defined(OS_MAC)
*KeychainPassword::service_name = app_name + " Safe Storage";
*KeychainPassword::account_name = app_name;
*KeychainPassword::service_name = app_name + " Safe Storage";
*KeychainPassword::account_name = app_name;
#endif
// The OSCrypt keys are process bound, so if network service is out of
// process, send it the required key.
if (content::IsOutOfProcessNetworkService()) {
#if defined(OS_LINUX)
// c.f.
// https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/net/system_network_context_manager.cc;l=515;drc=9d82515060b9b75fa941986f5db7390299669ef1;bpv=1;bpt=1
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
// c.f.
// https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/net/system_network_context_manager.cc;l=515;drc=9d82515060b9b75fa941986f5db7390299669ef1;bpv=1;bpt=1
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
network::mojom::CryptConfigPtr config =
network::mojom::CryptConfig::New();
config->application_name = app_name;
config->product_name = app_name;
// c.f.
// https://source.chromium.org/chromium/chromium/src/+/master:chrome/common/chrome_switches.cc;l=689;drc=9d82515060b9b75fa941986f5db7390299669ef1
config->store =
command_line.GetSwitchValueASCII(::switches::kPasswordStore);
config->should_use_preference =
command_line.HasSwitch(::switches::kEnableEncryptionSelection);
base::PathService::Get(chrome::DIR_USER_DATA, &config->user_data_path);
network_service->SetCryptConfig(std::move(config));
#else
network_service->SetEncryptionKey(OSCrypt::GetRawEncryptionKey());
auto config = std::make_unique<os_crypt::Config>();
config->store = command_line.GetSwitchValueASCII(::switches::kPasswordStore);
config->product_name = app_name;
config->application_name = app_name;
config->main_thread_runner = base::ThreadTaskRunnerHandle::Get();
// c.f.
// https://source.chromium.org/chromium/chromium/src/+/master:chrome/common/chrome_switches.cc;l=689;drc=9d82515060b9b75fa941986f5db7390299669ef1
config->should_use_preference =
command_line.HasSwitch(::switches::kEnableEncryptionSelection);
base::PathService::Get(chrome::DIR_USER_DATA, &config->user_data_path);
#endif
// The OSCrypt keys are process bound, so if network service is out of
// process, send it the required key.
if (content::IsOutOfProcessNetworkService() &&
electron::fuses::IsCookieEncryptionEnabled()) {
#if defined(OS_LINUX)
network::mojom::CryptConfigPtr network_crypt_config =
network::mojom::CryptConfig::New();
network_crypt_config->application_name = config->application_name;
network_crypt_config->product_name = config->product_name;
network_crypt_config->store = config->store;
network_crypt_config->should_use_preference = config->should_use_preference;
network_crypt_config->user_data_path = config->user_data_path;
network_service->SetCryptConfig(std::move(network_crypt_config));
#else
network_service->SetEncryptionKey(OSCrypt::GetRawEncryptionKey());
#endif
}
}
#if defined(OS_LINUX)
OSCrypt::SetConfig(std::move(config));
#endif
#if DCHECK_IS_ON()
electron::safestorage::SetElectronCryptoReady(true);
#endif
}
network::mojom::NetworkContextParamsPtr

View file

@ -60,6 +60,7 @@
V(electron_browser_power_save_blocker) \
V(electron_browser_protocol) \
V(electron_browser_printing) \
V(electron_browser_safe_storage) \
V(electron_browser_session) \
V(electron_browser_system_preferences) \
V(electron_browser_base_window) \