2 Unix SMB/CIFS implementation.
4 RFC2478 Compliant SPNEGO implementation
6 Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
7 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include "auth/auth.h"
29 #define DBGC_CLASS DBGC_AUTH
31 enum spnego_state_position {
42 enum spnego_message_type expected_packet;
43 enum spnego_state_position state_position;
44 struct gensec_security *sub_sec_security;
47 static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security)
49 struct spnego_state *spnego_state;
51 spnego_state = talloc_p(gensec_security, struct spnego_state);
53 return NT_STATUS_NO_MEMORY;
56 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
57 spnego_state->state_position = SPNEGO_CLIENT_START;
58 spnego_state->sub_sec_security = NULL;
60 gensec_security->private_data = spnego_state;
64 static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security)
66 struct spnego_state *spnego_state;
68 spnego_state = talloc_p(gensec_security, struct spnego_state);
70 return NT_STATUS_NO_MEMORY;
73 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
74 spnego_state->state_position = SPNEGO_SERVER_START;
75 spnego_state->sub_sec_security = NULL;
77 gensec_security->private_data = spnego_state;
82 wrappers for the spnego_*() functions
84 static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security,
86 uint8_t *data, size_t length,
87 const uint8_t *whole_pdu, size_t pdu_length,
90 struct spnego_state *spnego_state = gensec_security->private_data;
92 if (spnego_state->state_position != SPNEGO_DONE
93 && spnego_state->state_position != SPNEGO_FALLBACK) {
94 return NT_STATUS_INVALID_PARAMETER;
97 return gensec_unseal_packet(spnego_state->sub_sec_security,
100 whole_pdu, pdu_length,
104 static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security,
106 const uint8_t *data, size_t length,
107 const uint8_t *whole_pdu, size_t pdu_length,
108 const DATA_BLOB *sig)
110 struct spnego_state *spnego_state = gensec_security->private_data;
112 if (spnego_state->state_position != SPNEGO_DONE
113 && spnego_state->state_position != SPNEGO_FALLBACK) {
114 return NT_STATUS_INVALID_PARAMETER;
117 return gensec_check_packet(spnego_state->sub_sec_security,
120 whole_pdu, pdu_length,
124 static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security,
126 uint8_t *data, size_t length,
127 const uint8_t *whole_pdu, size_t pdu_length,
130 struct spnego_state *spnego_state = gensec_security->private_data;
132 if (spnego_state->state_position != SPNEGO_DONE
133 && spnego_state->state_position != SPNEGO_FALLBACK) {
134 return NT_STATUS_INVALID_PARAMETER;
137 return gensec_seal_packet(spnego_state->sub_sec_security,
140 whole_pdu, pdu_length,
144 static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security,
146 const uint8_t *data, size_t length,
147 const uint8_t *whole_pdu, size_t pdu_length,
150 struct spnego_state *spnego_state = gensec_security->private_data;
152 if (spnego_state->state_position != SPNEGO_DONE
153 && spnego_state->state_position != SPNEGO_FALLBACK) {
154 return NT_STATUS_INVALID_PARAMETER;
157 return gensec_sign_packet(spnego_state->sub_sec_security,
160 whole_pdu, pdu_length,
164 static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security)
166 struct spnego_state *spnego_state = gensec_security->private_data;
168 if (spnego_state->state_position != SPNEGO_DONE
169 && spnego_state->state_position != SPNEGO_FALLBACK) {
173 return gensec_sig_size(spnego_state->sub_sec_security);
176 static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security,
177 DATA_BLOB *session_key)
179 struct spnego_state *spnego_state = gensec_security->private_data;
180 if (!spnego_state->sub_sec_security) {
181 return NT_STATUS_INVALID_PARAMETER;
184 return gensec_session_key(spnego_state->sub_sec_security,
188 static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security,
189 struct auth_session_info **session_info)
191 struct spnego_state *spnego_state = gensec_security->private_data;
192 if (!spnego_state->sub_sec_security) {
193 return NT_STATUS_INVALID_PARAMETER;
196 return gensec_session_info(spnego_state->sub_sec_security,
200 /** Fallback to another GENSEC mechanism, based on magic strings
202 * This is the 'fallback' case, where we don't get SPNEGO, and have to
203 * try all the other options (and hope they all have a magic string
207 static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security,
208 struct spnego_state *spnego_state,
209 TALLOC_CTX *out_mem_ctx,
210 const DATA_BLOB in, DATA_BLOB *out)
214 const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
215 for (i=0; i < num_ops; i++) {
217 if (!all_ops[i]->oid) {
220 if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[i]->oid) == 0) {
224 nt_status = gensec_subcontext_start(gensec_security,
225 &spnego_state->sub_sec_security);
226 if (!NT_STATUS_IS_OK(nt_status)) {
229 /* select the sub context */
230 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
232 if (!NT_STATUS_IS_OK(nt_status)) {
233 gensec_end(&spnego_state->sub_sec_security);
236 nt_status = gensec_update(spnego_state->sub_sec_security,
237 out_mem_ctx, in, out);
238 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
239 spnego_state->state_position = SPNEGO_FALLBACK;
242 gensec_end(&spnego_state->sub_sec_security);
244 DEBUG(1, ("Failed to parse SPNEGO request\n"));
245 return NT_STATUS_INVALID_PARAMETER;
249 static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_security,
250 struct spnego_state *spnego_state,
251 TALLOC_CTX *out_mem_ctx,
252 const char **mechType,
253 const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out)
257 DATA_BLOB null_data_blob = data_blob(NULL,0);
259 for (i=0; mechType && mechType[i]; i++) {
260 nt_status = gensec_subcontext_start(gensec_security,
261 &spnego_state->sub_sec_security);
262 if (!NT_STATUS_IS_OK(nt_status)) {
265 /* select the sub context */
266 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
268 if (!NT_STATUS_IS_OK(nt_status)) {
269 gensec_end(&spnego_state->sub_sec_security);
274 nt_status = gensec_update(spnego_state->sub_sec_security,
279 /* only get the helping start blob for the first OID */
280 nt_status = gensec_update(spnego_state->sub_sec_security,
285 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
286 DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n",
287 spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
288 gensec_end(&spnego_state->sub_sec_security);
292 if (!mechType || !mechType[i]) {
293 DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
295 return NT_STATUS_INVALID_PARAMETER;
298 /** create a client negTokenInit
300 * This is the case, where the client is the first one who sends data
303 static NTSTATUS gensec_spnego_client_negTokenInit(struct gensec_security *gensec_security,
304 struct spnego_state *spnego_state,
305 TALLOC_CTX *out_mem_ctx,
306 const DATA_BLOB in, DATA_BLOB *out)
308 DATA_BLOB null_data_blob = data_blob(NULL,0);
310 const char **mechTypes = NULL;
311 DATA_BLOB unwrapped_out = data_blob(NULL,0);
313 mechTypes = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
316 DEBUG(1, ("no GENSEC OID backends available\n"));
317 return NT_STATUS_INVALID_PARAMETER;
320 nt_status = gensec_subcontext_start(gensec_security,
321 &spnego_state->sub_sec_security);
322 if (!NT_STATUS_IS_OK(nt_status)) {
325 /* select our preferred mech */
326 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
328 if (!NT_STATUS_IS_OK(nt_status)) {
329 gensec_end(&spnego_state->sub_sec_security);
332 nt_status = gensec_update(spnego_state->sub_sec_security,
333 out_mem_ctx, in, &unwrapped_out);
334 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
335 struct spnego_data spnego_out;
336 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
337 spnego_out.negTokenInit.mechTypes = mechTypes;
338 spnego_out.negTokenInit.reqFlags = 0;
339 spnego_out.negTokenInit.mechListMIC = null_data_blob;
340 spnego_out.negTokenInit.mechToken = unwrapped_out;
342 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
343 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
344 return NT_STATUS_INVALID_PARAMETER;
348 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
349 spnego_state->state_position = SPNEGO_CLIENT_TARG;
352 gensec_end(&spnego_state->sub_sec_security);
354 DEBUG(1, ("Failed to setup SPNEGO netTokenInit request\n"));
355 return NT_STATUS_INVALID_PARAMETER;
359 /** create a client negTokenTarg
361 * This is the case, where the client is the first one who sends data
364 static NTSTATUS gensec_spnego_server_negTokenTarg(struct gensec_security *gensec_security,
365 struct spnego_state *spnego_state,
366 TALLOC_CTX *out_mem_ctx,
368 const DATA_BLOB unwrapped_out, DATA_BLOB *out)
370 struct spnego_data spnego_out;
371 DATA_BLOB null_data_blob = data_blob(NULL, 0);
374 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
375 spnego_out.negTokenTarg.responseToken = unwrapped_out;
376 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
377 spnego_out.negTokenTarg.supportedMech = NULL;
379 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
380 spnego_out.negTokenTarg.supportedMech
381 = spnego_state->sub_sec_security->ops->oid;
382 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
383 spnego_state->state_position = SPNEGO_SERVER_TARG;
384 } else if (NT_STATUS_IS_OK(nt_status)) {
385 if (unwrapped_out.data) {
386 spnego_out.negTokenTarg.supportedMech
387 = spnego_state->sub_sec_security->ops->oid;
389 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
390 spnego_state->state_position = SPNEGO_DONE;
392 spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
393 DEBUG(1, ("SPNEGO login failed: %s\n", nt_errstr(nt_status)));
394 spnego_state->state_position = SPNEGO_DONE;
397 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
398 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
399 return NT_STATUS_INVALID_PARAMETER;
402 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
408 static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
409 const DATA_BLOB in, DATA_BLOB *out)
411 struct spnego_state *spnego_state = gensec_security->private_data;
412 DATA_BLOB null_data_blob = data_blob(NULL, 0);
413 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
414 struct spnego_data spnego_out;
415 struct spnego_data spnego;
419 *out = data_blob(NULL, 0);
422 out_mem_ctx = spnego_state;
425 /* and switch into the state machine */
427 switch (spnego_state->state_position) {
428 case SPNEGO_FALLBACK:
429 return gensec_update(spnego_state->sub_sec_security,
430 out_mem_ctx, in, out);
431 case SPNEGO_SERVER_START:
436 len = spnego_read_data(in, &spnego);
438 return gensec_spnego_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, in, out);
440 /* client sent NegTargetInit, we send NegTokenTarg */
442 /* OK, so it's real SPNEGO, check the packet's the one we expect */
443 if (spnego.type != spnego_state->expected_packet) {
444 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
445 spnego_state->expected_packet));
446 dump_data(1, in.data, in.length);
447 spnego_free_data(&spnego);
448 return NT_STATUS_INVALID_PARAMETER;
451 nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
454 spnego.negTokenInit.mechTypes,
455 spnego.negTokenInit.mechToken,
458 nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
465 spnego_free_data(&spnego);
469 const char **mechlist = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
470 const char *mechListMIC;
472 mechListMIC = talloc_asprintf(out_mem_ctx,"%s$@%s",
476 return NT_STATUS_NO_MEMORY;
479 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
480 spnego_out.negTokenInit.mechTypes = mechlist;
481 spnego_out.negTokenInit.reqFlags = 0;
482 spnego_out.negTokenInit.mechListMIC = data_blob_string_const(mechListMIC);
483 spnego_out.negTokenInit.mechToken = unwrapped_out;
485 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
486 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
487 return NT_STATUS_INVALID_PARAMETER;
491 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
492 spnego_state->state_position = SPNEGO_SERVER_TARG;
494 return NT_STATUS_MORE_PROCESSING_REQUIRED;
498 case SPNEGO_CLIENT_START:
500 /* The server offers a list of mechanisms */
502 const char *my_mechs[] = {NULL, NULL};
503 NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
506 /* client to produce negTokenInit */
507 return gensec_spnego_client_negTokenInit(gensec_security, spnego_state, out_mem_ctx, in, out);
510 len = spnego_read_data(in, &spnego);
513 DEBUG(1, ("Invalid SPNEGO request:\n"));
514 dump_data(1, in.data, in.length);
515 return NT_STATUS_INVALID_PARAMETER;
518 /* OK, so it's real SPNEGO, check the packet's the one we expect */
519 if (spnego.type != spnego_state->expected_packet) {
520 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
521 spnego_state->expected_packet));
522 dump_data(1, in.data, in.length);
523 spnego_free_data(&spnego);
524 return NT_STATUS_INVALID_PARAMETER;
527 if (spnego.negTokenInit.targetPrincipal) {
528 DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal));
529 nt_status = gensec_set_target_principal(gensec_security,
530 spnego.negTokenInit.targetPrincipal);
531 if (!NT_STATUS_IS_OK(nt_status)) {
532 spnego_free_data(&spnego);
537 nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
540 spnego.negTokenInit.mechTypes,
541 spnego.negTokenInit.mechToken,
544 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
545 spnego_free_data(&spnego);
550 my_mechs[0] = spnego_state->sub_sec_security->ops->oid;
552 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
553 spnego_out.negTokenInit.mechTypes = my_mechs;
554 spnego_out.negTokenInit.reqFlags = 0;
555 spnego_out.negTokenInit.mechListMIC = null_data_blob;
556 spnego_out.negTokenInit.mechToken = unwrapped_out;
558 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
559 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
560 return NT_STATUS_INVALID_PARAMETER;
564 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
565 spnego_state->state_position = SPNEGO_CLIENT_TARG;
567 return NT_STATUS_MORE_PROCESSING_REQUIRED;
569 case SPNEGO_SERVER_TARG:
573 return NT_STATUS_INVALID_PARAMETER;
576 len = spnego_read_data(in, &spnego);
579 DEBUG(1, ("Invalid SPNEGO request:\n"));
580 dump_data(1, in.data, in.length);
581 return NT_STATUS_INVALID_PARAMETER;
584 /* OK, so it's real SPNEGO, check the packet's the one we expect */
585 if (spnego.type != spnego_state->expected_packet) {
586 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
587 spnego_state->expected_packet));
588 dump_data(1, in.data, in.length);
589 spnego_free_data(&spnego);
590 return NT_STATUS_INVALID_PARAMETER;
593 nt_status = gensec_update(spnego_state->sub_sec_security,
595 spnego.negTokenTarg.responseToken,
598 nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
605 spnego_free_data(&spnego);
609 case SPNEGO_CLIENT_TARG:
613 return NT_STATUS_INVALID_PARAMETER;
616 len = spnego_read_data(in, &spnego);
619 DEBUG(1, ("Invalid SPNEGO request:\n"));
620 dump_data(1, in.data, in.length);
621 return NT_STATUS_INVALID_PARAMETER;
624 /* OK, so it's real SPNEGO, check the packet's the one we expect */
625 if (spnego.type != spnego_state->expected_packet) {
626 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
627 spnego_state->expected_packet));
628 dump_data(1, in.data, in.length);
629 spnego_free_data(&spnego);
630 return NT_STATUS_INVALID_PARAMETER;
633 if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
634 return NT_STATUS_ACCESS_DENIED;
637 nt_status = gensec_update(spnego_state->sub_sec_security,
639 spnego.negTokenTarg.responseToken,
643 if (NT_STATUS_IS_OK(nt_status)
644 && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) {
645 DEBUG(1,("gensec_update ok but not accepted\n"));
646 nt_status = NT_STATUS_INVALID_PARAMETER;
649 spnego_free_data(&spnego);
651 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
653 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
654 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
655 spnego_out.negTokenTarg.supportedMech = NULL;
656 spnego_out.negTokenTarg.responseToken = unwrapped_out;
657 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
659 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
660 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
661 return NT_STATUS_INVALID_PARAMETER;
664 spnego_state->state_position = SPNEGO_CLIENT_TARG;
665 } else if (NT_STATUS_IS_OK(nt_status)) {
666 /* all done - server has accepted, and we agree */
668 if (unwrapped_out.length) {
669 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
670 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
671 spnego_out.negTokenTarg.supportedMech = NULL;
672 spnego_out.negTokenTarg.responseToken = unwrapped_out;
673 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
675 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
676 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
677 return NT_STATUS_INVALID_PARAMETER;
680 *out = null_data_blob;
683 spnego_state->state_position = SPNEGO_DONE;
685 DEBUG(1, ("SPNEGO(%s) login failed: %s\n",
686 spnego_state->sub_sec_security->ops->name,
687 nt_errstr(nt_status)));
694 return NT_STATUS_INVALID_PARAMETER;
697 static void gensec_spnego_end(struct gensec_security *gensec_security)
699 struct spnego_state *spnego_state = gensec_security->private_data;
701 if (spnego_state->sub_sec_security) {
702 gensec_end(&spnego_state->sub_sec_security);
705 talloc_free(spnego_state);
707 gensec_security->private_data = NULL;
710 static const struct gensec_security_ops gensec_spnego_security_ops = {
712 .sasl_name = "GSS-SPNEGO",
713 .auth_type = DCERPC_AUTH_TYPE_SPNEGO,
714 .oid = GENSEC_OID_SPNEGO,
715 .client_start = gensec_spnego_client_start,
716 .server_start = gensec_spnego_server_start,
717 .update = gensec_spnego_update,
718 .seal_packet = gensec_spnego_seal_packet,
719 .sign_packet = gensec_spnego_sign_packet,
720 .sig_size = gensec_spnego_sig_size,
721 .check_packet = gensec_spnego_check_packet,
722 .unseal_packet = gensec_spnego_unseal_packet,
723 .session_key = gensec_spnego_session_key,
724 .session_info = gensec_spnego_session_info,
725 .end = gensec_spnego_end
728 NTSTATUS gensec_spnego_init(void)
731 ret = gensec_register(&gensec_spnego_security_ops);
732 if (!NT_STATUS_IS_OK(ret)) {
733 DEBUG(0,("Failed to register '%s' gensec backend!\n",
734 gensec_spnego_security_ops.name));