spnego: share spnego_parse.
[ira/wip.git] / libcli / auth / spnego_parse.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    RFC2478 Compliant SPNEGO implementation
5
6    Copyright (C) Jim McDonough <jmcd@us.ibm.com>   2003
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "../libcli/auth/spnego.h"
25 #include "../lib/util/asn1.h"
26
27 static bool read_negTokenInit(struct asn1_data *asn1, TALLOC_CTX *mem_ctx,
28                               struct spnego_negTokenInit *token)
29 {
30         ZERO_STRUCTP(token);
31
32         asn1_start_tag(asn1, ASN1_CONTEXT(0));
33         asn1_start_tag(asn1, ASN1_SEQUENCE(0));
34
35         while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) {
36                 int i;
37                 uint8_t context;
38                 if (!asn1_peek_uint8(asn1, &context)) {
39                         asn1->has_error = true;
40                         break;
41                 }
42
43                 switch (context) {
44                 /* Read mechTypes */
45                 case ASN1_CONTEXT(0):
46                         asn1_start_tag(asn1, ASN1_CONTEXT(0));
47                         asn1_start_tag(asn1, ASN1_SEQUENCE(0));
48
49                         token->mechTypes = talloc(NULL, const char *);
50                         for (i = 0; !asn1->has_error &&
51                                      0 < asn1_tag_remaining(asn1); i++) {
52                                 token->mechTypes = talloc_realloc(NULL,
53                                                                   token->mechTypes,
54                                                                   const char *, i+2);
55                                 asn1_read_OID(asn1, token->mechTypes, token->mechTypes + i);
56                         }
57                         token->mechTypes[i] = NULL;
58
59                         asn1_end_tag(asn1);
60                         asn1_end_tag(asn1);
61                         break;
62                 /* Read reqFlags */
63                 case ASN1_CONTEXT(1):
64                         asn1_start_tag(asn1, ASN1_CONTEXT(1));
65                         asn1_read_Integer(asn1, &token->reqFlags);
66                         token->reqFlags |= SPNEGO_REQ_FLAG;
67                         asn1_end_tag(asn1);
68                         break;
69                 /* Read mechToken */
70                 case ASN1_CONTEXT(2):
71                         asn1_start_tag(asn1, ASN1_CONTEXT(2));
72                         asn1_read_OctetString(asn1, mem_ctx, &token->mechToken);
73                         asn1_end_tag(asn1);
74                         break;
75                 /* Read mecListMIC */
76                 case ASN1_CONTEXT(3):
77                 {
78                         uint8_t type_peek;
79                         asn1_start_tag(asn1, ASN1_CONTEXT(3));
80                         if (!asn1_peek_uint8(asn1, &type_peek)) {
81                                 asn1->has_error = true;
82                                 break;
83                         }
84                         if (type_peek == ASN1_OCTET_STRING) {
85                                 asn1_read_OctetString(asn1, mem_ctx,
86                                                       &token->mechListMIC);
87                         } else {
88                                 /* RFC 2478 says we have an Octet String here,
89                                    but W2k sends something different... */
90                                 char *mechListMIC;
91                                 asn1_push_tag(asn1, ASN1_SEQUENCE(0));
92                                 asn1_push_tag(asn1, ASN1_CONTEXT(0));
93                                 asn1_read_GeneralString(asn1, mem_ctx, &mechListMIC);
94                                 asn1_pop_tag(asn1);
95                                 asn1_pop_tag(asn1);
96
97                                 token->targetPrincipal = mechListMIC;
98                         }
99                         asn1_end_tag(asn1);
100                         break;
101                 }
102                 default:
103                         asn1->has_error = true;
104                         break;
105                 }
106         }
107
108         asn1_end_tag(asn1);
109         asn1_end_tag(asn1);
110
111         return !asn1->has_error;
112 }
113
114 static bool write_negTokenInit(struct asn1_data *asn1, struct spnego_negTokenInit *token)
115 {
116         asn1_push_tag(asn1, ASN1_CONTEXT(0));
117         asn1_push_tag(asn1, ASN1_SEQUENCE(0));
118
119         /* Write mechTypes */
120         if (token->mechTypes && *token->mechTypes) {
121                 int i;
122
123                 asn1_push_tag(asn1, ASN1_CONTEXT(0));
124                 asn1_push_tag(asn1, ASN1_SEQUENCE(0));
125                 for (i = 0; token->mechTypes[i]; i++) {
126                         asn1_write_OID(asn1, token->mechTypes[i]);
127                 }
128                 asn1_pop_tag(asn1);
129                 asn1_pop_tag(asn1);
130         }
131
132         /* write reqFlags */
133         if (token->reqFlags & SPNEGO_REQ_FLAG) {
134                 int flags = token->reqFlags & ~SPNEGO_REQ_FLAG;
135
136                 asn1_push_tag(asn1, ASN1_CONTEXT(1));
137                 asn1_write_Integer(asn1, flags);
138                 asn1_pop_tag(asn1);
139         }
140
141         /* write mechToken */
142         if (token->mechToken.data) {
143                 asn1_push_tag(asn1, ASN1_CONTEXT(2));
144                 asn1_write_OctetString(asn1, token->mechToken.data,
145                                        token->mechToken.length);
146                 asn1_pop_tag(asn1);
147         }
148
149         /* write mechListMIC */
150         if (token->mechListMIC.data) {
151                 asn1_push_tag(asn1, ASN1_CONTEXT(3));
152 #if 0
153                 /* This is what RFC 2478 says ... */
154                 asn1_write_OctetString(asn1, token->mechListMIC.data,
155                                        token->mechListMIC.length);
156 #else
157                 /* ... but unfortunately this is what Windows
158                    sends/expects */
159                 asn1_push_tag(asn1, ASN1_SEQUENCE(0));
160                 asn1_push_tag(asn1, ASN1_CONTEXT(0));
161                 asn1_push_tag(asn1, ASN1_GENERAL_STRING);
162                 asn1_write(asn1, token->mechListMIC.data,
163                            token->mechListMIC.length);
164                 asn1_pop_tag(asn1);
165                 asn1_pop_tag(asn1);
166                 asn1_pop_tag(asn1);
167 #endif
168                 asn1_pop_tag(asn1);
169         }
170
171         asn1_pop_tag(asn1);
172         asn1_pop_tag(asn1);
173
174         return !asn1->has_error;
175 }
176
177 static bool read_negTokenTarg(struct asn1_data *asn1, TALLOC_CTX *mem_ctx,
178                               struct spnego_negTokenTarg *token)
179 {
180         ZERO_STRUCTP(token);
181
182         asn1_start_tag(asn1, ASN1_CONTEXT(1));
183         asn1_start_tag(asn1, ASN1_SEQUENCE(0));
184
185         while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) {
186                 uint8_t context;
187                 if (!asn1_peek_uint8(asn1, &context)) {
188                         asn1->has_error = true;
189                         break;
190                 }
191
192                 switch (context) {
193                 case ASN1_CONTEXT(0):
194                         asn1_start_tag(asn1, ASN1_CONTEXT(0));
195                         asn1_start_tag(asn1, ASN1_ENUMERATED);
196                         asn1_read_uint8(asn1, &token->negResult);
197                         asn1_end_tag(asn1);
198                         asn1_end_tag(asn1);
199                         break;
200                 case ASN1_CONTEXT(1):
201                         asn1_start_tag(asn1, ASN1_CONTEXT(1));
202                         asn1_read_OID(asn1, mem_ctx, &token->supportedMech);
203                         asn1_end_tag(asn1);
204                         break;
205                 case ASN1_CONTEXT(2):
206                         asn1_start_tag(asn1, ASN1_CONTEXT(2));
207                         asn1_read_OctetString(asn1, mem_ctx, &token->responseToken);
208                         asn1_end_tag(asn1);
209                         break;
210                 case ASN1_CONTEXT(3):
211                         asn1_start_tag(asn1, ASN1_CONTEXT(3));
212                         asn1_read_OctetString(asn1, mem_ctx, &token->mechListMIC);
213                         asn1_end_tag(asn1);
214                         break;
215                 default:
216                         asn1->has_error = true;
217                         break;
218                 }
219         }
220
221         asn1_end_tag(asn1);
222         asn1_end_tag(asn1);
223
224         return !asn1->has_error;
225 }
226
227 static bool write_negTokenTarg(struct asn1_data *asn1, struct spnego_negTokenTarg *token)
228 {
229         asn1_push_tag(asn1, ASN1_CONTEXT(1));
230         asn1_push_tag(asn1, ASN1_SEQUENCE(0));
231
232         if (token->negResult != SPNEGO_NONE_RESULT) {
233                 asn1_push_tag(asn1, ASN1_CONTEXT(0));
234                 asn1_write_enumerated(asn1, token->negResult);
235                 asn1_pop_tag(asn1);
236         }
237
238         if (token->supportedMech) {
239                 asn1_push_tag(asn1, ASN1_CONTEXT(1));
240                 asn1_write_OID(asn1, token->supportedMech);
241                 asn1_pop_tag(asn1);
242         }
243
244         if (token->responseToken.data) {
245                 asn1_push_tag(asn1, ASN1_CONTEXT(2));
246                 asn1_write_OctetString(asn1, token->responseToken.data,
247                                        token->responseToken.length);
248                 asn1_pop_tag(asn1);
249         }
250
251         if (token->mechListMIC.data) {
252                 asn1_push_tag(asn1, ASN1_CONTEXT(3));
253                 asn1_write_OctetString(asn1, token->mechListMIC.data,
254                                       token->mechListMIC.length);
255                 asn1_pop_tag(asn1);
256         }
257
258         asn1_pop_tag(asn1);
259         asn1_pop_tag(asn1);
260
261         return !asn1->has_error;
262 }
263
264 ssize_t spnego_read_data(TALLOC_CTX *mem_ctx, DATA_BLOB data, struct spnego_data *token)
265 {
266         struct asn1_data *asn1;
267         ssize_t ret = -1;
268         uint8_t context;
269
270         ZERO_STRUCTP(token);
271
272         if (data.length == 0) {
273                 return ret;
274         }
275
276         asn1 = asn1_init(mem_ctx);
277         if (asn1 == NULL) {
278                 return -1;
279         }
280
281         asn1_load(asn1, data);
282
283         if (!asn1_peek_uint8(asn1, &context)) {
284                 asn1->has_error = true;
285         } else {
286                 switch (context) {
287                 case ASN1_APPLICATION(0):
288                         asn1_start_tag(asn1, ASN1_APPLICATION(0));
289                         asn1_check_OID(asn1, OID_SPNEGO);
290                         if (read_negTokenInit(asn1, mem_ctx, &token->negTokenInit)) {
291                                 token->type = SPNEGO_NEG_TOKEN_INIT;
292                         }
293                         asn1_end_tag(asn1);
294                         break;
295                 case ASN1_CONTEXT(1):
296                         if (read_negTokenTarg(asn1, mem_ctx, &token->negTokenTarg)) {
297                                 token->type = SPNEGO_NEG_TOKEN_TARG;
298                         }
299                         break;
300                 default:
301                         asn1->has_error = true;
302                         break;
303                 }
304         }
305
306         if (!asn1->has_error) ret = asn1->ofs;
307         asn1_free(asn1);
308
309         return ret;
310 }
311
312 ssize_t spnego_write_data(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct spnego_data *spnego)
313 {
314         struct asn1_data *asn1 = asn1_init(mem_ctx);
315         ssize_t ret = -1;
316
317         if (asn1 == NULL) {
318                 return -1;
319         }
320
321         switch (spnego->type) {
322         case SPNEGO_NEG_TOKEN_INIT:
323                 asn1_push_tag(asn1, ASN1_APPLICATION(0));
324                 asn1_write_OID(asn1, OID_SPNEGO);
325                 write_negTokenInit(asn1, &spnego->negTokenInit);
326                 asn1_pop_tag(asn1);
327                 break;
328         case SPNEGO_NEG_TOKEN_TARG:
329                 write_negTokenTarg(asn1, &spnego->negTokenTarg);
330                 break;
331         default:
332                 asn1->has_error = true;
333                 break;
334         }
335
336         if (!asn1->has_error) {
337                 *blob = data_blob_talloc(mem_ctx, asn1->data, asn1->length);
338                 ret = asn1->ofs;
339         }
340         asn1_free(asn1);
341
342         return ret;
343 }
344
345 bool spnego_free_data(struct spnego_data *spnego)
346 {
347         bool ret = true;
348
349         if (!spnego) goto out;
350
351         switch(spnego->type) {
352         case SPNEGO_NEG_TOKEN_INIT:
353                 if (spnego->negTokenInit.mechTypes) {
354                         talloc_free(spnego->negTokenInit.mechTypes);
355                 }
356                 data_blob_free(&spnego->negTokenInit.mechToken);
357                 data_blob_free(&spnego->negTokenInit.mechListMIC);
358                 talloc_free(spnego->negTokenInit.targetPrincipal);
359                 break;
360         case SPNEGO_NEG_TOKEN_TARG:
361                 if (spnego->negTokenTarg.supportedMech) {
362                         talloc_free(discard_const(spnego->negTokenTarg.supportedMech));
363                 }
364                 data_blob_free(&spnego->negTokenTarg.responseToken);
365                 data_blob_free(&spnego->negTokenTarg.mechListMIC);
366                 break;
367         default:
368                 ret = false;
369                 break;
370         }
371         ZERO_STRUCTP(spnego);
372 out:
373         return ret;
374 }
375
376 bool spnego_write_mech_types(TALLOC_CTX *mem_ctx,
377                              const char **mech_types,
378                              DATA_BLOB *blob)
379 {
380         struct asn1_data *asn1 = asn1_init(mem_ctx);
381
382         /* Write mechTypes */
383         if (mech_types && *mech_types) {
384                 int i;
385
386                 asn1_push_tag(asn1, ASN1_SEQUENCE(0));
387                 for (i = 0; mech_types[i]; i++) {
388                         asn1_write_OID(asn1, mech_types[i]);
389                 }
390                 asn1_pop_tag(asn1);
391         }
392
393         if (asn1->has_error) {
394                 asn1_free(asn1);
395                 return false;
396         }
397
398         *blob = data_blob_talloc(mem_ctx, asn1->data, asn1->length);
399         if (blob->length != asn1->length) {
400                 asn1_free(asn1);
401                 return false;
402         }
403
404         asn1_free(asn1);
405
406         return true;
407 }