/* Unix SMB/CIFS implementation. RFC2478 Compliant SPNEGO implementation Copyright (C) Jim McDonough 2003 Copyright (C) Andrew Bartlett 2004-2005 Copyright (C) Stefan Metzmacher 2004-2008 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "../libcli/auth/spnego.h" #include "librpc/gen_ndr/ndr_dcerpc.h" #include "auth/credentials/credentials.h" #include "auth/gensec/gensec.h" #include "auth/gensec/gensec_internal.h" #include "param/param.h" #include "lib/util/asn1.h" #undef strcasecmp _PUBLIC_ NTSTATUS gensec_spnego_init(void); enum spnego_state_position { SPNEGO_SERVER_START, SPNEGO_CLIENT_START, SPNEGO_SERVER_TARG, SPNEGO_CLIENT_TARG, SPNEGO_FALLBACK, SPNEGO_DONE }; struct spnego_state { enum spnego_message_type expected_packet; enum spnego_state_position state_position; struct gensec_security *sub_sec_security; bool no_response_expected; const char *neg_oid; DATA_BLOB mech_types; /* * The following is used to implement * the update token fragmentation */ size_t in_needed; DATA_BLOB in_frag; size_t out_max_length; DATA_BLOB out_frag; NTSTATUS out_status; }; static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security) { struct spnego_state *spnego_state; spnego_state = talloc_zero(gensec_security, struct spnego_state); if (!spnego_state) { return NT_STATUS_NO_MEMORY; } spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; spnego_state->state_position = SPNEGO_CLIENT_START; spnego_state->sub_sec_security = NULL; spnego_state->no_response_expected = false; spnego_state->mech_types = data_blob(NULL, 0); spnego_state->out_max_length = gensec_max_update_size(gensec_security); spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; gensec_security->private_data = spnego_state; return NT_STATUS_OK; } static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security) { struct spnego_state *spnego_state; spnego_state = talloc_zero(gensec_security, struct spnego_state); if (!spnego_state) { return NT_STATUS_NO_MEMORY; } spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; spnego_state->state_position = SPNEGO_SERVER_START; spnego_state->sub_sec_security = NULL; spnego_state->no_response_expected = false; spnego_state->mech_types = data_blob(NULL, 0); spnego_state->out_max_length = gensec_max_update_size(gensec_security); spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; gensec_security->private_data = spnego_state; return NT_STATUS_OK; } /* wrappers for the spnego_*() functions */ static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, uint8_t *data, size_t length, const uint8_t *whole_pdu, size_t pdu_length, const DATA_BLOB *sig) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return gensec_unseal_packet(spnego_state->sub_sec_security, data, length, whole_pdu, pdu_length, sig); } static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, const uint8_t *data, size_t length, const uint8_t *whole_pdu, size_t pdu_length, const DATA_BLOB *sig) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return gensec_check_packet(spnego_state->sub_sec_security, data, length, whole_pdu, pdu_length, sig); } static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, uint8_t *data, size_t length, const uint8_t *whole_pdu, size_t pdu_length, DATA_BLOB *sig) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return gensec_seal_packet(spnego_state->sub_sec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); } static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const uint8_t *data, size_t length, const uint8_t *whole_pdu, size_t pdu_length, DATA_BLOB *sig) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return gensec_sign_packet(spnego_state->sub_sec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); } static NTSTATUS gensec_spnego_wrap(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const DATA_BLOB *in, DATA_BLOB *out) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { DEBUG(1, ("gensec_spnego_wrap: wrong state for wrap\n")); return NT_STATUS_INVALID_PARAMETER; } return gensec_wrap(spnego_state->sub_sec_security, mem_ctx, in, out); } static NTSTATUS gensec_spnego_unwrap(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const DATA_BLOB *in, DATA_BLOB *out) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { DEBUG(1, ("gensec_spnego_unwrap: wrong state for unwrap\n")); return NT_STATUS_INVALID_PARAMETER; } return gensec_unwrap(spnego_state->sub_sec_security, mem_ctx, in, out); } static NTSTATUS gensec_spnego_wrap_packets(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const DATA_BLOB *in, DATA_BLOB *out, size_t *len_processed) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { DEBUG(1, ("gensec_spnego_wrap: wrong state for wrap\n")); return NT_STATUS_INVALID_PARAMETER; } return gensec_wrap_packets(spnego_state->sub_sec_security, mem_ctx, in, out, len_processed); } static NTSTATUS gensec_spnego_packet_full_request(struct gensec_security *gensec_security, DATA_BLOB blob, size_t *size) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { DEBUG(1, ("gensec_spnego_unwrap: wrong state for unwrap\n")); return NT_STATUS_INVALID_PARAMETER; } return gensec_packet_full_request(spnego_state->sub_sec_security, blob, size); } static NTSTATUS gensec_spnego_unwrap_packets(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const DATA_BLOB *in, DATA_BLOB *out, size_t *len_processed) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { DEBUG(1, ("gensec_spnego_unwrap: wrong state for unwrap\n")); return NT_STATUS_INVALID_PARAMETER; } return gensec_unwrap_packets(spnego_state->sub_sec_security, mem_ctx, in, out, len_processed); } static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security, size_t data_size) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return 0; } return gensec_sig_size(spnego_state->sub_sec_security, data_size); } static size_t gensec_spnego_max_input_size(struct gensec_security *gensec_security) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return 0; } return gensec_max_input_size(spnego_state->sub_sec_security); } static size_t gensec_spnego_max_wrapped_size(struct gensec_security *gensec_security) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return 0; } return gensec_max_wrapped_size(spnego_state->sub_sec_security); } static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, DATA_BLOB *session_key) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (!spnego_state->sub_sec_security) { return NT_STATUS_INVALID_PARAMETER; } return gensec_session_key(spnego_state->sub_sec_security, mem_ctx, session_key); } static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, struct auth_session_info **session_info) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (!spnego_state->sub_sec_security) { return NT_STATUS_INVALID_PARAMETER; } return gensec_session_info(spnego_state->sub_sec_security, mem_ctx, session_info); } /** Fallback to another GENSEC mechanism, based on magic strings * * This is the 'fallback' case, where we don't get SPNEGO, and have to * try all the other options (and hope they all have a magic string * they check) */ static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security, struct spnego_state *spnego_state, struct tevent_context *ev, TALLOC_CTX *out_mem_ctx, const DATA_BLOB in, DATA_BLOB *out) { int i,j; const struct gensec_security_ops **all_ops; all_ops = gensec_security_mechs(gensec_security, out_mem_ctx); for (i=0; all_ops && all_ops[i]; i++) { bool is_spnego; NTSTATUS nt_status; if (gensec_security != NULL && !gensec_security_ops_enabled(all_ops[i], gensec_security)) continue; if (!all_ops[i]->oid) { continue; } is_spnego = false; for (j=0; all_ops[i]->oid[j]; j++) { if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[i]->oid[j]) == 0) { is_spnego = true; } } if (is_spnego) { continue; } if (!all_ops[i]->magic) { continue; } nt_status = all_ops[i]->magic(gensec_security, &in); if (!NT_STATUS_IS_OK(nt_status)) { continue; } spnego_state->state_position = SPNEGO_FALLBACK; nt_status = gensec_subcontext_start(spnego_state, gensec_security, &spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } /* select the sub context */ nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, all_ops[i]); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } nt_status = gensec_update(spnego_state->sub_sec_security, ev, out_mem_ctx, in, out); return nt_status; } DEBUG(1, ("Failed to parse SPNEGO request\n")); return NT_STATUS_INVALID_PARAMETER; } /* Parse the netTokenInit, either from the client, to the server, or from the server to the client. */ static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_security, struct spnego_state *spnego_state, TALLOC_CTX *out_mem_ctx, struct tevent_context *ev, const char * const *mechType, const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out) { int i; NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; DATA_BLOB null_data_blob = data_blob(NULL,0); bool ok; const struct gensec_security_ops_wrapper *all_sec = gensec_security_by_oid_list(gensec_security, out_mem_ctx, mechType, GENSEC_OID_SPNEGO); ok = spnego_write_mech_types(spnego_state, mechType, &spnego_state->mech_types); if (!ok) { DEBUG(1, ("SPNEGO: Failed to write mechTypes\n")); return NT_STATUS_NO_MEMORY; } if (spnego_state->state_position == SPNEGO_SERVER_START) { uint32_t j; for (j=0; mechType && mechType[j]; j++) { for (i=0; all_sec && all_sec[i].op; i++) { if (strcmp(mechType[j], all_sec[i].oid) != 0) { continue; } nt_status = gensec_subcontext_start(spnego_state, gensec_security, &spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } /* select the sub context */ nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, all_sec[i].op); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; break; } if (j > 0) { /* no optimistic token */ spnego_state->neg_oid = all_sec[i].oid; *unwrapped_out = data_blob_null; nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; break; } nt_status = gensec_update(spnego_state->sub_sec_security, out_mem_ctx, ev, unwrapped_in, unwrapped_out); if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) || NT_STATUS_EQUAL(nt_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) { /* Pretend we never started it (lets the first run find some incompatible demand) */ DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed to parse contents: %s\n", spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; break; } spnego_state->neg_oid = all_sec[i].oid; break; } if (spnego_state->sub_sec_security) { break; } } if (!spnego_state->sub_sec_security) { DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); return NT_STATUS_INVALID_PARAMETER; } } /* Having tried any optimistic token from the client (if we * were the server), if we didn't get anywhere, walk our list * in our preference order */ if (!spnego_state->sub_sec_security) { for (i=0; all_sec && all_sec[i].op; i++) { nt_status = gensec_subcontext_start(spnego_state, gensec_security, &spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } /* select the sub context */ nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, all_sec[i].op); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; continue; } spnego_state->neg_oid = all_sec[i].oid; /* only get the helping start blob for the first OID */ nt_status = gensec_update(spnego_state->sub_sec_security, out_mem_ctx, ev, null_data_blob, unwrapped_out); /* it is likely that a NULL input token will * not be liked by most server mechs, but if * we are in the client, we want the first * update packet to be able to abort the use * of this mech */ if (spnego_state->state_position != SPNEGO_SERVER_START) { if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) || NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_LOGON_SERVERS) || NT_STATUS_EQUAL(nt_status, NT_STATUS_TIME_DIFFERENCE_AT_DC) || NT_STATUS_EQUAL(nt_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) { /* Pretend we never started it (lets the first run find some incompatible demand) */ DEBUG(3, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n", spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; continue; } } break; } } if (spnego_state->sub_sec_security) { /* it is likely that a NULL input token will * not be liked by most server mechs, but this * does the right thing in the CIFS client. * just push us along the merry-go-round * again, and hope for better luck next * time */ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER)) { *unwrapped_out = data_blob(NULL, 0); nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; } if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) && !NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n", spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; /* We started the mech correctly, and the * input from the other side was valid. * Return the error (say bad password, invalid * ticket) */ return nt_status; } return nt_status; /* OK, INVALID_PARAMETER ore MORE PROCESSING */ } DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); /* we could re-negotiate here, but it would only work * if the client or server lied about what it could * support the first time. Lets keep this code to * reality */ return nt_status; } /** create a negTokenInit * * This is the same packet, no matter if the client or server sends it first, but it is always the first packet */ static NTSTATUS gensec_spnego_create_negTokenInit(struct gensec_security *gensec_security, struct spnego_state *spnego_state, TALLOC_CTX *out_mem_ctx, struct tevent_context *ev, const DATA_BLOB in, DATA_BLOB *out) { int i; NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; DATA_BLOB null_data_blob = data_blob(NULL,0); const char **mechTypes = NULL; DATA_BLOB unwrapped_out = data_blob(NULL, 0); const struct gensec_security_ops_wrapper *all_sec; mechTypes = gensec_security_oids(gensec_security, out_mem_ctx, GENSEC_OID_SPNEGO); all_sec = gensec_security_by_oid_list(gensec_security, out_mem_ctx, mechTypes, GENSEC_OID_SPNEGO); for (i=0; all_sec && all_sec[i].op; i++) { struct spnego_data spnego_out; const char **send_mech_types; bool ok; nt_status = gensec_subcontext_start(spnego_state, gensec_security, &spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } /* select the sub context */ nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, all_sec[i].op); if (!NT_STATUS_IS_OK(nt_status)) { talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; continue; } /* In the client, try and produce the first (optimistic) packet */ if (spnego_state->state_position == SPNEGO_CLIENT_START) { nt_status = gensec_update(spnego_state->sub_sec_security, out_mem_ctx, ev, null_data_blob, &unwrapped_out); if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { DEBUG(1, ("SPNEGO(%s) creating NEG_TOKEN_INIT failed: %s\n", spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; /* Pretend we never started it (lets the first run find some incompatible demand) */ continue; } } spnego_out.type = SPNEGO_NEG_TOKEN_INIT; send_mech_types = gensec_security_oids_from_ops_wrapped(out_mem_ctx, &all_sec[i]); ok = spnego_write_mech_types(spnego_state, send_mech_types, &spnego_state->mech_types); if (!ok) { DEBUG(1, ("SPNEGO: Failed to write mechTypes\n")); return NT_STATUS_NO_MEMORY; } /* List the remaining mechs as options */ spnego_out.negTokenInit.mechTypes = send_mech_types; spnego_out.negTokenInit.reqFlags = null_data_blob; spnego_out.negTokenInit.reqFlagsPadding = 0; if (spnego_state->state_position == SPNEGO_SERVER_START) { spnego_out.negTokenInit.mechListMIC = data_blob_string_const(ADS_IGNORE_PRINCIPAL); } else { spnego_out.negTokenInit.mechListMIC = null_data_blob; } spnego_out.negTokenInit.mechToken = unwrapped_out; if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { DEBUG(1, ("Failed to write NEG_TOKEN_INIT\n")); return NT_STATUS_INVALID_PARAMETER; } /* set next state */ spnego_state->neg_oid = all_sec[i].oid; if (NT_STATUS_IS_OK(nt_status)) { spnego_state->no_response_expected = true; } return NT_STATUS_MORE_PROCESSING_REQUIRED; } talloc_free(spnego_state->sub_sec_security); spnego_state->sub_sec_security = NULL; DEBUG(1, ("Failed to setup SPNEGO negTokenInit request: %s\n", nt_errstr(nt_status))); return NT_STATUS_INVALID_PARAMETER; } /** create a server negTokenTarg * * This is the case, where the client is the first one who sends data */ static NTSTATUS gensec_spnego_server_negTokenTarg(struct spnego_state *spnego_state, TALLOC_CTX *out_mem_ctx, NTSTATUS nt_status, const DATA_BLOB unwrapped_out, DATA_BLOB mech_list_mic, DATA_BLOB *out) { struct spnego_data spnego_out; DATA_BLOB null_data_blob = data_blob(NULL, 0); /* compose reply */ spnego_out.type = SPNEGO_NEG_TOKEN_TARG; spnego_out.negTokenTarg.responseToken = unwrapped_out; spnego_out.negTokenTarg.mechListMIC = null_data_blob; spnego_out.negTokenTarg.supportedMech = NULL; if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; spnego_state->state_position = SPNEGO_SERVER_TARG; } else if (NT_STATUS_IS_OK(nt_status)) { if (unwrapped_out.data) { spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; } spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED; spnego_out.negTokenTarg.mechListMIC = mech_list_mic; spnego_state->state_position = SPNEGO_DONE; } else { spnego_out.negTokenTarg.negResult = SPNEGO_REJECT; DEBUG(2, ("SPNEGO login failed: %s\n", nt_errstr(nt_status))); spnego_state->state_position = SPNEGO_DONE; } if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); return NT_STATUS_INVALID_PARAMETER; } spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; return nt_status; } static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, struct tevent_context *ev, const DATA_BLOB in, DATA_BLOB *out) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; DATA_BLOB null_data_blob = data_blob(NULL, 0); DATA_BLOB mech_list_mic = data_blob(NULL, 0); DATA_BLOB unwrapped_out = data_blob(NULL, 0); struct spnego_data spnego_out; struct spnego_data spnego; ssize_t len; *out = data_blob(NULL, 0); if (!out_mem_ctx) { out_mem_ctx = spnego_state; } /* and switch into the state machine */ switch (spnego_state->state_position) { case SPNEGO_FALLBACK: return gensec_update(spnego_state->sub_sec_security, ev, out_mem_ctx, in, out); case SPNEGO_SERVER_START: { NTSTATUS nt_status; if (in.length) { len = spnego_read_data(gensec_security, in, &spnego); if (len == -1) { return gensec_spnego_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, ev, in, out); } /* client sent NegTargetInit, we send NegTokenTarg */ /* OK, so it's real SPNEGO, check the packet's the one we expect */ if (spnego.type != spnego_state->expected_packet) { DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, spnego_state->expected_packet)); dump_data(1, in.data, in.length); spnego_free_data(&spnego); return NT_STATUS_INVALID_PARAMETER; } nt_status = gensec_spnego_parse_negTokenInit(gensec_security, spnego_state, out_mem_ctx, ev, spnego.negTokenInit.mechTypes, spnego.negTokenInit.mechToken, &unwrapped_out); nt_status = gensec_spnego_server_negTokenTarg(spnego_state, out_mem_ctx, nt_status, unwrapped_out, null_data_blob, out); spnego_free_data(&spnego); return nt_status; } else { nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state, out_mem_ctx, ev, in, out); spnego_state->state_position = SPNEGO_SERVER_START; spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; return nt_status; } } case SPNEGO_CLIENT_START: { /* The server offers a list of mechanisms */ const char *my_mechs[] = {NULL, NULL}; NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; if (!in.length) { /* client to produce negTokenInit */ nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state, out_mem_ctx, ev, in, out); spnego_state->state_position = SPNEGO_CLIENT_TARG; spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; return nt_status; } len = spnego_read_data(gensec_security, in, &spnego); if (len == -1) { DEBUG(1, ("Invalid SPNEGO request:\n")); dump_data(1, in.data, in.length); return NT_STATUS_INVALID_PARAMETER; } /* OK, so it's real SPNEGO, check the packet's the one we expect */ if (spnego.type != spnego_state->expected_packet) { DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, spnego_state->expected_packet)); dump_data(1, in.data, in.length); spnego_free_data(&spnego); return NT_STATUS_INVALID_PARAMETER; } if (spnego.negTokenInit.targetPrincipal && strcmp(spnego.negTokenInit.targetPrincipal, ADS_IGNORE_PRINCIPAL) != 0) { DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal)); if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) { gensec_set_target_principal(gensec_security, spnego.negTokenInit.targetPrincipal); } } nt_status = gensec_spnego_parse_negTokenInit(gensec_security, spnego_state, out_mem_ctx, ev, spnego.negTokenInit.mechTypes, spnego.negTokenInit.mechToken, &unwrapped_out); if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { spnego_free_data(&spnego); return nt_status; } my_mechs[0] = spnego_state->neg_oid; /* compose reply */ spnego_out.type = SPNEGO_NEG_TOKEN_INIT; spnego_out.negTokenInit.mechTypes = my_mechs; spnego_out.negTokenInit.reqFlags = null_data_blob; spnego_out.negTokenInit.reqFlagsPadding = 0; spnego_out.negTokenInit.mechListMIC = null_data_blob; spnego_out.negTokenInit.mechToken = unwrapped_out; if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n")); return NT_STATUS_INVALID_PARAMETER; } /* set next state */ spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; spnego_state->state_position = SPNEGO_CLIENT_TARG; if (NT_STATUS_IS_OK(nt_status)) { spnego_state->no_response_expected = true; } spnego_free_data(&spnego); return NT_STATUS_MORE_PROCESSING_REQUIRED; } case SPNEGO_SERVER_TARG: { NTSTATUS nt_status; bool new_spnego = false; if (!in.length) { return NT_STATUS_INVALID_PARAMETER; } len = spnego_read_data(gensec_security, in, &spnego); if (len == -1) { DEBUG(1, ("Invalid SPNEGO request:\n")); dump_data(1, in.data, in.length); return NT_STATUS_INVALID_PARAMETER; } /* OK, so it's real SPNEGO, check the packet's the one we expect */ if (spnego.type != spnego_state->expected_packet) { DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, spnego_state->expected_packet)); dump_data(1, in.data, in.length); spnego_free_data(&spnego); return NT_STATUS_INVALID_PARAMETER; } if (!spnego_state->sub_sec_security) { DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n")); spnego_free_data(&spnego); return NT_STATUS_INVALID_PARAMETER; } nt_status = gensec_update(spnego_state->sub_sec_security, out_mem_ctx, ev, spnego.negTokenTarg.responseToken, &unwrapped_out); if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) { new_spnego = true; nt_status = gensec_check_packet(spnego_state->sub_sec_security, spnego_state->mech_types.data, spnego_state->mech_types.length, spnego_state->mech_types.data, spnego_state->mech_types.length, &spnego.negTokenTarg.mechListMIC); if (!NT_STATUS_IS_OK(nt_status)) { DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n", nt_errstr(nt_status))); } } if (NT_STATUS_IS_OK(nt_status) && new_spnego) { nt_status = gensec_sign_packet(spnego_state->sub_sec_security, out_mem_ctx, spnego_state->mech_types.data, spnego_state->mech_types.length, spnego_state->mech_types.data, spnego_state->mech_types.length, &mech_list_mic); if (!NT_STATUS_IS_OK(nt_status)) { DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n", nt_errstr(nt_status))); } } nt_status = gensec_spnego_server_negTokenTarg(spnego_state, out_mem_ctx, nt_status, unwrapped_out, mech_list_mic, out); spnego_free_data(&spnego); return nt_status; } case SPNEGO_CLIENT_TARG: { NTSTATUS nt_status; if (!in.length) { return NT_STATUS_INVALID_PARAMETER; } len = spnego_read_data(gensec_security, in, &spnego); if (len == -1) { DEBUG(1, ("Invalid SPNEGO request:\n")); dump_data(1, in.data, in.length); return NT_STATUS_INVALID_PARAMETER; } /* OK, so it's real SPNEGO, check the packet's the one we expect */ if (spnego.type != spnego_state->expected_packet) { DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, spnego_state->expected_packet)); dump_data(1, in.data, in.length); spnego_free_data(&spnego); return NT_STATUS_INVALID_PARAMETER; } if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) { spnego_free_data(&spnego); return NT_STATUS_ACCESS_DENIED; } /* Server didn't like our choice of mech, and chose something else */ if ((spnego.negTokenTarg.negResult == SPNEGO_ACCEPT_INCOMPLETE) && spnego.negTokenTarg.supportedMech && strcmp(spnego.negTokenTarg.supportedMech, spnego_state->neg_oid) != 0) { DEBUG(3,("GENSEC SPNEGO: client preferred mech (%s) not accepted, server wants: %s\n", gensec_get_name_by_oid(gensec_security, spnego.negTokenTarg.supportedMech), gensec_get_name_by_oid(gensec_security, spnego_state->neg_oid))); talloc_free(spnego_state->sub_sec_security); nt_status = gensec_subcontext_start(spnego_state, gensec_security, &spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { spnego_free_data(&spnego); return nt_status; } /* select the sub context */ nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, spnego.negTokenTarg.supportedMech); if (!NT_STATUS_IS_OK(nt_status)) { spnego_free_data(&spnego); return nt_status; } nt_status = gensec_update(spnego_state->sub_sec_security, out_mem_ctx, ev, spnego.negTokenTarg.responseToken, &unwrapped_out); spnego_state->neg_oid = talloc_strdup(spnego_state, spnego.negTokenTarg.supportedMech); } else if (spnego_state->no_response_expected) { if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) { DEBUG(3,("GENSEC SPNEGO: client GENSEC accepted, but server rejected (bad password?)\n")); nt_status = NT_STATUS_INVALID_PARAMETER; } else if (spnego.negTokenTarg.responseToken.length) { DEBUG(2,("GENSEC SPNEGO: client GENSEC accepted, but server continued negotiation!\n")); nt_status = NT_STATUS_INVALID_PARAMETER; } else { nt_status = NT_STATUS_OK; } if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) { nt_status = gensec_check_packet(spnego_state->sub_sec_security, spnego_state->mech_types.data, spnego_state->mech_types.length, spnego_state->mech_types.data, spnego_state->mech_types.length, &spnego.negTokenTarg.mechListMIC); if (!NT_STATUS_IS_OK(nt_status)) { DEBUG(2,("GENSEC SPNEGO: failed to verify mechListMIC: %s\n", nt_errstr(nt_status))); } } } else { bool new_spnego = false; nt_status = gensec_update(spnego_state->sub_sec_security, out_mem_ctx, ev, spnego.negTokenTarg.responseToken, &unwrapped_out); if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) { new_spnego = gensec_have_feature(spnego_state->sub_sec_security, GENSEC_FEATURE_NEW_SPNEGO); } if (NT_STATUS_IS_OK(nt_status) && new_spnego) { nt_status = gensec_sign_packet(spnego_state->sub_sec_security, out_mem_ctx, spnego_state->mech_types.data, spnego_state->mech_types.length, spnego_state->mech_types.data, spnego_state->mech_types.length, &mech_list_mic); if (!NT_STATUS_IS_OK(nt_status)) { DEBUG(2,("GENSEC SPNEGO: failed to sign mechListMIC: %s\n", nt_errstr(nt_status))); } } if (NT_STATUS_IS_OK(nt_status)) { spnego_state->no_response_expected = true; } } spnego_free_data(&spnego); if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { DEBUG(1, ("SPNEGO(%s) login failed: %s\n", spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); return nt_status; } if (unwrapped_out.length || mech_list_mic.length) { /* compose reply */ spnego_out.type = SPNEGO_NEG_TOKEN_TARG; spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT; spnego_out.negTokenTarg.supportedMech = NULL; spnego_out.negTokenTarg.responseToken = unwrapped_out; spnego_out.negTokenTarg.mechListMIC = mech_list_mic; if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); return NT_STATUS_INVALID_PARAMETER; } spnego_state->state_position = SPNEGO_CLIENT_TARG; nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; } else { /* all done - server has accepted, and we agree */ *out = null_data_blob; if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) { /* unless of course it did not accept */ DEBUG(1,("gensec_update ok but not accepted\n")); nt_status = NT_STATUS_INVALID_PARAMETER; } spnego_state->state_position = SPNEGO_DONE; } return nt_status; } case SPNEGO_DONE: /* We should not be called after we are 'done' */ return NT_STATUS_INVALID_PARAMETER; } return NT_STATUS_INVALID_PARAMETER; } static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, const DATA_BLOB in, DATA_BLOB *full_in) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; size_t expected; NTSTATUS status; bool ok; *full_in = data_blob_null; if (spnego_state->in_needed == 0) { size_t size = 0; /* * try to work out the size of the full * input token, it might be fragmented */ status = asn1_peek_full_tag(in, ASN1_APPLICATION(0), &size); if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { status = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size); } if (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) { spnego_state->in_needed = size; } else { /* * If it is not an asn1 message * just call the next layer. */ spnego_state->in_needed = in.length; } } if (spnego_state->in_needed > UINT16_MAX) { /* * limit the incoming message to 0xFFFF * to avoid DoS attacks. */ return NT_STATUS_INVALID_BUFFER_SIZE; } if ((spnego_state->in_needed > 0) && (in.length == 0)) { /* * If we reach this, we know we got at least * part of an asn1 message, getting 0 means * the remote peer wants us to spin. */ return NT_STATUS_INVALID_PARAMETER; } expected = spnego_state->in_needed - spnego_state->in_frag.length; if (in.length > expected) { /* * we got more than expected */ return NT_STATUS_INVALID_PARAMETER; } if (in.length == spnego_state->in_needed) { /* * if the in.length contains the full blob * we are done. * * Note: this implies spnego_state->in_frag.length == 0, * but we do not need to check this explicitly * because we already know that we did not get * more than expected. */ *full_in = in; return NT_STATUS_OK; } ok = data_blob_append(spnego_state, &spnego_state->in_frag, in.data, in.length); if (!ok) { return NT_STATUS_NO_MEMORY; } if (spnego_state->in_needed > spnego_state->in_frag.length) { return NT_STATUS_MORE_PROCESSING_REQUIRED; } *full_in = spnego_state->in_frag; return NT_STATUS_OK; } static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, DATA_BLOB *_out) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; DATA_BLOB out = data_blob_null; *_out = data_blob_null; if (spnego_state->out_frag.length == 0) { return spnego_state->out_status; } /* * There is still more data to be delivered * to the remote peer. */ if (spnego_state->out_frag.length <= spnego_state->out_max_length) { /* * Fast path, we can deliver everything */ *_out = spnego_state->out_frag; talloc_steal(out_mem_ctx, _out->data); spnego_state->out_frag = data_blob_null; return spnego_state->out_status; } out = spnego_state->out_frag; /* * copy the remaining bytes */ spnego_state->out_frag = data_blob_talloc(spnego_state, out.data + spnego_state->out_max_length, out.length - spnego_state->out_max_length); if (spnego_state->out_frag.data == NULL) { return NT_STATUS_NO_MEMORY; } /* * truncate the buffer */ data_blob_realloc(spnego_state, &out, spnego_state->out_max_length); talloc_steal(out_mem_ctx, out.data); *_out = out; return NT_STATUS_MORE_PROCESSING_REQUIRED; } static NTSTATUS gensec_spnego_update_wrapper(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, struct tevent_context *ev, const DATA_BLOB in, DATA_BLOB *out) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; DATA_BLOB full_in = data_blob_null; NTSTATUS status; *out = data_blob_null; if (spnego_state->out_frag.length > 0) { if (in.length > 0) { return NT_STATUS_INVALID_PARAMETER; } return gensec_spnego_update_out(gensec_security, out_mem_ctx, out); } status = gensec_spnego_update_in(gensec_security, in, &full_in); if (!NT_STATUS_IS_OK(status)) { return status; } status = gensec_spnego_update(gensec_security, spnego_state, ev, full_in, &spnego_state->out_frag); data_blob_free(&spnego_state->in_frag); spnego_state->in_needed = 0; if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { return status; } spnego_state->out_status = status; return gensec_spnego_update_out(gensec_security, out_mem_ctx, out); } static void gensec_spnego_want_feature(struct gensec_security *gensec_security, uint32_t feature) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (!spnego_state || !spnego_state->sub_sec_security) { gensec_security->want_features |= feature; return; } gensec_want_feature(spnego_state->sub_sec_security, feature); } static bool gensec_spnego_have_feature(struct gensec_security *gensec_security, uint32_t feature) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (!spnego_state->sub_sec_security) { return false; } return gensec_have_feature(spnego_state->sub_sec_security, feature); } static NTTIME gensec_spnego_expire_time(struct gensec_security *gensec_security) { struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data; if (!spnego_state->sub_sec_security) { return GENSEC_EXPIRE_TIME_INFINITY; } return gensec_expire_time(spnego_state->sub_sec_security); } static const char *gensec_spnego_oids[] = { GENSEC_OID_SPNEGO, NULL }; static const struct gensec_security_ops gensec_spnego_security_ops = { .name = "spnego", .sasl_name = "GSS-SPNEGO", .auth_type = DCERPC_AUTH_TYPE_SPNEGO, .oid = gensec_spnego_oids, .client_start = gensec_spnego_client_start, .server_start = gensec_spnego_server_start, .update = gensec_spnego_update_wrapper, .seal_packet = gensec_spnego_seal_packet, .sign_packet = gensec_spnego_sign_packet, .sig_size = gensec_spnego_sig_size, .max_wrapped_size = gensec_spnego_max_wrapped_size, .max_input_size = gensec_spnego_max_input_size, .check_packet = gensec_spnego_check_packet, .unseal_packet = gensec_spnego_unseal_packet, .packet_full_request = gensec_spnego_packet_full_request, .wrap = gensec_spnego_wrap, .unwrap = gensec_spnego_unwrap, .wrap_packets = gensec_spnego_wrap_packets, .unwrap_packets = gensec_spnego_unwrap_packets, .session_key = gensec_spnego_session_key, .session_info = gensec_spnego_session_info, .want_feature = gensec_spnego_want_feature, .have_feature = gensec_spnego_have_feature, .expire_time = gensec_spnego_expire_time, .enabled = true, .priority = GENSEC_SPNEGO }; _PUBLIC_ NTSTATUS gensec_spnego_init(void) { NTSTATUS ret; ret = gensec_register(&gensec_spnego_security_ops); if (!NT_STATUS_IS_OK(ret)) { DEBUG(0,("Failed to register '%s' gensec backend!\n", gensec_spnego_security_ops.name)); return ret; } return ret; }