// 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 "base/mac/code_signature.h"

#include <variant>

#include "base/apple/osstatus_logging.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/mac/info_plist_data.h"
#include "base/strings/string_view_util.h"
#include "base/strings/sys_string_conversions.h"

using base::apple::ScopedCFTypeRef;

namespace base::mac {

namespace {

// Return a dictionary of attributes suitable for looking up `process` with
// `SecCodeCopyGuestWithAttributes`.
ScopedCFTypeRef<CFDictionaryRef> AttributesForGuestValidation(
    std::variant<audit_token_t, pid_t> process,
    SignatureValidationType validation_type,
    std::string_view info_plist_xml) {
  ScopedCFTypeRef<CFMutableDictionaryRef> attributes(
      CFDictionaryCreateMutable(nullptr, 3, &kCFTypeDictionaryKeyCallBacks,
                                &kCFTypeDictionaryValueCallBacks));

  if (audit_token_t* token = std::get_if<audit_token_t>(&process)) {
    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(std::holds_alternative<pid_t>(process));
    ScopedCFTypeRef<CFNumberRef> pid_cf(
        CFNumberCreate(nullptr, kCFNumberIntType, &std::get<pid_t>(process)));
    CFDictionarySetValue(attributes.get(), kSecGuestAttributePid, pid_cf.get());
  }

  if (validation_type == SignatureValidationType::DynamicOnly) {
    ScopedCFTypeRef<CFDataRef> info_plist(CFDataCreate(
        nullptr, reinterpret_cast<const UInt8*>(info_plist_xml.data()),
        static_cast<CFIndex>(info_plist_xml.length())));

    CFDictionarySetValue(attributes.get(), kSecGuestAttributeDynamicCode,
                         kCFBooleanTrue);
    CFDictionarySetValue(attributes.get(),
                         kSecGuestAttributeDynamicCodeInfoPlist,
                         info_plist.get());
  }

  return attributes;
}

OSStatus ValidateGuestWithAttributes(CFDictionaryRef attributes,
                                     SecRequirementRef requirement) {
  ScopedCFTypeRef<SecCodeRef> code;
  OSStatus status = SecCodeCopyGuestWithAttributes(
      nullptr, attributes, kSecCSDefaultFlags, code.InitializeInto());
  if (status != errSecSuccess) {
    OSSTATUS_LOG(ERROR, status) << "SecCodeCopyGuestWithAttributes";
    return status;
  }
  return SecCodeCheckValidity(code.get(), kSecCSDefaultFlags, requirement);
}

}  // namespace

OSStatus ProcessIsSignedAndFulfillsRequirement(
    audit_token_t audit_token,
    SecRequirementRef requirement,
    SignatureValidationType validation_type,
    std::string_view info_plist_xml) {
  ScopedCFTypeRef<CFDictionaryRef> attributes = AttributesForGuestValidation(
      audit_token, validation_type, info_plist_xml);
  return ValidateGuestWithAttributes(attributes.get(), requirement);
}

OSStatus ProcessIdIsSignedAndFulfillsRequirement_DoNotUse(
    pid_t pid,
    SecRequirementRef requirement,
    SignatureValidationType validation_type,
    std::string_view info_plist_xml) {
  ScopedCFTypeRef<CFDictionaryRef> attributes =
      AttributesForGuestValidation(pid, validation_type, info_plist_xml);
  return ValidateGuestWithAttributes(attributes.get(), requirement);
}

ScopedCFTypeRef<SecRequirementRef> RequirementFromString(
    std::string_view requirement_string) {
  ScopedCFTypeRef<CFStringRef> requirement_string_cf(
      base::SysUTF8ToCFStringRef(requirement_string));
  ScopedCFTypeRef<SecRequirementRef> requirement;
  OSStatus status = SecRequirementCreateWithString(
      requirement_string_cf.get(), kSecCSDefaultFlags,
      requirement.InitializeInto());
  if (status != errSecSuccess) {
    OSSTATUS_LOG(ERROR, status)
        << "SecRequirementCreateWithString: " << requirement_string;
    return base::apple::ScopedCFTypeRef<SecRequirementRef>(nullptr);
  }

  return requirement;
}

base::expected<ScopedCFTypeRef<SecCodeRef>, OSStatus>
DynamicCodeObjectForCurrentProcess() {
  std::vector<uint8_t> info_plist_xml = OuterBundleCachedInfoPlistData();
  ScopedCFTypeRef<CFDictionaryRef> attributes = AttributesForGuestValidation(
      getpid(), SignatureValidationType::DynamicOnly,
      base::as_string_view(info_plist_xml));

  ScopedCFTypeRef<SecCodeRef> code;
  OSStatus status = SecCodeCopyGuestWithAttributes(
      nullptr, attributes.get(), kSecCSDefaultFlags, code.InitializeInto());
  if (status != errSecSuccess) {
    OSSTATUS_LOG(ERROR, status) << "SecCodeCopyGuestWithAttributes";
    return base::unexpected(status);
  }

  return code;
}

}  // namespace base::mac
