File Coverage

hashx.c
Criterion Covered Total %
statement 206 219 94.0
branch 85 120 70.8
condition n/a
subroutine n/a
pod n/a
total 291 339 85.8


line stmt bran cond sub pod time code
1             /*
2             * hashx.c - Algorithm dispatch + multi-algo runner.
3             *
4             * Renamed from hash.c so the resulting hashx.o doesn't collide with
5             * Hash.o (the XS unit) on case-insensitive filesystems.
6             */
7              
8             #include "hashx.h"
9             #include
10             #include
11             #include
12              
13             /* ---------------- algorithm registry ---------------- */
14              
15             static const hash_algo_info_t HASH_ALGOS[HA_COUNT] = {
16             { "sha256", HA_SHA256, SHA256_DIGEST_SIZE, 1, SHA256_BLOCK_SIZE },
17             { "sha512", HA_SHA512, SHA512_DIGEST_SIZE, 1, SHA512_BLOCK_SIZE },
18             { "sha1", HA_SHA1, SHA1_DIGEST_SIZE, 1, SHA1_BLOCK_SIZE },
19             { "md5", HA_MD5, MD5_DIGEST_SIZE, 1, MD5_BLOCK_SIZE },
20             { "crc32", HA_CRC32, CRC32_DIGEST_SIZE, 0, 0 },
21             /* xxh64 has its own seed mechanism; HMAC is not defined for it. */
22             { "xxh64", HA_XXH64, XXH64_DIGEST_SIZE, 0, 0 },
23             /* BLAKE3 has built-in keyed-hash mode (not HMAC). v0.01 exposes
24             * unkeyed mode only; HMAC-BLAKE3 is intentionally rejected. */
25             { "blake3", HA_BLAKE3, BLAKE3_DIGEST_SIZE, 0, 0 }
26             };
27              
28             const hash_algo_info_t *
29 134           hash_algo_lookup(const char *name, size_t name_len)
30             {
31             /* 32 covers every alias we accept (sha-256, blake3-keyed, etc.). */
32             char nm[32];
33 134           size_t k, j = 0;
34             int i;
35              
36 134 50         if (name_len == 0 || name_len >= sizeof nm) return NULL;
    50          
37              
38             /* Lowercase + strip dashes/underscores so "SHA-256" / "sha_256"
39             * resolve the same as "sha256". */
40 855 100         for (k = 0; k < name_len; k++) {
41 721           unsigned char c = (unsigned char)name[k];
42 721 100         if (c == '-' || c == '_') continue;
    100          
43 718           nm[j++] = (char)tolower(c);
44             }
45 134           nm[j] = '\0';
46              
47 392 100         for (i = 0; i < HA_COUNT; i++) {
48 391 100         if (strcmp(nm, HASH_ALGOS[i].name) == 0)
49 133           return &HASH_ALGOS[i];
50             }
51 1           return NULL;
52             }
53              
54             const hash_algo_info_t *
55 272           hash_algo_by_id(hash_algo_id_t id)
56             {
57 272 50         if ((int)id < 0 || (int)id >= HA_COUNT) return NULL;
    50          
58 272           return &HASH_ALGOS[id];
59             }
60              
61             /* ---------------- format parser ---------------- */
62              
63             int
64 9           hash_format_parse(const char *name, size_t name_len, hash_format_t *out)
65             {
66             /* "hex" lower vs "HEX" upper is case-sensitive (the case is the
67             * signal). Other names match case-insensitively. */
68 9 100         if (name_len == 3 && memcmp(name, "hex", 3) == 0) {
    100          
69 1           *out = HF_HEX; return 0;
70             }
71 8 100         if (name_len == 3 && memcmp(name, "HEX", 3) == 0) {
    100          
72 1           *out = HF_HEX_UPPER; return 0;
73             }
74 7 100         if (name_len == 6 && (memcmp(name, "base64", 6) == 0
    50          
75 0 0         || memcmp(name, "BASE64", 6) == 0)) {
76 2           *out = HF_BASE64; return 0;
77             }
78 5 100         if (name_len == 9 && (memcmp(name, "base64url", 9) == 0
    50          
79 0 0         || memcmp(name, "BASE64URL", 9) == 0)) {
80 1           *out = HF_BASE64URL; return 0;
81             }
82 4 50         if (name_len == 3 && (memcmp(name, "raw", 3) == 0
    100          
83 1 50         || memcmp(name, "RAW", 3) == 0)) {
84 3           *out = HF_RAW; return 0;
85             }
86 1           return -1;
87             }
88              
89             /* ---------------- formatting helpers ---------------- */
90              
91             static void
92 109           to_hex(const unsigned char *in, size_t n, char *out, int upper)
93             {
94             static const char *lo = "0123456789abcdef";
95             static const char *hi = "0123456789ABCDEF";
96 109 100         const char *t = upper ? hi : lo;
97             size_t i;
98 2929 100         for (i = 0; i < n; i++) {
99 2820           out[i*2] = t[(in[i] >> 4) & 0xF];
100 2820           out[i*2+1] = t[ in[i] & 0xF];
101             }
102 109           out[n*2] = '\0';
103 109           }
104              
105             static size_t
106 3           to_base64(const unsigned char *in, size_t n, char *out, int urlsafe)
107             {
108             static const char std[] =
109             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
110             static const char url[] =
111             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
112 3 100         const char *a = urlsafe ? url : std;
113 3           size_t i = 0, o = 0;
114 33 100         while (i + 3 <= n) {
115 30           unsigned t = ((unsigned)in[i] << 16)
116 30           | ((unsigned)in[i+1] << 8)
117 30           | (unsigned)in[i+2];
118 30           out[o++] = a[(t >> 18) & 0x3F];
119 30           out[o++] = a[(t >> 12) & 0x3F];
120 30           out[o++] = a[(t >> 6) & 0x3F];
121 30           out[o++] = a[ t & 0x3F];
122 30           i += 3;
123             }
124 3 50         if (i < n) {
125 3           unsigned t = (unsigned)in[i] << 16;
126 3 50         if (i + 1 < n) t |= (unsigned)in[i+1] << 8;
127 3           out[o++] = a[(t >> 18) & 0x3F];
128 3           out[o++] = a[(t >> 12) & 0x3F];
129 3 50         if (i + 1 < n) {
130 3           out[o++] = a[(t >> 6) & 0x3F];
131 3 100         if (!urlsafe) out[o++] = '=';
132 0 0         } else if (!urlsafe) {
133 0           out[o++] = '=';
134 0           out[o++] = '=';
135             }
136             }
137 3           out[o] = '\0';
138 3           return o;
139             }
140              
141             static size_t
142 230           formatted_len(size_t digest_bytes, hash_format_t fmt)
143             {
144 230           switch (fmt) {
145 218           case HF_HEX:
146             case HF_HEX_UPPER:
147 218           return digest_bytes * 2;
148 4           case HF_BASE64:
149 4           return ((digest_bytes + 2) / 3) * 4; /* padded */
150 2           case HF_BASE64URL: {
151 2           size_t full = digest_bytes / 3;
152 2           size_t tail = digest_bytes % 3;
153 2 50         return full * 4 + (tail == 0 ? 0 : tail + 1); /* unpadded */
154             }
155 6           case HF_RAW:
156 6           return digest_bytes;
157             }
158 0           return digest_bytes * 2;
159             }
160              
161             /* ---------------- runner ---------------- */
162              
163             /* Per-algo init helper. Used both by hash_runner_init and during the
164             * HMAC outer pass at finish time. */
165             static void
166 140           algo_init(hash_one_t *a, uint64_t xxh64_seed)
167             {
168 140           switch (a->id) {
169 75           case HA_SHA256: sha256_init(&a->u.sha256); break;
170 5           case HA_SHA512: sha512_init(&a->u.sha512); break;
171 11           case HA_SHA1: sha1_init (&a->u.sha1); break;
172 18           case HA_MD5: md5_init (&a->u.md5); break;
173 10           case HA_CRC32: crc32_init (&a->u.crc32); break;
174 10           case HA_XXH64: xxh64_init (&a->u.xxh64, xxh64_seed); break;
175 11           case HA_BLAKE3: blake3_init(&a->u.blake3); break;
176 0           case HA_COUNT: break;
177             }
178 140           }
179              
180             static void
181 224           algo_update(hash_one_t *a, const void *data, size_t len)
182             {
183 224           switch (a->id) {
184 162           case HA_SHA256: sha256_update(&a->u.sha256, data, len); break;
185 3           case HA_SHA512: sha512_update(&a->u.sha512, data, len); break;
186 11           case HA_SHA1: sha1_update (&a->u.sha1, data, len); break;
187 21           case HA_MD5: md5_update (&a->u.md5, data, len); break;
188 11           case HA_CRC32: crc32_update (&a->u.crc32, data, len); break;
189 8           case HA_XXH64: xxh64_update (&a->u.xxh64, data, len); break;
190 8           case HA_BLAKE3: blake3_update(&a->u.blake3, data, len); break;
191 0           case HA_COUNT: break;
192             }
193 224           }
194              
195             static void
196 128           algo_final(hash_one_t *a, unsigned char *digest)
197             {
198 128           switch (a->id) {
199 67           case HA_SHA256: sha256_final(&a->u.sha256, digest); break;
200 5           case HA_SHA512: sha512_final(&a->u.sha512, digest); break;
201 9           case HA_SHA1: sha1_final (&a->u.sha1, digest); break;
202 16           case HA_MD5: md5_final (&a->u.md5, digest); break;
203 10           case HA_CRC32: crc32_final (&a->u.crc32, digest); break;
204 10           case HA_XXH64: xxh64_final (&a->u.xxh64, digest); break;
205 11           case HA_BLAKE3: blake3_final(&a->u.blake3, digest); break;
206 0           case HA_COUNT: break;
207             }
208 128           }
209              
210             int
211 97           hash_runner_init(hash_runner_t *r, const hash_algo_id_t *ids, int n,
212             hash_format_t format, uint64_t xxh64_seed)
213             {
214             int i;
215 97 50         if (!r || n < 1) return -1;
    50          
216 97           r->count = n;
217 97           r->format = format;
218 97           r->xxh64_seed = xxh64_seed;
219 97           r->_finish_results = NULL;
220 97           r->_finish_blob = NULL;
221 97           r->algos = (hash_one_t *)calloc((size_t)n, sizeof *r->algos);
222 97 50         if (!r->algos) return -1;
223              
224 212 100         for (i = 0; i < n; i++) {
225 115           hash_algo_id_t id = ids[i];
226 115 50         if ((int)id < 0 || (int)id >= HA_COUNT) {
    50          
227 0           free(r->algos);
228 0           r->algos = NULL;
229 0           r->count = 0;
230 0           return -1;
231             }
232 115           r->algos[i].id = id;
233 115           r->algos[i].hmac_mode = 0;
234 115           algo_init(&r->algos[i], xxh64_seed);
235             }
236 97           return 0;
237             }
238              
239             int
240 10           hash_runner_set_hmac(hash_runner_t *r,
241             const unsigned char *key, size_t key_len)
242             {
243             int i;
244             unsigned char k_prime[HMAC_MAX_BLOCK_SIZE];
245             unsigned char ipad_key[HMAC_MAX_BLOCK_SIZE];
246 10 50         if (!r || !r->algos) return -1;
    50          
247              
248             /* Pre-flight: every algo in the runner must be HMAC-able. */
249 22 100         for (i = 0; i < r->count; i++) {
250 12           const hash_algo_info_t *info = hash_algo_by_id(r->algos[i].id);
251 12 50         if (!info->hmac_able) return -1;
252             }
253              
254 22 100         for (i = 0; i < r->count; i++) {
255 12           hash_one_t *a = &r->algos[i];
256 12           const hash_algo_info_t *info = hash_algo_by_id(a->id);
257 12           size_t B = info->hmac_block_size;
258             size_t j;
259              
260             /* Derive K': hash the key down if it's longer than B,
261             * then zero-pad to B. */
262 12 100         if (key_len > B) {
263             /* Run the algo over the key into k_prime. We use a fresh
264             * temporary context to avoid disturbing a->u (which is
265             * about to be re-initialised for the inner pass). */
266             hash_one_t tmp;
267             unsigned char digest[HASH_MAX_DIGEST_SIZE];
268 1           tmp.id = a->id;
269 1           algo_init(&tmp, r->xxh64_seed);
270 1           algo_update(&tmp, key, key_len);
271 1           algo_final(&tmp, digest);
272 1           memcpy(k_prime, digest, info->digest_size);
273 33 100         for (j = info->digest_size; j < B; j++) k_prime[j] = 0;
274             } else {
275 11           memcpy(k_prime, key, key_len);
276 604 100         for (j = key_len; j < B; j++) k_prime[j] = 0;
277             }
278              
279             /* ipad and opad keys. */
280 780 100         for (j = 0; j < B; j++) {
281 768           ipad_key[j] = k_prime[j] ^ 0x36;
282 768           a->hmac_opad_key[j] = k_prime[j] ^ 0x5c;
283             }
284 12           a->hmac_block_size = B;
285 12           a->hmac_mode = 1;
286              
287             /* Re-init inner ctx (still in the same union slot) and feed
288             * the ipad key. The actual user data will follow via
289             * hash_runner_update. */
290 12           algo_init(a, r->xxh64_seed);
291 12           algo_update(a, ipad_key, B);
292             }
293 10           return 0;
294             }
295              
296             void
297 163           hash_runner_update(hash_runner_t *r, const void *data, size_t len)
298             {
299             int i;
300 163 50         if (!r || !r->algos || len == 0) return;
    50          
    50          
301 350 100         for (i = 0; i < r->count; i++) {
302 187           algo_update(&r->algos[i], data, len);
303             }
304             }
305              
306             int
307 97           hash_runner_finish(hash_runner_t *r, const hash_result_t **out)
308             {
309             int i;
310 97           size_t total_bytes = 0;
311             char *cursor;
312             hash_result_t *res;
313             char *blob;
314             unsigned char digest[HASH_MAX_DIGEST_SIZE];
315              
316 97 50         if (!r || !r->algos) return -1;
    50          
317              
318             /* Free any prior finish result so a second call doesn't leak. */
319 97           free(r->_finish_results);
320 97           free(r->_finish_blob);
321 97           r->_finish_results = NULL;
322 97           r->_finish_blob = NULL;
323              
324 212 100         for (i = 0; i < r->count; i++) {
325 115           const hash_algo_info_t *info = hash_algo_by_id(r->algos[i].id);
326 115           total_bytes += formatted_len(info->digest_size, r->format) + 1;
327             }
328              
329 97           res = (hash_result_t *)calloc((size_t)r->count, sizeof *res);
330 97 50         if (!res) return -1;
331 97 50         blob = (char *)malloc(total_bytes ? total_bytes : 1);
332 97 50         if (!blob) { free(res); return -1; }
333              
334 97           cursor = blob;
335 212 100         for (i = 0; i < r->count; i++) {
336 115           hash_one_t *a = &r->algos[i];
337 115           const hash_algo_info_t *info = hash_algo_by_id(a->id);
338             size_t flen;
339              
340 115           algo_final(a, digest);
341              
342             /* HMAC outer pass: hash(opad_key || inner_digest) replaces
343             * digest with the keyed MAC. */
344 115 100         if (a->hmac_mode) {
345             unsigned char outer[HASH_MAX_DIGEST_SIZE];
346 12           algo_init(a, r->xxh64_seed);
347 12           algo_update(a, a->hmac_opad_key, a->hmac_block_size);
348 12           algo_update(a, digest, info->digest_size);
349 12           algo_final(a, outer);
350 12           memcpy(digest, outer, info->digest_size);
351             }
352              
353 115           flen = formatted_len(info->digest_size, r->format);
354 115           switch (r->format) {
355 108           case HF_HEX:
356 108           to_hex(digest, info->digest_size, cursor, 0);
357 108           break;
358 1           case HF_HEX_UPPER:
359 1           to_hex(digest, info->digest_size, cursor, 1);
360 1           break;
361 2           case HF_BASE64:
362 2           (void)to_base64(digest, info->digest_size, cursor, 0);
363 2           break;
364 1           case HF_BASE64URL:
365 1           (void)to_base64(digest, info->digest_size, cursor, 1);
366 1           break;
367 3           case HF_RAW:
368 3           memcpy(cursor, digest, info->digest_size);
369 3           cursor[info->digest_size] = '\0';
370 3           break;
371             }
372              
373 115           res[i].id = a->id;
374 115           res[i].name = info->name;
375 115           res[i].out = cursor;
376 115           res[i].out_len = flen;
377              
378 115           cursor += flen + 1;
379             }
380              
381 97           r->_finish_results = res;
382 97           r->_finish_blob = blob;
383 97           *out = res;
384 97           return 0;
385             }
386              
387             void
388 97           hash_runner_free(hash_runner_t *r)
389             {
390 97 50         if (!r) return;
391 97           free(r->_finish_results);
392 97           free(r->_finish_blob);
393 97           r->_finish_results = NULL;
394 97           r->_finish_blob = NULL;
395 97           free(r->algos);
396 97           r->algos = NULL;
397 97           r->count = 0;
398             }