DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
spnego.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 spnego.cpp
6 * @brief SPNEGO GSS-API DER wrapping implementation (see spnego.h). Definite-length DER; the
7 * nested lengths are computed bottom-up, then emitted forward with no temp buffers.
8 */
9
10#include "spnego.h"
11
12#if DETWS_ENABLE_SMB
13
14#include <string.h>
15
16// OID TLVs (tag + length + content).
17static const uint8_t SPNEGO_OID[] = {0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02}; // 1.3.6.1.5.5.2
18static const uint8_t NTLM_OID[] = {0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
19 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a}; // 1.3.6.1.4.1.311.2.2.10
20
21static size_t der_len_size(size_t n)
22{
23 return n < 0x80 ? 1 : n < 0x100 ? 2 : n < 0x10000 ? 3 : 4;
24}
25static size_t tlv_size(size_t clen)
26{
27 return 1 + der_len_size(clen) + clen;
28}
29// Write a tag + definite-length header for @p clen bytes of content; advance *p.
30static void wr_tag_len(uint8_t *out, size_t *p, uint8_t tag, size_t clen)
31{
32 out[(*p)++] = tag;
33 if (clen < 0x80)
34 out[(*p)++] = (uint8_t)clen;
35 else if (clen < 0x100)
36 {
37 out[(*p)++] = 0x81;
38 out[(*p)++] = (uint8_t)clen;
39 }
40 else if (clen < 0x10000)
41 {
42 out[(*p)++] = 0x82;
43 out[(*p)++] = (uint8_t)(clen >> 8);
44 out[(*p)++] = (uint8_t)clen;
45 }
46 else
47 {
48 out[(*p)++] = 0x83;
49 out[(*p)++] = (uint8_t)(clen >> 16);
50 out[(*p)++] = (uint8_t)(clen >> 8);
51 out[(*p)++] = (uint8_t)clen;
52 }
53}
54
55// Read one DER TLV at *pos in [buf,buf+len): set tag / content length / content offset and advance
56// *pos past the whole TLV. Definite short/long form only. Returns false on truncation / bad length.
57static bool der_read(const uint8_t *buf, size_t len, size_t *pos, uint8_t *tag, size_t *clen, size_t *cstart)
58{
59 size_t p = *pos;
60 if (p + 2 > len)
61 return false;
62 *tag = buf[p++];
63 size_t l = buf[p++];
64 if (l & 0x80)
65 {
66 size_t nb = l & 0x7f;
67 if (nb == 0 || nb > 4 || p + nb > len)
68 return false; // indefinite / oversized length not supported
69 l = 0;
70 for (size_t i = 0; i < nb; i++)
71 l = (l << 8) | buf[p++];
72 }
73 if (p + l > len)
74 return false;
75 *cstart = p;
76 *clen = l;
77 *pos = p + l;
78 return true;
79}
80
81size_t spnego_wrap_negotiate(const uint8_t *ntlm, size_t ntlm_len, uint8_t *out, size_t cap)
82{
83 if (!ntlm)
84 return 0;
85 size_t octet = tlv_size(ntlm_len); // OCTET STRING(mechToken)
86 size_t mt = tlv_size(octet); // [2] mechToken
87 size_t seqof = tlv_size(sizeof(NTLM_OID)); // SEQUENCE OF { NTLM OID }
88 size_t mtypes = tlv_size(seqof); // [0] mechTypes
89 size_t seq = tlv_size(mtypes + mt); // SEQUENCE (NegTokenInit)
90 size_t nti = tlv_size(seq); // [0] negTokenInit
91 size_t ictbody = sizeof(SPNEGO_OID) + nti;
92 size_t total = tlv_size(ictbody); // [APPLICATION 0] InitialContextToken
93 if (!out || total > cap)
94 return 0;
95
96 size_t p = 0;
97 wr_tag_len(out, &p, 0x60, ictbody);
98 memcpy(out + p, SPNEGO_OID, sizeof(SPNEGO_OID));
99 p += sizeof(SPNEGO_OID);
100 wr_tag_len(out, &p, 0xa0, seq); // [0] negTokenInit
101 wr_tag_len(out, &p, 0x30, mtypes + mt); // SEQUENCE
102 wr_tag_len(out, &p, 0xa0, seqof); // [0] mechTypes
103 wr_tag_len(out, &p, 0x30, sizeof(NTLM_OID)); // SEQUENCE OF
104 memcpy(out + p, NTLM_OID, sizeof(NTLM_OID));
105 p += sizeof(NTLM_OID);
106 wr_tag_len(out, &p, 0xa2, octet); // [2] mechToken
107 wr_tag_len(out, &p, 0x04, ntlm_len);
108 memcpy(out + p, ntlm, ntlm_len);
109 p += ntlm_len;
110 return p;
111}
112
113size_t spnego_wrap_authenticate(const uint8_t *ntlm, size_t ntlm_len, uint8_t *out, size_t cap)
114{
115 if (!ntlm)
116 return 0;
117 size_t octet = tlv_size(ntlm_len); // OCTET STRING(responseToken)
118 size_t rt = tlv_size(octet); // [2] responseToken
119 size_t seq = tlv_size(rt); // SEQUENCE
120 size_t total = tlv_size(seq); // [1] NegTokenResp
121 if (!out || total > cap)
122 return 0;
123
124 size_t p = 0;
125 wr_tag_len(out, &p, 0xa1, seq); // [1] NegTokenResp
126 wr_tag_len(out, &p, 0x30, rt); // SEQUENCE
127 wr_tag_len(out, &p, 0xa2, octet); // [2] responseToken
128 wr_tag_len(out, &p, 0x04, ntlm_len);
129 memcpy(out + p, ntlm, ntlm_len);
130 p += ntlm_len;
131 return p;
132}
133
134bool spnego_parse_response(const uint8_t *blob, size_t len, const uint8_t **resp_token, size_t *resp_len)
135{
136 if (!blob || !resp_token || !resp_len)
137 return false;
138 size_t pos = 0;
139 size_t cstart;
140 size_t clen;
141 uint8_t tag;
142 // [1] NegTokenResp
143 if (!der_read(blob, len, &pos, &tag, &clen, &cstart) || tag != 0xa1)
144 return false;
145 size_t neg_end = cstart + clen;
146 size_t p = cstart;
147 // SEQUENCE
148 if (!der_read(blob, neg_end, &p, &tag, &clen, &cstart) || tag != 0x30)
149 return false;
150 size_t seq_end = cstart + clen;
151 p = cstart;
152 // walk the fields for [2] responseToken
153 while (p < seq_end)
154 {
155 if (!der_read(blob, seq_end, &p, &tag, &clen, &cstart))
156 return false;
157 if (tag == 0xa2)
158 {
159 size_t q = cstart;
160 size_t cs2;
161 size_t cl2;
162 uint8_t t2;
163 if (!der_read(blob, cstart + clen, &q, &t2, &cl2, &cs2) || t2 != 0x04)
164 return false;
165 *resp_token = blob + cs2;
166 *resp_len = cl2;
167 return true;
168 }
169 }
170 return false;
171}
172
173#endif // DETWS_ENABLE_SMB
SPNEGO (RFC 4178) GSS-API wrapping of the NTLMSSP tokens for the SMB2 client (DETWS_ENABLE_SMB).