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.
28 #define DBGC_CLASS DBGC_AUTH
30 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;
50 TALLOC_CTX *mem_ctx = talloc_init("gensec_spnego_client_start");
52 return NT_STATUS_NO_MEMORY;
54 spnego_state = talloc_p(mem_ctx, struct spnego_state);
57 return NT_STATUS_NO_MEMORY;
60 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
61 spnego_state->state_position = SPNEGO_CLIENT_START;
62 spnego_state->mem_ctx = mem_ctx;
63 spnego_state->sub_sec_security = NULL;
65 gensec_security->private_data = spnego_state;
70 wrappers for the spnego_*() functions
72 static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security,
74 uint8_t *data, size_t length, DATA_BLOB *sig)
76 struct spnego_state *spnego_state = gensec_security->private_data;
78 if (spnego_state->state_position != SPNEGO_DONE
79 && spnego_state->state_position != SPNEGO_FALLBACK) {
80 return NT_STATUS_INVALID_PARAMETER;
83 return gensec_unseal_packet(spnego_state->sub_sec_security,
84 mem_ctx, data, length, sig);
87 static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security,
89 const uint8_t *data, size_t length,
92 struct spnego_state *spnego_state = gensec_security->private_data;
94 return NT_STATUS_NOT_IMPLEMENTED;
95 if (spnego_state->state_position != SPNEGO_DONE
96 && spnego_state->state_position != SPNEGO_FALLBACK) {
97 return NT_STATUS_INVALID_PARAMETER;
100 return gensec_check_packet(spnego_state->sub_sec_security,
101 mem_ctx, data, length, sig);
104 static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security,
106 uint8_t *data, size_t length,
109 struct spnego_state *spnego_state = gensec_security->private_data;
111 return NT_STATUS_NOT_IMPLEMENTED;
112 if (spnego_state->state_position != SPNEGO_DONE
113 && spnego_state->state_position != SPNEGO_FALLBACK) {
114 return NT_STATUS_INVALID_PARAMETER;
117 return gensec_seal_packet(spnego_state->sub_sec_security,
118 mem_ctx, data, length, sig);
121 static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security,
123 const uint8_t *data, size_t length,
126 struct spnego_state *spnego_state = gensec_security->private_data;
128 if (spnego_state->state_position != SPNEGO_DONE
129 && spnego_state->state_position != SPNEGO_FALLBACK) {
130 return NT_STATUS_INVALID_PARAMETER;
133 return gensec_sign_packet(spnego_state->sub_sec_security,
134 mem_ctx, data, length, sig);
137 static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security,
138 DATA_BLOB *session_key)
140 struct spnego_state *spnego_state = gensec_security->private_data;
141 if (!spnego_state->sub_sec_security) {
142 return NT_STATUS_INVALID_PARAMETER;
145 return gensec_session_key(spnego_state->sub_sec_security,
149 /** Fallback to another GENSEC mechanism, based on magic strings
151 * This is the 'fallback' case, where we don't get SPNEGO, and have to
152 * try all the other options (and hope they all have a magic string
156 static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security,
157 struct spnego_state *spnego_state,
158 TALLOC_CTX *out_mem_ctx,
159 const DATA_BLOB in, DATA_BLOB *out)
163 const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
164 for (i=0; i < num_ops; i++) {
166 if (!all_ops[i]->oid) {
169 nt_status = gensec_subcontext_start(gensec_security,
170 &spnego_state->sub_sec_security);
171 if (!NT_STATUS_IS_OK(nt_status)) {
174 /* select the sub context */
175 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
177 if (!NT_STATUS_IS_OK(nt_status)) {
178 gensec_end(&spnego_state->sub_sec_security);
181 nt_status = gensec_update(spnego_state->sub_sec_security,
182 out_mem_ctx, in, out);
183 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
184 spnego_state->state_position = SPNEGO_FALLBACK;
187 gensec_end(&spnego_state->sub_sec_security);
189 DEBUG(1, ("Failed to parse SPNEGO request\n"));
190 return NT_STATUS_INVALID_PARAMETER;
194 /** create a client netTokenInit
196 * This is the case, where the client is the first one who sends data
199 static NTSTATUS gensec_spnego_client_netTokenInit(struct gensec_security *gensec_security,
200 struct spnego_state *spnego_state,
201 TALLOC_CTX *out_mem_ctx,
202 const DATA_BLOB in, DATA_BLOB *out)
207 char **mechTypes = NULL;
208 const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
209 DATA_BLOB null_data_blob = data_blob(NULL,0);
210 DATA_BLOB unwrapped_out = data_blob(NULL,0);
213 DEBUG(1, ("no GENSEC backends available\n"));
214 return NT_STATUS_INVALID_PARAMETER;
217 /* build a mechTypes list we want to offer */
218 for (i=0; i < num_ops; i++) {
219 if (!all_ops[i]->oid) {
223 /* skip SPNEGO itself */
224 if (strcmp(OID_SPNEGO,all_ops[i]->oid)==0) {
228 mechTypes = talloc_realloc_p(out_mem_ctx, mechTypes, char *, i+2);
230 DEBUG(1, ("talloc_realloc_p(out_mem_ctx, mechTypes, char *, i+1) failed\n"));
231 return NT_STATUS_NO_MEMORY;
234 mechTypes[i] = all_ops[i]->oid;
235 mechTypes[i+1] = NULL;
239 DEBUG(1, ("no GENSEC OID backends available\n"));
240 return NT_STATUS_INVALID_PARAMETER;
243 nt_status = gensec_subcontext_start(gensec_security,
244 &spnego_state->sub_sec_security);
245 if (!NT_STATUS_IS_OK(nt_status)) {
248 /* select our preferred mech */
249 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
251 if (!NT_STATUS_IS_OK(nt_status)) {
252 gensec_end(&spnego_state->sub_sec_security);
255 nt_status = gensec_update(spnego_state->sub_sec_security,
256 out_mem_ctx, in, &unwrapped_out);
257 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
258 struct spnego_data spnego_out;
259 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
260 spnego_out.negTokenInit.mechTypes = mechTypes;
261 spnego_out.negTokenInit.reqFlags = 0;
262 spnego_out.negTokenInit.mechListMIC = null_data_blob;
263 spnego_out.negTokenInit.mechToken = unwrapped_out;
265 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
266 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
267 return NT_STATUS_INVALID_PARAMETER;
271 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
272 spnego_state->state_position = SPNEGO_CLIENT_TARG;
275 gensec_end(&spnego_state->sub_sec_security);
277 DEBUG(1, ("Failed to setup SPNEGO netTokenInit request\n"));
278 return NT_STATUS_INVALID_PARAMETER;
281 static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
282 const DATA_BLOB in, DATA_BLOB *out)
284 struct spnego_state *spnego_state = gensec_security->private_data;
285 DATA_BLOB null_data_blob = data_blob(NULL, 0);
286 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
287 struct spnego_data spnego_out;
288 struct spnego_data spnego;
293 out_mem_ctx = spnego_state->mem_ctx;
296 /* and switch into the state machine */
298 switch (spnego_state->state_position) {
299 case SPNEGO_FALLBACK:
300 return gensec_update(spnego_state->sub_sec_security,
301 out_mem_ctx, in, out);
302 case SPNEGO_SERVER_START:
305 len = spnego_read_data(in, &spnego);
307 return gensec_spnego_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, in, out);
309 /* client sent NegTargetInit */
312 /* server needs to send NegTargetInit */
314 return NT_STATUS_INVALID_PARAMETER;
317 case SPNEGO_CLIENT_START:
319 /* The server offers a list of mechanisms */
322 char *my_mechs[] = {NULL, NULL};
324 NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER;
327 /* client to produce negTokenInit */
328 return gensec_spnego_client_netTokenInit(gensec_security, spnego_state, out_mem_ctx, in, out);
331 len = spnego_read_data(in, &spnego);
334 DEBUG(1, ("Invalid SPNEGO request:\n"));
335 dump_data(1, (const char *)in.data, in.length);
336 return NT_STATUS_INVALID_PARAMETER;
339 /* OK, so it's real SPNEGO, check the packet's the one we expect */
340 if (spnego.type != spnego_state->expected_packet) {
341 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
342 spnego_state->expected_packet));
343 dump_data(1, (const char *)in.data, in.length);
344 spnego_free_data(&spnego);
345 return NT_STATUS_INVALID_PARAMETER;
348 if (spnego.negTokenInit.targetPrincipal) {
349 DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal));
350 nt_status = gensec_set_target_principal(gensec_security,
351 spnego.negTokenInit.targetPrincipal);
352 if (!NT_STATUS_IS_OK(nt_status)) {
357 mechType = spnego.negTokenInit.mechTypes;
358 for (i=0; mechType && mechType[i]; i++) {
359 nt_status = gensec_subcontext_start(gensec_security,
360 &spnego_state->sub_sec_security);
361 if (!NT_STATUS_IS_OK(nt_status)) {
364 /* select the sub context */
365 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
367 if (!NT_STATUS_IS_OK(nt_status)) {
368 gensec_end(&spnego_state->sub_sec_security);
373 nt_status = gensec_update(spnego_state->sub_sec_security,
375 spnego.negTokenInit.mechToken,
378 /* only get the helping start blob for the first OID */
379 nt_status = gensec_update(spnego_state->sub_sec_security,
384 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
385 DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n",
386 spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
387 gensec_end(&spnego_state->sub_sec_security);
392 if (!mechType || !mechType[i]) {
393 DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
396 spnego_free_data(&spnego);
397 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) {
402 my_mechs[0] = spnego_state->sub_sec_security->ops->oid;
404 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
405 spnego_out.negTokenInit.mechTypes = my_mechs;
406 spnego_out.negTokenInit.reqFlags = 0;
407 spnego_out.negTokenInit.mechListMIC = null_data_blob;
408 spnego_out.negTokenInit.mechToken = unwrapped_out;
410 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
411 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"));
412 return NT_STATUS_INVALID_PARAMETER;
416 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
417 spnego_state->state_position = SPNEGO_CLIENT_TARG;
419 return NT_STATUS_MORE_PROCESSING_REQUIRED;
421 case SPNEGO_SERVER_TARG:
425 return NT_STATUS_INVALID_PARAMETER;
428 len = spnego_read_data(in, &spnego);
431 DEBUG(1, ("Invalid SPNEGO request:\n"));
432 dump_data(1, (const char *)in.data, in.length);
433 return NT_STATUS_INVALID_PARAMETER;
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_update(spnego_state->sub_sec_security,
447 spnego.negTokenTarg.responseToken,
450 spnego_free_data(&spnego);
453 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
454 spnego_out.negTokenTarg.supportedMech
455 = spnego_state->sub_sec_security->ops->oid;
456 spnego_out.negTokenTarg.responseToken = unwrapped_out;
457 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
459 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
460 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
461 spnego_state->state_position = SPNEGO_SERVER_TARG;
462 } else if (NT_STATUS_IS_OK(nt_status)) {
463 spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED;
464 spnego_state->state_position = SPNEGO_DONE;
466 spnego_out.negTokenTarg.negResult = SPNEGO_REJECT;
467 DEBUG(1, ("SPNEGO(%s) login failed: %s\n",
468 spnego_state->sub_sec_security->ops->name,
469 nt_errstr(nt_status)));
470 spnego_state->state_position = SPNEGO_DONE;
473 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
474 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
475 return NT_STATUS_INVALID_PARAMETER;
480 case SPNEGO_CLIENT_TARG:
484 return NT_STATUS_INVALID_PARAMETER;
487 len = spnego_read_data(in, &spnego);
490 DEBUG(1, ("Invalid SPNEGO request:\n"));
491 dump_data(1, (const char *)in.data, in.length);
492 return NT_STATUS_INVALID_PARAMETER;
495 /* OK, so it's real SPNEGO, check the packet's the one we expect */
496 if (spnego.type != spnego_state->expected_packet) {
497 DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type,
498 spnego_state->expected_packet));
499 dump_data(1, (const char *)in.data, in.length);
500 spnego_free_data(&spnego);
501 return NT_STATUS_INVALID_PARAMETER;
504 if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
505 return NT_STATUS_ACCESS_DENIED;
508 nt_status = gensec_update(spnego_state->sub_sec_security,
510 spnego.negTokenTarg.responseToken,
514 if (NT_STATUS_IS_OK(nt_status)
515 && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) {
516 DEBUG(1,("gensec_update ok but not accepted\n"));
517 nt_status = NT_STATUS_INVALID_PARAMETER;
520 spnego_free_data(&spnego);
522 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
524 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
525 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
526 spnego_out.negTokenTarg.supportedMech = NULL;
527 spnego_out.negTokenTarg.responseToken = unwrapped_out;
528 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
530 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
531 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
532 return NT_STATUS_INVALID_PARAMETER;
535 spnego_state->state_position = SPNEGO_CLIENT_TARG;
536 } else if (NT_STATUS_IS_OK(nt_status)) {
537 /* all done - server has accepted, and we agree */
539 if (unwrapped_out.length) {
540 spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
541 spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT;
542 spnego_out.negTokenTarg.supportedMech = NULL;
543 spnego_out.negTokenTarg.responseToken = unwrapped_out;
544 spnego_out.negTokenTarg.mechListMIC = null_data_blob;
546 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
547 DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n"));
548 return NT_STATUS_INVALID_PARAMETER;
551 *out = null_data_blob;
554 spnego_state->state_position = SPNEGO_DONE;
556 DEBUG(1, ("SPNEGO(%s) login failed: %s\n",
557 spnego_state->sub_sec_security->ops->name,
558 nt_errstr(nt_status)));
565 return NT_STATUS_INVALID_PARAMETER;
568 static void gensec_spnego_end(struct gensec_security *gensec_security)
570 struct spnego_state *spnego_state = gensec_security->private_data;
572 if (spnego_state->sub_sec_security) {
573 gensec_end(&spnego_state->sub_sec_security);
576 talloc_destroy(spnego_state->mem_ctx);
578 gensec_security->private_data = NULL;
581 static const struct gensec_security_ops gensec_spnego_security_ops = {
583 .sasl_name = "GSS-SPNEGO",
584 .auth_type = DCERPC_AUTH_TYPE_SPNEGO,
586 .client_start = gensec_spnego_client_start,
587 .update = gensec_spnego_update,
588 .seal_packet = gensec_spnego_seal_packet,
589 .sign_packet = gensec_spnego_sign_packet,
590 .check_packet = gensec_spnego_check_packet,
591 .unseal_packet = gensec_spnego_unseal_packet,
592 .session_key = gensec_spnego_session_key,
593 .end = gensec_spnego_end
596 NTSTATUS gensec_spnego_init(void)
599 ret = register_backend("gensec", &gensec_spnego_security_ops);
600 if (!NT_STATUS_IS_OK(ret)) {
601 DEBUG(0,("Failed to register '%s' gensec backend!\n",
602 gensec_spnego_security_ops.name));