DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ntlm.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ntlm.cpp
6 * @brief NTLMv2 response computation (see ntlm.h).
7 */
8
9#include "ntlm.h"
10
11#if DETWS_ENABLE_SMB
12
13#include "services/smb/smb_md.h"
14#include <string.h>
15
16void ntlm_nt_hash(const char *password, uint8_t nt_hash[16])
17{
18 MdCtx c;
19 md4_init(&c);
20 for (const char *p = password; *p; p++)
21 {
22 uint8_t pair[2] = {(uint8_t)*p, 0}; // UTF-16LE (ASCII/UTF-8 code unit + high byte 0)
23 md4_update(&c, pair, 2);
24 }
25 md4_final(&c, nt_hash);
26}
27
28bool ntlm_ntowfv2(const uint8_t nt_hash[16], const char *user, const char *domain, uint8_t owf[16])
29{
30 uint8_t buf[512]; // UTF-16LE of Uppercase(user) + domain; 256 chars max
31 size_t n = 0;
32 for (const char *p = user; *p; p++)
33 {
34 char up = *p;
35 if (up >= 'a' && up <= 'z')
36 up = (char)(up - 32); // ASCII uppercase (only the user, per MS-NLMP)
37 if (n + 2 > sizeof(buf))
38 return false;
39 buf[n++] = (uint8_t)up;
40 buf[n++] = 0;
41 }
42 for (const char *p = domain; *p; p++)
43 {
44 if (n + 2 > sizeof(buf))
45 return false;
46 buf[n++] = (uint8_t)*p;
47 buf[n++] = 0;
48 }
49 hmac_md5(nt_hash, 16, buf, n, owf);
50 return true;
51}
52
53// HMAC-MD5 over a two-part message (the key here is always the 16-byte NTOWFv2, < 64 bytes,
54// so no key-shortening is needed).
55static void hmac_md5_2(const uint8_t key[16], const uint8_t *m1, size_t l1, const uint8_t *m2, size_t l2,
56 uint8_t out[16])
57{
58 uint8_t ipad[64];
59 uint8_t opad[64];
60 for (int i = 0; i < 64; i++)
61 {
62 uint8_t k = (i < 16) ? key[i] : 0;
63 ipad[i] = (uint8_t)(k ^ 0x36);
64 opad[i] = (uint8_t)(k ^ 0x5c);
65 }
66 uint8_t inner[16];
67 MdCtx c;
68 md5_init(&c);
69 md5_update(&c, ipad, 64);
70 md5_update(&c, m1, l1);
71 if (m2 && l2)
72 md5_update(&c, m2, l2);
73 md5_final(&c, inner);
74 md5_init(&c);
75 md5_update(&c, opad, 64);
76 md5_update(&c, inner, 16);
77 md5_final(&c, out);
78}
79
80size_t ntlm_v2_response(const uint8_t owf[16], const uint8_t server_challenge[8], const uint8_t client_challenge[8],
81 const uint8_t timestamp[8], const uint8_t *target_info, size_t ti_len, uint8_t *out,
82 size_t out_cap, uint8_t session_key[16])
83{
84 const size_t temp_len = 2 + 6 + 8 + 8 + 4 + ti_len + 4; // MS-NLMP temp layout
85 const size_t resp_len = 16 + temp_len; // NTProofStr(16) + temp
86 if (!out || resp_len > out_cap)
87 return 0;
88
89 // Build temp in place at out+16, so the result is NTProofStr(16) || temp contiguously.
90 uint8_t *temp = out + 16;
91 size_t k = 0;
92 temp[k++] = 0x01; // Responserversion
93 temp[k++] = 0x01; // HiResponserversion
94 memset(temp + k, 0, 6); // Z(6)
95 k += 6;
96 memcpy(temp + k, timestamp, 8);
97 k += 8;
98 memcpy(temp + k, client_challenge, 8);
99 k += 8;
100 memset(temp + k, 0, 4); // Z(4)
101 k += 4;
102 memcpy(temp + k, target_info, ti_len);
103 k += ti_len;
104 memset(temp + k, 0, 4); // Z(4) trailer; temp_len (line 83) already accounts for it, so k is done
105
106 uint8_t ntproof[16];
107 hmac_md5_2(owf, server_challenge, 8, temp, temp_len, ntproof);
108 memcpy(out, ntproof, 16); // out = NTProofStr || temp
109 if (session_key)
110 hmac_md5(owf, 16, ntproof, 16, session_key);
111 return resp_len;
112}
113
114#endif // DETWS_ENABLE_SMB
NTLMv2 response computation (MS-NLMP §3.3.2) for the SMB2 client (DETWS_ENABLE_SMB).
MD4 (RFC 1320), MD5 (RFC 1321), and HMAC-MD5 (RFC 2104) - the legacy digests NTLM needs,...