Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile.cbm
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ FOUNDATION_SRCS = \
src/foundation/profile.c \
src/foundation/dump_verify.c \
src/foundation/limits.c \
src/foundation/subprocess.c
src/foundation/subprocess.c \
src/foundation/sha256.c

# Existing extraction C code (compiled from current location)
EXTRACTION_SRCS = \
Expand Down
52 changes: 28 additions & 24 deletions src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "foundation/compat.h"
#include "foundation/platform.h"
#include "foundation/constants.h"
#include "foundation/sha256.h"
#include "mcp/mcp.h" // cbm_mcp_tool_input_schema — CLI flag parser + per-tool --help

/* CLI buffer size constants. */
Expand Down Expand Up @@ -2960,44 +2961,47 @@ static bool prompt_yn(const char *question) {
return (buf[0] == 'y' || buf[0] == 'Y') ? true : false;
}

/* ── SHA-CBM_SZ_256 checksum verification ─────────────────────────────── */
/* ── SHA-256 checksum verification ─────────────────────────────── */

/* SHA-CBM_SZ_256 hex digest: CBM_SZ_64 hex chars + NUL */
/* SHA-256 hex digest: 64 hex chars + NUL */
#define SHA256_HEX_LEN CBM_SZ_64
#define SHA256_BUF_SIZE (SHA256_HEX_LEN + CLI_SKIP_ONE)
/* Minimum line length in checksums.txt: CBM_SZ_64 hex + 2 spaces + 1 char filename */
/* Minimum line length in checksums.txt: 64 hex + 2 spaces + 1 char filename */
#define CHECKSUM_LINE_MIN (SHA256_HEX_LEN + 2)

/* Compute the SHA-256 of a file using platform tools (sha256sum/shasum).
* Writes a 64-char hex digest + NUL to out. Returns 0 on success. Not static:
/* Compute the SHA-256 of a file in-process (no external hashing tool — those
* differ per OS, may be absent, and mis-quote paths under cmd.exe). Writes a
* 64-char hex digest + NUL to out. Returns 0 on success. Not static:
* exercised directly by the self-update checksum regression test. */
int cbm_cli_sha256_file(const char *path, char *out, size_t out_size) {
if (out_size < SHA256_BUF_SIZE) {
return CLI_ERR;
}
char cmd[CLI_BUF_1K];
#ifdef __APPLE__
snprintf(cmd, sizeof(cmd), "shasum -a 256 '%s' 2>/dev/null", path);
#else
snprintf(cmd, sizeof(cmd), "sha256sum '%s' 2>/dev/null", path);
#endif
FILE *fp = cbm_popen(cmd, "r");
FILE *fp = cbm_fopen(path, "rb");
if (!fp) {
return CLI_ERR;
}
char line[CLI_BUF_256];
if (fgets(line, sizeof(line), fp)) {
/* Output format: <CBM_SZ_64-char hash> <filename> */
char *space = strchr(line, ' ');
if (space && space - line == SHA256_HEX_LEN) {
memcpy(out, line, SHA256_HEX_LEN);
out[SHA256_HEX_LEN] = '\0';
cbm_pclose(fp);
return 0;
}
cbm_sha256_ctx ctx;
cbm_sha256_init(&ctx);
unsigned char buf[CLI_BUF_1K];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
cbm_sha256_update(&ctx, buf, n);
}
cbm_pclose(fp);
return CLI_ERR;
int read_err = ferror(fp);
fclose(fp);
if (read_err) {
return CLI_ERR;
}
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_final(&ctx, digest);
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < CBM_SHA256_DIGEST_LEN; i++) {
out[i * 2] = hex[digest[i] >> 4];
out[i * 2 + 1] = hex[digest[i] & 0x0f];
}
out[SHA256_HEX_LEN] = '\0';
return 0;
}

/* ── Download helper (shell-free curl via exec) ───────────────── */
Expand Down
129 changes: 129 additions & 0 deletions src/foundation/sha256.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/* SHA-256 per FIPS 180-4. Straightforward reference implementation; validated
* against the NIST test vectors in tests/test_cli.c. */

#include "foundation/sha256.h"

#include <string.h>

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};

#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
#define EP1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
#define SIG0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ ((x) >> 3))
#define SIG1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ ((x) >> 10))

static void sha256_transform(cbm_sha256_ctx *c, const uint8_t *data) {
uint32_t m[64];
for (int i = 0, j = 0; i < 16; i++, j += 4) {
m[i] = ((uint32_t)data[j] << 24) | ((uint32_t)data[j + 1] << 16) |
((uint32_t)data[j + 2] << 8) | (uint32_t)data[j + 3];
}
for (int i = 16; i < 64; i++) {
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
}

uint32_t a = c->state[0], b = c->state[1], cc = c->state[2], d = c->state[3];
uint32_t e = c->state[4], f = c->state[5], g = c->state[6], h = c->state[7];

for (int i = 0; i < 64; i++) {
uint32_t t1 = h + EP1(e) + CH(e, f, g) + K[i] + m[i];
uint32_t t2 = EP0(a) + MAJ(a, b, cc);
h = g;
g = f;
f = e;
e = d + t1;
d = cc;
cc = b;
b = a;
a = t1 + t2;
}

c->state[0] += a;
c->state[1] += b;
c->state[2] += cc;
c->state[3] += d;
c->state[4] += e;
c->state[5] += f;
c->state[6] += g;
c->state[7] += h;
}

