mac: Perform Developer ID certificate reauthorization
The code signing certificate is changing! Previously, Chrome was signed with a designated requirement binding it to a specific Developer ID certificate. The designated requirement was used as the basis for the access control list that protected the Chrome Safe Storage item in the user's Keychain. Every 5 years, as those certificates expired and were renewed, these Keychain items needed to be "reauthorized" to ensure that newer versions of Chrome, signed with the new certificate, would continue to have access. These projects were previously conducted under bug 629906 and bug 116210. Recently, Chrome started being signed with a designated requirement binding it more traditionally for Developer ID code signing. Rather than using a specific signing certificate, the requirement is now summarized as "Apple issued a Developer ID Application certificate to Google (indicated by team ID)". Provided the team ID doesn't change (it hasn't since the inception of the Developer ID program), the new requirement should spell the end of periodic reauthorization projects. Except for this one. For (hopefully) one last time, the Safe Storage items in users' Keychains need to be reauthorized according to the new requirement. The strategy is quite similar to previous reauthorization projects. In fact, most of this round was developed on fairly clean reverts of2c29863d60
andf54c4ad6c9
, which removed the code used for the previous reauthorization. Reauthorization can be performed by any process that has access to Safe Storage items that only allow access to "old-signed" Chrome. Initially, this includes the browser process (see REAUTHORIZE_IN_APP) but that will cease to be true when the code signing certificate changes. To allow reauthorizations to be performed even after that date, the reauthorization stub executable is introduced. It's a helper executable that's signed as though it were Chrome, and thus has the necessary access to old Safe Storage item. Like other Chrome helpers, the stub loads the framework and jumps to a well-known entry point. The code signing mechanism (in particular, the "library" option for library validation, and the "runtime" option for the hardened runtime) prevent abuse. The stub can be launched by Chrome even after Chrome's own code signing certificate changes. In this change, the stub executable is built and signed. Once a signed stub is available, it will be "frozen" and embedded as a binary component, signed using the old certificate, and maintaining its validity because it was signed before that certificate expired. Since the stub contains no code other than that necessary to load and jump to the framework, it is anticipated that it will not be a problem for it to exist as a binary component for the duration of the reauthorization project. Another follow-up change will switch REAUTHORIZE_IN_APP to 0 in anticipation of the certificate change for Chrome code. This will cause all reauthorizations to be performed by the stub. In addition to performing reauthorization when Chrome is started, it is also done at update time when updating from a user ticket (as this is only possible when the updater is running as the user that runs Chrome) by launching the stub. Bug: 1263152 Change-Id: Id26437419cfa9bbd0176430ce65be120aee7fe10 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3445684 Reviewed-by: Robert Sesek <rsesek@chromium.org> Reviewed-by: Adam Langley <agl@chromium.org> Reviewed-by: Avi Drissman <avi@chromium.org> Commit-Queue: Mark Mentovai <mark@chromium.org> Cr-Commit-Position: refs/heads/main@{#969436}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
fdfe2f283c
commit
b4e1bfc7a9
base/threading
chrome
BUILD.gn
app
browser
installer
mac
crypto
@ -147,6 +147,11 @@ namespace cc {
|
||||
class CompletionEvent;
|
||||
class TileTaskManagerImpl;
|
||||
}
|
||||
namespace chrome {
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
void DeveloperIDCertificateReauthorizeInApp();
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
} // namespace chrome
|
||||
namespace chromecast {
|
||||
class CrashUtil;
|
||||
}
|
||||
@ -484,6 +489,9 @@ class BASE_EXPORT ScopedAllowBlocking {
|
||||
|
||||
friend Profile* ::GetLastProfileMac(); // crbug.com/1176734
|
||||
friend bool PathProviderWin(int, FilePath*);
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
friend void chrome::DeveloperIDCertificateReauthorizeInApp();
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
friend bool chromeos::system::IsCoreSchedulingAvailable();
|
||||
friend int chromeos::system::NumberOfPhysicalCores();
|
||||
friend bool ::HasWaylandDisplay(base::Environment* env); // crbug.com/1246928
|
||||
|
@ -857,11 +857,13 @@ if (is_win) {
|
||||
sources = [
|
||||
"$root_out_dir/app_mode_loader",
|
||||
"$root_out_dir/chrome_crashpad_handler",
|
||||
"$root_out_dir/developer_id_certificate_reauthorize",
|
||||
]
|
||||
|
||||
outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]
|
||||
|
||||
public_deps = [
|
||||
"browser:developer_id_certificate_reauthorize",
|
||||
"//chrome/app_shim:app_mode_loader",
|
||||
"//components/crash/core/app:chrome_crashpad_handler",
|
||||
]
|
||||
@ -1200,7 +1202,16 @@ if (is_win) {
|
||||
if (!is_component_build) {
|
||||
# Specify a sensible install_name for static builds. The library is
|
||||
# dlopen()ed so this is not used to resolve the module.
|
||||
ldflags += [ "-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/Versions/$chrome_version_full/$chrome_framework_name" ]
|
||||
# ldflags += [ "-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/Versions/$chrome_version_full/$chrome_framework_name" ]
|
||||
#
|
||||
# TODO(mark): The above is more sensible, but since nothing links against
|
||||
# the framework directly aside from the
|
||||
# developer_id_certificate_reauthorize stub executable, set the
|
||||
# framework's install name properly to make the stub's link simpler. Once
|
||||
# the stub is no longer built, switch back to the above.
|
||||
# https://crbug.com/1263152.
|
||||
ldflags +=
|
||||
[ "-Wl,-install_name,@executable_path/../$chrome_framework_name" ]
|
||||
} else {
|
||||
# In the component build, both the :chrome_app and various
|
||||
# :chrome_helper* targets directly link to the Framework target. Use
|
||||
@ -1266,6 +1277,7 @@ if (is_win) {
|
||||
_chrome_symbols_sources = [
|
||||
"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_name",
|
||||
"$root_out_dir/chrome_crashpad_handler",
|
||||
"$root_out_dir/developer_id_certificate_reauthorize",
|
||||
"$root_out_dir/libEGL.dylib",
|
||||
"$root_out_dir/libGLESv2.dylib",
|
||||
"$root_out_dir/libswiftshader_libEGL.dylib",
|
||||
@ -1303,6 +1315,7 @@ if (is_win) {
|
||||
deps = [
|
||||
":chrome_app",
|
||||
":chrome_framework",
|
||||
"browser:developer_id_certificate_reauthorize",
|
||||
"//components/crash/core/app:chrome_crashpad_handler",
|
||||
"//third_party/angle:libEGL",
|
||||
"//third_party/angle:libGLESv2",
|
||||
@ -1331,6 +1344,7 @@ if (is_win) {
|
||||
"$root_out_dir/$chrome_framework_name.dSYM",
|
||||
"$root_out_dir/$chrome_product_full_name.dSYM",
|
||||
"$root_out_dir/chrome_crashpad_handler.dSYM",
|
||||
"$root_out_dir/developer_id_certificate_reauthorize.dSYM",
|
||||
"$root_out_dir/libEGL.dylib.dSYM",
|
||||
"$root_out_dir/libGLESv2.dylib.dSYM",
|
||||
"$root_out_dir/libswiftshader_libEGL.dylib.dSYM",
|
||||
@ -1344,6 +1358,7 @@ if (is_win) {
|
||||
deps = [
|
||||
":chrome_app",
|
||||
":chrome_framework",
|
||||
"browser:developer_id_certificate_reauthorize",
|
||||
"//components/crash/core/app:chrome_crashpad_handler",
|
||||
"//third_party/angle:libEGL",
|
||||
"//third_party/angle:libGLESv2",
|
||||
|
@ -21,6 +21,9 @@ ___asan_default_options
|
||||
# Entry point from the app mode loader.
|
||||
_ChromeAppModeStart_v6
|
||||
|
||||
# Entry point for the certificate reauthorization stub.
|
||||
_DeveloperIDCertificateReauthorizeFromStub
|
||||
|
||||
# _ChromeMain must be listed last. That's the whole point of this file.
|
||||
_ChromeMain
|
||||
|
||||
|
@ -5627,6 +5627,8 @@ static_library("browser") {
|
||||
"mac/auth_session_request.mm",
|
||||
"mac/bluetooth_utility.h",
|
||||
"mac/bluetooth_utility.mm",
|
||||
"mac/developer_id_certificate_reauthorize.h",
|
||||
"mac/developer_id_certificate_reauthorize.mm",
|
||||
"mac/dock.h",
|
||||
"mac/dock.mm",
|
||||
"mac/exception_processor.h",
|
||||
@ -8009,3 +8011,33 @@ if (is_chromeos_lacros) {
|
||||
sources = [ "chromeos/policy/dlp/dlp_policy_event.proto" ]
|
||||
}
|
||||
}
|
||||
|
||||
if (is_mac) {
|
||||
executable("developer_id_certificate_reauthorize") {
|
||||
# There is no C++ in here, so save ~150kB per architecture by omitting a
|
||||
# copy of libc++.
|
||||
no_default_deps = true
|
||||
|
||||
sources = [
|
||||
"mac/developer_id_certificate_reauthorize.h",
|
||||
"mac/developer_id_certificate_reauthorize_stub_main.c",
|
||||
]
|
||||
configs += [ "//build/config/compiler:wexit_time_destructors" ]
|
||||
deps = [ "//chrome:chrome_framework+link_nested" ]
|
||||
ldflags = []
|
||||
if (is_component_build) {
|
||||
ldflags += [
|
||||
# The stub is in Product.app/Contents/Frameworks/Product
|
||||
# Framework.framework/Versions/X/Helpers so set rpath up to the base.
|
||||
"-Wl,-rpath,@loader_path/../../../../../../..",
|
||||
|
||||
# ... and up to Contents/Frameworks.
|
||||
"-Wl,-rpath,@executable_path/../../../..",
|
||||
]
|
||||
}
|
||||
if (enable_stripping) {
|
||||
ldflags += [ "-Wl,-exported_symbols_list," +
|
||||
rebase_path("../app/app.exports", root_build_dir) ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "chrome/browser/buildflags.h"
|
||||
#import "chrome/browser/chrome_browser_application_mac.h"
|
||||
#include "chrome/browser/first_run/first_run.h"
|
||||
#include "chrome/browser/mac/developer_id_certificate_reauthorize.h"
|
||||
#include "chrome/browser/mac/install_from_dmg.h"
|
||||
#import "chrome/browser/mac/keystone_glue.h"
|
||||
#include "chrome/browser/mac/mac_startup_profiler.h"
|
||||
@ -119,6 +120,8 @@ void ChromeBrowserMainPartsMac::PreCreateMainMessageLoop() {
|
||||
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), false);
|
||||
[app_controller mainMenuCreated];
|
||||
|
||||
chrome::DeveloperIDCertificateReauthorizeInApp();
|
||||
|
||||
PrefService* local_state = g_browser_process->local_state();
|
||||
DCHECK(local_state);
|
||||
|
||||
|
41
chrome/browser/extensions/api/enterprise_reporting_private/chrome_desktop_report_request_helper.cc
41
chrome/browser/extensions/api/enterprise_reporting_private/chrome_desktop_report_request_helper.cc
@ -135,31 +135,6 @@ LONG CreateRandomSecret(std::string* secret) {
|
||||
constexpr char kServiceName[] = "Endpoint Verification Safe Storage";
|
||||
constexpr char kAccountName[] = "Endpoint Verification";
|
||||
|
||||
class ScopedKeychianUserInteractionAllowed {
|
||||
public:
|
||||
explicit ScopedKeychianUserInteractionAllowed(Boolean allowed) {
|
||||
status_ = SecKeychainGetUserInteractionAllowed(&was_allowed_);
|
||||
if (status_ != noErr)
|
||||
return;
|
||||
if (was_allowed_ == allowed)
|
||||
return;
|
||||
status_ = SecKeychainSetUserInteractionAllowed(allowed);
|
||||
needs_reset_ = true;
|
||||
}
|
||||
|
||||
~ScopedKeychianUserInteractionAllowed() {
|
||||
if (needs_reset_)
|
||||
SecKeychainSetUserInteractionAllowed(was_allowed_);
|
||||
}
|
||||
|
||||
OSStatus status() { return status_; }
|
||||
|
||||
private:
|
||||
OSStatus status_;
|
||||
Boolean was_allowed_;
|
||||
bool needs_reset_ = false;
|
||||
};
|
||||
|
||||
OSStatus AddRandomPasswordToKeychain(const crypto::AppleKeychain& keychain,
|
||||
std::string* secret) {
|
||||
// Generate a password with 128 bits of randomness.
|
||||
@ -176,15 +151,19 @@ OSStatus AddRandomPasswordToKeychain(const crypto::AppleKeychain& keychain,
|
||||
}
|
||||
|
||||
OSStatus ReadEncryptedSecret(std::string* password, bool force_recreate) {
|
||||
password->clear();
|
||||
|
||||
OSStatus status;
|
||||
crypto::ScopedKeychainUserInteractionAllowed user_interaction_allowed(
|
||||
FALSE, &status);
|
||||
if (status != noErr)
|
||||
return status;
|
||||
|
||||
crypto::AppleKeychain keychain;
|
||||
UInt32 password_length = 0;
|
||||
void* password_data = nullptr;
|
||||
crypto::AppleKeychain keychain;
|
||||
password->clear();
|
||||
base::ScopedCFTypeRef<SecKeychainItemRef> item_ref;
|
||||
ScopedKeychianUserInteractionAllowed user_interaction_allowed(FALSE);
|
||||
if (user_interaction_allowed.status() != noErr)
|
||||
return user_interaction_allowed.status();
|
||||
OSStatus status = keychain.FindGenericPassword(
|
||||
status = keychain.FindGenericPassword(
|
||||
strlen(kServiceName), kServiceName, strlen(kAccountName), kAccountName,
|
||||
&password_length, &password_data, item_ref.InitializeInto());
|
||||
if (status == noErr) {
|
||||
|
41
chrome/browser/mac/developer_id_certificate_reauthorize.h
Normal file
41
chrome/browser/mac/developer_id_certificate_reauthorize.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2022 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 CHROME_BROWSER_MAC_DEVELOPER_ID_CERTIFICATE_REAUTHORIZE_H_
|
||||
#define CHROME_BROWSER_MAC_DEVELOPER_ID_CERTIFICATE_REAUTHORIZE_H_
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
||||
namespace chrome {
|
||||
|
||||
// Performs Developer ID certificate reauthorization. In branded builds, this
|
||||
// rewrites the Safe Storage item in the Keychain anew, so that its access
|
||||
// control list includes the identity of the running process taken from its
|
||||
// designated requirement. This is done so that the product can retain access to
|
||||
// the Safe Storage item through a Developer ID code signing certificate change.
|
||||
// Reauthorization is attempted a maximum of two times, and is not attempted if
|
||||
// a successful reauthorizatin already occurred. If reauthorization is to be
|
||||
// attempted and the running code has access to Safe Storage items even when
|
||||
// limited to being accessed by applications signed with the old certificate,
|
||||
// the attempt will be made in-process. Otherwise, a helper stub executable
|
||||
// signed with the old certificate will be launched to attempt reauthorization.
|
||||
void DeveloperIDCertificateReauthorizeInApp();
|
||||
|
||||
} // namespace chrome
|
||||
|
||||
extern "C" {
|
||||
|
||||
#endif // defined(__cplusplus)
|
||||
|
||||
// The developer_id_certificate_reauthorize stub executable's entry point. This
|
||||
// is nearly identical to DeveloperIDCertificateReauthorizeInApp above, except
|
||||
// no limitation is placed on the maximum number of times it may be attempted.
|
||||
__attribute__((visibility("default"))) int
|
||||
DeveloperIDCertificateReauthorizeFromStub(int argc, const char* const* argv);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} // extern "C"
|
||||
#endif // defined(__cplusplus)
|
||||
|
||||
#endif // CHROME_BROWSER_MAC_DEVELOPER_ID_CERTIFICATE_REAUTHORIZE_H_
|
298
chrome/browser/mac/developer_id_certificate_reauthorize.mm
Normal file
298
chrome/browser/mac/developer_id_certificate_reauthorize.mm
Normal file
@ -0,0 +1,298 @@
|
||||
// Copyright 2022 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 "chrome/browser/mac/developer_id_certificate_reauthorize.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <Security/Security.h>
|
||||
#include <crt_externs.h>
|
||||
#include <errno.h>
|
||||
#include <spawn.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/mac/bundle_locations.h"
|
||||
#include "base/mac/foundation_util.h"
|
||||
#include "base/mac/mac_logging.h"
|
||||
#include "base/mac/scoped_cftyperef.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/process/launch.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/scoped_generic.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "build/branding_buildflags.h"
|
||||
#include "chrome/common/chrome_version.h"
|
||||
#include "components/os_crypt/keychain_password_mac.h"
|
||||
#include "crypto/apple_keychain.h"
|
||||
|
||||
namespace chrome {
|
||||
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
|
||||
struct VectorScramblerTraits {
|
||||
static std::vector<uint8_t>* InvalidValue() { return nullptr; }
|
||||
|
||||
static void Free(std::vector<uint8_t>* buf) {
|
||||
memset(buf->data(), 0, buf->size());
|
||||
delete buf;
|
||||
}
|
||||
};
|
||||
|
||||
using ScopedVectorScrambler =
|
||||
base::ScopedGeneric<std::vector<uint8_t>*, VectorScramblerTraits>;
|
||||
|
||||
// Reauthorizes the Safe Storage keychain item, which protects the randomly
|
||||
// generated password that encrypts the user's saved passwords. This reads the
|
||||
// Keychain item, deletes it, and adds it back to the Keychain. This works
|
||||
// because the Keychain uses the code's designated requirement at the time a
|
||||
// Keychain item is written to build the ACL consulted when subsequently reading
|
||||
// it. The application is now signed with a designated requirement that
|
||||
// tolerates both old and new certificates.
|
||||
bool KeychainReauthorize() {
|
||||
// Don't allow user interaction. If the running code doesn't have access to
|
||||
// the existing Keychain item, it's pointless to prompt now just to
|
||||
// reauthorize it. Just let the OS prompt the user when the secret is actually
|
||||
// needed.
|
||||
crypto::ScopedKeychainUserInteractionAllowed
|
||||
keychain_user_interaction_allowed(FALSE);
|
||||
|
||||
const std::string& keychain_service_name = KeychainPassword::GetServiceName();
|
||||
const std::string& keychain_account_name = KeychainPassword::GetAccountName();
|
||||
|
||||
crypto::AppleKeychain keychain;
|
||||
UInt32 password_length = 0;
|
||||
void* password_data = nullptr;
|
||||
base::ScopedCFTypeRef<SecKeychainItemRef> keychain_item;
|
||||
OSStatus status = keychain.FindGenericPassword(
|
||||
keychain_service_name.size(), keychain_service_name.c_str(),
|
||||
keychain_account_name.size(), keychain_account_name.c_str(),
|
||||
&password_length, &password_data, keychain_item.InitializeInto());
|
||||
|
||||
// As in components/os_crypt/keychain_password_mac.mm, this string is
|
||||
// user-facing, but is also used as the key to access data in the Keychain, so
|
||||
// DO NOT LOCALIZE.
|
||||
const std::string backup_service_name = keychain_service_name + " Backup";
|
||||
base::ScopedCFTypeRef<SecKeychainItemRef> backup_item;
|
||||
if (status != noErr) {
|
||||
if (OSStatus backup_status = keychain.FindGenericPassword(
|
||||
backup_service_name.size(), backup_service_name.data(),
|
||||
keychain_account_name.size(), keychain_account_name.c_str(),
|
||||
&password_length, &password_data, backup_item.InitializeInto());
|
||||
backup_status != noErr) {
|
||||
// Neither the main nor backup item exists. It's not possible to continue.
|
||||
OSSTATUS_LOG(ERROR, status) << "SecKeychainFindGenericPassword (main)";
|
||||
OSSTATUS_LOG(ERROR, backup_status)
|
||||
<< "SecKeychainFindGenericPassword (backup)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// The main item was absent, but the backup was present. Proceed with it.
|
||||
OSSTATUS_LOG(WARNING, status) << "SecKeychainFindGenericPassword (main)";
|
||||
}
|
||||
|
||||
// A password was retrieved, either from the main or backup item. Store it in
|
||||
// the self-scrambling |password|, and release the copy decrypted from the
|
||||
// Keychain item.
|
||||
ScopedVectorScrambler password;
|
||||
password.reset(new std::vector<uint8_t>(
|
||||
static_cast<uint8_t*>(password_data),
|
||||
static_cast<uint8_t*>(password_data) + password_length));
|
||||
memset(password_data, 0, password_length);
|
||||
keychain.ItemFreeContent(password_data);
|
||||
|
||||
if (keychain_item) {
|
||||
// If the main item was obtained, attempt to save the backup item, as a
|
||||
// non-critical step. If it fails, continue on to reauthorizing the main
|
||||
// item anyway.
|
||||
status = keychain.AddGenericPassword(
|
||||
backup_service_name.size(), backup_service_name.data(),
|
||||
keychain_account_name.size(), keychain_account_name.c_str(),
|
||||
password.get()->size(), password.get()->data(),
|
||||
backup_item.InitializeInto());
|
||||
OSSTATUS_LOG_IF(WARNING, status != noErr, status)
|
||||
<< "SecKeychainAddGenericPassword (backup)";
|
||||
|
||||
status = keychain.ItemDelete(keychain_item);
|
||||
OSSTATUS_LOG_IF(WARNING, status != noErr, status)
|
||||
<< "SecKeychainItemDelete (main)";
|
||||
}
|
||||
|
||||
// Store a new copy of the main item, with the password that was retrieved.
|
||||
// The designated requirement associated with the currently-running code will
|
||||
// be used to form the access requirement for the new item.
|
||||
status = keychain.AddGenericPassword(
|
||||
keychain_service_name.size(), keychain_service_name.c_str(),
|
||||
keychain_account_name.size(), keychain_account_name.c_str(),
|
||||
password.get()->size(), password.get()->data(), nullptr);
|
||||
if (status != noErr) {
|
||||
OSSTATUS_LOG(ERROR, status) << "SecKeychainAddGenericPassword (main)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!backup_item) {
|
||||
// Even if no backup entry was used thus far, attempt to locate one. It may
|
||||
// be present if a previous reauthorization attempt crashed after writing
|
||||
// the backup entry and before deleting the main entry. Don't log anything
|
||||
// because the backup entry is presumed not to exist.
|
||||
keychain.FindGenericPassword(
|
||||
backup_service_name.size(), backup_service_name.data(),
|
||||
keychain_account_name.size(), keychain_account_name.c_str(), 0, nullptr,
|
||||
backup_item.InitializeInto());
|
||||
}
|
||||
|
||||
// If it exists, remove the backup item.
|
||||
if (backup_item) {
|
||||
status = keychain.ItemDelete(backup_item);
|
||||
OSSTATUS_LOG_IF(WARNING, status != noErr, status)
|
||||
<< "SecKeychainItemDelete (backup)";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NSUserDefaults* UserDefaults() {
|
||||
// This returns an NSUserDefaults that is appropriate for tracking whether
|
||||
// or not Developer ID certifiate reauthorization has occurred or been
|
||||
// attempted.
|
||||
//
|
||||
// There is just one safe storage Keychain item, not one per profile or one
|
||||
// per channel. The scoping of the preference used to track reauthorization
|
||||
// should match that of the Keychain item itself.
|
||||
//
|
||||
// If a regular preference in the Chrome profile was used here,
|
||||
// reauthorization would be tracked per profile.
|
||||
//
|
||||
// If +[NSUserDefaults standardUserDefaults] was used, it would return a
|
||||
// NSUserDefaults object for a preference domain matching the running process'
|
||||
// bundle ID (approximately equivalent to the product's channel for these
|
||||
// purposes). The reauthorization stub executable would be provided with an
|
||||
// NSUserDefaults object using a preference domain specific to itself. This
|
||||
// would result in reauthorization being tracked per channel (and stub).
|
||||
//
|
||||
// In order to get all channels and the stub executable using the same
|
||||
// preference, -[NSUserDefaults initWithSuiteName:] is used, specifying a
|
||||
// suite name matching the product's unchannelized base bundle ID. To keep
|
||||
// things interesting, this method doesn't work when the suite name provided
|
||||
// is the same as the running application's own bundle ID, as nil is returned
|
||||
// in that case. In that situation, the old standby +[NSUserDefaults
|
||||
// standardUserDefaults] sets things up to the desired preference domain as
|
||||
// standard, and all others can specify a suite name to arrive at the same
|
||||
// preference domain.
|
||||
NSString* const kPreferenceBundleID = @MAC_BUNDLE_IDENTIFIER_STRING;
|
||||
if ([[base::mac::MainBundle() bundleIdentifier]
|
||||
isEqualToString:kPreferenceBundleID]) {
|
||||
return [NSUserDefaults standardUserDefaults];
|
||||
}
|
||||
return [[[NSUserDefaults alloc] initWithSuiteName:kPreferenceBundleID]
|
||||
autorelease];
|
||||
}
|
||||
|
||||
NSString* const kPreferenceKeyBase =
|
||||
@"DeveloperIDCertificateReauthorizeWinter20212022";
|
||||
NSString* const kPreferenceKeyAttemptsSuffix = @"Attempts";
|
||||
NSString* const kPreferenceKeySuccessSuffix = @"Success";
|
||||
|
||||
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
|
||||
} // namespace
|
||||
|
||||
void DeveloperIDCertificateReauthorizeInApp() {
|
||||
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
NSUserDefaults* user_defaults = UserDefaults();
|
||||
NSString* attempts_pref_key =
|
||||
[kPreferenceKeyBase stringByAppendingString:kPreferenceKeyAttemptsSuffix];
|
||||
int attempts_pref_value = [user_defaults integerForKey:attempts_pref_key];
|
||||
|
||||
constexpr int kMaxAttempts = 2;
|
||||
if (attempts_pref_value >= kMaxAttempts) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* success_pref_key =
|
||||
[kPreferenceKeyBase stringByAppendingString:kPreferenceKeySuccessSuffix];
|
||||
BOOL success_pref_value = [user_defaults boolForKey:success_pref_key];
|
||||
if (success_pref_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
++attempts_pref_value;
|
||||
[user_defaults setInteger:attempts_pref_value forKey:attempts_pref_key];
|
||||
[user_defaults synchronize];
|
||||
|
||||
// While the main application is signed with the old certificate but with the
|
||||
// new designated requirement, kReauthorizeInApp may be true to perform the
|
||||
// reauthorization directly in-process. When the main application's code
|
||||
// signing certificate changes, set kReauthorizeInApp to false to cause
|
||||
// reauthorization to be performed by the stub executable, which is signed
|
||||
// with the old certificate and the new designated requirement, and which will
|
||||
// not be re-signed even when the application's code signing certificate
|
||||
// changes.
|
||||
constexpr bool kReauthorizeInApp = true;
|
||||
|
||||
if constexpr (kReauthorizeInApp) {
|
||||
bool success = KeychainReauthorize();
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
[user_defaults setBool:YES forKey:success_pref_key];
|
||||
[user_defaults synchronize];
|
||||
} else {
|
||||
base::FilePath reauthorize_executable_path =
|
||||
base::mac::FrameworkBundlePath().Append("Helpers").Append(
|
||||
"developer_id_certificate_reauthorize");
|
||||
std::string identifier =
|
||||
base::SysNSStringToUTF8([base::mac::OuterBundle() bundleIdentifier]);
|
||||
|
||||
std::vector<std::string> argv = {reauthorize_executable_path.value(),
|
||||
identifier};
|
||||
|
||||
// Execution must block waiting for the stub executable to avoid a race
|
||||
// between it and the application attempting to access the Keychain item.
|
||||
base::LaunchOptions launch_options;
|
||||
launch_options.wait = true;
|
||||
base::ScopedAllowBlocking allow_blocking;
|
||||
|
||||
base::LaunchProcess(argv, launch_options);
|
||||
}
|
||||
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
}
|
||||
|
||||
} // namespace chrome
|
||||
|
||||
int DeveloperIDCertificateReauthorizeFromStub(int argc,
|
||||
const char* const* argv) {
|
||||
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
@autoreleasepool {
|
||||
NSUserDefaults* user_defaults = chrome::UserDefaults();
|
||||
|
||||
NSString* success_pref_key = [chrome::kPreferenceKeyBase
|
||||
stringByAppendingString:chrome::kPreferenceKeySuccessSuffix];
|
||||
BOOL success_pref_value = [user_defaults boolForKey:success_pref_key];
|
||||
if (success_pref_value) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
bool success = chrome::KeychainReauthorize();
|
||||
if (!success) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
[user_defaults setBool:YES forKey:success_pref_key];
|
||||
[user_defaults synchronize];
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
#else
|
||||
return EXIT_FAILURE;
|
||||
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
// Copyright 2022 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 <stdlib.h>
|
||||
|
||||
#include "chrome/browser/mac/developer_id_certificate_reauthorize.h"
|
||||
|
||||
__attribute__((visibility("default"))) int main(int argc, char* argv[]) {
|
||||
exit(DeveloperIDCertificateReauthorizeFromStub(argc,
|
||||
(const char* const*)argv));
|
||||
}
|
@ -1006,8 +1006,7 @@ framework_${update_version_app_old}_${update_version_app}.dirpatch"
|
||||
exit 5
|
||||
fi
|
||||
|
||||
local new_versioned_dir
|
||||
new_versioned_dir="${installed_versions_dir}/${update_version_app}"
|
||||
local new_versioned_dir="${installed_versions_dir}/${update_version_app}"
|
||||
note "new_versioned_dir = ${new_versioned_dir}"
|
||||
|
||||
# If there's an entry at ${new_versioned_dir} but it's not a directory
|
||||
@ -1220,9 +1219,6 @@ framework_${update_version_app_old}_${update_version_app}.dirpatch"
|
||||
fi
|
||||
note "new_version_app = ${new_version_app}"
|
||||
|
||||
local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
|
||||
note "new_versioned_dir = ${new_versioned_dir}"
|
||||
|
||||
local new_ks_plist="${installed_app_plist}"
|
||||
note "new_ks_plist = ${new_ks_plist}"
|
||||
|
||||
@ -1580,6 +1576,49 @@ framework_${update_version_app_old}_${update_version_app}.dirpatch"
|
||||
|
||||
xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null
|
||||
|
||||
# Do Developer ID certificate reauthorization. This involves running a stub
|
||||
# executable within the newly-updated .app bundle. The stub loads the updated
|
||||
# framework and jumps to it to perform the reauthorization. The stub
|
||||
# executable is an unbundled (flat file) executable whose identifier for code
|
||||
# signing purposes matches the main application's bundle identifier, so it's
|
||||
# permitted access to Keychain items in the same manner as the main
|
||||
# application. The stub executable remains signed by the old certificate even
|
||||
# after the rest of Chrome has switched to the new certificate, so it
|
||||
# maintains Chrome's identity and access to the Safe Storage Keychain item
|
||||
# even if that Keychain item's access was locked to the old certificate.
|
||||
#
|
||||
# Doing a reauthorization step at update time reauthorizes Keychain items for
|
||||
# users who never bother restarting Chrome, but it only works for non-system
|
||||
# ticket installations of Chrome, because the updater runs as root when on a
|
||||
# system ticket, and root can't access individual user Keychains. Additional
|
||||
# opportunities for reauthorization exist within the application itself either
|
||||
# by performing the reauthorization directly (if permitted) or by launching
|
||||
# the same stub executable as is used here.
|
||||
#
|
||||
# Even if the reauthorization tool is launched, it doesn't necessarily try
|
||||
# to do anything. It will only attempt to perform a reauthorization if it
|
||||
# determines that no reauthorization has been completed.
|
||||
note "maybe performing Developer ID certificate reauthorization"
|
||||
|
||||
if [[ -z "${system_ticket}" ]]; then
|
||||
local developer_id_certificate_reauthorize_path="\
|
||||
${new_versioned_dir}/Helpers/developer_id_certificate_reauthorize"
|
||||
note "developer_id_certificate_reauthorize_path = \
|
||||
${developer_id_certificate_reauthorize_path}"
|
||||
|
||||
local new_bundleid_app="$(defaults read "${installed_app_plist}" \
|
||||
"${APP_BUNDLEID_KEY}" || true)"
|
||||
note "new_bundleid_app = ${new_bundleid_app}"
|
||||
|
||||
if [[ -x "${developer_id_certificate_reauthorize_path}" ]]; then
|
||||
note "performing Developer ID certificate reauthorization"
|
||||
"${developer_id_certificate_reauthorize_path}" "${new_bundleid_app}"
|
||||
fi
|
||||
else
|
||||
note "system ticket, not performing \
|
||||
Developer ID certificate reauthorization"
|
||||
fi
|
||||
|
||||
# Great success!
|
||||
note "done!"
|
||||
|
||||
|
@ -58,6 +58,15 @@ def get_parts(config):
|
||||
'chrome_crashpad_handler',
|
||||
options=CodeSignOptions.FULL_HARDENED_RUNTIME_OPTIONS,
|
||||
verify_options=verify_options),
|
||||
'developer_id_certificate_reauthorize':
|
||||
CodeSignedProduct(
|
||||
'{.framework_dir}/Helpers/developer_id_certificate_reauthorize'
|
||||
.format(config),
|
||||
uncustomized_bundle_id,
|
||||
options=CodeSignOptions.FULL_HARDENED_RUNTIME_OPTIONS,
|
||||
requirements=config.codesign_requirements_outer_app,
|
||||
sign_with_identifier=True,
|
||||
verify_options=verify_options),
|
||||
'helper-app':
|
||||
CodeSignedProduct(
|
||||
'{0.framework_dir}/Helpers/{0.product} Helper.app'.format(
|
||||
|
@ -79,7 +79,7 @@ component("crypto") {
|
||||
|
||||
if (is_mac) {
|
||||
sources += [
|
||||
"apple_keychain_mac.mm",
|
||||
"apple_keychain_mac.cc",
|
||||
|
||||
# TODO(brettw): these mocks should be moved to a test_support_crypto
|
||||
# target if possible.
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <Security/Security.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "crypto/crypto_export.h"
|
||||
|
||||
@ -35,29 +37,54 @@ class CRYPTO_EXPORT AppleKeychain {
|
||||
|
||||
virtual ~AppleKeychain();
|
||||
|
||||
virtual OSStatus FindGenericPassword(UInt32 serviceNameLength,
|
||||
const char* serviceName,
|
||||
UInt32 accountNameLength,
|
||||
const char* accountName,
|
||||
UInt32* passwordLength,
|
||||
void** passwordData,
|
||||
AppleSecKeychainItemRef* itemRef) const;
|
||||
virtual OSStatus FindGenericPassword(UInt32 service_name_length,
|
||||
const char* service_name,
|
||||
UInt32 account_name_length,
|
||||
const char* account_name,
|
||||
UInt32* password_length,
|
||||
void** password_data,
|
||||
AppleSecKeychainItemRef* item) const;
|
||||
|
||||
virtual OSStatus ItemFreeContent(void* data) const;
|
||||
|
||||
virtual OSStatus AddGenericPassword(UInt32 serviceNameLength,
|
||||
const char* serviceName,
|
||||
UInt32 accountNameLength,
|
||||
const char* accountName,
|
||||
UInt32 passwordLength,
|
||||
const void* passwordData,
|
||||
AppleSecKeychainItemRef* itemRef) const;
|
||||
virtual OSStatus AddGenericPassword(UInt32 service_name_length,
|
||||
const char* service_name,
|
||||
UInt32 account_name_length,
|
||||
const char* account_name,
|
||||
UInt32 password_length,
|
||||
const void* password_data,
|
||||
AppleSecKeychainItemRef* item) const;
|
||||
|
||||
#if !BUILDFLAG(IS_IOS)
|
||||
virtual OSStatus ItemDelete(AppleSecKeychainItemRef itemRef) const;
|
||||
#endif // !BUILDFLAG(IS_IOS)
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
virtual OSStatus ItemDelete(AppleSecKeychainItemRef item) const;
|
||||
#endif // !BUILDFLAG(IS_MAC)
|
||||
};
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
|
||||
// Sets whether Keychain Services is permitted to display UI if needed by
|
||||
// calling SecKeychainSetUserInteractionAllowed. This operates in a scoped
|
||||
// fashion: on destruction, the previous state will be restored. This is useful
|
||||
// to interact with the Keychain on a best-effort basis, without displaying any
|
||||
// Keychain Services UI (which is beyond the application's control) to the user.
|
||||
class CRYPTO_EXPORT ScopedKeychainUserInteractionAllowed {
|
||||
public:
|
||||
ScopedKeychainUserInteractionAllowed(
|
||||
const ScopedKeychainUserInteractionAllowed&) = delete;
|
||||
ScopedKeychainUserInteractionAllowed& operator=(
|
||||
const ScopedKeychainUserInteractionAllowed&) = delete;
|
||||
|
||||
explicit ScopedKeychainUserInteractionAllowed(Boolean allowed,
|
||||
OSStatus* status = nullptr);
|
||||
|
||||
~ScopedKeychainUserInteractionAllowed();
|
||||
|
||||
private:
|
||||
std::optional<Boolean> was_allowed_;
|
||||
};
|
||||
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
|
||||
} // namespace crypto
|
||||
|
||||
#endif // CRYPTO_APPLE_KEYCHAIN_H_
|
||||
|
111
crypto/apple_keychain_mac.cc
Normal file
111
crypto/apple_keychain_mac.cc
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2012 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 "crypto/apple_keychain.h"
|
||||
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "crypto/mac_security_services_lock.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Supports the pattern where a function F(T* out) allows |out| to be nullptr
|
||||
// but its implementation requires a T variable even in the absence of |out|.
|
||||
// Such a function can maintain a local OptionalOutParameter<T> to provide the
|
||||
// internal T value, assigning its value to *out on destruction if possible.
|
||||
template <typename T>
|
||||
class OptionalOutParameter {
|
||||
public:
|
||||
OptionalOutParameter(const OptionalOutParameter&) = delete;
|
||||
OptionalOutParameter& operator=(const OptionalOutParameter&) = delete;
|
||||
|
||||
OptionalOutParameter(T* out, T value = T()) : out_(out), value_(value) {}
|
||||
|
||||
~OptionalOutParameter() {
|
||||
if (out_) {
|
||||
*out_ = value_;
|
||||
}
|
||||
}
|
||||
|
||||
OptionalOutParameter& operator=(T value) {
|
||||
value_ = value;
|
||||
return *this;
|
||||
}
|
||||
operator T() const { return value_; }
|
||||
|
||||
private:
|
||||
T* const out_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace crypto {
|
||||
|
||||
AppleKeychain::AppleKeychain() = default;
|
||||
|
||||
AppleKeychain::~AppleKeychain() = default;
|
||||
|
||||
OSStatus AppleKeychain::FindGenericPassword(
|
||||
UInt32 service_name_length,
|
||||
const char* service_name,
|
||||
UInt32 account_name_length,
|
||||
const char* account_name,
|
||||
UInt32* password_length,
|
||||
void** password_data,
|
||||
AppleSecKeychainItemRef* item) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainFindGenericPassword(
|
||||
nullptr, service_name_length, service_name, account_name_length,
|
||||
account_name, password_length, password_data, item);
|
||||
}
|
||||
|
||||
OSStatus AppleKeychain::ItemFreeContent(void* data) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainItemFreeContent(nullptr, data);
|
||||
}
|
||||
|
||||
OSStatus AppleKeychain::AddGenericPassword(
|
||||
UInt32 service_name_length,
|
||||
const char* service_name,
|
||||
UInt32 account_name_length,
|
||||
const char* account_name,
|
||||
UInt32 password_length,
|
||||
const void* password_data,
|
||||
AppleSecKeychainItemRef* item) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainAddGenericPassword(
|
||||
nullptr, service_name_length, service_name, account_name_length,
|
||||
account_name, password_length, password_data, item);
|
||||
}
|
||||
|
||||
OSStatus AppleKeychain::ItemDelete(AppleSecKeychainItemRef item) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainItemDelete(item);
|
||||
}
|
||||
|
||||
ScopedKeychainUserInteractionAllowed::ScopedKeychainUserInteractionAllowed(
|
||||
Boolean allowed,
|
||||
OSStatus* status) {
|
||||
Boolean was_allowed;
|
||||
OptionalOutParameter<OSStatus> local_status(
|
||||
status, SecKeychainGetUserInteractionAllowed(&was_allowed));
|
||||
if (local_status != noErr) {
|
||||
return;
|
||||
}
|
||||
|
||||
local_status = SecKeychainSetUserInteractionAllowed(allowed);
|
||||
if (local_status != noErr) {
|
||||
return;
|
||||
}
|
||||
|
||||
was_allowed_ = was_allowed;
|
||||
}
|
||||
|
||||
ScopedKeychainUserInteractionAllowed::~ScopedKeychainUserInteractionAllowed() {
|
||||
if (was_allowed_) {
|
||||
SecKeychainSetUserInteractionAllowed(*was_allowed_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crypto
|
@ -1,56 +0,0 @@
|
||||
// Copyright (c) 2012 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 "crypto/apple_keychain.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "crypto/mac_security_services_lock.h"
|
||||
|
||||
namespace crypto {
|
||||
|
||||
AppleKeychain::AppleKeychain() {}
|
||||
|
||||
AppleKeychain::~AppleKeychain() {}
|
||||
|
||||
OSStatus AppleKeychain::ItemDelete(AppleSecKeychainItemRef itemRef) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainItemDelete(itemRef);
|
||||
}
|
||||
|
||||
OSStatus AppleKeychain::FindGenericPassword(
|
||||
UInt32 serviceNameLength,
|
||||
const char* serviceName,
|
||||
UInt32 accountNameLength,
|
||||
const char* accountName,
|
||||
UInt32* passwordLength,
|
||||
void** passwordData,
|
||||
AppleSecKeychainItemRef* itemRef) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainFindGenericPassword(nullptr, serviceNameLength, serviceName,
|
||||
accountNameLength, accountName,
|
||||
passwordLength, passwordData, itemRef);
|
||||
}
|
||||
|
||||
OSStatus AppleKeychain::ItemFreeContent(void* data) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainItemFreeContent(nullptr, data);
|
||||
}
|
||||
|
||||
OSStatus AppleKeychain::AddGenericPassword(
|
||||
UInt32 serviceNameLength,
|
||||
const char* serviceName,
|
||||
UInt32 accountNameLength,
|
||||
const char* accountName,
|
||||
UInt32 passwordLength,
|
||||
const void* passwordData,
|
||||
AppleSecKeychainItemRef* itemRef) const {
|
||||
base::AutoLock lock(GetMacSecurityServicesLock());
|
||||
return SecKeychainAddGenericPassword(nullptr, serviceNameLength, serviceName,
|
||||
accountNameLength, accountName,
|
||||
passwordLength, passwordData, itemRef);
|
||||
}
|
||||
|
||||
} // namespace crypto
|
Reference in New Issue
Block a user