CVE-2023-40012 (C++, published 2023-08-09T16:15Z)
Description: value: uthenticode is a small cross-platform library for partially verifying Authenticode digital signatures. Versions of uthenticode prior to the 2.x series did not check Extended Key Usages in certificates, in violation of the Authenticode X.509 certificate profile. As a result, a malicious user could produce a "signed" PE file that uthenticode would verify and consider valid using an X.509 certificate that isn't entitled to produce code signatures (e.g., a SSL certificate). By design, uthenticode does not perform full-chain validation. However, the absence of EKU validation was an unintended oversight. The 2.0.0 release series includes EKU checks. There are no workarounds to this vulnerability.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-347 \u2014 Improper Verification of Cryptographic Signature
The product does not verify, or incorrectly verifies, the cryptographic signature for data.
Repository: https://github.com/trailofbits/uthenticode
Files changed:
README.md
src/CMakeLists.txt
src/uthenticode.cpp
test/helpers.h
test/signeddata-test.cpp
Commit message: src: bump OpenSSL, EKU check (#78)

* src: bump OpenSSL, EKU check

Signed-off-by: William Woodruff <william@trailofbits.com>

* README: bump OpenSSL

Signed-off-by: William Woodruff <william@trailofbits.com>

* test: EKU test, testcase

Signed-off-by: William Woodruff <william@trailofbits.com>

---------

Signed-off-by: William Woodruff <william@trailofbits.com>

Vulnerable code:
and OpenSSL 1.1.0, which are installed via `vcpkg` by following these steps:
  OpenSSL 1.1

Fixed code:
and OpenSSL 3.0 or higher, which are installed via `vcpkg` by following these steps:
  OpenSSL 3.0
#include <openssl/x509v3.h>
  auto *signers_stack_ptr = PKCS7_get0_signers(p7_, nullptr, 0);
  if (signers_stack_ptr == nullptr) {
    return false;
  }
  auto signers_stack = impl::STACK_OF_X509_ptr(signers_stack_ptr, impl::SK_X509_free);

  /* NOTE(ww): Authenticode specification, page 13: the signer must have the
   * codeSigning EKU, **or** no member of the signer's chain may have it.
   *
   * The check below is more strict than that: **every** signer must have
   * the codeSigning EKU, and we don't check the embedded chain (since
   * we can't do full chain verification anyways).
   */
  for (auto i = 0; i < sk_X509_num(signers_stack.get()); ++i) {
    auto *signer = sk_X509_value(signers_stack.get(), i);

    /* NOTE(ww): Ths should really be X509_check_purpose with
     * X509_PURPOSE_CODE_SIGN, but this is inexplicably not present
     * in even the latest releases of OpenSSL as of 2023-05.
     */
    auto xku_flags = X509_get_extended_key_usage(signer);
    if (!(xku_flags & XKU_CODE_SIGN)) {
      return false;
    }
  }


class MissingEKUTest : public ::testing::Test {
 protected:
  void SetUp() override {
    auto *file = UTHENTICODE_TEST_ASSETS "/A_SSLippery_Slope.exe";

    pe = peparse::ParsePEFromFile(file);
    ASSERT_TRUE(pe != nullptr);
  }

  void TearDown() override {
    peparse::DestructParsedPE(pe);
  }

  peparse::parsed_pe *pe{nullptr};
};

TEST_F(MissingEKUTest, SignedData_missing_codesigning_EKU) {
  auto certs = uthenticode::read_certs(pe);
  auto signed_data = certs[0].as_signed_data();

  ASSERT_FALSE(signed_data->verify_signature());
}