ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 PC_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 {
35 out[(*p)++] = (uint8_t)clen;
36 }
37 else if (clen < 0x100)
38 {
39 out[(*p)++] = 0x81;
40 out[(*p)++] = (uint8_t)clen;
41 }
42 else if (clen < 0x10000)
43 {
44 out[(*p)++] = 0x82;
45 out[(*p)++] = (uint8_t)(clen >> 8);
46 out[(*p)++] = (uint8_t)clen;
47 }
48 else
49 {
50 out[(*p)++] = 0x83;
51 out[(*p)++] = (uint8_t)(clen >> 16);
52 out[(*p)++] = (uint8_t)(clen >> 8);
53 out[(*p)++] = (uint8_t)clen;
54 }
55}
56
57// Read one DER TLV at *pos in [buf,buf+len): set tag / content length / content offset and advance
58// *pos past the whole TLV. Definite short/long form only. Returns false on truncation / bad length.
59static bool der_read(const uint8_t *buf, size_t len, size_t *pos, uint8_t *tag, size_t *clen, size_t *cstart)
60{
61 size_t p = *pos;
62 if (p + 2 > len)
63 {
64 return false;
65 }
66 *tag = buf[p++];
67 size_t l = buf[p++];
68 if (l & 0x80)
69 {
70 size_t nb = l & 0x7f;
71 if (nb == 0 || nb > 4 || p + nb > len)
72 {
73 return false; // indefinite / oversized length not supported
74 }
75 l = 0;
76 for (size_t i = 0; i < nb; i++)
77 {
78 l = (l << 8) | buf[p++];
79 }
80 }
81 if (p + l > len)
82 {
83 return false;
84 }
85 *cstart = p;
86 *clen = l;
87 *pos = p + l;
88 return true;
89}
90
91size_t pc_spnego_wrap_negotiate(const uint8_t *ntlm, size_t pc_ntlm_len, uint8_t *out, size_t cap)
92{
93 if (!ntlm)
94 {
95 return 0;
96 }
97 size_t octet = tlv_size(pc_ntlm_len); // OCTET STRING(mechToken)
98 size_t mt = tlv_size(octet); // [2] mechToken
99 size_t seqof = tlv_size(sizeof(NTLM_OID)); // SEQUENCE OF { NTLM OID }
100 size_t mtypes = tlv_size(seqof); // [0] mechTypes
101 size_t seq = tlv_size(mtypes + mt); // SEQUENCE (NegTokenInit)
102 size_t nti = tlv_size(seq); // [0] negTokenInit
103 size_t ictbody = sizeof(SPNEGO_OID) + nti;
104 size_t total = tlv_size(ictbody); // [APPLICATION 0] InitialContextToken
105 if (!out || total > cap)
106 {
107 return 0;
108 }
109
110 size_t p = 0;
111 wr_tag_len(out, &p, 0x60, ictbody);
112 memcpy(out + p, SPNEGO_OID, sizeof(SPNEGO_OID));
113 p += sizeof(SPNEGO_OID);
114 wr_tag_len(out, &p, 0xa0, seq); // [0] negTokenInit
115 wr_tag_len(out, &p, 0x30, mtypes + mt); // SEQUENCE
116 wr_tag_len(out, &p, 0xa0, seqof); // [0] mechTypes
117 wr_tag_len(out, &p, 0x30, sizeof(NTLM_OID)); // SEQUENCE OF
118 memcpy(out + p, NTLM_OID, sizeof(NTLM_OID));
119 p += sizeof(NTLM_OID);
120 wr_tag_len(out, &p, 0xa2, octet); // [2] mechToken
121 wr_tag_len(out, &p, 0x04, pc_ntlm_len);
122 memcpy(out + p, ntlm, pc_ntlm_len);
123 p += pc_ntlm_len;
124 return p;
125}
126
127size_t pc_spnego_wrap_authenticate(const uint8_t *ntlm, size_t pc_ntlm_len, uint8_t *out, size_t cap)
128{
129 if (!ntlm)
130 {
131 return 0;
132 }
133 size_t octet = tlv_size(pc_ntlm_len); // OCTET STRING(responseToken)
134 size_t rt = tlv_size(octet); // [2] responseToken
135 size_t seq = tlv_size(rt); // SEQUENCE
136 size_t total = tlv_size(seq); // [1] NegTokenResp
137 if (!out || total > cap)
138 {
139 return 0;
140 }
141
142 size_t p = 0;
143 wr_tag_len(out, &p, 0xa1, seq); // [1] NegTokenResp
144 wr_tag_len(out, &p, 0x30, rt); // SEQUENCE
145 wr_tag_len(out, &p, 0xa2, octet); // [2] responseToken
146 wr_tag_len(out, &p, 0x04, pc_ntlm_len);
147 memcpy(out + p, ntlm, pc_ntlm_len);
148 p += pc_ntlm_len;
149 return p;
150}
151
152bool pc_spnego_parse_response(const uint8_t *blob, size_t len, const uint8_t **pc_resp_token, size_t *pc_resp_len)
153{
154 if (!blob || !pc_resp_token || !pc_resp_len)
155 {
156 return false;
157 }
158 size_t pos = 0;
159 size_t cstart;
160 size_t clen;
161 uint8_t tag;
162 // [1] NegTokenResp
163 if (!der_read(blob, len, &pos, &tag, &clen, &cstart) || tag != 0xa1)
164 {
165 return false;
166 }
167 size_t neg_end = cstart + clen;
168 size_t p = cstart;
169 // SEQUENCE
170 if (!der_read(blob, neg_end, &p, &tag, &clen, &cstart) || tag != 0x30)
171 {
172 return false;
173 }
174 size_t seq_end = cstart + clen;
175 p = cstart;
176 // walk the fields for [2] responseToken
177 while (p < seq_end)
178 {
179 if (!der_read(blob, seq_end, &p, &tag, &clen, &cstart))
180 {
181 return false;
182 }
183 if (tag == 0xa2)
184 {
185 size_t q = cstart;
186 size_t cs2;
187 size_t cl2;
188 uint8_t t2;
189 if (!der_read(blob, cstart + clen, &q, &t2, &cl2, &cs2) || t2 != 0x04)
190 {
191 return false;
192 }
193 *pc_resp_token = blob + cs2;
194 *pc_resp_len = cl2;
195 return true;
196 }
197 }
198 return false;
199}
200
201#endif // PC_ENABLE_SMB
SPNEGO (RFC 4178) GSS-API wrapping of the NTLMSSP tokens for the SMB2 client (PC_ENABLE_SMB).