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"
30 #define DBGC_CLASS DBGC_AUTH
32 enum spnego_state_position {
43 enum spnego_message_type expected_packet;
44 enum spnego_state_position state_position;
45 struct gensec_security *sub_sec_security;
48 static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security)
50 struct spnego_state *spnego_state;
52 spnego_state = talloc_p(gensec_security, struct spnego_state);
54 return NT_STATUS_NO_MEMORY;
57 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
58 spnego_state->state_position = SPNEGO_CLIENT_START;
59 spnego_state->sub_sec_security = NULL;
61 gensec_security->private_data = spnego_state;
65 static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security)
67 struct spnego_state *spnego_state;
69 spnego_state = talloc_p(gensec_security, struct spnego_state);
71 return NT_STATUS_NO_MEMORY;
74 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
75 spnego_state->state_position = SPNEGO_SERVER_START;
76 spnego_state->sub_sec_security = NULL;
78 gensec_security->private_data = spnego_state;
83 wrappers for the spnego_*() functions
85 static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security,
87 uint8_t *data, size_t length,
88 const uint8_t *whole_pdu, size_t pdu_length,
91 struct spnego_state *spnego_state = gensec_security->private_data;
93 if (spnego_state->state_position != SPNEGO_DONE
94 && spnego_state->state_position != SPNEGO_FALLBACK) {
95 return NT_STATUS_INVALID_PARAMETER;
98 return gensec_unseal_packet(spnego_state->sub_sec_security,
101 whole_pdu, pdu_length,
105 static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security,
107 const uint8_t *data, size_t length,
108 const uint8_t *whole_pdu, size_t pdu_length,
109 const DATA_BLOB *sig)
111 struct spnego_state *spnego_state = gensec_security->private_data;
113 if (spnego_state->state_position != SPNEGO_DONE
114 && spnego_state->state_position != SPNEGO_FALLBACK) {
115 return NT_STATUS_INVALID_PARAMETER;
118 return gensec_check_packet(spnego_state->sub_sec_security,
121 whole_pdu, pdu_length,
125 static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security,
127 uint8_t *data, size_t length,
128 const uint8_t *whole_pdu, size_t pdu_length,
131 struct spnego_state *spnego_state = gensec_security->private_data;
133 if (spnego_state->state_position != SPNEGO_DONE
134 && spnego_state->state_position != SPNEGO_FALLBACK) {
135 return NT_STATUS_INVALID_PARAMETER;
138 return gensec_seal_packet(spnego_state->sub_sec_security,
141 whole_pdu, pdu_length,
145 static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security,
147 const uint8_t *data, size_t length,
148 const uint8_t *whole_pdu, size_t pdu_length,
151 struct spnego_state *spnego_state = gensec_security->private_data;
153 if (spnego_state->state_position != SPNEGO_DONE
154 && spnego_state->state_position != SPNEGO_FALLBACK) {
155 return NT_STATUS_INVALID_PARAMETER;
158 return gensec_sign_packet(spnego_state->sub_sec_security,
161 whole_pdu, pdu_length,
165 static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security)
167 struct spnego_state *spnego_state = gensec_security->private_data;
169 if (spnego_state->state_position != SPNEGO_DONE
170 && spnego_state->state_position != SPNEGO_FALLBACK) {
174 return gensec_sig_size(spnego_state->sub_sec_security);
177 static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security,
178 DATA_BLOB *session_key)
180 struct spnego_state *spnego_state = gensec_security->private_data;
181 if (!spnego_state->sub_sec_security) {
182 return NT_STATUS_INVALID_PARAMETER;
185 return gensec_session_key(spnego_state->sub_sec_security,
189 static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security,
190 struct auth_session_info **session_info)
192 struct spnego_state *spnego_state = gensec_security->private_data;
193 if (!spnego_state->sub_sec_security) {
194 return NT_STATUS_INVALID_PARAMETER;
197 return gensec_session_info(spnego_state->sub_sec_security,
201 /** Fallback to another GENSEC mechanism, based on magic strings
203 * This is the 'fallback' case, where we don't get SPNEGO, and have to
204 * try all the other options (and hope they all have a magic string
208 static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security,
209 struct spnego_state *spnego_state,
210 TALLOC_CTX *out_mem_ctx,
211 const DATA_BLOB in, DATA_BLOB *out)
215 const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
216 for (i=0; i < num_ops; i++) {
218 if (!all_ops[i]->oid) {
221 nt_status = gensec_subcontext_start(gensec_security,
222 &spnego_state->sub_sec_security);
223 if (!NT_STATUS_IS_OK(nt_status)) {
226 /* select the sub context */
227 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
229 if (!NT_STATUS_IS_OK(nt_status)) {
230 gensec_end(&spnego_state->sub_sec_security);
233 nt_status = gensec_update(spnego_state->sub_sec_security,
234 out_mem_ctx, in, out);
235 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
236 spnego_state->state_position = SPNEGO_FALLBACK;
239 gensec_end(&spnego_state->sub_sec_security);
241 DEBUG(1, ("Failed to parse SPNEGO request\n"));
242 return NT_STATUS_INVALID_PARAMETER;
246 static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_security,
247 struct spnego_state *spnego_state,
248 TALLOC_CTX *out_mem_ctx,
249 const char **mechType,
250 const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out)
254 DATA_BLOB null_data_blob = data_blob(NULL,0);
256 for (i=0; mechType && mechType[i]; i++) {
257 nt_status = gensec_subcontext_start(gensec_security,
258 &spnego_state->sub_sec_security);
259 if (!NT_STATUS_IS_OK(nt_status)) {
262 /* select the sub context */
263 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
265 if (!NT_STATUS_IS_OK(nt_status)) {
266 gensec_end(&spnego_state->sub_sec_security);
271 nt_status = gensec_update(spnego_state->sub_sec_security,
276 /* only get the helping start blob for the first OID */
277 nt_status = gensec_update(spnego_state->sub_sec_security,
282 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
283 DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n",
284 spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
285 gensec_end(&spnego_state->sub_sec_security);
289 if (!mechType || !mechType[i]) {
290 DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
292 return NT_STATUS_INVALID_PARAMETER;
295 /** create a client negTokenInit
297 * This is the case, where the client is the first one who sends data
300 static NTSTATUS gensec_spnego_client_negTokenInit(struct gensec_security *gensec_security,
301 struct spnego_state *spnego_state,
302 TALLOC_CTX *out_mem_ctx,
303 const DATA_BLOB in, DATA_BLOB *out)
305 DATA_BLOB null_data_blob = data_blob(NULL,0);
307 const char **mechTypes = NULL;
308 DATA_BLOB unwrapped_out = data_blob(NULL,0);
310 mechTypes = gensec_security_oids(out_mem_ctx, OID_SPNEGO);
313 DEBUG(1, ("no GENSEC OID backends available\n"));
314 return NT_STATUS_INVALID_PARAMETER;
317 nt_status = gensec_subcontext_start(gensec_security,
318 &spnego_state->sub_sec_security);
319 if (!NT_STATUS_IS_OK(nt_status)) {
322 /* select our preferred mech */
323 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
325 if (!NT_STATUS_IS_OK(nt_status)) {
326 gensec_end(&spnego_state->sub_sec_security);
329 nt_status = gensec_update(spnego_state->sub_sec_security,
330 out_mem_ctx, in, &unwrapped_out);
331 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
332 struct spnego_data spnego_out;
333 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
334 spnego_out.negTokenInit.mechTypes = mechTypes;
335 spnego_out.negTokenInit.reqFlags = 0;
336 spnego_out.negTokenInit.mechListMIC = null_data_blob;
337 spnego_out.negTokenInit.mechToken = unwrapped_out;
339 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
340 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
341 return NT_STATUS_INVALID_PARAMETER;
345 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
346 spnego_state->state_position = SPNEGO_CLIENT_TARG;
349 gensec_end(&spnego_state->sub_sec_security);
351 DEBUG(1, ("Failed to setup SPNEGO netTokenInit request\n"));
352 return NT_STATUS_INVALID_PARAMETER;
356 /** create a client negTokenTarg
358 * This is the case, where the client is the first one who sends data
361 static NTSTATUS gensec_spnego_server_negTokenTarg(struct gensec_security *gensec_security,
362 struct spnego_state *spnego_state,
363 TALLOC_CTX *out_mem_ctx,
365 const DATA_BLOB unwrapped_out, DATA_BLOB *out)
367 struct spnego_data spnego_out;
368 DATA_BLOB null_data_blob = data_blob(NULL, 0);
371 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
372 spnego_out.negTokenTarg.responseToken = unwrapped_out;
373 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
375 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
376 spnego_out.negTokenTarg.supportedMech
377 = spnego_state->sub_sec_security->ops->oid;
378 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
379 spnego_state->state_position = SPNEGO_SERVER_TARG;
380 } else if (NT_STATUS_IS_OK(nt_status)) {
381 spnego_out.negTokenTarg.supportedMech = NULL;
382 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
383 spnego_state->state_position = SPNEGO_DONE;
385 spnego_out.negTokenTarg.supportedMech = NULL;
386 spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
387 DEBUG(1, ("SPNEGO login failed: %s\n", nt_errstr(nt_status)));
388 spnego_state->state_position = SPNEGO_DONE;
391 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
392 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
393 return NT_STATUS_INVALID_PARAMETER;
396 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
402 static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
403 const DATA_BLOB in, DATA_BLOB *out)
405 struct spnego_state *spnego_state = gensec_security->private_data;
406 DATA_BLOB null_data_blob = data_blob(NULL, 0);
407 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
408 struct spnego_data spnego_out;
409 struct spnego_data spnego;
413 *out = data_blob(NULL, 0);
416 out_mem_ctx = spnego_state;
419 /* and switch into the state machine */
421 switch (spnego_state->state_position) {
422 case SPNEGO_FALLBACK:
423 return gensec_update(spnego_state->sub_sec_security,
424 out_mem_ctx, in, out);
425 case SPNEGO_SERVER_START:
430 len = spnego_read_data(in, &spnego);
432 return gensec_spnego_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, in, out);
434 /* client sent NegTargetInit, we send NegTokenTarg */
436 /* OK, so it's real SPNEGO, check the packet's the one we expect */
437 if (spnego.type != spnego_state->expected_packet) {
438 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
439 spnego_state->expected_packet));
440 dump_data(1, (const char *)in.data, in.length);
441 spnego_free_data(&spnego);
442 return NT_STATUS_INVALID_PARAMETER;
445 nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
448 spnego.negTokenInit.mechTypes,
449 spnego.negTokenInit.mechToken,
452 nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
459 spnego_free_data(&spnego);
463 const char **mechlist = gensec_security_oids(out_mem_ctx, OID_SPNEGO);
465 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
466 spnego_out.negTokenInit.mechTypes = mechlist;
467 spnego_out.negTokenInit.reqFlags = 0;
468 spnego_out.negTokenInit.mechListMIC = null_data_blob;
469 spnego_out.negTokenInit.mechToken = unwrapped_out;
471 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
472 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
473 return NT_STATUS_INVALID_PARAMETER;
477 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
478 spnego_state->state_position = SPNEGO_SERVER_TARG;
480 return NT_STATUS_MORE_PROCESSING_REQUIRED;
484 case SPNEGO_CLIENT_START:
486 /* The server offers a list of mechanisms */
488 const char *my_mechs[] = {NULL, NULL};
489 NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
492 /* client to produce negTokenInit */
493 return gensec_spnego_client_negTokenInit(gensec_security, spnego_state, out_mem_ctx, in, out);
496 len = spnego_read_data(in, &spnego);
499 DEBUG(1, ("Invalid SPNEGO request:\n"));
500 dump_data(1, (const char *)in.data, in.length);
501 return NT_STATUS_INVALID_PARAMETER;
504 /* OK, so it's real SPNEGO, check the packet's the one we expect */
505 if (spnego.type != spnego_state->expected_packet) {
506 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
507 spnego_state->expected_packet));
508 dump_data(1, (const char *)in.data, in.length);
509 spnego_free_data(&spnego);
510 return NT_STATUS_INVALID_PARAMETER;
513 if (spnego.negTokenInit.targetPrincipal) {
514 DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal));
515 nt_status = gensec_set_target_principal(gensec_security,
516 spnego.negTokenInit.targetPrincipal);
517 if (!NT_STATUS_IS_OK(nt_status)) {
518 spnego_free_data(&spnego);
523 nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
526 spnego.negTokenInit.mechTypes,
527 spnego.negTokenInit.mechToken,
530 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
531 spnego_free_data(&spnego);
536 my_mechs[0] = spnego_state->sub_sec_security->ops->oid;
538 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
539 spnego_out.negTokenInit.mechTypes = my_mechs;
540 spnego_out.negTokenInit.reqFlags = 0;
541 spnego_out.negTokenInit.mechListMIC = null_data_blob;
542 spnego_out.negTokenInit.mechToken = unwrapped_out;
544 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
545 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
546 return NT_STATUS_INVALID_PARAMETER;
550 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
551 spnego_state->state_position = SPNEGO_CLIENT_TARG;
553 return NT_STATUS_MORE_PROCESSING_REQUIRED;
555 case SPNEGO_SERVER_TARG:
559 return NT_STATUS_INVALID_PARAMETER;
562 len = spnego_read_data(in, &spnego);
565 DEBUG(1, ("Invalid SPNEGO request:\n"));
566 dump_data(1, (const char *)in.data, in.length);
567 return NT_STATUS_INVALID_PARAMETER;
570 /* OK, so it's real SPNEGO, check the packet's the one we expect */
571 if (spnego.type != spnego_state->expected_packet) {
572 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
573 spnego_state->expected_packet));
574 dump_data(1, (const char *)in.data, in.length);
575 spnego_free_data(&spnego);
576 return NT_STATUS_INVALID_PARAMETER;
579 nt_status = gensec_update(spnego_state->sub_sec_security,
581 spnego.negTokenTarg.responseToken,
584 nt_status = gensec_spnego_server_negTokenTarg(gensec_security,
591 spnego_free_data(&spnego);
595 case SPNEGO_CLIENT_TARG:
599 return NT_STATUS_INVALID_PARAMETER;
602 len = spnego_read_data(in, &spnego);
605 DEBUG(1, ("Invalid SPNEGO request:\n"));
606 dump_data(1, (const char *)in.data, in.length);
607 return NT_STATUS_INVALID_PARAMETER;
610 /* OK, so it's real SPNEGO, check the packet's the one we expect */
611 if (spnego.type != spnego_state->expected_packet) {
612 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
613 spnego_state->expected_packet));
614 dump_data(1, (const char *)in.data, in.length);
615 spnego_free_data(&spnego);
616 return NT_STATUS_INVALID_PARAMETER;
619 if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
620 return NT_STATUS_ACCESS_DENIED;
623 nt_status = gensec_update(spnego_state->sub_sec_security,
625 spnego.negTokenTarg.responseToken,
629 if (NT_STATUS_IS_OK(nt_status)
630 && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) {
631 DEBUG(1,("gensec_update ok but not accepted\n"));
632 nt_status = NT_STATUS_INVALID_PARAMETER;
635 spnego_free_data(&spnego);
637 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
639 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
640 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
641 spnego_out.negTokenTarg.supportedMech = NULL;
642 spnego_out.negTokenTarg.responseToken = unwrapped_out;
643 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
645 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
646 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
647 return NT_STATUS_INVALID_PARAMETER;
650 spnego_state->state_position = SPNEGO_CLIENT_TARG;
651 } else if (NT_STATUS_IS_OK(nt_status)) {
652 /* all done - server has accepted, and we agree */
654 if (unwrapped_out.length) {
655 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
656 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
657 spnego_out.negTokenTarg.supportedMech = NULL;
658 spnego_out.negTokenTarg.responseToken = unwrapped_out;
659 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
661 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
662 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
663 return NT_STATUS_INVALID_PARAMETER;
666 *out = null_data_blob;
669 spnego_state->state_position = SPNEGO_DONE;
671 DEBUG(1, ("SPNEGO(%s) login failed: %s\n",
672 spnego_state->sub_sec_security->ops->name,
673 nt_errstr(nt_status)));
680 return NT_STATUS_INVALID_PARAMETER;
683 static void gensec_spnego_end(struct gensec_security *gensec_security)
685 struct spnego_state *spnego_state = gensec_security->private_data;
687 if (spnego_state->sub_sec_security) {
688 gensec_end(&spnego_state->sub_sec_security);
691 talloc_free(spnego_state);
693 gensec_security->private_data = NULL;
696 static const struct gensec_security_ops gensec_spnego_security_ops = {
698 .sasl_name = "GSS-SPNEGO",
699 .auth_type = DCERPC_AUTH_TYPE_SPNEGO,
701 .client_start = gensec_spnego_client_start,
702 .server_start = gensec_spnego_server_start,
703 .update = gensec_spnego_update,
704 .seal_packet = gensec_spnego_seal_packet,
705 .sign_packet = gensec_spnego_sign_packet,
706 .sig_size = gensec_spnego_sig_size,
707 .check_packet = gensec_spnego_check_packet,
708 .unseal_packet = gensec_spnego_unseal_packet,
709 .session_key = gensec_spnego_session_key,
710 .session_info = gensec_spnego_session_info,
711 .end = gensec_spnego_end
714 NTSTATUS gensec_spnego_init(void)
717 ret = gensec_register(&gensec_spnego_security_ops);
718 if (!NT_STATUS_IS_OK(ret)) {
719 DEBUG(0,("Failed to register '%s' gensec backend!\n",
720 gensec_spnego_security_ops.name));