ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
md.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file md.h
6 * @brief MD4 (RFC 1320), MD5 (RFC 1321), and HMAC-MD5 (RFC 2104) - the legacy digests NTLM needs.
7 *
8 * The shared library home for the MD-family digests. The only consumer is the SMB2 client's NTLMv2
9 * (MS-NLMP): the NT hash is MD4(UTF-16LE(password)); the NTLMv2 response and session key are HMAC-MD5
10 * chains. MD4/MD5 are cryptographically broken and are included ONLY because SMB/NTLM requires them on
11 * the wire - do not use them for anything security-new. Zero heap, streaming; verified against the RFC
12 * test vectors (see test_smb_crypto).
13 *
14 * @author Douglas Quigg (dstroy0)
15 * @date 2026
16 */
17
18#ifndef PROTOCORE_MD_H
19#define PROTOCORE_MD_H
20
21#include "protocore_config.h" // the entry point: protocore_types.h for the widths
22
23#if PROTOCORE_ENABLE_MD
24
26
27/** @brief MD digest length in bytes. MD4 and MD5 are both 128-bit. */
28#define PROTOCORE_MD_DIGEST_LEN 16
29
30// PROTOCORE_MD_BORROW - the bytes a digest runs out of - is stated in protocore_config.h, which sums
31// it into the secure arena. A caller takes them once and passes the pointer to every call.
32
33/** @brief One chunk fed to a running digest. */
34typedef struct
35{
36 const uint8_t *data; ///< the bytes
37 size_t len; ///< how many
38} MdUpdateArgs;
39
40/** @brief Where a finished digest lands. */
41typedef struct
42{
43 uint8_t *out; ///< PROTOCORE_MD_DIGEST_LEN bytes
44} MdFinalArgs;
45
46/** @brief The key and message an HMAC-MD5 is taken over. */
47typedef struct
48{
49 const uint8_t *key; ///< MAC key bytes
50 size_t key_len; ///< key length
51 const uint8_t *msg; ///< the message
52 size_t msg_len; ///< its length
53 uint8_t *out; ///< PROTOCORE_MD_DIGEST_LEN bytes
54} MdHmacArgs;
55
56/**
57 * @brief MD4 / MD5 / HMAC-MD5.
58 *
59 * A caller sets the members a call takes, invokes it through ::Md, and reads the outcome off the same
60 * handle, with the bytes it runs out of. How those bytes are carved is this module's and is never
61 * named here.
62 *
63 * Md.md4_init(work);
64 * Md.update_args.data = pw;
65 * Md.update_args.len = pw_len;
66 * Md.update(work);
67 * Md.final_args.out = nt_hash;
68 * Md.final(work);
69 *
70 * @var MdNs::update_args one chunk fed to a running digest
71 * @var MdNs::final_args where a finished digest lands
72 * @var MdNs::hmac_args the key and message an HMAC-MD5 is taken over
73 * @var MdNs::ok a call's true/false outcome
74 * @var MdNs::md5_init start an MD5
75 * @var MdNs::md4_init start an MD4
76 * @var MdNs::update feed the running digest a chunk
77 * @var MdNs::final pad, compress the last block, write the 16 bytes out
78 * @var MdNs::md5 init, update and final in one call
79 * @var MdNs::md4 the same for MD4, the NT-hash primitive
80 * @var MdNs::hmac_md5 HMAC-MD5 (RFC 2104), the NTLMv2 MAC primitive
81 *
82 * @c work is PROTOCORE_MD_BORROW secure bytes the CALLER took, at an address it knows. It is not held past the call, so
83 * nothing here aliases it. The caller releases it, and the pool wipes on release; this module neither takes it, holds
84 * it, releases it, nor wipes it. That is what keeps the NTLM password and session-key material in it from outliving the
85 * caller. The borrow IS the digest, so two running digests are two borrows and never collide.
86 *
87 * No storage member and no context: a caller sets operands and reads @ref MdNs::ok, and that is all
88 * the surface there is.
89 */
90typedef struct
91{
92 MdUpdateArgs update_args;
93 MdFinalArgs final_args;
94 MdHmacArgs hmac_args;
95 proto_bool ok;
96} MdVars;
97
98/** @brief The operands and the outcome. */
99extern MdVars MdV;
100
101/** @brief The entries. */
102typedef struct
103{
104 void (*const md5_init)(uint8_t *work);
105 void (*const md4_init)(uint8_t *work);
106 void (*const update)(uint8_t *work);
107 void (*const final)(uint8_t *work);
108 void (*const md5)(uint8_t *work);
109 void (*const md4)(uint8_t *work);
110 void (*const hmac_md5)(uint8_t *work);
111} MdNs;
112
113// What the table binds, defined once in the .c and taking one parameter each: everything
114// else an entry needs is an operand in MdV or a region of the borrow at a fixed offset.
115void protocore_md_md5_init(uint8_t *work);
116void protocore_md_md4_init(uint8_t *work);
117void protocore_md_update(uint8_t *work);
118void protocore_md_final(uint8_t *work);
119void protocore_md_md5(uint8_t *work);
120void protocore_md_md4(uint8_t *work);
121void protocore_md_hmac_md5(uint8_t *work);
122
123// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
124// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
125// `Md.md5_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
126// leaves the call indirect and the symbol live at every level, -O2 -flto included.
127static const MdNs Md __attribute__((unused)) = {
128 .md5_init = protocore_md_md5_init,
129 .md4_init = protocore_md_md4_init,
130 .update = protocore_md_update,
131 .final = protocore_md_final,
132 .md5 = protocore_md_md5,
133 .md4 = protocore_md_md4,
134 .hmac_md5 = protocore_md_hmac_md5,
135};
136
138
139#endif // PROTOCORE_ENABLE_MD
140
141#endif // PROTOCORE_MD_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97