// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/apps/app_shim/code_signature_mac.h"
#include <variant>
#include "base/apple/bundle_locations.h"
#include "base/apple/foundation_util.h"
#include "base/apple/osstatus_logging.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/debug/dump_without_crashing.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/types/expected_macros.h"
#include "components/crash/core/common/crash_key.h"
namespace {
// A crash key that is used when dumping because of errors when validating code
// signatures.
crash_reporter::CrashKeyString<256> code_signature_crash_key("CodeSignature");
// This function logs the status and error_details using OSSTATUS_LOG(). It also
// calls base::debug::DumpWithoutCrashing() using code_signature_crash_key
// as a crash key. The status and error_details are appended to the crash key.
void DumpOSStatusError(OSStatus status, std::string error_details) {
OSSTATUS_LOG(ERROR, status) << error_details;
crash_reporter::ScopedCrashKeyString crash_key_value(
&code_signature_crash_key,
base::StringPrintf("%s: %s (%d)", error_details.c_str(),
logging::DescriptionFromOSStatus(status).c_str(),
status));
base::debug::DumpWithoutCrashing();
}
// This function is similar to DumpOSStatusError(), however it operates without
// an OSStatus.
void DumpError(std::string error_details) {
LOG(ERROR) << error_details;
crash_reporter::ScopedCrashKeyString crash_key_value(
&code_signature_crash_key, error_details);
base::debug::DumpWithoutCrashing();
}
} // namespace
namespace apps {
base::expected<base::apple::ScopedCFTypeRef<CFStringRef>,
MissingRequirementReason>
FrameworkBundleDesignatedRequirementString() {
// Note: Don't validate |framework_code|: We don't need to waste time
// validating. We are only interested in discovering if the framework bundle
// is code-signed, and if so what the designated requirement is.
base::apple::ScopedCFTypeRef<CFURLRef> framework_url =
base::apple::FilePathToCFURL(base::apple::FrameworkBundlePath());
base::apple::ScopedCFTypeRef<SecStaticCodeRef> framework_code;
OSStatus status = SecStaticCodeCreateWithPath(
framework_url.get(), kSecCSDefaultFlags, framework_code.InitializeInto());
// If the framework bundle is unsigned there is nothing else to do. We treat
// this as success because there’s no identity to protect or even match, so
// it’s not dangerous to let the shim connect.
if (status == errSecCSUnsigned) {
return base::unexpected(MissingRequirementReason::NoOrAdHocSignature);
}
// If there was an error obtaining the SecStaticCodeRef something is very
// broken or something bad is happening, deny.
if (status != errSecSuccess) {
DumpOSStatusError(status, "SecStaticCodeCreateWithPath");
return base::unexpected(MissingRequirementReason::Error);
}
// Copy the signing info from the SecStaticCodeRef.
base::apple::ScopedCFTypeRef<CFDictionaryRef> framework_signing_info;
status = SecCodeCopySigningInformation(
framework_code.get(), kSecCSSigningInformation,
framework_signing_info.InitializeInto());
if (status != errSecSuccess) {
DumpOSStatusError(status, "SecCodeCopySigningInformation");
return base::unexpected(MissingRequirementReason::Error);
}
// Look up the code signing flags. If the flags are absent treat this as
// unsigned. This decision is consistent with the StaticCode source:
// https://github.com/apple-oss-distributions/Security/blob/Security-60157.40.30.0.1/OSX/libsecurity_codesigning/lib/StaticCode.cpp#L2270
CFNumberRef framework_signing_info_flags =
base::apple::GetValueFromDictionary<CFNumberRef>(
framework_signing_info.get(), kSecCodeInfoFlags);
if (!framework_signing_info_flags) {
return base::unexpected(MissingRequirementReason::NoOrAdHocSignature);
}
// If the framework bundle is ad-hoc signed there is nothing else to
// do. While the framework bundle is code-signed an ad-hoc signature does not
// contain any identities to match against. Treat this as a success.
//
// Note: Using a long long to extract the value from the CFNumberRef to be
// consistent with how it was packed by Security.framework.
// https://github.com/apple-oss-distributions/Security/blob/Security-60157.40.30.0.1/OSX/libsecurity_utilities/lib/cfutilities.h#L262
long long flags;
if (!CFNumberGetValue(framework_signing_info_flags, kCFNumberLongLongType,
&flags)) {
DumpError("CFNumberGetValue");
return base::unexpected(MissingRequirementReason::Error);
}
if (static_cast<uint32_t>(flags) & kSecCodeSignatureAdhoc) {
return base::unexpected(MissingRequirementReason::NoOrAdHocSignature);
}
// Time to start building a requirement that we will use to validate
// another code signature. First let's get the framework bundle requirement.
// We will build a suitable requirement based off that.
base::apple::ScopedCFTypeRef<SecRequirementRef> framework_requirement;
status =
SecCodeCopyDesignatedRequirement(framework_code.get(), kSecCSDefaultFlags,
framework_requirement.InitializeInto());
if (status != errSecSuccess) {
DumpOSStatusError(status, "SecCodeCopyDesignatedRequirement");
return base::unexpected(MissingRequirementReason::Error);
}
base::apple::ScopedCFTypeRef<CFStringRef> framework_requirement_string;
status =
SecRequirementCopyString(framework_requirement.get(), kSecCSDefaultFlags,
framework_requirement_string.InitializeInto());
if (status != errSecSuccess) {
DumpOSStatusError(status, "SecRequirementCopyString");
DumpOSStatusError(status, "SecCodeCopyDesignatedRequirement");
}
return framework_requirement_string;
}
base::apple::ScopedCFTypeRef<SecRequirementRef> RequirementFromString(
CFStringRef requirement_string) {
base::apple::ScopedCFTypeRef<SecRequirementRef> requirement;
OSStatus status = SecRequirementCreateWithString(
requirement_string, kSecCSDefaultFlags, requirement.InitializeInto());
if (status != errSecSuccess) {
DumpOSStatusError(status,
std::string("SecRequirementCreateWithString: ") +
base::SysCFStringRefToUTF8(requirement_string));
return base::apple::ScopedCFTypeRef<SecRequirementRef>(nullptr);
}
return requirement;
}
namespace {
// Return a dictionary of attributes suitable for looking up `process` with
// `SecCodeCopyGuestWithAttributes`.
base::apple::ScopedCFTypeRef<CFDictionaryRef> AttributesForGuestValidation(
absl::variant<audit_token_t, pid_t> process,
SignatureValidationType validation_type,
std::string_view info_plist_xml) {
base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> attributes(
CFDictionaryCreateMutable(nullptr, 3, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
if (audit_token_t* token = absl::get_if<audit_token_t>(&process)) {
base::apple::ScopedCFTypeRef<CFDataRef> audit_token_cf(CFDataCreate(
nullptr, reinterpret_cast<const UInt8*>(token), sizeof(audit_token_t)));
CFDictionarySetValue(attributes.get(), kSecGuestAttributeAudit,
audit_token_cf.get());
} else {
CHECK(absl::holds_alternative<pid_t>(process));
base::apple::ScopedCFTypeRef<CFNumberRef> pid_cf(
CFNumberCreate(nullptr, kCFNumberIntType, &absl::get<pid_t>(process)));
CFDictionarySetValue(attributes.get(), kSecGuestAttributePid, pid_cf.get());
}
if (validation_type == SignatureValidationType::DynamicOnly) {
base::apple::ScopedCFTypeRef<CFDataRef> info_plist(CFDataCreate(
nullptr, reinterpret_cast<const UInt8*>(info_plist_xml.data()),
info_plist_xml.length()));
CFDictionarySetValue(attributes.get(), kSecGuestAttributeDynamicCode,
kCFBooleanTrue);
CFDictionarySetValue(attributes.get(),
kSecGuestAttributeDynamicCodeInfoPlist,
info_plist.get());
}
return attributes;
}
} // namespace
// Verify the code signature of |audit_token| against |requirement|.
OSStatus ProcessIsSignedAndFulfillsRequirement(
audit_token_t audit_token,
SecRequirementRef requirement,
SignatureValidationType validation_type,
std::string_view info_plist_xml) {
base::apple::ScopedCFTypeRef<SecCodeRef> code;
base::apple::ScopedCFTypeRef<CFDictionaryRef> attributes =
AttributesForGuestValidation(audit_token, validation_type,
info_plist_xml);
OSStatus status = SecCodeCopyGuestWithAttributes(
nullptr, attributes.get(), kSecCSDefaultFlags, code.InitializeInto());
if (status != errSecSuccess) {
DumpOSStatusError(status, "SecCodeCopyGuestWithAttributes");
return status;
}
return SecCodeCheckValidity(code.get(), kSecCSDefaultFlags, requirement);
}
// Verify the code signature of |pid| against |requirement|.
OSStatus ProcessIsSignedAndFulfillsRequirement(
pid_t pid,
SecRequirementRef requirement,
SignatureValidationType validation_type,
std::string_view info_plist_xml) {
base::apple::ScopedCFTypeRef<SecCodeRef> code;
base::apple::ScopedCFTypeRef<CFDictionaryRef> attributes =
AttributesForGuestValidation(pid, validation_type, info_plist_xml);
OSStatus status = SecCodeCopyGuestWithAttributes(
nullptr, attributes.get(), kSecCSDefaultFlags, code.InitializeInto());
if (status != errSecSuccess) {
DumpOSStatusError(status, "SecCodeCopyGuestWithAttributes");
return status;
}
return SecCodeCheckValidity(code.get(), kSecCSDefaultFlags, requirement);
}
} // namespace apps