void cbm_sha256_init(cbm_sha256_ctx *c) {
c->bitlen = 0;
c->buflen = 0;
c->state[0] = 0x6a09e667;
c->state[1] = 0xbb67ae85;
c->state[2] = 0x3c6ef372;
c->state[3] = 0xa54ff53a;
c->state[4] = 0x510e527f;
c->state[5] = 0x9b05688c;
c->state[6] = 0x1f83d9ab;
c->state[7] = 0x5be0cd19;
}

void cbm_sha256_update(cbm_sha256_ctx *c, const void *data, size_t len) {
const uint8_t *p = (const uint8_t *)data;
for (size_t i = 0; i < len; i++) {
c->buf[c->buflen++] = p[i];
if (c->buflen == 64) {
sha256_transform(c, c->buf);
c->bitlen += 512;
c->buflen = 0;
}
}
}

void cbm_sha256_final(cbm_sha256_ctx *c, uint8_t out[CBM_SHA256_DIGEST_LEN]) {
c->bitlen += (uint64_t)c->buflen * 8;

size_t i = c->buflen;
c->buf[i++] = 0x80; /* append the '1' bit + zero padding */
if (i > 56) {
while (i < 64) {
c->buf[i++] = 0;
}
sha256_transform(c, c->buf);
i = 0;
}
while (i < 56) {
c->buf[i++] = 0;
}
/* append the 64-bit big-endian message length */
for (int j = 0; j < 8; j++) {
c->buf[56 + j] = (uint8_t)(c->bitlen >> (56 - 8 * j));
}
sha256_transform(c, c->buf);

for (int j = 0; j < 8; j++) {
out[j * 4] = (uint8_t)(c->state[j] >> 24);
out[j * 4 + 1] = (uint8_t)(c->state[j] >> 16);
out[j * 4 + 2] = (uint8_t)(c->state[j] >> 8);
out[j * 4 + 3] = (uint8_t)(c->state[j]);
}
}

void cbm_sha256_hex(const void *data, size_t len, char out[CBM_SHA256_HEX_LEN + 1]) {
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_ctx c;
cbm_sha256_init(&c);
cbm_sha256_update(&c, data, len);
cbm_sha256_final(&c, digest);

static const char hex[] = "0123456789abcdef";
for (int i = 0; i < CBM_SHA256_DIGEST_LEN; i++) {
out[i * 2] = hex[digest[i] >> 4];
out[i * 2 + 1] = hex[digest[i] & 0x0f];
}
out[CBM_SHA256_HEX_LEN] = '\0';
}
30 changes: 30 additions & 0 deletions src/foundation/sha256.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef CBM_SHA256_H
#define CBM_SHA256_H

/* In-process SHA-256 (FIPS 180-4). Used to verify the integrity of a
* downloaded release before installing it, without shelling out to a
* platform hashing tool (shasum / sha256sum / certutil) — those differ per
* OS, may be absent, and mis-quote paths under cmd.exe. */

#include <stddef.h>
#include <stdint.h>

#define CBM_SHA256_DIGEST_LEN 32 /* raw digest bytes */
#define CBM_SHA256_HEX_LEN 64 /* lowercase hex chars (no NUL) */

typedef struct {
uint32_t state[8];
uint64_t bitlen;
uint8_t buf[64];
size_t buflen;
} cbm_sha256_ctx;

void cbm_sha256_init(cbm_sha256_ctx *c);
void cbm_sha256_update(cbm_sha256_ctx *c, const void *data, size_t len);
void cbm_sha256_final(cbm_sha256_ctx *c, uint8_t out[CBM_SHA256_DIGEST_LEN]);

/* One-shot hash of a buffer to lowercase hex. `out` must hold
* CBM_SHA256_HEX_LEN + 1 bytes (hex chars + NUL). */
void cbm_sha256_hex(const void *data, size_t len, char out[CBM_SHA256_HEX_LEN + 1]);

#endif /* CBM_SHA256_H */
31 changes: 25 additions & 6 deletions tests/test_cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -3018,21 +3018,40 @@ TEST(cli_print_tool_help_issue680) {
* Guard the digest itself against a known vector. */
extern int cbm_cli_sha256_file(const char *path, char *out, size_t out_size);

TEST(cli_sha256_file_matches_known_vector) {
/* Hash `content` (len bytes) via a temp file and compare to expected hex.
* Returns 1 on match, 0 otherwise. */
static int sha256_vector_ok(const void *content, size_t len, const char *expected) {
char path[512];
snprintf(path, sizeof(path), "%s/cbm_sha_XXXXXX", cbm_tmpdir());
int fd = cbm_mkstemp(path);
ASSERT_TRUE(fd >= 0);
if (fd < 0) {
return 0;
}
FILE *fp = fdopen(fd, "wb");
ASSERT_NOT_NULL(fp);
fwrite("abc", 1, 3, fp); /* sha256("abc") is a NIST test vector */
if (!fp) {
return 0;
}
if (len > 0) {
fwrite(content, 1, len, fp);
}
fclose(fp);

char digest[128] = {0};
int rc = cbm_cli_sha256_file(path, digest, sizeof(digest));
remove(path);
ASSERT_EQ(rc, 0);
ASSERT_STR_EQ(digest, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
return rc == 0 && strcmp(digest, expected) == 0;
}

/* NIST FIPS 180-4 SHA-256 test vectors: empty input, a single block ("abc"),
* and a 56-byte input that forces the length padding into a second block. */
TEST(cli_sha256_file_matches_known_vector) {
ASSERT_TRUE(sha256_vector_ok(
"", 0, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"));
ASSERT_TRUE(sha256_vector_ok(
"abc", 3, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"));
ASSERT_TRUE(sha256_vector_ok(
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56,
"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"));
PASS();
}

Expand Down
Loading