From e4a0c9cd5ae1d7951c501ed1b53a24aa7de2dbfa Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 14:55:24 +0000 Subject: [PATCH] Compute and show the code signing cdhash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dependency-free SHA-1 / SHA-256 (libmoex must stay Qt-free) and use it to compute the Code Directory cdhash — the digest of the whole Code Directory blob per its hash type, truncated to the canonical 20 bytes. The cdhash is the identity used for notarization, provisioning, and TCC, so surfacing it makes the code signature view far more useful. SHA implementations are verified against standard test vectors. https://claude.ai/code/session_013kBiVXftgoEsyGVyrvfGok --- src/libmoex/base/digest/Sha.h | 128 ++++++++++++++++++ .../viewnode/views/CodeSignatureViewNode.cpp | 18 +++ 2 files changed, 146 insertions(+) create mode 100644 src/libmoex/base/digest/Sha.h diff --git a/src/libmoex/base/digest/Sha.h b/src/libmoex/base/digest/Sha.h new file mode 100644 index 0000000..ec7b1f8 --- /dev/null +++ b/src/libmoex/base/digest/Sha.h @@ -0,0 +1,128 @@ +// +// Created by everettjf +// Copyright © 2017 everettjf. All rights reserved. +// +// Small dependency-free SHA-1 / SHA-256 used to compute code-signing cdhash. +// libmoex must not depend on Qt, so we cannot use QCryptographicHash here. +#ifndef MOEX_DIGEST_SHA_H +#define MOEX_DIGEST_SHA_H + +#include +#include +#include +#include + +namespace moex { namespace digest { + +inline std::vector Sha1(const uint8_t *data, std::size_t len) { + uint32_t h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0; + const uint64_t bitlen = static_cast(len) * 8; + std::vector msg(data, data + len); + msg.push_back(0x80); + while (msg.size() % 64 != 56) msg.push_back(0x00); + for (int i = 7; i >= 0; --i) msg.push_back(static_cast(bitlen >> (i * 8))); + + auto rol = [](uint32_t x, uint32_t n) { return (x << n) | (x >> (32 - n)); }; + for (std::size_t off = 0; off < msg.size(); off += 64) { + uint32_t w[80]; + for (int i = 0; i < 16; ++i) + w[i] = (static_cast(msg[off + i * 4]) << 24) | + (static_cast(msg[off + i * 4 + 1]) << 16) | + (static_cast(msg[off + i * 4 + 2]) << 8) | + static_cast(msg[off + i * 4 + 3]); + for (int i = 16; i < 80; ++i) w[i] = rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + + uint32_t a = h0, b = h1, c = h2, d = h3, e = h4; + for (int i = 0; i < 80; ++i) { + uint32_t f, k; + if (i < 20) { f = (b & c) | ((~b) & d); k = 0x5A827999; } + else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; } + else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; } + else { f = b ^ c ^ d; k = 0xCA62C1D6; } + const uint32_t tmp = rol(a, 5) + f + e + k + w[i]; + e = d; d = c; c = rol(b, 30); b = a; a = tmp; + } + h0 += a; h1 += b; h2 += c; h3 += d; h4 += e; + } + + const uint32_t hs[5] = {h0, h1, h2, h3, h4}; + std::vector out(20); + for (int i = 0; i < 5; ++i) { + out[i * 4] = static_cast(hs[i] >> 24); + out[i * 4 + 1] = static_cast(hs[i] >> 16); + out[i * 4 + 2] = static_cast(hs[i] >> 8); + out[i * 4 + 3] = static_cast(hs[i]); + } + return out; +} + +inline std::vector Sha256(const uint8_t *data, std::size_t len) { + static const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + + const uint64_t bitlen = static_cast(len) * 8; + std::vector msg(data, data + len); + msg.push_back(0x80); + while (msg.size() % 64 != 56) msg.push_back(0x00); + for (int i = 7; i >= 0; --i) msg.push_back(static_cast(bitlen >> (i * 8))); + + auto rotr = [](uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); }; + for (std::size_t off = 0; off < msg.size(); off += 64) { + uint32_t w[64]; + for (int i = 0; i < 16; ++i) + w[i] = (static_cast(msg[off + i * 4]) << 24) | + (static_cast(msg[off + i * 4 + 1]) << 16) | + (static_cast(msg[off + i * 4 + 2]) << 8) | + static_cast(msg[off + i * 4 + 3]); + for (int i = 16; i < 64; ++i) { + const uint32_t s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + const uint32_t s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + + uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7]; + for (int i = 0; i < 64; ++i) { + const uint32_t S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25); + const uint32_t ch = (e & f) ^ ((~e) & g); + const uint32_t t1 = hh + S1 + ch + K[i] + w[i]; + const uint32_t S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22); + const uint32_t maj = (a & b) ^ (a & c) ^ (b & c); + const uint32_t t2 = S0 + maj; + hh = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2; + } + h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e; h[5] += f; h[6] += g; h[7] += hh; + } + + std::vector out(32); + for (int i = 0; i < 8; ++i) { + out[i * 4] = static_cast(h[i] >> 24); + out[i * 4 + 1] = static_cast(h[i] >> 16); + out[i * 4 + 2] = static_cast(h[i] >> 8); + out[i * 4 + 3] = static_cast(h[i]); + } + return out; +} + +inline std::string ToHex(const std::vector &d) { + static const char *hx = "0123456789abcdef"; + std::string s; + s.reserve(d.size() * 2); + for (uint8_t b : d) { + s += hx[b >> 4]; + s += hx[b & 0x0f]; + } + return s; +} + +}} // namespace moex::digest + +#endif // MOEX_DIGEST_SHA_H diff --git a/src/libmoex/viewnode/views/CodeSignatureViewNode.cpp b/src/libmoex/viewnode/views/CodeSignatureViewNode.cpp index 4b6ad2e..70d23d8 100644 --- a/src/libmoex/viewnode/views/CodeSignatureViewNode.cpp +++ b/src/libmoex/viewnode/views/CodeSignatureViewNode.cpp @@ -3,6 +3,7 @@ // #include "CodeSignatureViewNode.h" +#include "libmoex/base/digest/Sha.h" #include MOEX_NAMESPACE_BEGIN @@ -205,6 +206,23 @@ void CodeSignatureViewNode::InitViewDatas() { t->AddRow({AsAddress(base_off + cd_off), "Code Directory", fmt::format("version=0x{}", AsShortHexString(cd_version))}); + // cdhash: the digest of the whole Code Directory blob (per its hash + // type), truncated to the canonical 20 bytes. + const uint32_t cd_len = ReadBE32(cd + 4); + if (cd_len >= 8 && static_cast(cd_off) + cd_len <= datasize) { + std::vector digest; + if (hash_type == 1) { + digest = moex::digest::Sha1(cd, cd_len); + } else if (hash_type == 2 || hash_type == 3) { + digest = moex::digest::Sha256(cd, cd_len); + } + if (!digest.empty()) { + if (digest.size() > 20) digest.resize(20); + t->AddRow({AsAddress(base_off + cd_off), "CD Hash (cdhash)", + moex::digest::ToHex(digest)}); + } + } + const std::string identifier = ReadBlobCString(cd, datasize - cd_off, ident_off); if (!identifier.empty()) {