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;
48 static int gensec_spnego_destroy(void *ptr)
50 struct spnego_state *spnego_state = ptr;
52 if (spnego_state->sub_sec_security) {
53 gensec_end(&spnego_state->sub_sec_security);
58 static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security)
60 struct spnego_state *spnego_state;
62 spnego_state = talloc_p(gensec_security, struct spnego_state);
64 return NT_STATUS_NO_MEMORY;
67 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
68 spnego_state->state_position = SPNEGO_CLIENT_START;
69 spnego_state->sub_sec_security = NULL;
71 talloc_set_destructor(spnego_state, gensec_spnego_destroy);
73 gensec_security->private_data = spnego_state;
77 static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security)
79 struct spnego_state *spnego_state;
81 spnego_state = talloc_p(gensec_security, struct spnego_state);
83 return NT_STATUS_NO_MEMORY;
86 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
87 spnego_state->state_position = SPNEGO_SERVER_START;
88 spnego_state->sub_sec_security = NULL;
90 talloc_set_destructor(spnego_state, gensec_spnego_destroy);
92 gensec_security->private_data = spnego_state;
97 wrappers for the spnego_*() functions
99 static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security,
101 uint8_t *data, size_t length,
102 const uint8_t *whole_pdu, size_t pdu_length,
105 struct spnego_state *spnego_state = gensec_security->private_data;
107 if (spnego_state->state_position != SPNEGO_DONE
108 && spnego_state->state_position != SPNEGO_FALLBACK) {
109 return NT_STATUS_INVALID_PARAMETER;
112 return gensec_unseal_packet(spnego_state->sub_sec_security,
115 whole_pdu, pdu_length,
119 static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security,
121 const uint8_t *data, size_t length,
122 const uint8_t *whole_pdu, size_t pdu_length,
123 const DATA_BLOB *sig)
125 struct spnego_state *spnego_state = gensec_security->private_data;
127 if (spnego_state->state_position != SPNEGO_DONE
128 && spnego_state->state_position != SPNEGO_FALLBACK) {
129 return NT_STATUS_INVALID_PARAMETER;
132 return gensec_check_packet(spnego_state->sub_sec_security,
135 whole_pdu, pdu_length,
139 static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security,
141 uint8_t *data, size_t length,
142 const uint8_t *whole_pdu, size_t pdu_length,
145 struct spnego_state *spnego_state = gensec_security->private_data;
147 if (spnego_state->state_position != SPNEGO_DONE
148 && spnego_state->state_position != SPNEGO_FALLBACK) {
149 return NT_STATUS_INVALID_PARAMETER;
152 return gensec_seal_packet(spnego_state->sub_sec_security,
155 whole_pdu, pdu_length,
159 static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security,
161 const uint8_t *data, size_t length,
162 const uint8_t *whole_pdu, size_t pdu_length,
165 struct spnego_state *spnego_state = gensec_security->private_data;
167 if (spnego_state->state_position != SPNEGO_DONE
168 && spnego_state->state_position != SPNEGO_FALLBACK) {
169 return NT_STATUS_INVALID_PARAMETER;
172 return gensec_sign_packet(spnego_state->sub_sec_security,
175 whole_pdu, pdu_length,
179 static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security)
181 struct spnego_state *spnego_state = gensec_security->private_data;
183 if (spnego_state->state_position != SPNEGO_DONE
184 && spnego_state->state_position != SPNEGO_FALLBACK) {
188 return gensec_sig_size(spnego_state->sub_sec_security);
191 static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security,
192 DATA_BLOB *session_key)
194 struct spnego_state *spnego_state = gensec_security->private_data;
195 if (!spnego_state->sub_sec_security) {
196 return NT_STATUS_INVALID_PARAMETER;
199 return gensec_session_key(spnego_state->sub_sec_security,
203 static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security,
204 struct auth_session_info **session_info)
206 struct spnego_state *spnego_state = gensec_security->private_data;
207 if (!spnego_state->sub_sec_security) {
208 return NT_STATUS_INVALID_PARAMETER;
211 return gensec_session_info(spnego_state->sub_sec_security,
215 /** Fallback to another GENSEC mechanism, based on magic strings
217 * This is the 'fallback' case, where we don't get SPNEGO, and have to
218 * try all the other options (and hope they all have a magic string
222 static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security,
223 struct spnego_state *spnego_state,
224 TALLOC_CTX *out_mem_ctx,
225 const DATA_BLOB in, DATA_BLOB *out)
229 const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
230 for (i=0; i < num_ops; i++) {
232 if (!all_ops[i]->oid) {
235 if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[i]->oid) == 0) {
239 nt_status = gensec_subcontext_start(spnego_state,
241 &spnego_state->sub_sec_security);
242 if (!NT_STATUS_IS_OK(nt_status)) {
245 /* select the sub context */
246 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
248 if (!NT_STATUS_IS_OK(nt_status)) {
249 gensec_end(&spnego_state->sub_sec_security);
252 nt_status = gensec_update(spnego_state->sub_sec_security,
253 out_mem_ctx, in, out);
254 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
255 spnego_state->state_position = SPNEGO_FALLBACK;
258 gensec_end(&spnego_state->sub_sec_security);
260 DEBUG(1, ("Failed to parse SPNEGO request\n"));
261 return NT_STATUS_INVALID_PARAMETER;
265 static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_security,
266 struct spnego_state *spnego_state,
267 TALLOC_CTX *out_mem_ctx,
268 const char **mechType,
269 const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out)
273 DATA_BLOB null_data_blob = data_blob(NULL,0);
275 for (i=0; mechType && mechType[i]; i++) {
276 nt_status = gensec_subcontext_start(spnego_state,
278 &spnego_state->sub_sec_security);
279 if (!NT_STATUS_IS_OK(nt_status)) {
282 /* select the sub context */
283 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
285 if (!NT_STATUS_IS_OK(nt_status)) {
286 gensec_end(&spnego_state->sub_sec_security);
291 nt_status = gensec_update(spnego_state->sub_sec_security,
296 /* only get the helping start blob for the first OID */
297 nt_status = gensec_update(spnego_state->sub_sec_security,
302 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
303 DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n",
304 spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
305 gensec_end(&spnego_state->sub_sec_security);
309 if (!mechType || !mechType[i]) {
310 DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
312 return NT_STATUS_INVALID_PARAMETER;
315 /** create a client negTokenInit
317 * This is the case, where the client is the first one who sends data
320 static NTSTATUS gensec_spnego_client_negTokenInit(struct gensec_security *gensec_security,
321 struct spnego_state *spnego_state,
322 TALLOC_CTX *out_mem_ctx,
323 const DATA_BLOB in, DATA_BLOB *out)
325 DATA_BLOB null_data_blob = data_blob(NULL,0);
327 const char **mechTypes = NULL;
328 DATA_BLOB unwrapped_out = data_blob(NULL,0);
330 mechTypes = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
333 DEBUG(1, ("no GENSEC OID backends available\n"));
334 return NT_STATUS_INVALID_PARAMETER;
337 nt_status = gensec_subcontext_start(spnego_state,
339 &spnego_state->sub_sec_security);
340 if (!NT_STATUS_IS_OK(nt_status)) {
343 /* select our preferred mech */
344 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
346 if (!NT_STATUS_IS_OK(nt_status)) {
347 gensec_end(&spnego_state->sub_sec_security);
350 nt_status = gensec_update(spnego_state->sub_sec_security,
351 out_mem_ctx, in, &unwrapped_out);
352 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
353 struct spnego_data spnego_out;
354 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
355 spnego_out.negTokenInit.mechTypes = mechTypes;
356 spnego_out.negTokenInit.reqFlags = 0;
357 spnego_out.negTokenInit.mechListMIC = null_data_blob;
358 spnego_out.negTokenInit.mechToken = unwrapped_out;
360 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
361 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
362 return NT_STATUS_INVALID_PARAMETER;
366 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
367 spnego_state->state_position = SPNEGO_CLIENT_TARG;
370 gensec_end(&spnego_state->sub_sec_security);
372 DEBUG(1, ("Failed to setup SPNEGO netTokenInit request\n"));
373 return NT_STATUS_INVALID_PARAMETER;
377 /** create a client negTokenTarg
379 * This is the case, where the client is the first one who sends data
382 static NTSTATUS gensec_spnego_server_negTokenTarg(struct gensec_security *gensec_security,
383 struct spnego_state *spnego_state,
384 TALLOC_CTX *out_mem_ctx,
386 const DATA_BLOB unwrapped_out, DATA_BLOB *out)
388 struct spnego_data spnego_out;
389 DATA_BLOB null_data_blob = data_blob(NULL, 0);
392 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
393 spnego_out.negTokenTarg.responseToken = unwrapped_out;
394 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
395 spnego_out.negTokenTarg.supportedMech = NULL;
397 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
398 spnego_out.negTokenTarg.supportedMech
399 = spnego_state->sub_sec_security->ops->oid;
400 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
401 spnego_state->state_position = SPNEGO_SERVER_TARG;
402 } else if (NT_STATUS_IS_OK(nt_status)) {
403 if (unwrapped_out.data) {
404 spnego_out.negTokenTarg.supportedMech
405 = spnego_state->sub_sec_security->ops->oid;
407 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
408 spnego_state->state_position = SPNEGO_DONE;
410 spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
411 DEBUG(1, ("SPNEGO login failed: %s\n", nt_errstr(nt_status)));
412 spnego_state->state_position = SPNEGO_DONE;
415 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
416 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
417 return NT_STATUS_INVALID_PARAMETER;
420 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
426 static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
427 const DATA_BLOB in, DATA_BLOB *out)
429 struct spnego_state *spnego_state = gensec_security->private_data;
430 DATA_BLOB null_data_blob = data_blob(NULL, 0);
431 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
432 struct spnego_data spnego_out;
433 struct spnego_data spnego;
437 *out = data_blob(NULL, 0);
440 out_mem_ctx = spnego_state;
443 /* and switch into the state machine */
445 switch (spnego_state->state_position) {
446 case SPNEGO_FALLBACK:
447 return gensec_update(spnego_state->sub_sec_security,
448 out_mem_ctx, in, out);
449 case SPNEGO_SERVER_START:
454 len = spnego_read_data(in, &spnego);
456 return gensec_spnego_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, in, out);
458 /* client sent NegTargetInit, we send NegTokenTarg */
460 /* OK, so it's real SPNEGO, check the packet's the one we expect */
461 if (spnego.type != spnego_state->expected_packet) {
462 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
463 spnego_state->expected_packet));
464 dump_data(1, in.data, in.length);
465 spnego_free_data(&spnego);
466 return NT_STATUS_INVALID_PARAMETER;
469 nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
472 spnego.negTokenInit.mechTypes,
473 spnego.negTokenInit.mechToken,
476 nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
483 spnego_free_data(&spnego);
487 const char **mechlist = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO);
489 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
490 spnego_out.negTokenInit.mechTypes = mechlist;
491 spnego_out.negTokenInit.reqFlags = 0;
492 spnego_out.negTokenInit.mechListMIC
493 = data_blob_string_const(gensec_get_target_principal(gensec_security));
494 spnego_out.negTokenInit.mechToken = unwrapped_out;
496 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
497 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
498 return NT_STATUS_INVALID_PARAMETER;
502 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
503 spnego_state->state_position = SPNEGO_SERVER_TARG;
505 return NT_STATUS_MORE_PROCESSING_REQUIRED;
509 case SPNEGO_CLIENT_START:
511 /* The server offers a list of mechanisms */
513 const char *my_mechs[] = {NULL, NULL};
514 NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
517 /* client to produce negTokenInit */
518 return gensec_spnego_client_negTokenInit(gensec_security, spnego_state, out_mem_ctx, in, out);
521 len = spnego_read_data(in, &spnego);
524 DEBUG(1, ("Invalid SPNEGO request:\n"));
525 dump_data(1, in.data, in.length);
526 return NT_STATUS_INVALID_PARAMETER;
529 /* OK, so it's real SPNEGO, check the packet's the one we expect */
530 if (spnego.type != spnego_state->expected_packet) {
531 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
532 spnego_state->expected_packet));
533 dump_data(1, in.data, in.length);
534 spnego_free_data(&spnego);
535 return NT_STATUS_INVALID_PARAMETER;
538 if (spnego.negTokenInit.targetPrincipal) {
539 DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal));
540 nt_status = gensec_set_target_principal(gensec_security,
541 spnego.negTokenInit.targetPrincipal);
542 if (!NT_STATUS_IS_OK(nt_status)) {
543 spnego_free_data(&spnego);
548 nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
551 spnego.negTokenInit.mechTypes,
552 spnego.negTokenInit.mechToken,
555 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
556 spnego_free_data(&spnego);
561 my_mechs[0] = spnego_state->sub_sec_security->ops->oid;
563 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
564 spnego_out.negTokenInit.mechTypes = my_mechs;
565 spnego_out.negTokenInit.reqFlags = 0;
566 spnego_out.negTokenInit.mechListMIC = null_data_blob;
567 spnego_out.negTokenInit.mechToken = unwrapped_out;
569 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
570 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
571 return NT_STATUS_INVALID_PARAMETER;
575 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
576 spnego_state->state_position = SPNEGO_CLIENT_TARG;
578 return NT_STATUS_MORE_PROCESSING_REQUIRED;
580 case SPNEGO_SERVER_TARG:
584 return NT_STATUS_INVALID_PARAMETER;
587 len = spnego_read_data(in, &spnego);
590 DEBUG(1, ("Invalid SPNEGO request:\n"));
591 dump_data(1, in.data, in.length);
592 return NT_STATUS_INVALID_PARAMETER;
595 /* OK, so it's real SPNEGO, check the packet's the one we expect */
596 if (spnego.type != spnego_state->expected_packet) {
597 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
598 spnego_state->expected_packet));
599 dump_data(1, in.data, in.length);
600 spnego_free_data(&spnego);
601 return NT_STATUS_INVALID_PARAMETER;
604 nt_status = gensec_update(spnego_state->sub_sec_security,
606 spnego.negTokenTarg.responseToken,
609 nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
616 spnego_free_data(&spnego);
620 case SPNEGO_CLIENT_TARG:
624 return NT_STATUS_INVALID_PARAMETER;
627 len = spnego_read_data(in, &spnego);
630 DEBUG(1, ("Invalid SPNEGO request:\n"));
631 dump_data(1, in.data, in.length);
632 return NT_STATUS_INVALID_PARAMETER;
635 /* OK, so it's real SPNEGO, check the packet's the one we expect */
636 if (spnego.type != spnego_state->expected_packet) {
637 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
638 spnego_state->expected_packet));
639 dump_data(1, in.data, in.length);
640 spnego_free_data(&spnego);
641 return NT_STATUS_INVALID_PARAMETER;
644 if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
645 return NT_STATUS_ACCESS_DENIED;
648 nt_status = gensec_update(spnego_state->sub_sec_security,
650 spnego.negTokenTarg.responseToken,
654 if (NT_STATUS_IS_OK(nt_status)
655 && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) {
656 DEBUG(1,("gensec_update ok but not accepted\n"));
657 nt_status = NT_STATUS_INVALID_PARAMETER;
660 spnego_free_data(&spnego);
662 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
664 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
665 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
666 spnego_out.negTokenTarg.supportedMech = NULL;
667 spnego_out.negTokenTarg.responseToken = unwrapped_out;
668 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
670 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
671 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
672 return NT_STATUS_INVALID_PARAMETER;
675 spnego_state->state_position = SPNEGO_CLIENT_TARG;
676 } else if (NT_STATUS_IS_OK(nt_status)) {
677 /* all done - server has accepted, and we agree */
679 if (unwrapped_out.length) {
680 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
681 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
682 spnego_out.negTokenTarg.supportedMech = NULL;
683 spnego_out.negTokenTarg.responseToken = unwrapped_out;
684 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
686 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
687 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
688 return NT_STATUS_INVALID_PARAMETER;
691 *out = null_data_blob;
694 spnego_state->state_position = SPNEGO_DONE;
696 DEBUG(1, ("SPNEGO(%s) login failed: %s\n",
697 spnego_state->sub_sec_security->ops->name,
698 nt_errstr(nt_status)));
705 return NT_STATUS_INVALID_PARAMETER;
708 static const struct gensec_security_ops gensec_spnego_security_ops = {
710 .sasl_name = "GSS-SPNEGO",
711 .auth_type = DCERPC_AUTH_TYPE_SPNEGO,
712 .oid = GENSEC_OID_SPNEGO,
713 .client_start = gensec_spnego_client_start,
714 .server_start = gensec_spnego_server_start,
715 .update = gensec_spnego_update,
716 .seal_packet = gensec_spnego_seal_packet,
717 .sign_packet = gensec_spnego_sign_packet,
718 .sig_size = gensec_spnego_sig_size,
719 .check_packet = gensec_spnego_check_packet,
720 .unseal_packet = gensec_spnego_unseal_packet,
721 .session_key = gensec_spnego_session_key,
722 .session_info = gensec_spnego_session_info,
725 NTSTATUS gensec_spnego_init(void)
728 ret = gensec_register(&gensec_spnego_security_ops);
729 if (!NT_STATUS_IS_OK(ret)) {
730 DEBUG(0,("Failed to register '%s' gensec backend!\n",
731 gensec_spnego_security_ops.name));