chromium/components/device_signals/core/system_signals/win/win_platform_delegate.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/device_signals/core/system_signals/win/win_platform_delegate.h"

// clang-format off
#include <windows.h> // Must be in front of other Windows header files.
// clang-format on

#include <softpub.h>
#include <wincrypt.h>
#include <wintrust.h>

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "components/device_signals/core/common/common_types.h"
#include "components/device_signals/core/common/platform_utils.h"
#include "crypto/scoped_capi_types.h"
#include "crypto/sha2.h"
#include "net/cert/asn1_util.h"

namespace device_signals {

namespace {

// Returns the SHA-256 hash for the DER-encoded SPKI and subject from the first
// signer cert chain's leaf cert. Return std::nullopt if unable to get to that
// certificate.
std::pair<std::optional<std::string>, std::optional<std::string>> GetSPKIHash(
    HANDLE verify_trust_state_data) {
  std::pair<std::optional<std::string>, std::optional<std::string>> ret;

  CRYPT_PROVIDER_DATA* crypt_provider_data =
      WTHelperProvDataFromStateData(verify_trust_state_data);
  if (!crypt_provider_data) {
    return ret;
  }

  CRYPT_PROVIDER_SGNR* provider_sgnr =
      WTHelperGetProvSignerFromChain(crypt_provider_data,
                                     /*idxSigner=*/0,
                                     /*fCounterSigner=*/false,
                                     /*idxCounterSigner=*/0);
  if (!provider_sgnr) {
    return ret;
  }

  CRYPT_PROVIDER_CERT* provider_cert =
      WTHelperGetProvCertFromChain(provider_sgnr, /*idcCert=*/0);

  if (!provider_cert || !provider_cert->pChainElement) {
    return ret;
  }

  const CERT_CHAIN_ELEMENT* element = provider_cert->pChainElement;
  const CERT_CONTEXT* cert_context = element->pCertContext;
  if (!cert_context) {
    return ret;
  }

  // Get the hash and subject.
  if (cert_context->pbCertEncoded) {
    std::string_view der_bytes(
        reinterpret_cast<const char*>(cert_context->pbCertEncoded),
        cert_context->cbCertEncoded);

    std::string_view spki;
    if (net::asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) {
      ret.first = crypto::SHA256HashString(spki);
    }

    // Get the subject. First ask how long the name is, including null
    // terminator.
    size_t length = CertGetNameStringA(
        cert_context, CERT_NAME_SIMPLE_DISPLAY_TYPE, /*dwFlags=*/0,
        /*pvTypePara=*/nullptr, /*pszNameString=*/nullptr, /*cchNameString=*/0);
    if (length > 0) {
      std::vector<char> subject(length);
      CertGetNameStringA(
          cert_context, CERT_NAME_SIMPLE_DISPLAY_TYPE, /*dwFlags=*/0,
          /*pvTypePara=*/nullptr, /*pszNameString=*/subject.data(),
          /*cchNameString=*/subject.size());
      ret.second = subject.data();
    }
  }

  return ret;
}

}  // namespace

WinPlatformDelegate::WinPlatformDelegate() = default;

WinPlatformDelegate::~WinPlatformDelegate() = default;

bool WinPlatformDelegate::ResolveFilePath(const base::FilePath& file_path,
                                          base::FilePath* resolved_file_path) {
  return ResolvePath(file_path, resolved_file_path);
}

std::optional<PlatformDelegate::SigningCertificatesPublicKeys>
WinPlatformDelegate::GetSigningCertificatesPublicKeys(
    const base::FilePath& file_path) {
  SigningCertificatesPublicKeys public_keys;

  WINTRUST_FILE_INFO file_info{};
  file_info.cbStruct = sizeof(file_info);
  file_info.pcwszFilePath = file_path.value().c_str();
  file_info.hFile = NULL;
  file_info.pgKnownSubject = NULL;

  WINTRUST_DATA wintrust_data{};
  wintrust_data.cbStruct = sizeof(wintrust_data);
  wintrust_data.pPolicyCallbackData = NULL;
  wintrust_data.pSIPClientData = NULL;
  wintrust_data.dwUIChoice = WTD_UI_NONE;
  wintrust_data.fdwRevocationChecks = WTD_REVOKE_NONE;
  wintrust_data.dwUnionChoice = WTD_CHOICE_FILE;
  wintrust_data.pFile = &file_info;
  wintrust_data.dwStateAction = WTD_STATEACTION_VERIFY;
  wintrust_data.hWVTStateData = NULL;
  wintrust_data.pwszURLReference = NULL;
  // Disallow revocation checks over the network.
  wintrust_data.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;

  // Check first signature first while getting the secondary signatures'
  // count, iterate over those after.
  WINTRUST_SIGNATURE_SETTINGS signature_settings{};
  signature_settings.cbStruct = sizeof(signature_settings);
  signature_settings.dwIndex = 0;
  signature_settings.dwFlags =
      WSS_VERIFY_SPECIFIC | WSS_GET_SECONDARY_SIG_COUNT;

  wintrust_data.pSignatureSettings = &signature_settings;

  GUID policy_guid = WINTRUST_ACTION_GENERIC_VERIFY_V2;

  LONG trust = WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE),
                              &policy_guid, &wintrust_data);
  auto primary_hash_subject = GetSPKIHash(wintrust_data.hWVTStateData);
  if (!primary_hash_subject.first) {
    // No values could be extracted for the primary signature, return early.
    return public_keys;
  }

  public_keys.is_os_verified = trust == 0;
  public_keys.hashes.push_back(primary_hash_subject.first.value());

  if (primary_hash_subject.second) {
    public_keys.subject_name = primary_hash_subject.second.value();
  }

  // Collect SPKI hashes for secondary signatures' certs.
  DWORD secondary_signatures_count =
      wintrust_data.pSignatureSettings->cSecondarySigs;
  wintrust_data.pSignatureSettings->dwFlags = WSS_VERIFY_SPECIFIC;
  for (DWORD i = 0; i < secondary_signatures_count; ++i) {
    // Free the previous provider data.
    wintrust_data.dwStateAction = WTD_STATEACTION_CLOSE;
    WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &policy_guid,
                   &wintrust_data);
    wintrust_data.hWVTStateData = NULL;

    // Secondary signatures start at index 1 as 0 is the primary signature.
    wintrust_data.dwStateAction = WTD_STATEACTION_VERIFY;
    wintrust_data.pSignatureSettings->dwIndex = i + 1;
    WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &policy_guid,
                   &wintrust_data);

    auto secondary_hash_subject = GetSPKIHash(wintrust_data.hWVTStateData);
    if (secondary_hash_subject.first) {
      public_keys.hashes.push_back(secondary_hash_subject.first.value());
    }
  }

  // Free the previous provider data.
  wintrust_data.dwStateAction = WTD_STATEACTION_CLOSE;
  WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &policy_guid,
                 &wintrust_data);

  return public_keys;
}

}  // namespace device_signals