diff options
Diffstat (limited to '')
-rw-r--r-- | auth/gensec/external.c | 127 | ||||
-rw-r--r-- | auth/gensec/gensec.c | 854 | ||||
-rw-r--r-- | auth/gensec/gensec.h | 327 | ||||
-rw-r--r-- | auth/gensec/gensec_internal.h | 183 | ||||
-rw-r--r-- | auth/gensec/gensec_start.c | 1147 | ||||
-rw-r--r-- | auth/gensec/gensec_util.c | 338 | ||||
-rw-r--r-- | auth/gensec/ncalrpc.c | 356 | ||||
-rw-r--r-- | auth/gensec/schannel.c | 1222 | ||||
-rw-r--r-- | auth/gensec/spnego.c | 2249 | ||||
-rw-r--r-- | auth/gensec/wscript_build | 37 |
10 files changed, 6840 insertions, 0 deletions
diff --git a/auth/gensec/external.c b/auth/gensec/external.c new file mode 100644 index 0000000..300ce6b --- /dev/null +++ b/auth/gensec/external.c @@ -0,0 +1,127 @@ +/* + Unix SMB/CIFS implementation. + + SASL/EXTERNAL authentication. + + Copyright (C) Howard Chu <hyc@symas.com> 2013 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/gensec/gensec_proto.h" +#include "auth/gensec/gensec_toplevel_proto.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/* SASL/EXTERNAL is essentially a no-op; it is only usable when the transport + * layer is already mutually authenticated. + */ + +NTSTATUS gensec_external_init(TALLOC_CTX *ctx); + +static NTSTATUS gensec_external_start(struct gensec_security *gensec_security) +{ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) + return NT_STATUS_INVALID_PARAMETER; + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) + return NT_STATUS_INVALID_PARAMETER; + + return NT_STATUS_OK; +} + +struct gensec_external_update_state { + DATA_BLOB out; +}; + +static struct tevent_req *gensec_external_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req; + struct gensec_external_update_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_external_update_state); + if (req == NULL) { + return NULL; + } + + state->out = data_blob_talloc(state, "", 0); + if (tevent_req_nomem(state->out.data, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_external_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_external_update_state *state = + tevent_req_data(req, + struct gensec_external_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + tevent_req_received(req); + return NT_STATUS_OK; +} + +/* We have no features */ +static bool gensec_external_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + return false; +} + +static const struct gensec_security_ops gensec_external_ops = { + .name = "sasl-EXTERNAL", + .sasl_name = "EXTERNAL", + .client_start = gensec_external_start, + .update_send = gensec_external_update_send, + .update_recv = gensec_external_update_recv, + .have_feature = gensec_external_have_feature, + .enabled = true, + .priority = GENSEC_EXTERNAL +}; + + +NTSTATUS gensec_external_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = gensec_register(ctx, &gensec_external_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_external_ops.name)); + } + return ret; +} diff --git a/auth/gensec/gensec.c b/auth/gensec/gensec.c new file mode 100644 index 0000000..3641d4b --- /dev/null +++ b/auth/gensec/gensec.c @@ -0,0 +1,854 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#define TEVENT_DEPRECATED 1 +#include <tevent.h> +#include "lib/tsocket/tsocket.h" +#include "lib/util/tevent_ntstatus.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "auth/common_auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +_PRIVATE_ NTSTATUS gensec_may_reset_crypto(struct gensec_security *gensec_security, + bool full_reset) +{ + if (!gensec_security->ops->may_reset_crypto) { + return NT_STATUS_OK; + } + + return gensec_security->ops->may_reset_crypto(gensec_security, full_reset); +} + +/* + wrappers for the gensec function pointers +*/ +_PUBLIC_ NTSTATUS gensec_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) +{ + if (!gensec_security->ops->unseal_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->unseal_packet(gensec_security, + data, length, + whole_pdu, pdu_length, + sig); +} + +_PUBLIC_ NTSTATUS gensec_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) +{ + if (!gensec_security->ops->check_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->check_packet(gensec_security, data, length, whole_pdu, pdu_length, sig); +} + +_PUBLIC_ NTSTATUS gensec_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) +{ + if (!gensec_security->ops->seal_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->seal_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +_PUBLIC_ NTSTATUS gensec_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) +{ + if (!gensec_security->ops->sign_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->sign_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +_PUBLIC_ size_t gensec_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + if (!gensec_security->ops->sig_size) { + return 0; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return 0; + } + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE)) { + return 0; + } + } + + return gensec_security->ops->sig_size(gensec_security, data_size); +} + +_PUBLIC_ size_t gensec_max_wrapped_size(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->max_wrapped_size) { + return (1 << 17); + } + + return gensec_security->ops->max_wrapped_size(gensec_security); +} + +_PUBLIC_ size_t gensec_max_input_size(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->max_input_size) { + return (1 << 17) - gensec_sig_size(gensec_security, 1 << 17); + } + + return gensec_security->ops->max_input_size(gensec_security); +} + +_PUBLIC_ NTSTATUS gensec_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (!gensec_security->ops->wrap) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->wrap(gensec_security, mem_ctx, in, out); +} + +_PUBLIC_ NTSTATUS gensec_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (!gensec_security->ops->unwrap) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->unwrap(gensec_security, mem_ctx, in, out); +} + +_PUBLIC_ NTSTATUS gensec_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SESSION_KEY)) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!gensec_security->ops->session_key) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return gensec_security->ops->session_key(gensec_security, mem_ctx, session_key); +} + +const char *gensec_final_auth_type(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->final_auth_type) { + return gensec_security->ops->name; + } + + return gensec_security->ops->final_auth_type(gensec_security); +} + +/* + * Log details of a successful GENSEC authorization to a service. + * + * Only successful authorizations are logged, as only these call gensec_session_info() + * + * The service may later refuse authorization due to an ACL. + * + */ +static void log_successful_gensec_authz_event(struct gensec_security *gensec_security, + struct auth_session_info *session_info) +{ + const struct tsocket_address *remote + = gensec_get_remote_address(gensec_security); + const struct tsocket_address *local + = gensec_get_local_address(gensec_security); + const char *service_description + = gensec_get_target_service_description(gensec_security); + const char *final_auth_type + = gensec_final_auth_type(gensec_security); + const char *transport_protection = NULL; + if (gensec_security->want_features & GENSEC_FEATURE_SMB_TRANSPORT) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SMB; + } else if (gensec_security->want_features & GENSEC_FEATURE_LDAPS_TRANSPORT) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_TLS; + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SEAL; + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SIGN; + } else { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE; + } + log_successful_authz_event(gensec_security->auth_context->msg_ctx, + gensec_security->auth_context->lp_ctx, + remote, local, + service_description, + final_auth_type, + transport_protection, + session_info); +} + + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. This will also make an + * authorization log entry, as it is already called by all the + * callers. + * + */ + +_PUBLIC_ NTSTATUS gensec_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) +{ + NTSTATUS status; + if (!gensec_security->ops->session_info) { + return NT_STATUS_NOT_IMPLEMENTED; + } + status = gensec_security->ops->session_info(gensec_security, mem_ctx, session_info); + + if (NT_STATUS_IS_OK(status) && !gensec_security->subcontext + && (gensec_security->want_features & GENSEC_FEATURE_NO_AUTHZ_LOG) == 0) { + log_successful_gensec_authz_event(gensec_security, *session_info); + } + + return status; +} + +_PUBLIC_ void gensec_set_max_update_size(struct gensec_security *gensec_security, + uint32_t max_update_size) +{ + gensec_security->max_update_size = max_update_size; +} + +_PUBLIC_ size_t gensec_max_update_size(struct gensec_security *gensec_security) +{ + if (gensec_security->max_update_size == 0) { + return UINT32_MAX; + } + + return gensec_security->max_update_size; +} + +static NTSTATUS gensec_verify_features(struct gensec_security *gensec_security) +{ + bool ok; + + /* + * gensec_want_feature(GENSEC_FEATURE_SIGN) + * and + * gensec_want_feature(GENSEC_FEATURE_SEAL) + * require these flags to be available. + */ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(0,("Did not manage to negotiate mandatory feature " + "SIGN\n")); + return NT_STATUS_ACCESS_DENIED; + } + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + DEBUG(0,("Did not manage to negotiate mandatory feature " + "SEAL\n")); + return NT_STATUS_ACCESS_DENIED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(0,("Did not manage to negotiate mandatory feature " + "SIGN for SEAL\n")); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (gensec_security->dcerpc_auth_level < DCERPC_AUTH_LEVEL_PACKET) { + return NT_STATUS_OK; + } + + ok = gensec_have_feature(gensec_security, + GENSEC_FEATURE_SIGN_PKT_HEADER); + if (!ok) { + DBG_ERR("backend [%s] does not support header signing! " + "auth_level[0x%x]\n", + gensec_security->ops->name, + gensec_security->dcerpc_auth_level); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +/** + * Next state function for the GENSEC state machine + * + * @param gensec_security GENSEC State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ +_PUBLIC_ NTSTATUS gensec_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + NTSTATUS status; + TALLOC_CTX *frame = NULL; + struct tevent_context *ev = NULL; + struct tevent_req *subreq = NULL; + bool ok; + + if (gensec_security->subcontext) { + /* + * gensec modules are not allowed to call the sync version. + */ + return NT_STATUS_INTERNAL_ERROR; + } + + frame = talloc_stackframe(); + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + /* + * TODO: remove this hack once the backends + * are fixed. + */ + tevent_loop_allow_nesting(ev); + + subreq = gensec_update_send(frame, ev, gensec_security, in); + if (subreq == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + ok = tevent_req_poll_ntstatus(subreq, ev, &status); + if (!ok) { + goto fail; + } + status = gensec_update_recv(subreq, out_mem_ctx, out); + fail: + TALLOC_FREE(frame); + return status; +} + +struct gensec_update_state { + const struct gensec_security_ops *ops; + struct gensec_security *gensec_security; + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state); +static void gensec_update_done(struct tevent_req *subreq); + +/** + * Next state function for the GENSEC state machine async version + * + * @param mem_ctx The memory context for the request + * @param ev The event context for the request + * @param gensec_security GENSEC State + * @param in The request, as a DATA_BLOB + * + * @return The request handle or NULL on no memory failure + */ + +_PUBLIC_ struct tevent_req *gensec_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req = NULL; + struct gensec_update_state *state = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_update_state); + if (req == NULL) { + return NULL; + } + state->ops = gensec_security->ops; + state->gensec_security = gensec_security; + + if (gensec_security->update_busy_ptr != NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return tevent_req_post(req, ev); + } + + if (gensec_security->child_security != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + gensec_security->update_busy_ptr = &state->gensec_security; + tevent_req_set_cleanup_fn(req, gensec_update_cleanup); + + subreq = state->ops->update_send(state, ev, gensec_security, in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, gensec_update_done, req); + + DBG_DEBUG("%s[%p]: subreq: %p\n", state->ops->name, + state->gensec_security, subreq); + + return req; +} + +static void gensec_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct gensec_update_state *state = + tevent_req_data(req, + struct gensec_update_state); + + if (state->gensec_security == NULL) { + return; + } + + if (state->gensec_security->update_busy_ptr == &state->gensec_security) { + state->gensec_security->update_busy_ptr = NULL; + } + + state->gensec_security = NULL; +} + +static void gensec_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_update_state *state = + tevent_req_data(req, + struct gensec_update_state); + NTSTATUS status; + const char *debug_subreq = NULL; + + if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) { + /* + * We need to call tevent_req_print() + * before calling the _recv function, + * before tevent_req_received() was called. + * in order to print the pointer value of + * the subreq state. + */ + debug_subreq = tevent_req_print(state, subreq); + } + + status = state->ops->update_recv(subreq, state, &state->out); + TALLOC_FREE(subreq); + state->status = status; + if (GENSEC_UPDATE_IS_NTERROR(status)) { + NTSTATUS orig_status = status; + bool force_no_such_user = false; + + /* + * callers only expect NT_STATUS_NO_SUCH_USER. + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_ACCOUNT_NAME)) { + force_no_such_user = true; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) { + force_no_such_user = true; + } + + if (state->gensec_security->subcontext) { + /* + * We should only map on the outer + * gensec_update exchange, spnego + * needs the raw status. + */ + force_no_such_user = false; + } + + if (force_no_such_user) { + /* + * nt_status_squash() may map + * to NT_STATUS_LOGON_FAILURE later + */ + status = NT_STATUS_NO_SUCH_USER; + } + + DBG_INFO("%s[%p]: %s%s%s%s%s\n", + state->ops->name, + state->gensec_security, + NT_STATUS_EQUAL(status, orig_status) ? + "" : nt_errstr(orig_status), + NT_STATUS_EQUAL(status, orig_status) ? + "" : " ", + nt_errstr(status), + debug_subreq ? " " : "", + debug_subreq ? debug_subreq : ""); + tevent_req_nterror(req, status); + return; + } + DBG_DEBUG("%s[%p]: %s %s\n", state->ops->name, + state->gensec_security, nt_errstr(status), + debug_subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return; + } + + /* + * Because callers using the + * gensec_start_mech_by_authtype() never call + * gensec_want_feature(), it isn't sensible for them + * to have to call gensec_have_feature() manually, and + * these are not points of negotiation, but are + * asserted by the client + */ + status = gensec_verify_features(state->gensec_security); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +/** + * Next state function for the GENSEC state machine + * + * @param req request state + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ +_PUBLIC_ NTSTATUS gensec_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_update_state *state = + tevent_req_data(req, struct gensec_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, out->data); + status = state->status; + tevent_req_received(req); + return status; +} + +/** + * Set the requirement for a certain feature on the connection + * + */ + +_PUBLIC_ void gensec_want_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (!gensec_security->ops || !gensec_security->ops->want_feature) { + gensec_security->want_features |= feature; + return; + } + gensec_security->ops->want_feature(gensec_security, feature); +} + +/** + * Check the requirement for a certain feature on the connection + * + */ + +_PUBLIC_ bool gensec_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (!gensec_security->ops || !gensec_security->ops->have_feature) { + return false; + } + + /* We might 'have' features that we don't 'want', because the + * other end demanded them, or we can't negotiate them off */ + return gensec_security->ops->have_feature(gensec_security, feature); +} + +_PUBLIC_ NTTIME gensec_expire_time(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->expire_time) { + return GENSEC_EXPIRE_TIME_INFINITY; + } + + return gensec_security->ops->expire_time(gensec_security); +} +/** + * Return the credentials structure associated with a GENSEC context + * + */ + +_PUBLIC_ struct cli_credentials *gensec_get_credentials(struct gensec_security *gensec_security) +{ + if (!gensec_security) { + return NULL; + } + return gensec_security->credentials; +} + +/** + * Set the target service (such as 'http' or 'host') on a GENSEC context - ensures it is talloc()ed + * + * This is used for Kerberos service principal name resolution. + */ + +_PUBLIC_ NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service) +{ + gensec_security->target.service = talloc_strdup(gensec_security, service); + if (!gensec_security->target.service) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_service(struct gensec_security *gensec_security) +{ + if (gensec_security->target.service) { + return gensec_security->target.service; + } + + return "host"; +} + +/** + * Set the target service (such as 'samr') on an GENSEC context - ensures it is talloc()ed. + * + * This is not the Kerberos service principal, instead this is a + * constant value that can be logged as part of authentication and + * authorization logging + */ +_PUBLIC_ NTSTATUS gensec_set_target_service_description(struct gensec_security *gensec_security, + const char *service) +{ + gensec_security->target.service_description = talloc_strdup(gensec_security, service); + if (!gensec_security->target.service_description) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_service_description(struct gensec_security *gensec_security) +{ + if (gensec_security->target.service_description) { + return gensec_security->target.service_description; + } else if (gensec_security->target.service) { + return gensec_security->target.service; + } + + return NULL; +} + +/** + * Set the target hostname (suitable for kerberos resolutation) on a GENSEC context - ensures it is talloc()ed + * + */ + +_PUBLIC_ NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname) +{ + gensec_security->target.hostname = talloc_strdup(gensec_security, hostname); + if (hostname && !gensec_security->target.hostname) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_hostname(struct gensec_security *gensec_security) +{ + /* We allow the target hostname to be overridden for testing purposes */ + if (gensec_security->settings->target_hostname) { + return gensec_security->settings->target_hostname; + } + + if (gensec_security->target.hostname) { + return gensec_security->target.hostname; + } + + /* We could add use the 'set sockaddr' call, and do a reverse + * lookup, but this would be both insecure (compromising the + * way kerberos works) and add DNS timeouts */ + return NULL; +} + +/** + * Set (and copy) local and peer socket addresses onto a socket + * context on the GENSEC context. + * + * This is so that kerberos can include these addresses in + * cryptographic tokens, to avoid certain attacks. + */ + +/** + * @brief Set the local gensec address. + * + * @param gensec_security The gensec security context to use. + * + * @param remote The local address to set. + * + * @return On success NT_STATUS_OK is returned or an NT_STATUS + * error. + */ +_PUBLIC_ NTSTATUS gensec_set_local_address(struct gensec_security *gensec_security, + const struct tsocket_address *local) +{ + TALLOC_FREE(gensec_security->local_addr); + + if (local == NULL) { + return NT_STATUS_OK; + } + + gensec_security->local_addr = tsocket_address_copy(local, gensec_security); + if (gensec_security->local_addr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/** + * @brief Set the remote gensec address. + * + * @param gensec_security The gensec security context to use. + * + * @param remote The remote address to set. + * + * @return On success NT_STATUS_OK is returned or an NT_STATUS + * error. + */ +_PUBLIC_ NTSTATUS gensec_set_remote_address(struct gensec_security *gensec_security, + const struct tsocket_address *remote) +{ + TALLOC_FREE(gensec_security->remote_addr); + + if (remote == NULL) { + return NT_STATUS_OK; + } + + gensec_security->remote_addr = tsocket_address_copy(remote, gensec_security); + if (gensec_security->remote_addr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/** + * @brief Get the local address from a gensec security context. + * + * @param gensec_security The security context to get the address from. + * + * @return The address as tsocket_address which could be NULL if + * no address is set. + */ +_PUBLIC_ const struct tsocket_address *gensec_get_local_address(struct gensec_security *gensec_security) +{ + if (gensec_security == NULL) { + return NULL; + } + return gensec_security->local_addr; +} + +/** + * @brief Get the remote address from a gensec security context. + * + * @param gensec_security The security context to get the address from. + * + * @return The address as tsocket_address which could be NULL if + * no address is set. + */ +_PUBLIC_ const struct tsocket_address *gensec_get_remote_address(struct gensec_security *gensec_security) +{ + if (gensec_security == NULL) { + return NULL; + } + return gensec_security->remote_addr; +} + +/** + * Set the target principal (assuming it it known, say from the SPNEGO reply) + * - ensures it is talloc()ed + * + */ + +_PUBLIC_ NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, const char *principal) +{ + gensec_security->target.principal = talloc_strdup(gensec_security, principal); + if (!gensec_security->target.principal) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_principal(struct gensec_security *gensec_security) +{ + if (gensec_security->target.principal) { + return gensec_security->target.principal; + } + + return NULL; +} diff --git a/auth/gensec/gensec.h b/auth/gensec/gensec.h new file mode 100644 index 0000000..29d5e92 --- /dev/null +++ b/auth/gensec/gensec.h @@ -0,0 +1,327 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __GENSEC_H__ +#define __GENSEC_H__ + +#include "../lib/util/data_blob.h" +#include "libcli/util/ntstatus.h" + +#define GENSEC_SASL_NAME_NTLMSSP "NTLM" + +#define GENSEC_OID_NTLMSSP "1.3.6.1.4.1.311.2.2.10" +#define GENSEC_OID_SPNEGO "1.3.6.1.5.5.2" +#define GENSEC_OID_KERBEROS5 "1.2.840.113554.1.2.2" +#define GENSEC_OID_KERBEROS5_OLD "1.2.840.48018.1.2.2" +#define GENSEC_OID_KERBEROS5_USER2USER "1.2.840.113554.1.2.2.3" + +#define GENSEC_FINAL_AUTH_TYPE_KRB5 "krb5" +#define GENSEC_FINAL_AUTH_TYPE_NTLMSSP "NTLMSSP" + +enum gensec_priority { + GENSEC_SPNEGO = 90, + GENSEC_GSSAPI = 80, + GENSEC_KRB5 = 70, + GENSEC_SCHANNEL = 60, + GENSEC_NTLMSSP = 50, + GENSEC_SASL = 20, + GENSEC_OTHER = 10, + GENSEC_EXTERNAL = 0 +}; + +struct gensec_security; +struct gensec_target { + const char *principal; + const char *hostname; + const char *service; + const char *service_description; +}; + +#define GENSEC_FEATURE_SESSION_KEY 0x00000001 +#define GENSEC_FEATURE_SIGN 0x00000002 +#define GENSEC_FEATURE_SEAL 0x00000004 +#define GENSEC_FEATURE_DCE_STYLE 0x00000008 +#define GENSEC_FEATURE_ASYNC_REPLIES 0x00000010 +#define GENSEC_FEATURE_DATAGRAM_MODE 0x00000020 +#define GENSEC_FEATURE_SIGN_PKT_HEADER 0x00000040 +#define GENSEC_FEATURE_NEW_SPNEGO 0x00000080 +#define GENSEC_FEATURE_UNIX_TOKEN 0x00000100 +#define GENSEC_FEATURE_NTLM_CCACHE 0x00000200 +#define GENSEC_FEATURE_LDAP_STYLE 0x00000400 +#define GENSEC_FEATURE_NO_AUTHZ_LOG 0x00000800 +#define GENSEC_FEATURE_SMB_TRANSPORT 0x00001000 +#define GENSEC_FEATURE_LDAPS_TRANSPORT 0x00002000 + +#define GENSEC_EXPIRE_TIME_INFINITY (NTTIME)0x8000000000000000LL + +/* GENSEC mode */ +enum gensec_role +{ + GENSEC_SERVER, + GENSEC_CLIENT +}; + +struct auth_session_info; +struct cli_credentials; +struct gensec_settings; +struct tevent_context; +struct tevent_req; +struct smb_krb5_context; +struct tsocket_address; + +struct gensec_settings { + struct loadparm_context *lp_ctx; + const char *target_hostname; + + /* this allows callers to specify a specific set of ops that + * should be used, rather than those loaded by the plugin + * mechanism */ + const struct gensec_security_ops * const *backends; + + /* To fill in our own name in the NTLMSSP server */ + const char *server_dns_domain; + const char *server_dns_name; + const char *server_netbios_domain; + const char *server_netbios_name; +}; + +struct gensec_security_ops; +struct gensec_security_ops_wrapper; + +/* Change to 1, loadable modules now take a TALLOC_CTX * init() parameter. */ +#define GENSEC_INTERFACE_VERSION 1 + +/* this structure is used by backends to determine the size of some critical types */ +struct gensec_critical_sizes; +const struct gensec_critical_sizes *gensec_interface_version(void); + +/* Socket wrapper */ + +struct gensec_security; +struct auth4_context; +struct auth_user_info_dc; + +struct loadparm_context; + +NTSTATUS gensec_subcontext_start(TALLOC_CTX *mem_ctx, + struct gensec_security *parent, + struct gensec_security **gensec_security); +NTSTATUS gensec_client_start(TALLOC_CTX *mem_ctx, + struct gensec_security **gensec_security, + struct gensec_settings *settings); +NTSTATUS gensec_start_mech_by_ops(struct gensec_security *gensec_security, + const struct gensec_security_ops *ops); +NTSTATUS gensec_start_mech_by_sasl_list(struct gensec_security *gensec_security, + const char **sasl_names); +void gensec_set_max_update_size(struct gensec_security *gensec_security, + uint32_t max_update_size); +size_t gensec_max_update_size(struct gensec_security *gensec_security); +NTSTATUS gensec_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); +struct tevent_req *gensec_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); +NTSTATUS gensec_update_recv(struct tevent_req *req, TALLOC_CTX *out_mem_ctx, DATA_BLOB *out); + +#define GENSEC_UPDATE_IS_NTERROR(status) ( \ + !NT_STATUS_IS_OK(status) && \ + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) \ + ) + +/** + * @brief Ask for features for a following authentication + * + * Typically only one specific feature bit should be passed, + * but it also works to ask for more features. + * + * The features must be requested before starting the + * gensec_update*() loop. + * + * The current exception is GENSEC_FEATURE_SIGN_PKT_HEADER, + * it can also be requested once the gensec_update*() loop + * returned NT_STATUS_OK. + * + * The features should not be changed during the gensec_update*() + * loop. + * + * @param[in] gensec_security The context to be used + * + * @param[in] feature The requested feature[s]. + * + */ +void gensec_want_feature(struct gensec_security *gensec_security, + uint32_t feature); +/** + * @brief Ask for one feature after the finished authentication + * + * Because the return value is bool, the caller can only + * ask for one feature at a time. + * + * The features must be requested after the finished + * gensec_update*() loop. + * + * The current exception is GENSEC_FEATURE_SIGN_PKT_HEADER, + * it can also be requested before the gensec_update*() loop, + * as the return value only indicates if the backend supports + * dcerpc header signing, not if header signing will be used + * between client and server. It will be used only if the caller + * also used gensec_want_feature(GENSEC_FEATURE_SIGN_PKT_HEADER). + * + * @param[in] gensec_security The context to be used. + * + * @param[in] feature The requested feature. + * + * @return true if the feature is supported, false if not. + */ +bool gensec_have_feature(struct gensec_security *gensec_security, + uint32_t feature); +NTTIME gensec_expire_time(struct gensec_security *gensec_security); +NTSTATUS gensec_set_credentials(struct gensec_security *gensec_security, struct cli_credentials *credentials); +/** + * Set the target service (such as 'http' or 'host') on a GENSEC context - ensures it is talloc()ed + * + * This is used for Kerberos service principal name resolution. + */ + +NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service); +const char *gensec_get_target_service(struct gensec_security *gensec_security); +NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname); +const char *gensec_get_target_hostname(struct gensec_security *gensec_security); +/** + * Set the target service (such as 'samr') on an GENSEC context - ensures it is talloc()ed. + * + * This is not the Kerberos service principal, instead this is a + * constant value that can be logged as part of authentication and + * authorization logging + */ +const char *gensec_get_target_service_description(struct gensec_security *gensec_security); +NTSTATUS gensec_set_target_service_description(struct gensec_security *gensec_security, + const char *service); +NTSTATUS gensec_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); +NTSTATUS gensec_start_mech_by_oid(struct gensec_security *gensec_security, + const char *mech_oid); +const char *gensec_get_name_by_oid(struct gensec_security *gensec_security, const char *oid_string); +struct cli_credentials *gensec_get_credentials(struct gensec_security *gensec_security); +NTSTATUS gensec_init(void); +NTSTATUS gensec_register(TALLOC_CTX *ctx, + const struct gensec_security_ops *ops); +const struct gensec_security_ops *gensec_security_by_oid(struct gensec_security *gensec_security, + const char *oid_string); +const struct gensec_security_ops *gensec_security_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name); +const struct gensec_security_ops *gensec_security_by_auth_type( + struct gensec_security *gensec_security, + uint32_t auth_type); +const struct gensec_security_ops *gensec_security_by_name(struct gensec_security *gensec_security, + const char *name); +const struct gensec_security_ops **gensec_security_mechs(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx); +const struct gensec_security_ops_wrapper *gensec_security_by_oid_list( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char * const *oid_strings, + const char *skip); +const char **gensec_security_oids(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char *skip); +const char **gensec_security_oids_from_ops_wrapped(TALLOC_CTX *mem_ctx, + const struct gensec_security_ops_wrapper *wops); +size_t gensec_max_input_size(struct gensec_security *gensec_security); +size_t gensec_max_wrapped_size(struct gensec_security *gensec_security); +NTSTATUS gensec_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); +NTSTATUS gensec_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); +size_t gensec_sig_size(struct gensec_security *gensec_security, size_t data_size); +NTSTATUS gensec_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); +NTSTATUS gensec_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); +NTSTATUS gensec_start_mech_by_authtype(struct gensec_security *gensec_security, + uint8_t auth_type, uint8_t auth_level); +const char *gensec_get_name_by_authtype(struct gensec_security *gensec_security, uint8_t authtype); +NTSTATUS gensec_server_start(TALLOC_CTX *mem_ctx, + struct gensec_settings *settings, + struct auth4_context *auth_context, + struct gensec_security **gensec_security); +NTSTATUS gensec_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info); + +NTSTATUS gensec_set_local_address(struct gensec_security *gensec_security, + const struct tsocket_address *local); +NTSTATUS gensec_set_remote_address(struct gensec_security *gensec_security, + const struct tsocket_address *remote); +const struct tsocket_address *gensec_get_local_address(struct gensec_security *gensec_security); +const struct tsocket_address *gensec_get_remote_address(struct gensec_security *gensec_security); + +NTSTATUS gensec_start_mech_by_name(struct gensec_security *gensec_security, + const char *name); + +NTSTATUS gensec_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS gensec_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + +bool gensec_security_ops_enabled(const struct gensec_security_ops *ops, struct gensec_security *security); + +NTSTATUS gensec_start_mech_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name); +const char **gensec_security_sasl_names(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx); + +int gensec_setting_int(struct gensec_settings *settings, const char *mechanism, const char *name, int default_value); +bool gensec_setting_bool(struct gensec_settings *settings, const char *mechanism, const char *name, bool default_value); + +NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, const char *principal); +const char *gensec_get_target_principal(struct gensec_security *gensec_security); + +NTSTATUS gensec_generate_session_info_pac(TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *principal_string, + const struct tsocket_address *remote_address, + struct auth_session_info **session_info); + +NTSTATUS gensec_magic_check_krb5_oid(struct gensec_security *unused, + const DATA_BLOB *blob); + +#endif /* __GENSEC_H__ */ diff --git a/auth/gensec/gensec_internal.h b/auth/gensec/gensec_internal.h new file mode 100644 index 0000000..8efb1bd --- /dev/null +++ b/auth/gensec/gensec_internal.h @@ -0,0 +1,183 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef __GENSEC_INTERNAL_H__ +#define __GENSEC_INTERNAL_H__ + +struct gensec_security; + +struct gensec_security_ops { + const char *name; + const char *sasl_name; + bool weak_crypto; + uint8_t auth_type; /* 0 if not offered on DCE-RPC */ + const char **oid; /* NULL if not offered by SPNEGO */ + NTSTATUS (*client_start)(struct gensec_security *gensec_security); + NTSTATUS (*server_start)(struct gensec_security *gensec_security); + /** + Determine if a packet has the right 'magic' for this mechanism + */ + NTSTATUS (*magic)(struct gensec_security *gensec_security, + const DATA_BLOB *first_packet); + struct tevent_req *(*update_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); + NTSTATUS (*update_recv)(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); + NTSTATUS (*may_reset_crypto)(struct gensec_security *gensec_security, + bool full_reset); + NTSTATUS (*seal_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + NTSTATUS (*sign_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + size_t (*sig_size)(struct gensec_security *gensec_security, size_t data_size); + size_t (*max_input_size)(struct gensec_security *gensec_security); + size_t (*max_wrapped_size)(struct gensec_security *gensec_security); + NTSTATUS (*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); + NTSTATUS (*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); + NTSTATUS (*wrap)(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + NTSTATUS (*unwrap)(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + NTSTATUS (*session_key)(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); + NTSTATUS (*session_info)(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info); + void (*want_feature)(struct gensec_security *gensec_security, + uint32_t feature); + bool (*have_feature)(struct gensec_security *gensec_security, + uint32_t feature); + NTTIME (*expire_time)(struct gensec_security *gensec_security); + const char *(*final_auth_type)(struct gensec_security *gensec_security); + bool enabled; + bool kerberos; + enum gensec_priority priority; + bool glue; +}; + +struct gensec_security_ops_wrapper { + const struct gensec_security_ops *op; + const char *oid; +}; + +struct gensec_security { + const struct gensec_security_ops *ops; + void *private_data; + struct cli_credentials *credentials; + struct gensec_target target; + enum gensec_role gensec_role; + bool subcontext; + uint32_t want_features; + uint32_t max_update_size; + uint8_t dcerpc_auth_level; + struct tsocket_address *local_addr, *remote_addr; + struct gensec_settings *settings; + + /* When we are a server, this may be filled in to provide an + * NTLM authentication backend, and user lookup (such as if no + * PAC is found) */ + struct auth4_context *auth_context; + + struct gensec_security *parent_security; + struct gensec_security *child_security; + + /* + * This is used to mark the context as being + * busy in an async gensec_update_send(). + */ + struct gensec_security **update_busy_ptr; +}; + +/* this structure is used by backends to determine the size of some critical types */ +struct gensec_critical_sizes { + int interface_version; + int sizeof_gensec_security_ops; + int sizeof_gensec_security; +}; + +NTSTATUS gensec_may_reset_crypto(struct gensec_security *gensec_security, + bool full_reset); + +const char *gensec_final_auth_type(struct gensec_security *gensec_security); + +NTSTATUS gensec_child_ready(struct gensec_security *parent, + struct gensec_security *child); +void gensec_child_want_feature(struct gensec_security *gensec_security, + uint32_t feature); +bool gensec_child_have_feature(struct gensec_security *gensec_security, + uint32_t feature); +NTSTATUS gensec_child_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); +NTSTATUS gensec_child_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); +NTSTATUS gensec_child_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); +NTSTATUS gensec_child_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); +NTSTATUS gensec_child_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS gensec_child_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +size_t gensec_child_sig_size(struct gensec_security *gensec_security, + size_t data_size); +size_t gensec_child_max_input_size(struct gensec_security *gensec_security); +size_t gensec_child_max_wrapped_size(struct gensec_security *gensec_security); +NTSTATUS gensec_child_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); +NTSTATUS gensec_child_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info); +NTTIME gensec_child_expire_time(struct gensec_security *gensec_security); +const char *gensec_child_final_auth_type(struct gensec_security *gensec_security); + +#endif /* __GENSEC_H__ */ diff --git a/auth/gensec/gensec_start.c b/auth/gensec/gensec_start.c new file mode 100644 index 0000000..bd5b725 --- /dev/null +++ b/auth/gensec/gensec_start.c @@ -0,0 +1,1147 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "tevent.h" +#include "../lib/util/tevent_ntstatus.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "lib/param/param.h" +#include "lib/param/loadparm.h" +#include "lib/util/tsort.h" +#include "lib/util/samba_modules.h" +#include "lib/util/base64.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#undef strcasecmp + +/* the list of currently registered GENSEC backends */ +static const struct gensec_security_ops **generic_security_ops; +static int gensec_num_backends; + +bool gensec_security_ops_enabled(const struct gensec_security_ops *ops, struct gensec_security *security) +{ + bool ok = lpcfg_parm_bool(security->settings->lp_ctx, + NULL, + "gensec", + ops->name, + ops->enabled); + + if (ops->weak_crypto && + lpcfg_weak_crypto(security->settings->lp_ctx) != SAMBA_WEAK_CRYPTO_ALLOWED) { + ok = false; + } + + return ok; +} + +/* Sometimes we want to force only kerberos, sometimes we want to + * force it's avoidance. The old list could be either + * gensec_security_all(), or from cli_credentials_gensec_list() (ie, + * an existing list we have trimmed down) + * + * The intended logic is: + * + * if we are in the default AUTO have kerberos: + * - take a reference to the master list + * otherwise + * - always add spnego then: + * - if we 'MUST' have kerberos: + * only add kerberos mechs + * - if we 'DONT' want kerberos': + * only add non-kerberos mechs + * + * Once we get things like NegoEx or moonshot, this will of course get + * more compplex. + */ + +static const struct gensec_security_ops **gensec_use_kerberos_mechs( + TALLOC_CTX *mem_ctx, + const struct gensec_security_ops * const *old_gensec_list, + enum credentials_use_kerberos use_kerberos, + bool keep_schannel) +{ + const struct gensec_security_ops **new_gensec_list; + int i, j, num_mechs_in; + + for (num_mechs_in=0; old_gensec_list && old_gensec_list[num_mechs_in]; num_mechs_in++) { + /* noop */ + } + + new_gensec_list = talloc_array(mem_ctx, + const struct gensec_security_ops *, + num_mechs_in + 1); + if (!new_gensec_list) { + return NULL; + } + + j = 0; + for (i=0; old_gensec_list && old_gensec_list[i]; i++) { + bool keep = false; + + /* + * We want to keep SPNGEO and other backends + */ + keep = old_gensec_list[i]->glue; + + if (old_gensec_list[i]->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + keep = keep_schannel; + } + + switch (use_kerberos) { + case CRED_USE_KERBEROS_DESIRED: + keep = true; + break; + + case CRED_USE_KERBEROS_DISABLED: + if (old_gensec_list[i]->kerberos == false) { + keep = true; + } + + break; + + case CRED_USE_KERBEROS_REQUIRED: + if (old_gensec_list[i]->kerberos == true) { + keep = true; + } + + break; + default: + /* Can't happen or invalid parameter */ + return NULL; + } + + if (!keep) { + continue; + } + + new_gensec_list[j] = old_gensec_list[i]; + j++; + } + new_gensec_list[j] = NULL; + + return new_gensec_list; +} + +_PUBLIC_ const struct gensec_security_ops **gensec_security_mechs( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx) +{ + const struct gensec_security_ops * const *backends = + generic_security_ops; + enum credentials_use_kerberos use_kerberos = CRED_USE_KERBEROS_DESIRED; + bool keep_schannel = false; + + if (gensec_security != NULL) { + struct cli_credentials *creds = NULL; + + creds = gensec_get_credentials(gensec_security); + if (creds != NULL) { + use_kerberos = cli_credentials_get_kerberos_state(creds); + if (cli_credentials_get_netlogon_creds(creds) != NULL) { + keep_schannel = true; + } + + /* + * Even if Kerberos is set to REQUIRED, keep the + * schannel auth mechanism that machine accounts are + * able to authenticate via netlogon. + */ + if (gensec_security->gensec_role == GENSEC_SERVER) { + keep_schannel = true; + } + } + + if (gensec_security->settings->backends) { + backends = gensec_security->settings->backends; + } + } + + return gensec_use_kerberos_mechs(mem_ctx, backends, + use_kerberos, keep_schannel); + +} + +_PUBLIC_ const struct gensec_security_ops *gensec_security_by_oid( + struct gensec_security *gensec_security, + const char *oid_string) +{ + int i, j; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], + gensec_security)) + continue; + if (backends[i]->oid) { + for (j=0; backends[i]->oid[j]; j++) { + if (backends[i]->oid[j] && + (strcmp(backends[i]->oid[j], oid_string) == 0)) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + } + } + talloc_free(mem_ctx); + + return NULL; +} + +_PUBLIC_ const struct gensec_security_ops *gensec_security_by_sasl_name( + struct gensec_security *gensec_security, + const char *sasl_name) +{ + int i; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) { + continue; + } + if (backends[i]->sasl_name + && (strcmp(backends[i]->sasl_name, sasl_name) == 0)) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + talloc_free(mem_ctx); + + return NULL; +} + +_PUBLIC_ const struct gensec_security_ops *gensec_security_by_auth_type( + struct gensec_security *gensec_security, + uint32_t auth_type) +{ + int i; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx; + + if (auth_type == DCERPC_AUTH_TYPE_NONE) { + return NULL; + } + + mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) { + continue; + } + if (backends[i]->auth_type == auth_type) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + talloc_free(mem_ctx); + + return NULL; +} + +const struct gensec_security_ops *gensec_security_by_name(struct gensec_security *gensec_security, + const char *name) +{ + int i; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) + continue; + if (backends[i]->name + && (strcmp(backends[i]->name, name) == 0)) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + talloc_free(mem_ctx); + return NULL; +} + +static const char **gensec_security_sasl_names_from_ops( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const struct gensec_security_ops * const *ops) +{ + const char **sasl_names = NULL; + size_t i, sasl_names_count = 0; + + if (ops == NULL) { + return NULL; + } + + sasl_names = talloc_array(mem_ctx, const char *, 1); + if (sasl_names == NULL) { + return NULL; + } + + for (i = 0; ops[i] != NULL; i++) { + enum gensec_role role = GENSEC_SERVER; + const char **tmp = NULL; + + if (ops[i]->sasl_name == NULL) { + continue; + } + + if (gensec_security != NULL) { + if (!gensec_security_ops_enabled(ops[i], + gensec_security)) { + continue; + } + + role = gensec_security->gensec_role; + } + + switch (role) { + case GENSEC_CLIENT: + if (ops[i]->client_start == NULL) { + continue; + } + break; + case GENSEC_SERVER: + if (ops[i]->server_start == NULL) { + continue; + } + break; + } + + tmp = talloc_realloc(mem_ctx, + sasl_names, + const char *, + sasl_names_count + 2); + if (tmp == NULL) { + TALLOC_FREE(sasl_names); + return NULL; + } + sasl_names = tmp; + + sasl_names[sasl_names_count] = ops[i]->sasl_name; + sasl_names_count++; + } + sasl_names[sasl_names_count] = NULL; + + return sasl_names; +} + +/** + * @brief Get the sasl names from the gensec security context. + * + * @param[in] gensec_security The gensec security context. + * + * @param[in] mem_ctx The memory context to allocate memory on. + * + * @return An allocated array with sasl names, NULL on error. + */ +_PUBLIC_ +const char **gensec_security_sasl_names(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx) +{ + const struct gensec_security_ops **ops = NULL; + + ops = gensec_security_mechs(gensec_security, mem_ctx); + + return gensec_security_sasl_names_from_ops(gensec_security, + mem_ctx, + ops); +} + +/** + * Return a unique list of security subsystems from those specified in + * the list of SASL names. + * + * Use the list of enabled GENSEC mechanisms from the credentials + * attached to the gensec_security, and return in our preferred order. + */ + +static const struct gensec_security_ops **gensec_security_by_sasl_list( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char **sasl_names) +{ + const struct gensec_security_ops **backends_out; + const struct gensec_security_ops **backends; + int i, k, sasl_idx; + int num_backends_out = 0; + + if (!sasl_names) { + return NULL; + } + + backends = gensec_security_mechs(gensec_security, mem_ctx); + + backends_out = talloc_array(mem_ctx, const struct gensec_security_ops *, 1); + if (!backends_out) { + return NULL; + } + backends_out[0] = NULL; + + /* Find backends in our preferred order, by walking our list, + * then looking in the supplied list */ + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) + continue; + for (sasl_idx = 0; sasl_names[sasl_idx]; sasl_idx++) { + if (!backends[i]->sasl_name || + !(strcmp(backends[i]->sasl_name, + sasl_names[sasl_idx]) == 0)) { + continue; + } + + for (k=0; backends_out[k]; k++) { + if (backends_out[k] == backends[i]) { + break; + } + } + + if (k < num_backends_out) { + /* already in there */ + continue; + } + + backends_out = talloc_realloc(mem_ctx, backends_out, + const struct gensec_security_ops *, + num_backends_out + 2); + if (!backends_out) { + return NULL; + } + + backends_out[num_backends_out] = backends[i]; + num_backends_out++; + backends_out[num_backends_out] = NULL; + } + } + return backends_out; +} + +/** + * Return a unique list of security subsystems from those specified in + * the OID list. That is, where two OIDs refer to the same module, + * return that module only once. + * + * Use the list of enabled GENSEC mechanisms from the credentials + * attached to the gensec_security, and return in our preferred order. + */ + +_PUBLIC_ const struct gensec_security_ops_wrapper *gensec_security_by_oid_list( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char * const *oid_strings, + const char *skip) +{ + struct gensec_security_ops_wrapper *backends_out; + const struct gensec_security_ops **backends; + int i, j, k, oid_idx; + int num_backends_out = 0; + + if (!oid_strings) { + return NULL; + } + + backends = gensec_security_mechs(gensec_security, gensec_security); + + backends_out = talloc_array(mem_ctx, struct gensec_security_ops_wrapper, 1); + if (!backends_out) { + return NULL; + } + backends_out[0].op = NULL; + backends_out[0].oid = NULL; + + /* Find backends in our preferred order, by walking our list, + * then looking in the supplied list */ + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) + continue; + if (!backends[i]->oid) { + continue; + } + for (oid_idx = 0; oid_strings[oid_idx]; oid_idx++) { + if (strcmp(oid_strings[oid_idx], skip) == 0) { + continue; + } + + for (j=0; backends[i]->oid[j]; j++) { + if (!backends[i]->oid[j] || + !(strcmp(backends[i]->oid[j], + oid_strings[oid_idx]) == 0)) { + continue; + } + + for (k=0; backends_out[k].op; k++) { + if (backends_out[k].op == backends[i]) { + break; + } + } + + if (k < num_backends_out) { + /* already in there */ + continue; + } + + backends_out = talloc_realloc(mem_ctx, backends_out, + struct gensec_security_ops_wrapper, + num_backends_out + 2); + if (!backends_out) { + return NULL; + } + + backends_out[num_backends_out].op = backends[i]; + backends_out[num_backends_out].oid = backends[i]->oid[j]; + num_backends_out++; + backends_out[num_backends_out].op = NULL; + backends_out[num_backends_out].oid = NULL; + } + } + } + return backends_out; +} + +/** + * Return OIDS from the security subsystems listed + */ + +static const char **gensec_security_oids_from_ops( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const struct gensec_security_ops * const *ops, + const char *skip) +{ + int i; + int j = 0; + int k; + const char **oid_list; + if (!ops) { + return NULL; + } + oid_list = talloc_array(mem_ctx, const char *, 1); + if (!oid_list) { + return NULL; + } + + for (i=0; ops && ops[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(ops[i], gensec_security)) { + continue; + } + if (!ops[i]->oid) { + continue; + } + + for (k = 0; ops[i]->oid[k]; k++) { + if (skip && strcmp(skip, ops[i]->oid[k])==0) { + } else { + oid_list = talloc_realloc(mem_ctx, oid_list, const char *, j + 2); + if (!oid_list) { + return NULL; + } + oid_list[j] = ops[i]->oid[k]; + j++; + } + } + } + oid_list[j] = NULL; + return oid_list; +} + + +/** + * Return OIDS from the security subsystems listed + */ + +_PUBLIC_ const char **gensec_security_oids_from_ops_wrapped(TALLOC_CTX *mem_ctx, + const struct gensec_security_ops_wrapper *wops) +{ + int i; + int j = 0; + int k; + const char **oid_list; + if (!wops) { + return NULL; + } + oid_list = talloc_array(mem_ctx, const char *, 1); + if (!oid_list) { + return NULL; + } + + for (i=0; wops[i].op; i++) { + if (!wops[i].op->oid) { + continue; + } + + for (k = 0; wops[i].op->oid[k]; k++) { + oid_list = talloc_realloc(mem_ctx, oid_list, const char *, j + 2); + if (!oid_list) { + return NULL; + } + oid_list[j] = wops[i].op->oid[k]; + j++; + } + } + oid_list[j] = NULL; + return oid_list; +} + + +/** + * Return all the security subsystems currently enabled on a GENSEC context. + * + * This is taken from a list attached to the cli_credentials, and + * skips the OID in 'skip'. (Typically the SPNEGO OID) + * + */ + +_PUBLIC_ const char **gensec_security_oids(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char *skip) +{ + const struct gensec_security_ops **ops; + + ops = gensec_security_mechs(gensec_security, mem_ctx); + + return gensec_security_oids_from_ops(gensec_security, mem_ctx, ops, skip); +} + +static int gensec_security_destructor(struct gensec_security *gctx) +{ + if (gctx->parent_security != NULL) { + if (gctx->parent_security->child_security == gctx) { + gctx->parent_security->child_security = NULL; + } + gctx->parent_security = NULL; + } + + if (gctx->child_security != NULL) { + if (gctx->child_security->parent_security == gctx) { + gctx->child_security->parent_security = NULL; + } + gctx->child_security = NULL; + } + + return 0; +} + +/** + Start the GENSEC system, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. + @note, the auth context is moved to be a referenced pointer of the + @ gensec_security return +*/ +static NTSTATUS gensec_start(TALLOC_CTX *mem_ctx, + struct gensec_settings *settings, + struct auth4_context *auth_context, + struct gensec_security **gensec_security) +{ + (*gensec_security) = talloc_zero(mem_ctx, struct gensec_security); + NT_STATUS_HAVE_NO_MEMORY(*gensec_security); + + (*gensec_security)->max_update_size = 0; + + SMB_ASSERT(settings->lp_ctx != NULL); + (*gensec_security)->settings = talloc_reference(*gensec_security, settings); + + /* We need to reference this, not steal, as the caller may be + * python, which won't like it if we steal it's object away + * from it */ + (*gensec_security)->auth_context = talloc_reference(*gensec_security, auth_context); + + talloc_set_destructor((*gensec_security), gensec_security_destructor); + return NT_STATUS_OK; +} + +/** + * Start a GENSEC subcontext, with a copy of the properties of the parent + * @param mem_ctx The parent TALLOC memory context. + * @param parent The parent GENSEC context + * @param gensec_security Returned GENSEC context pointer. + * @note Used by SPNEGO in particular, for the actual implementation mechanism + */ + +_PUBLIC_ NTSTATUS gensec_subcontext_start(TALLOC_CTX *mem_ctx, + struct gensec_security *parent, + struct gensec_security **gensec_security) +{ + if (parent->child_security != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + (*gensec_security) = talloc_zero(mem_ctx, struct gensec_security); + NT_STATUS_HAVE_NO_MEMORY(*gensec_security); + + (**gensec_security) = *parent; + (*gensec_security)->ops = NULL; + (*gensec_security)->private_data = NULL; + (*gensec_security)->update_busy_ptr = NULL; + + (*gensec_security)->subcontext = true; + (*gensec_security)->want_features = parent->want_features; + (*gensec_security)->max_update_size = parent->max_update_size; + (*gensec_security)->dcerpc_auth_level = parent->dcerpc_auth_level; + (*gensec_security)->auth_context = talloc_reference(*gensec_security, parent->auth_context); + (*gensec_security)->settings = talloc_reference(*gensec_security, parent->settings); + (*gensec_security)->auth_context = talloc_reference(*gensec_security, parent->auth_context); + + talloc_set_destructor((*gensec_security), gensec_security_destructor); + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS gensec_child_ready(struct gensec_security *parent, + struct gensec_security *child) +{ + if (parent->child_security != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (child->parent_security != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + parent->child_security = child; + child->parent_security = parent; + return NT_STATUS_OK; +} + +/** + Start the GENSEC system, in client mode, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +_PUBLIC_ NTSTATUS gensec_client_start(TALLOC_CTX *mem_ctx, + struct gensec_security **gensec_security, + struct gensec_settings *settings) +{ + NTSTATUS status; + + if (settings == NULL) { + DEBUG(0,("gensec_client_start: no settings given!\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + status = gensec_start(mem_ctx, settings, NULL, gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*gensec_security)->gensec_role = GENSEC_CLIENT; + + return status; +} + + + +/** + Start the GENSEC system, in server mode, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +_PUBLIC_ NTSTATUS gensec_server_start(TALLOC_CTX *mem_ctx, + struct gensec_settings *settings, + struct auth4_context *auth_context, + struct gensec_security **gensec_security) +{ + NTSTATUS status; + + if (!settings) { + DEBUG(0,("gensec_server_start: no settings given!\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + status = gensec_start(mem_ctx, settings, auth_context, gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*gensec_security)->gensec_role = GENSEC_SERVER; + + return status; +} + +static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security) +{ + NTSTATUS status; + + /* + * Callers sometimes just reuse a context, we should + * clear the internal state before starting it again. + */ + talloc_unlink(gensec_security, gensec_security->private_data); + gensec_security->private_data = NULL; + + if (gensec_security->child_security != NULL) { + /* + * The talloc_unlink(.., gensec_security->private_data) + * should have cleared this via + * gensec_security_destructor(). + */ + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec_security->credentials) { + const char *forced_mech = cli_credentials_get_forced_sasl_mech(gensec_security->credentials); + if (forced_mech && + (gensec_security->ops->sasl_name == NULL || + strcasecmp(forced_mech, gensec_security->ops->sasl_name) != 0)) { + DEBUG(5, ("GENSEC mechanism %s (%s) skipped, as it " + "did not match forced mechanism %s\n", + gensec_security->ops->name, + gensec_security->ops->sasl_name, + forced_mech)); + return NT_STATUS_INVALID_PARAMETER; + } + } + DEBUG(5, ("Starting GENSEC %smechanism %s\n", + gensec_security->subcontext ? "sub" : "", + gensec_security->ops->name)); + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + if (gensec_security->ops->client_start) { + status = gensec_security->ops->client_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(gensec_security->subcontext?4:2, ("Failed to start GENSEC client mech %s: %s\n", + gensec_security->ops->name, nt_errstr(status))); + } + return status; + } + break; + case GENSEC_SERVER: + if (gensec_security->ops->server_start) { + status = gensec_security->ops->server_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC server mech %s: %s\n", + gensec_security->ops->name, nt_errstr(status))); + } + return status; + } + break; + } + return NT_STATUS_INVALID_PARAMETER; +} + +/** + * Start a GENSEC sub-mechanism with a specified mechansim structure, used in SPNEGO + * + */ + +NTSTATUS gensec_start_mech_by_ops(struct gensec_security *gensec_security, + const struct gensec_security_ops *ops) +{ + gensec_security->ops = ops; + return gensec_start_mech(gensec_security); +} + + +/** + * Start a GENSEC sub-mechanism by DCERPC allocated 'auth type' number + * @param gensec_security GENSEC context pointer. + * @param auth_type DCERPC auth type + * @param auth_level DCERPC auth level + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_authtype(struct gensec_security *gensec_security, + uint8_t auth_type, uint8_t auth_level) +{ + gensec_security->ops = gensec_security_by_auth_type(gensec_security, auth_type); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for auth_type=%d\n", (int)auth_type)); + return NT_STATUS_INVALID_PARAMETER; + } + gensec_security->dcerpc_auth_level = auth_level; + /* + * We need to reset sign/seal in order to reset it. + * We may got some default features inherited by the credentials + */ + gensec_security->want_features &= ~GENSEC_FEATURE_SIGN; + gensec_security->want_features &= ~GENSEC_FEATURE_SEAL; + gensec_want_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE); + gensec_want_feature(gensec_security, GENSEC_FEATURE_ASYNC_REPLIES); + if (auth_level == DCERPC_AUTH_LEVEL_INTEGRITY) { + if (gensec_security->gensec_role == GENSEC_CLIENT) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + } + } else if (auth_level == DCERPC_AUTH_LEVEL_PACKET) { + /* + * For connection oriented DCERPC DCERPC_AUTH_LEVEL_PACKET (4) + * has the same behavior as DCERPC_AUTH_LEVEL_INTEGRITY (5). + */ + if (gensec_security->gensec_role == GENSEC_CLIENT) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + } + } else if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL); + } else if (auth_level == DCERPC_AUTH_LEVEL_CONNECT) { + /* Default features */ + } else { + DEBUG(2,("auth_level %d not supported in DCE/RPC authentication\n", + auth_level)); + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_start_mech(gensec_security); +} + +_PUBLIC_ const char *gensec_get_name_by_authtype(struct gensec_security *gensec_security, uint8_t authtype) +{ + const struct gensec_security_ops *ops; + ops = gensec_security_by_auth_type(gensec_security, authtype); + if (ops) { + return ops->name; + } + return NULL; +} + + +_PUBLIC_ const char *gensec_get_name_by_oid(struct gensec_security *gensec_security, + const char *oid_string) +{ + const struct gensec_security_ops *ops; + ops = gensec_security_by_oid(gensec_security, oid_string); + if (ops) { + return ops->name; + } + return oid_string; +} + +/** + * Start a GENSEC sub-mechanism by OID, used in SPNEGO + * + * @note This should also be used when you wish to just start NLTMSSP (for example), as it uses a + * well-known #define to hook it in. + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_oid(struct gensec_security *gensec_security, + const char *mech_oid) +{ + SMB_ASSERT(gensec_security != NULL); + + gensec_security->ops = gensec_security_by_oid(gensec_security, mech_oid); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for oid=%s\n", mech_oid)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Start a GENSEC sub-mechanism by a well know SASL name + * + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name) +{ + gensec_security->ops = gensec_security_by_sasl_name(gensec_security, sasl_name); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for sasl_name=%s\n", sasl_name)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Start a GENSEC sub-mechanism with the preferred option from a SASL name list + * + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_sasl_list(struct gensec_security *gensec_security, + const char **sasl_names) +{ + NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + const struct gensec_security_ops **ops; + int i; + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + ops = gensec_security_by_sasl_list(gensec_security, mem_ctx, sasl_names); + if (!ops || !*ops) { + DEBUG(3, ("Could not find GENSEC backend for any of sasl_name = %s\n", + str_list_join(mem_ctx, + sasl_names, ' '))); + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + for (i=0; ops[i]; i++) { + nt_status = gensec_start_mech_by_ops(gensec_security, ops[i]); + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER)) { + break; + } + } + talloc_free(mem_ctx); + return nt_status; +} + +/** + * Start a GENSEC sub-mechanism by an internal name + * + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_name(struct gensec_security *gensec_security, + const char *name) +{ + gensec_security->ops = gensec_security_by_name(gensec_security, name); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for name=%s\n", name)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Associate a credentials structure with a GENSEC context - talloc_reference()s it to the context + * + */ + +_PUBLIC_ NTSTATUS gensec_set_credentials(struct gensec_security *gensec_security, struct cli_credentials *credentials) +{ + gensec_security->credentials = talloc_reference(gensec_security, credentials); + NT_STATUS_HAVE_NO_MEMORY(gensec_security->credentials); + gensec_want_feature(gensec_security, cli_credentials_get_gensec_features(gensec_security->credentials)); + return NT_STATUS_OK; +} + +/* + register a GENSEC backend. + + The 'name' can be later used by other backends to find the operations + structure for this backend. +*/ +_PUBLIC_ NTSTATUS gensec_register(TALLOC_CTX *ctx, + const struct gensec_security_ops *ops) +{ + if (gensec_security_by_name(NULL, ops->name) != NULL) { + /* its already registered! */ + DEBUG(0,("GENSEC backend '%s' already registered\n", + ops->name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + generic_security_ops = talloc_realloc(ctx, + generic_security_ops, + const struct gensec_security_ops *, + gensec_num_backends+2); + if (!generic_security_ops) { + return NT_STATUS_NO_MEMORY; + } + + generic_security_ops[gensec_num_backends] = ops; + gensec_num_backends++; + generic_security_ops[gensec_num_backends] = NULL; + + DEBUG(3,("GENSEC backend '%s' registered\n", + ops->name)); + + return NT_STATUS_OK; +} + +/* + return the GENSEC interface version, and the size of some critical types + This can be used by backends to either detect compilation errors, or provide + multiple implementations for different smbd compilation options in one module +*/ +_PUBLIC_ const struct gensec_critical_sizes *gensec_interface_version(void) +{ + static const struct gensec_critical_sizes critical_sizes = { + GENSEC_INTERFACE_VERSION, + sizeof(struct gensec_security_ops), + sizeof(struct gensec_security), + }; + + return &critical_sizes; +} + +static int sort_gensec(const struct gensec_security_ops **gs1, const struct gensec_security_ops **gs2) { + return (*gs2)->priority - (*gs1)->priority; +} + +int gensec_setting_int(struct gensec_settings *settings, const char *mechanism, const char *name, int default_value) +{ + return lpcfg_parm_int(settings->lp_ctx, NULL, mechanism, name, default_value); +} + +bool gensec_setting_bool(struct gensec_settings *settings, const char *mechanism, const char *name, bool default_value) +{ + return lpcfg_parm_bool(settings->lp_ctx, NULL, mechanism, name, default_value); +} + +/* + initialise the GENSEC subsystem +*/ +_PUBLIC_ NTSTATUS gensec_init(void) +{ + static bool initialized = false; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); +#ifdef STATIC_gensec_MODULES + STATIC_gensec_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_gensec_MODULES }; +#else + init_module_fn *static_init = NULL; +#endif + init_module_fn *shared_init; + + if (initialized) return NT_STATUS_OK; + initialized = true; + + shared_init = load_samba_modules(NULL, "gensec"); + + run_init_functions(NULL, static_init); + run_init_functions(NULL, shared_init); + + talloc_free(shared_init); + + TYPESAFE_QSORT(generic_security_ops, gensec_num_backends, sort_gensec); + + return NT_STATUS_OK; +} diff --git a/auth/gensec/gensec_util.c b/auth/gensec/gensec_util.c new file mode 100644 index 0000000..1075b9f --- /dev/null +++ b/auth/gensec/gensec_util.c @@ -0,0 +1,338 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2006 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/common_auth.h" +#include "../lib/util/asn1.h" +#include "param/param.h" +#include "libds/common/roles.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +NTSTATUS gensec_generate_session_info_pac(TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *principal_string, + const struct tsocket_address *remote_address, + struct auth_session_info **session_info) +{ + uint32_t session_info_flags = 0; + struct auth4_context *auth_context = NULL; + NTSTATUS status; + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS; + + if (!pac_blob) { + enum server_role server_role = + lpcfg_server_role(gensec_security->settings->lp_ctx); + + /* + * For any domain setup (DC or member) we require having + * a PAC, as the service ticket comes from an AD DC, + * which will always provide a PAC, unless + * UF_NO_AUTH_DATA_REQUIRED is configured for our + * account, but that's just an invalid configuration, + * the admin configured for us! + * + * As a legacy case, we still allow kerberos tickets from an MIT + * realm, but only in standalone mode. In that mode we'll only + * ever accept a kerberos authentication with a keytab file + * being explicitly configured via the 'keytab method' option. + */ + if (server_role != ROLE_STANDALONE) { + DBG_WARNING("Unable to find PAC in ticket from %s, " + "failing to allow access\n", + principal_string); + return NT_STATUS_NO_IMPERSONATION_TOKEN; + } + DBG_NOTICE("Unable to find PAC for %s, resorting to local " + "user lookup\n", principal_string); + } + + auth_context = gensec_security->auth_context; + + if ((auth_context == NULL) || + (auth_context->generate_session_info_pac == NULL)) { + DBG_ERR("Cannot generate a session_info without " + "the auth_context\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + status = auth_context->generate_session_info_pac( + auth_context, + mem_ctx, + smb_krb5_context, + pac_blob, + principal_string, + remote_address, + session_info_flags, + session_info); + return status; +} + +/* + magic check a GSS-API wrapper packet for an Kerberos OID +*/ +static bool gensec_gssapi_check_oid(const DATA_BLOB *blob, const char *oid) +{ + bool ret = false; + struct asn1_data *data = asn1_init(NULL, ASN1_MAX_TREE_DEPTH); + + if (!data) return false; + + if (!asn1_load(data, *blob)) goto err; + if (!asn1_start_tag(data, ASN1_APPLICATION(0))) goto err; + if (!asn1_check_OID(data, oid)) goto err; + + ret = !asn1_has_error(data); + + err: + + asn1_free(data); + return ret; +} + +/** + * Check if the packet is one for the KRB5 mechansim + * + * NOTE: This is a helper that can be employed by multiple mechanisms, do + * not make assumptions about the private_data + * + * @param gensec_security GENSEC state, unused + * @param in The request, as a DATA_BLOB + * @return Error, INVALID_PARAMETER if it's not a packet for us + * or NT_STATUS_OK if the packet is ok. + */ + +NTSTATUS gensec_magic_check_krb5_oid(struct gensec_security *unused, + const DATA_BLOB *blob) +{ + if (gensec_gssapi_check_oid(blob, GENSEC_OID_KERBEROS5)) { + return NT_STATUS_OK; + } else { + return NT_STATUS_INVALID_PARAMETER; + } +} + +void gensec_child_want_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_security *child_security = gensec_security->child_security; + + gensec_security->want_features |= feature; + if (child_security == NULL) { + return; + } + gensec_want_feature(child_security, feature); +} + +bool gensec_child_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_security *child_security = gensec_security->child_security; + + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + /* + * All mechs with sub (child) mechs need to provide DCERPC + * header signing! This is required because the negotiation + * of header signing is done before the authentication + * is completed. + */ + return true; + } + + if (child_security == NULL) { + return false; + } + + return gensec_have_feature(child_security, feature); +} + +NTSTATUS gensec_child_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) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_unseal_packet(gensec_security->child_security, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_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) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_check_packet(gensec_security->child_security, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_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) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_seal_packet(gensec_security->child_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_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) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_sign_packet(gensec_security->child_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_wrap(gensec_security->child_security, + mem_ctx, in, out); +} + +NTSTATUS gensec_child_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_unwrap(gensec_security->child_security, + mem_ctx, in, out); +} + +size_t gensec_child_sig_size(struct gensec_security *gensec_security, + size_t data_size) +{ + if (gensec_security->child_security == NULL) { + return 0; + } + + return gensec_sig_size(gensec_security->child_security, data_size); +} + +size_t gensec_child_max_input_size(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return 0; + } + + return gensec_max_input_size(gensec_security->child_security); +} + +size_t gensec_child_max_wrapped_size(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return 0; + } + + return gensec_max_wrapped_size(gensec_security->child_security); +} + +NTSTATUS gensec_child_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_session_key(gensec_security->child_security, + mem_ctx, + session_key); +} + +NTSTATUS gensec_child_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_session_info(gensec_security->child_security, + mem_ctx, + session_info); +} + +NTTIME gensec_child_expire_time(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return GENSEC_EXPIRE_TIME_INFINITY; + } + + return gensec_expire_time(gensec_security->child_security); +} + +const char *gensec_child_final_auth_type(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return "NONE"; + } + + return gensec_final_auth_type(gensec_security->child_security); +} diff --git a/auth/gensec/ncalrpc.c b/auth/gensec/ncalrpc.c new file mode 100644 index 0000000..f845947 --- /dev/null +++ b/auth/gensec/ncalrpc.c @@ -0,0 +1,356 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc ncalrpc as system operations + + Copyright (C) 2014 Andreas Schneider <asn@samba.org> + Copyright (C) 2014 Stefan Metzmacher <metze@samba.org> + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "lib/param/param.h" +#include "tsocket.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +_PUBLIC_ NTSTATUS gensec_ncalrpc_as_system_init(TALLOC_CTX *ctx); + +struct gensec_ncalrpc_state { + enum { + GENSEC_NCALRPC_START, + GENSEC_NCALRPC_MORE, + GENSEC_NCALRPC_DONE, + GENSEC_NCALRPC_ERROR, + } step; + + struct auth_user_info_dc *user_info_dc; +}; + +static NTSTATUS gensec_ncalrpc_client_start(struct gensec_security *gensec_security) +{ + struct gensec_ncalrpc_state *state; + + state = talloc_zero(gensec_security, + struct gensec_ncalrpc_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + gensec_security->private_data = state; + + state->step = GENSEC_NCALRPC_START; + return NT_STATUS_OK; +} + +static NTSTATUS gensec_ncalrpc_server_start(struct gensec_security *gensec_security) +{ + struct gensec_ncalrpc_state *state; + + state = talloc_zero(gensec_security, + struct gensec_ncalrpc_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + gensec_security->private_data = state; + + state->step = GENSEC_NCALRPC_START; + return NT_STATUS_OK; +} + +struct gensec_ncalrpc_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static NTSTATUS gensec_ncalrpc_update_internal( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in, + DATA_BLOB *out); + +static struct tevent_req *gensec_ncalrpc_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req; + struct gensec_ncalrpc_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_ncalrpc_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_ncalrpc_update_internal(gensec_security, + state, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_ncalrpc_update_internal( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in, + DATA_BLOB *out) +{ + struct gensec_ncalrpc_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ncalrpc_state); + DATA_BLOB magic_req = data_blob_string_const("NCALRPC_AUTH_TOKEN"); + DATA_BLOB magic_ok = data_blob_string_const("NCALRPC_AUTH_OK"); + DATA_BLOB magic_fail = data_blob_string_const("NCALRPC_AUTH_FAIL"); + char *unix_path = NULL; + int cmp; + NTSTATUS status; + + *out = data_blob_null; + + if (state->step >= GENSEC_NCALRPC_DONE) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + switch (state->step) { + case GENSEC_NCALRPC_START: + *out = data_blob_dup_talloc(mem_ctx, magic_req); + if (out->data == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_NO_MEMORY; + } + + state->step = GENSEC_NCALRPC_MORE; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + + case GENSEC_NCALRPC_MORE: + cmp = data_blob_cmp(&in, &magic_ok); + if (cmp != 0) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_LOGON_FAILURE; + } + + state->step = GENSEC_NCALRPC_DONE; + return NT_STATUS_OK; + + case GENSEC_NCALRPC_DONE: + case GENSEC_NCALRPC_ERROR: + break; + } + + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_INTERNAL_ERROR; + + case GENSEC_SERVER: + if (state->step != GENSEC_NCALRPC_START) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_INTERNAL_ERROR; + } + + cmp = data_blob_cmp(&in, &magic_req); + if (cmp != 0) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + if (gensec_security->remote_addr == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + unix_path = tsocket_address_unix_path(gensec_security->remote_addr, + state); + if (unix_path == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + cmp = strcmp(unix_path, AS_SYSTEM_MAGIC_PATH_TOKEN); + TALLOC_FREE(unix_path); + if (cmp != 0) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + status = auth_system_user_info_dc(state, + lpcfg_netbios_name(gensec_security->settings->lp_ctx), + &state->user_info_dc); + if (!NT_STATUS_IS_OK(status)) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return status; + } + + *out = data_blob_dup_talloc(mem_ctx, magic_ok); + if (out->data == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_NO_MEMORY; + } + + state->step = GENSEC_NCALRPC_DONE; + return NT_STATUS_OK; + } + + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_ncalrpc_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_ncalrpc_update_state *state = + tevent_req_data(req, + struct gensec_ncalrpc_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + status = state->status; + talloc_steal(out_mem_ctx, state->out.data); + *out = state->out; + tevent_req_received(req); + return status; +} + +static NTSTATUS gensec_ncalrpc_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **psession_info) +{ + struct gensec_ncalrpc_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ncalrpc_state); + struct auth4_context *auth_ctx = gensec_security->auth_context; + struct auth_session_info *session_info = NULL; + uint32_t session_info_flags = 0; + NTSTATUS status; + + if (gensec_security->gensec_role != GENSEC_SERVER) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (state->step != GENSEC_NCALRPC_DONE) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (auth_ctx == NULL) { + DEBUG(0, ("Cannot generate a session_info without the auth_context\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (auth_ctx->generate_session_info == NULL) { + DEBUG(0, ("Cannot generate a session_info without the generate_session_info hook\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES; + + status = auth_ctx->generate_session_info( + auth_ctx, + mem_ctx, + state->user_info_dc, + state->user_info_dc->info->account_name, + session_info_flags, + &session_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *psession_info = session_info; + return NT_STATUS_OK; +} + +/* We have no features */ +static bool gensec_ncalrpc_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return true; + } + + return false; +} + +static const struct gensec_security_ops gensec_ncalrpc_security_ops = { + .name = "ncalrpc_as_system", + .auth_type = DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM, + .client_start = gensec_ncalrpc_client_start, + .server_start = gensec_ncalrpc_server_start, + .update_send = gensec_ncalrpc_update_send, + .update_recv = gensec_ncalrpc_update_recv, + .session_info = gensec_ncalrpc_session_info, + .have_feature = gensec_ncalrpc_have_feature, + .enabled = true, + .priority = GENSEC_EXTERNAL, +}; + +_PUBLIC_ NTSTATUS gensec_ncalrpc_as_system_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = gensec_register(ctx, &gensec_ncalrpc_security_ops); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to register '%s' gensec backend!\n", + gensec_ncalrpc_security_ops.name)); + return status; + } + + return status; +} diff --git a/auth/gensec/schannel.c b/auth/gensec/schannel.c new file mode 100644 index 0000000..9860559 --- /dev/null +++ b/auth/gensec/schannel.c @@ -0,0 +1,1222 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "librpc/gen_ndr/ndr_schannel.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/gensec/gensec_proto.h" +#include "../libcli/auth/schannel.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "param/param.h" +#include "auth/gensec/gensec_toplevel_proto.h" +#include "libds/common/roles.h" + +#ifndef HAVE_GNUTLS_AES_CFB8 +#include "lib/crypto/aes.h" +#endif + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +struct schannel_state { + struct gensec_security *gensec; + uint64_t seq_num; + bool initiator; + struct netlogon_creds_CredentialState *creds; + struct auth_user_info_dc *user_info_dc; +}; + +#define SETUP_SEQNUM(state, buf, initiator) do { \ + uint8_t *_buf = buf; \ + uint32_t _seq_num_low = (state)->seq_num & UINT32_MAX; \ + uint32_t _seq_num_high = (state)->seq_num >> 32; \ + if (initiator) { \ + _seq_num_high |= 0x80000000; \ + } \ + RSIVAL(_buf, 0, _seq_num_low); \ + RSIVAL(_buf, 4, _seq_num_high); \ +} while(0) + +static struct schannel_state *netsec_create_state( + struct gensec_security *gensec, + struct netlogon_creds_CredentialState *creds, + bool initiator) +{ + struct schannel_state *state; + + state = talloc_zero(gensec, struct schannel_state); + if (state == NULL) { + return NULL; + } + + state->gensec = gensec; + state->initiator = initiator; + state->creds = netlogon_creds_copy(state, creds); + if (state->creds == NULL) { + talloc_free(state); + return NULL; + } + + gensec->private_data = state; + + return state; +} + +static void netsec_offset_and_sizes(struct schannel_state *state, + bool do_seal, + uint32_t *_min_sig_size, + uint32_t *_used_sig_size, + uint32_t *_checksum_length, + uint32_t *_confounder_ofs) +{ + uint32_t min_sig_size; + uint32_t used_sig_size; + uint32_t checksum_length; + uint32_t confounder_ofs; + + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + min_sig_size = 48; + used_sig_size = 56; + /* + * Note: windows has a bug here and uses the old values... + * + * checksum_length = 32; + * confounder_ofs = 48; + */ + checksum_length = 8; + confounder_ofs = 24; + } else { + min_sig_size = 24; + used_sig_size = 32; + checksum_length = 8; + confounder_ofs = 24; + } + + if (do_seal) { + min_sig_size += 8; + } + + if (_min_sig_size) { + *_min_sig_size = min_sig_size; + } + + if (_used_sig_size) { + *_used_sig_size = used_sig_size; + } + + if (_checksum_length) { + *_checksum_length = checksum_length; + } + + if (_confounder_ofs) { + *_confounder_ofs = confounder_ofs; + } +} + +/******************************************************************* + Encode or Decode the sequence number (which is symmetric) + ********************************************************************/ +static NTSTATUS netsec_do_seq_num(struct schannel_state *state, + const uint8_t *checksum, + uint32_t checksum_length, + uint8_t seq_num[8]) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { +#ifdef HAVE_GNUTLS_AES_CFB8 + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t key = { + .data = state->creds->session_key, + .size = sizeof(state->creds->session_key), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + int rc; + + ZERO_ARRAY(_iv); + + memcpy(iv.data + 0, checksum, 8); + memcpy(iv.data + 8, checksum, 8); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, seq_num, 8); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + +#else /* NOT HAVE_GNUTLS_AES_CFB8 */ + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE]; + + AES_set_encrypt_key(state->creds->session_key, 128, &key); + ZERO_STRUCT(iv); + memcpy(iv+0, checksum, 8); + memcpy(iv+8, checksum, 8); + + aes_cfb8_encrypt(seq_num, seq_num, 8, &key, iv, AES_ENCRYPT); +#endif /* HAVE_GNUTLS_AES_CFB8 */ + } else { + static const uint8_t zeros[4]; + uint8_t _sequence_key[16]; + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t sequence_key = { + .data = _sequence_key, + .size = sizeof(_sequence_key), + }; + uint8_t digest1[16]; + int rc; + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + state->creds->session_key, + sizeof(state->creds->session_key), + zeros, + sizeof(zeros), + digest1); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + digest1, + sizeof(digest1), + checksum, + checksum_length, + _sequence_key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + ZERO_ARRAY(digest1); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &sequence_key, + NULL); + if (rc < 0) { + ZERO_ARRAY(_sequence_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + seq_num, + 8); + gnutls_cipher_deinit(cipher_hnd); + ZERO_ARRAY(_sequence_key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + state->seq_num++; + + return NT_STATUS_OK; +} + +static NTSTATUS netsec_do_seal(struct schannel_state *state, + const uint8_t seq_num[8], + uint8_t confounder[8], + uint8_t *data, uint32_t length, + bool forward) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { +#ifdef HAVE_GNUTLS_AES_CFB8 + gnutls_cipher_hd_t cipher_hnd = NULL; + uint8_t sess_kf0[16] = {0}; + gnutls_datum_t key = { + .data = sess_kf0, + .size = sizeof(sess_kf0), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + uint32_t i; + int rc; + + for (i = 0; i < key.size; i++) { + key.data[i] = state->creds->session_key[i] ^ 0xf0; + } + + ZERO_ARRAY(_iv); + + memcpy(iv.data + 0, seq_num, 8); + memcpy(iv.data + 8, seq_num, 8); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + if (rc < 0) { + DBG_ERR("ERROR: gnutls_cipher_init: %s\n", + gnutls_strerror(rc)); + return NT_STATUS_NO_MEMORY; + } + + if (forward) { + rc = gnutls_cipher_encrypt(cipher_hnd, + confounder, + 8); + if (rc < 0) { + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + data, + length); + if (rc < 0) { + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + } else { + + /* + * Workaround bug present in gnutls 3.6.8: + * + * gnutls_cipher_decrypt() uses an optimization + * internally that breaks decryption when processing + * buffers with their length not being a multiple + * of the blocksize. + */ + + uint8_t tmp[16] = { 0, }; + uint32_t tmp_dlength = MIN(length, sizeof(tmp) - 8); + + memcpy(tmp, confounder, 8); + memcpy(tmp + 8, data, tmp_dlength); + + rc = gnutls_cipher_decrypt(cipher_hnd, + tmp, + 8 + tmp_dlength); + if (rc < 0) { + ZERO_STRUCT(tmp); + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + memcpy(confounder, tmp, 8); + memcpy(data, tmp + 8, tmp_dlength); + ZERO_STRUCT(tmp); + + if (length > tmp_dlength) { + rc = gnutls_cipher_decrypt(cipher_hnd, + data + tmp_dlength, + length - tmp_dlength); + if (rc < 0) { + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + } + } + gnutls_cipher_deinit(cipher_hnd); +#else /* NOT HAVE_GNUTLS_AES_CFB8 */ + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE]; + uint8_t sess_kf0[16]; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = state->creds->session_key[i] ^ 0xf0; + } + + AES_set_encrypt_key(sess_kf0, 128, &key); + ZERO_STRUCT(iv); + memcpy(iv+0, seq_num, 8); + memcpy(iv+8, seq_num, 8); + + if (forward) { + aes_cfb8_encrypt(confounder, confounder, 8, &key, iv, AES_ENCRYPT); + aes_cfb8_encrypt(data, data, length, &key, iv, AES_ENCRYPT); + } else { + aes_cfb8_encrypt(confounder, confounder, 8, &key, iv, AES_DECRYPT); + aes_cfb8_encrypt(data, data, length, &key, iv, AES_DECRYPT); + } +#endif /* HAVE_GNUTLS_AES_CFB8 */ + } else { + gnutls_cipher_hd_t cipher_hnd; + uint8_t _sealing_key[16]; + gnutls_datum_t sealing_key = { + .data = _sealing_key, + .size = sizeof(_sealing_key), + }; + static const uint8_t zeros[4]; + uint8_t digest2[16]; + uint8_t sess_kf0[16]; + int rc; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = state->creds->session_key[i] ^ 0xf0; + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + sess_kf0, + sizeof(sess_kf0), + zeros, + 4, + digest2); + if (rc < 0) { + ZERO_ARRAY(digest2); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + digest2, + sizeof(digest2), + seq_num, + 8, + _sealing_key); + + ZERO_ARRAY(digest2); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &sealing_key, + NULL); + if (rc < 0) { + ZERO_ARRAY(_sealing_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + confounder, + 8); + if (rc < 0) { + ZERO_ARRAY(_sealing_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + gnutls_cipher_deinit(cipher_hnd); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &sealing_key, + NULL); + if (rc < 0) { + ZERO_ARRAY(_sealing_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + data, + length); + gnutls_cipher_deinit(cipher_hnd); + ZERO_ARRAY(_sealing_key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + } + + return NT_STATUS_OK; +} + +/******************************************************************* + Create a digest over the entire packet (including the data), and + MD5 it with the session key. + ********************************************************************/ +static NTSTATUS netsec_do_sign(struct schannel_state *state, + const uint8_t *confounder, + const uint8_t *data, size_t length, + uint8_t header[8], + uint8_t *checksum) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + gnutls_hmac_hd_t hmac_hnd = NULL; + int rc; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA256, + state->creds->session_key, + sizeof(state->creds->session_key)); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + if (confounder) { + SSVAL(header, 0, NL_SIGN_HMAC_SHA256); + SSVAL(header, 2, NL_SEAL_AES128); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hmac(hmac_hnd, header, 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, confounder, 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } else { + SSVAL(header, 0, NL_SIGN_HMAC_SHA256); + SSVAL(header, 2, NL_SEAL_NONE); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hmac(hmac_hnd, header, 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + rc = gnutls_hmac(hmac_hnd, data, length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + gnutls_hmac_deinit(hmac_hnd, checksum); + } else { + uint8_t packet_digest[16]; + static const uint8_t zeros[4]; + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hash(hash_hnd, zeros, sizeof(zeros)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + if (confounder) { + SSVAL(header, 0, NL_SIGN_HMAC_MD5); + SSVAL(header, 2, NL_SEAL_RC4); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hash(hash_hnd, header, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hash(hash_hnd, confounder, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } else { + SSVAL(header, 0, NL_SIGN_HMAC_MD5); + SSVAL(header, 2, NL_SEAL_NONE); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hash(hash_hnd, header, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + rc = gnutls_hash(hash_hnd, data, length); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + gnutls_hash_deinit(hash_hnd, packet_digest); + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + state->creds->session_key, + sizeof(state->creds->session_key), + packet_digest, + sizeof(packet_digest), + checksum); + ZERO_ARRAY(packet_digest); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS netsec_incoming_packet(struct schannel_state *state, + bool do_unseal, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + uint32_t min_sig_size = 0; + uint8_t header[8]; + uint8_t checksum[32]; + uint32_t checksum_length = sizeof(checksum_length); + uint8_t _confounder[8]; + uint8_t *confounder = NULL; + uint32_t confounder_ofs = 0; + uint8_t seq_num[8]; + bool ret; + const uint8_t *sign_data = NULL; + size_t sign_length = 0; + NTSTATUS status; + + netsec_offset_and_sizes(state, + do_unseal, + &min_sig_size, + NULL, + &checksum_length, + &confounder_ofs); + + if (sig->length < min_sig_size) { + return NT_STATUS_ACCESS_DENIED; + } + + if (do_unseal) { + confounder = _confounder; + memcpy(confounder, sig->data+confounder_ofs, 8); + } else { + confounder = NULL; + } + + SETUP_SEQNUM(state, seq_num, !state->initiator); + + if (do_unseal) { + status = netsec_do_seal(state, + seq_num, + confounder, + data, + length, + false); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seal failed: %s\n", nt_errstr(status)); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (state->gensec->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + sign_data = whole_pdu; + sign_length = pdu_length; + } else { + sign_data = data; + sign_length = length; + } + + status = netsec_do_sign(state, + confounder, + sign_data, + sign_length, + header, + checksum); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_sign failed: %s\n", nt_errstr(status)); + return NT_STATUS_ACCESS_DENIED; + } + + ret = mem_equal_const_time(checksum, sig->data+16, checksum_length); + if (!ret) { + dump_data_pw("calc digest:", checksum, checksum_length); + dump_data_pw("wire digest:", sig->data+16, checksum_length); + return NT_STATUS_ACCESS_DENIED; + } + + status = netsec_do_seq_num(state, checksum, checksum_length, seq_num); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seq_num failed: %s\n", + nt_errstr(status)); + return status; + } + + ZERO_ARRAY(checksum); + + ret = mem_equal_const_time(seq_num, sig->data+8, 8); + if (!ret) { + dump_data_pw("calc seq num:", seq_num, 8); + dump_data_pw("wire seq num:", sig->data+8, 8); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +static uint32_t netsec_outgoing_sig_size(struct schannel_state *state) +{ + uint32_t sig_size = 0; + + netsec_offset_and_sizes(state, + true, + NULL, + &sig_size, + NULL, + NULL); + + return sig_size; +} + +static NTSTATUS netsec_outgoing_packet(struct schannel_state *state, + TALLOC_CTX *mem_ctx, + bool do_seal, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + uint32_t min_sig_size = 0; + uint32_t used_sig_size = 0; + uint8_t header[8]; + uint8_t checksum[32]; + uint32_t checksum_length = sizeof(checksum_length); + uint8_t _confounder[8]; + uint8_t *confounder = NULL; + uint32_t confounder_ofs = 0; + uint8_t seq_num[8]; + const uint8_t *sign_data = NULL; + size_t sign_length = 0; + NTSTATUS status; + + netsec_offset_and_sizes(state, + do_seal, + &min_sig_size, + &used_sig_size, + &checksum_length, + &confounder_ofs); + + SETUP_SEQNUM(state, seq_num, state->initiator); + + if (do_seal) { + confounder = _confounder; + generate_random_buffer(confounder, 8); + } else { + confounder = NULL; + } + + if (state->gensec->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + sign_data = whole_pdu; + sign_length = pdu_length; + } else { + sign_data = data; + sign_length = length; + } + + status = netsec_do_sign(state, + confounder, + sign_data, + sign_length, + header, + checksum); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_sign failed: %s\n", nt_errstr(status)); + return NT_STATUS_ACCESS_DENIED; + } + + if (do_seal) { + status = netsec_do_seal(state, + seq_num, + confounder, + data, + length, + true); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seal failed: %s\n", + nt_errstr(status)); + return status; + } + } + + status = netsec_do_seq_num(state, checksum, checksum_length, seq_num); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seq_num failed: %s\n", + nt_errstr(status)); + return status; + } + + (*sig) = data_blob_talloc_zero(mem_ctx, used_sig_size); + + memcpy(sig->data, header, 8); + memcpy(sig->data+8, seq_num, 8); + memcpy(sig->data+16, checksum, checksum_length); + + if (confounder) { + memcpy(sig->data+confounder_ofs, confounder, 8); + } + + dump_data_pw("signature:", sig->data+ 0, 8); + dump_data_pw("seq_num :", sig->data+ 8, 8); + dump_data_pw("digest :", sig->data+16, checksum_length); + dump_data_pw("confound :", sig->data+confounder_ofs, 8); + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS gensec_schannel_init(TALLOC_CTX *ctx); + +static size_t schannel_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + struct schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_outgoing_sig_size(state); +} + +struct schannel_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static NTSTATUS schannel_update_internal(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); + +static struct tevent_req *schannel_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req; + struct schannel_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct schannel_update_state); + if (req == NULL) { + return NULL; + } + + status = schannel_update_internal(gensec_security, + state, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS schannel_update_internal(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct schannel_state *state = + talloc_get_type(gensec_security->private_data, + struct schannel_state); + NTSTATUS status; + enum ndr_err_code ndr_err; + struct NL_AUTH_MESSAGE bind_schannel = { + .Flags = 0, + }; + struct NL_AUTH_MESSAGE bind_schannel_ack; + struct netlogon_creds_CredentialState *creds; + const char *workstation; + const char *domain; + + *out = data_blob(NULL, 0); + + if (gensec_security->dcerpc_auth_level < DCERPC_AUTH_LEVEL_INTEGRITY) { + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + return NT_STATUS_INVALID_PARAMETER_MIX; + case GENSEC_SERVER: + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_INTERNAL_ERROR; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + if (state != NULL) { + /* we could parse the bind ack, but we don't know what it is yet */ + return NT_STATUS_OK; + } + + creds = cli_credentials_get_netlogon_creds(gensec_security->credentials); + if (creds == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + state = netsec_create_state(gensec_security, + creds, true /* initiator */); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + bind_schannel.MessageType = NL_NEGOTIATE_REQUEST; + + bind_schannel.Flags = NL_FLAG_OEM_NETBIOS_DOMAIN_NAME | + NL_FLAG_OEM_NETBIOS_COMPUTER_NAME; + bind_schannel.oem_netbios_domain.a = cli_credentials_get_domain(gensec_security->credentials); + bind_schannel.oem_netbios_computer.a = creds->computer_name; + + if (creds->secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + bind_schannel.Flags |= NL_FLAG_UTF8_DNS_DOMAIN_NAME; + bind_schannel.utf8_dns_domain.u = cli_credentials_get_realm(gensec_security->credentials); + + bind_schannel.Flags |= NL_FLAG_UTF8_NETBIOS_COMPUTER_NAME; + bind_schannel.utf8_netbios_computer.u = creds->computer_name; + } + + ndr_err = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel, + (ndr_push_flags_fn_t)ndr_push_NL_AUTH_MESSAGE); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3, ("Could not create schannel bind: %s\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + case GENSEC_SERVER: + + if (state != NULL) { + /* no third leg on this protocol */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the schannel startup blob */ + ndr_err = ndr_pull_struct_blob(&in, out_mem_ctx, &bind_schannel, + (ndr_pull_flags_fn_t)ndr_pull_NL_AUTH_MESSAGE); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3, ("Could not parse incoming schannel bind: %s\n", + nt_errstr(status))); + return status; + } + + if (bind_schannel.Flags & NL_FLAG_OEM_NETBIOS_DOMAIN_NAME) { + domain = bind_schannel.oem_netbios_domain.a; + if (strcasecmp_m(domain, lpcfg_workgroup(gensec_security->settings->lp_ctx)) != 0) { + DEBUG(3, ("Request for schannel to incorrect domain: %s != our domain %s\n", + domain, lpcfg_workgroup(gensec_security->settings->lp_ctx))); + return NT_STATUS_LOGON_FAILURE; + } + } else if (bind_schannel.Flags & NL_FLAG_UTF8_DNS_DOMAIN_NAME) { + domain = bind_schannel.utf8_dns_domain.u; + if (strcasecmp_m(domain, lpcfg_dnsdomain(gensec_security->settings->lp_ctx)) != 0) { + DEBUG(3, ("Request for schannel to incorrect domain: %s != our domain %s\n", + domain, lpcfg_dnsdomain(gensec_security->settings->lp_ctx))); + return NT_STATUS_LOGON_FAILURE; + } + } else { + DEBUG(3, ("Request for schannel to without domain\n")); + return NT_STATUS_LOGON_FAILURE; + } + + if (bind_schannel.Flags & NL_FLAG_OEM_NETBIOS_COMPUTER_NAME) { + workstation = bind_schannel.oem_netbios_computer.a; + } else if (bind_schannel.Flags & NL_FLAG_UTF8_NETBIOS_COMPUTER_NAME) { + workstation = bind_schannel.utf8_netbios_computer.u; + } else { + DEBUG(3, ("Request for schannel to without netbios workstation\n")); + return NT_STATUS_LOGON_FAILURE; + } + + status = schannel_get_creds_state(out_mem_ctx, + gensec_security->settings->lp_ctx, + workstation, &creds); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not find session key for attempted schannel connection from %s: %s\n", + workstation, nt_errstr(status))); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) { + return NT_STATUS_LOGON_FAILURE; + } + return status; + } + + state = netsec_create_state(gensec_security, + creds, false /* not initiator */); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = auth_anonymous_user_info_dc(state, + lpcfg_netbios_name(gensec_security->settings->lp_ctx), + &state->user_info_dc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + bind_schannel_ack.MessageType = NL_NEGOTIATE_RESPONSE; + bind_schannel_ack.Flags = 0; + bind_schannel_ack.Buffer.dummy = 0x6c0000; /* actually I think + * this does not have + * any meaning here + * - gd */ + + ndr_err = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel_ack, + (ndr_push_flags_fn_t)ndr_push_NL_AUTH_MESSAGE); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3, ("Could not return schannel bind ack for client %s: %s\n", + workstation, nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; + } + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS schannel_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct schannel_update_state *state = + tevent_req_data(req, + struct schannel_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + status = state->status; + talloc_steal(out_mem_ctx, state->out.data); + *out = state->out; + tevent_req_received(req); + return status; +} + +/** + * Returns anonymous credentials for schannel, matching Win2k3. + * + */ + +static NTSTATUS schannel_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **_session_info) +{ + struct schannel_state *state = + talloc_get_type(gensec_security->private_data, + struct schannel_state); + struct auth4_context *auth_ctx = gensec_security->auth_context; + struct auth_session_info *session_info = NULL; + uint32_t session_info_flags = 0; + NTSTATUS status; + + if (auth_ctx == NULL) { + DEBUG(0, ("Cannot generate a session_info without the auth_context\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (auth_ctx->generate_session_info == NULL) { + DEBUG(0, ("Cannot generate a session_info without the generate_session_info hook\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES; + + status = auth_ctx->generate_session_info( + auth_ctx, + mem_ctx, + state->user_info_dc, + state->user_info_dc->info->account_name, + session_info_flags, + &session_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_session_info = session_info; + return NT_STATUS_OK; +} + +/* + * Reduce the attack surface by ensuring schannel is not availble when + * we are not a DC + */ +static NTSTATUS schannel_server_start(struct gensec_security *gensec_security) +{ + enum server_role server_role + = lpcfg_server_role(gensec_security->settings->lp_ctx); + + switch (server_role) { + case ROLE_DOMAIN_BDC: + case ROLE_DOMAIN_PDC: + case ROLE_ACTIVE_DIRECTORY_DC: + case ROLE_IPA_DC: + return NT_STATUS_OK; + default: + return NT_STATUS_NOT_IMPLEMENTED; + } +} + +static NTSTATUS schannel_client_start(struct gensec_security *gensec_security) +{ + return NT_STATUS_OK; +} + +static bool schannel_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (gensec_security->dcerpc_auth_level >= DCERPC_AUTH_LEVEL_INTEGRITY) { + if (feature & GENSEC_FEATURE_SIGN) { + return true; + } + } + if (gensec_security->dcerpc_auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + if (feature & GENSEC_FEATURE_SEAL) { + return true; + } + } + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return true; + } + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + return true; + } + return false; +} + +/* + unseal a packet +*/ +static NTSTATUS schannel_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 schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_incoming_packet(state, true, + discard_const_p(uint8_t, data), + length, + whole_pdu, pdu_length, + sig); +} + +/* + check the signature on a packet +*/ +static NTSTATUS schannel_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 schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_incoming_packet(state, false, + discard_const_p(uint8_t, data), + length, + whole_pdu, pdu_length, + sig); +} +/* + seal a packet +*/ +static NTSTATUS schannel_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 schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_outgoing_packet(state, mem_ctx, true, + data, length, + whole_pdu, pdu_length, + sig); +} + +/* + sign a packet +*/ +static NTSTATUS schannel_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 schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_outgoing_packet(state, mem_ctx, false, + discard_const_p(uint8_t, data), + length, + whole_pdu, pdu_length, + sig); +} + +static const struct gensec_security_ops gensec_schannel_security_ops = { + .name = "schannel", + .auth_type = DCERPC_AUTH_TYPE_SCHANNEL, + .client_start = schannel_client_start, + .server_start = schannel_server_start, + .update_send = schannel_update_send, + .update_recv = schannel_update_recv, + .seal_packet = schannel_seal_packet, + .sign_packet = schannel_sign_packet, + .check_packet = schannel_check_packet, + .unseal_packet = schannel_unseal_packet, + .session_info = schannel_session_info, + .sig_size = schannel_sig_size, + .have_feature = schannel_have_feature, + .enabled = true, + .priority = GENSEC_SCHANNEL +}; + +_PUBLIC_ NTSTATUS gensec_schannel_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + ret = gensec_register(ctx, &gensec_schannel_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_schannel_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/auth/gensec/spnego.c b/auth/gensec/spnego.c new file mode 100644 index 0000000..87545d8 --- /dev/null +++ b/auth/gensec/spnego.c @@ -0,0 +1,2249 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Stefan Metzmacher <metze@samba.org> 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 <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.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" +#include "lib/util/base64.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#undef strcasecmp + +_PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx); + +enum spnego_state_position { + SPNEGO_SERVER_START, + SPNEGO_CLIENT_START, + SPNEGO_SERVER_TARG, + SPNEGO_CLIENT_TARG, + SPNEGO_FALLBACK, + SPNEGO_DONE +}; + +struct spnego_state; +struct spnego_neg_ops; +struct spnego_neg_state; + +struct spnego_neg_state { + const struct spnego_neg_ops *ops; + const struct gensec_security_ops_wrapper *all_sec; + size_t all_idx; + const char * const *mech_types; + size_t mech_idx; +}; + +struct spnego_neg_ops { + const char *name; + /* + * The start hook does the initial processing on the incoming paket and + * may starts the first possible subcontext. It indicates that + * gensec_update() is required on the subcontext by returning + * NT_STATUS_MORE_PROCESSING_REQUIRED and return something useful in + * 'in_next'. Note that 'in_mem_ctx' is just passed as a hint, the + * caller should treat 'in_next' as const and don't attempt to free the + * content. NT_STATUS_OK indicates the finish hook should be invoked + * directly within the need of gensec_update() on the subcontext. + * Every other error indicates an error that's returned to the caller. + */ + NTSTATUS (*start_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next); + /* + * The step hook processes the result of a failed gensec_update() and + * can decide to ignore a failure and continue the negotiation by + * setting up the next possible subcontext. It indicates that + * gensec_update() is required on the subcontext by returning + * NT_STATUS_MORE_PROCESSING_REQUIRED and return something useful in + * 'in_next'. Note that 'in_mem_ctx' is just passed as a hint, the + * caller should treat 'in_next' as const and don't attempt to free the + * content. NT_STATUS_OK indicates the finish hook should be invoked + * directly within the need of gensec_update() on the subcontext. + * Every other error indicates an error that's returned to the caller. + */ + NTSTATUS (*step_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next); + /* + * The finish hook processes the result of a successful gensec_update() + * (NT_STATUS_OK or NT_STATUS_MORE_PROCESSING_REQUIRED). It forms the + * response pdu that will be returned from the toplevel gensec_update() + * together with NT_STATUS_OK or NT_STATUS_MORE_PROCESSING_REQUIRED. It + * may also alter the state machine to prepare receiving the next pdu + * from the peer. + */ + NTSTATUS (*finish_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); +}; + +struct spnego_state { + enum spnego_message_type expected_packet; + enum spnego_state_position state_position; + struct gensec_security *sub_sec_security; + bool sub_sec_ready; + + const char *neg_oid; + + DATA_BLOB mech_types; + size_t num_targs; + bool downgraded; + bool mic_requested; + bool needs_mic_sign; + bool needs_mic_check; + bool may_skip_mic_check; + bool done_mic_check; + + bool simulate_w2k; + bool no_optimistic; + + /* + * 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 struct spnego_neg_state *gensec_spnego_neg_state(TALLOC_CTX *mem_ctx, + const struct spnego_neg_ops *ops) +{ + struct spnego_neg_state *n = NULL; + + n = talloc_zero(mem_ctx, struct spnego_neg_state); + if (n == NULL) { + return NULL; + } + n->ops = ops; + + return n; +} + +static void gensec_spnego_reset_sub_sec(struct spnego_state *spnego_state) +{ + spnego_state->sub_sec_ready = false; + TALLOC_FREE(spnego_state->sub_sec_security); +} + +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->sub_sec_ready = false; + spnego_state->mech_types = data_blob_null; + spnego_state->out_max_length = gensec_max_update_size(gensec_security); + spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + + spnego_state->simulate_w2k = gensec_setting_bool(gensec_security->settings, + "spnego", "simulate_w2k", false); + spnego_state->no_optimistic = gensec_setting_bool(gensec_security->settings, + "spnego", + "client_no_optimistic", + false); + + 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->sub_sec_ready = false; + spnego_state->mech_types = data_blob_null; + spnego_state->out_max_length = gensec_max_update_size(gensec_security); + spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + + spnego_state->simulate_w2k = gensec_setting_bool(gensec_security->settings, + "spnego", "simulate_w2k", false); + + gensec_security->private_data = spnego_state; + return NT_STATUS_OK; +} + +/** 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, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in) +{ + int i,j; + const struct gensec_security_ops **all_ops; + + all_ops = gensec_security_mechs(gensec_security, 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; + } + + return NT_STATUS_OK; + } + DEBUG(1, ("Failed to parse SPNEGO request\n")); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_create_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + n->mech_idx = 0; + n->mech_types = gensec_security_oids(gensec_security, n, + GENSEC_OID_SPNEGO); + if (n->mech_types == NULL) { + DBG_WARNING("gensec_security_oids() failed\n"); + return NT_STATUS_NO_MEMORY; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_NO_MEMORY; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_create_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + const char *principal = NULL; + int dbg_level = DBGLVL_WARNING; + NTSTATUS status = last_status; + + if (cur_sec[1].op != NULL) { + next_sec = &cur_sec[1]; + } + + if (next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } + + if (gensec_security->target.principal != NULL) { + principal = gensec_security->target.principal; + } else if (gensec_security->target.service != NULL && + gensec_security->target.hostname != NULL) + { + principal = talloc_asprintf(spnego_state->sub_sec_security, + "%s/%s", + gensec_security->target.service, + gensec_security->target.hostname); + } else { + principal = gensec_security->target.hostname; + } + + DBG_PREFIX(dbg_level, ( + "%s: creating NEG_TOKEN_INIT for %s failed " + "(next[%s]): %s\n", cur_sec->op->name, + principal, next, nt_errstr(status))); + + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one... + */ + n->all_idx += 1; + } + + for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + NTSTATUS status; + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + /* In the client, try and produce the first (optimistic) packet */ + if (spnego_state->state_position == SPNEGO_CLIENT_START) { + *in_next = data_blob_null; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + *in_next = data_blob_null; + return NT_STATUS_OK; + } + + DBG_WARNING("Failed to setup SPNEGO negTokenInit request\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_create_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + struct spnego_data spnego_out; + bool ok; + + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + + n->mech_types = gensec_security_oids_from_ops_wrapped(n, cur_sec); + if (n->mech_types == NULL) { + DBG_WARNING("gensec_security_oids_from_ops_wrapped() failed\n"); + return NT_STATUS_NO_MEMORY; + } + + ok = spnego_write_mech_types(spnego_state, + n->mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("Failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + /* List the remaining mechs as options */ + spnego_out.negTokenInit.mechTypes = n->mech_types; + spnego_out.negTokenInit.reqFlags = data_blob_null; + 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 = data_blob_null; + } + + spnego_out.negTokenInit.mechToken = sub_out; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_ERR("Failed to write NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* set next state */ + if (spnego_state->state_position == SPNEGO_SERVER_START) { + spnego_state->state_position = SPNEGO_SERVER_START; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + } else { + spnego_state->state_position = SPNEGO_CLIENT_TARG; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + } + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_create_negTokenInit_ops = { + .name = "create_negTokenInit", + .start_fn = gensec_spnego_create_negTokenInit_start, + .step_fn = gensec_spnego_create_negTokenInit_step, + .finish_fn = gensec_spnego_create_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_client_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + const char *tp = NULL; + + /* The server offers a list of mechanisms */ + + tp = spnego_in->negTokenInit.targetPrincipal; + if (tp != NULL && strcmp(tp, ADS_IGNORE_PRINCIPAL) != 0) { + DBG_INFO("Server claims it's principal name is %s\n", tp); + if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) { + gensec_set_target_principal(gensec_security, tp); + } + } + + n->mech_idx = 0; + + /* Do not use server mech list as it isn't protected. Instead, get all + * supported mechs (excluding SPNEGO). */ + n->mech_types = gensec_security_oids(gensec_security, n, + GENSEC_OID_SPNEGO); + if (n->mech_types == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_client_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + const char *principal = NULL; + int dbg_level = DBGLVL_WARNING; + bool allow_fallback = false; + NTSTATUS status = last_status; + + if (cur_sec[1].op != NULL) { + next_sec = &cur_sec[1]; + } + + /* + * 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 (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_ACCOUNT_NAME) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_COMPUTER_NAME) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_LOGON_SERVERS) || + NT_STATUS_EQUAL(status, NT_STATUS_TIME_DIFFERENCE_AT_DC) || + NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) + { + allow_fallback = true; + } + + if (allow_fallback && next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } + + if (gensec_security->target.principal != NULL) { + principal = gensec_security->target.principal; + } else if (gensec_security->target.service != NULL && + gensec_security->target.hostname != NULL) + { + principal = talloc_asprintf(spnego_state->sub_sec_security, + "%s/%s", + gensec_security->target.service, + gensec_security->target.hostname); + } else { + principal = gensec_security->target.hostname; + } + + DBG_PREFIX(dbg_level, ( + "%s: creating NEG_TOKEN_INIT for %s failed " + "(next[%s]): %s\n", cur_sec->op->name, + principal, next, nt_errstr(status))); + + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it. + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one... + */ + n->all_idx += 1; + } + + for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + NTSTATUS status; + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* + * As client we don't use an optimistic token from the server. + * But try to produce one for the server. + */ + *in_next = data_blob_null; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + DBG_WARNING("Could not find a suitable mechtype in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_client_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct spnego_data spnego_out; + const char * const *mech_types = NULL; + bool ok; + + if (n->mech_types == NULL) { + DBG_WARNING("No mech_types list\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + for (mech_types = n->mech_types; *mech_types != NULL; mech_types++) { + int cmp = strcmp(*mech_types, spnego_state->neg_oid); + + if (cmp == 0) { + break; + } + } + + if (*mech_types == NULL) { + DBG_ERR("Can't find selected sub mechanism in mech_types\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + spnego_out.negTokenInit.mechTypes = mech_types; + spnego_out.negTokenInit.reqFlags = data_blob_null; + spnego_out.negTokenInit.reqFlagsPadding = 0; + spnego_out.negTokenInit.mechListMIC = data_blob_null; + spnego_out.negTokenInit.mechToken = sub_out; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_ERR("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = spnego_write_mech_types(spnego_state, + mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_CLIENT_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_client_negTokenInit_ops = { + .name = "client_negTokenInit", + .start_fn = gensec_spnego_client_negTokenInit_start, + .step_fn = gensec_spnego_client_negTokenInit_step, + .finish_fn = gensec_spnego_client_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_client_negTokenTarg_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + NTSTATUS status; + + spnego_state->num_targs++; + + if (ta->negResult == SPNEGO_REJECT) { + return NT_STATUS_LOGON_FAILURE; + } + + if (ta->negResult == SPNEGO_REQUEST_MIC) { + spnego_state->mic_requested = true; + } + + if (ta->mechListMIC.length > 0) { + DATA_BLOB *m = &ta->mechListMIC; + const DATA_BLOB *r = &ta->responseToken; + + /* + * Windows 2000 has a bug, it repeats the + * responseToken in the mechListMIC field. + */ + if (m->length == r->length) { + int cmp; + + cmp = memcmp(m->data, r->data, m->length); + if (cmp == 0) { + data_blob_free(m); + } + } + } + + /* Server didn't like our choice of mech, and chose something else */ + if (((ta->negResult == SPNEGO_ACCEPT_INCOMPLETE) || + (ta->negResult == SPNEGO_REQUEST_MIC)) && + ta->supportedMech != NULL && + strcmp(ta->supportedMech, spnego_state->neg_oid) != 0) + { + const char *client_mech = NULL; + const char *client_oid = NULL; + const char *server_mech = NULL; + const char *server_oid = NULL; + + client_mech = gensec_get_name_by_oid(gensec_security, + spnego_state->neg_oid); + client_oid = spnego_state->neg_oid; + server_mech = gensec_get_name_by_oid(gensec_security, + ta->supportedMech); + server_oid = ta->supportedMech; + + DBG_NOTICE("client preferred mech (%s[%s]) not accepted, " + "server wants: %s[%s]\n", + client_mech, client_oid, server_mech, server_oid); + + spnego_state->downgraded = true; + gensec_spnego_reset_sub_sec(spnego_state); + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + ta->supportedMech); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + spnego_state->neg_oid = talloc_strdup(spnego_state, + ta->supportedMech); + if (spnego_state->neg_oid == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (ta->mechListMIC.length > 0) { + if (spnego_state->sub_sec_ready) { + spnego_state->needs_mic_check = true; + } + } + + if (spnego_state->needs_mic_check) { + if (ta->responseToken.length != 0) { + DBG_WARNING("non empty response token not expected\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (ta->mechListMIC.length == 0 + && spnego_state->may_skip_mic_check) { + /* + * In this case we don't require + * a mechListMIC from the server. + * + * This works around bugs in the Azure + * and Apple spnego implementations. + * + * See + * https://bugzilla.samba.org/show_bug.cgi?id=11994 + */ + spnego_state->needs_mic_check = false; + return NT_STATUS_OK; + } + + 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, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + return NT_STATUS_OK; + } + + if (!spnego_state->sub_sec_ready) { + *in_next = ta->responseToken; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_client_negTokenTarg_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (GENSEC_UPDATE_IS_NTERROR(last_status)) { + DBG_WARNING("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(last_status)); + return last_status; + } + + /* + * This should never be reached! + * The step function is only called on errors! + */ + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_spnego_client_negTokenTarg_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct spnego_negTokenTarg *ta = + &spnego_in->negTokenTarg; + DATA_BLOB mech_list_mic = data_blob_null; + NTSTATUS status; + struct spnego_data spnego_out; + + if (!spnego_state->sub_sec_ready) { + /* + * We're not yet ready to deal with signatures. + */ + goto client_response; + } + + if (spnego_state->done_mic_check) { + /* + * We already checked the mic, + * either the in last round here + * in gensec_spnego_client_negTokenTarg_finish() + * or during this round in + * gensec_spnego_client_negTokenTarg_start(). + * + * Both cases we're sure we don't have to + * call gensec_sign_packet(). + */ + goto client_response; + } + + if (spnego_state->may_skip_mic_check) { + /* + * This can only be set during + * the last round here in + * gensec_spnego_client_negTokenTarg_finish() + * below. And during this round + * we already passed the checks in + * gensec_spnego_client_negTokenTarg_start(). + * + * So we need to skip to deal with + * any signatures now. + */ + goto client_response; + } + + if (!spnego_state->done_mic_check) { + bool have_sign = true; + bool new_spnego = false; + + have_sign = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_SIGN); + if (spnego_state->simulate_w2k) { + have_sign = false; + } + new_spnego = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_NEW_SPNEGO); + + switch (ta->negResult) { + case SPNEGO_ACCEPT_COMPLETED: + case SPNEGO_NONE_RESULT: + if (spnego_state->num_targs == 1) { + /* + * the first exchange doesn't require + * verification + */ + new_spnego = false; + } + + break; + + case SPNEGO_ACCEPT_INCOMPLETE: + if (ta->mechListMIC.length > 0) { + new_spnego = true; + break; + } + + if (spnego_state->downgraded) { + /* + * A downgrade should be protected if + * supported + */ + break; + } + + /* + * The caller may just asked for + * GENSEC_FEATURE_SESSION_KEY, this + * is only reflected in the want_features. + * + * As it will imply + * gensec_have_features(GENSEC_FEATURE_SIGN) + * to return true. + */ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + break; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + break; + } + /* + * Here we're sure our preferred mech was + * selected by the server and our caller doesn't + * need GENSEC_FEATURE_SIGN nor + * GENSEC_FEATURE_SEAL support. + * + * In this case we don't require + * a mechListMIC from the server. + * + * This works around bugs in the Azure + * and Apple spnego implementations. + * + * See + * https://bugzilla.samba.org/show_bug.cgi?id=11994 + */ + spnego_state->may_skip_mic_check = true; + break; + + case SPNEGO_REQUEST_MIC: + if (ta->mechListMIC.length > 0) { + new_spnego = true; + } + break; + default: + break; + } + + if (spnego_state->mic_requested) { + if (have_sign) { + new_spnego = true; + } + } + + if (have_sign && new_spnego) { + spnego_state->needs_mic_check = true; + spnego_state->needs_mic_sign = true; + } + } + + if (ta->mechListMIC.length > 0) { + 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, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + } + + if (spnego_state->needs_mic_sign) { + status = gensec_sign_packet(spnego_state->sub_sec_security, + n, + 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(status)) { + DBG_WARNING("failed to sign mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_sign = false; + } + + client_response: + if (sub_out.length == 0 && mech_list_mic.length == 0) { + *out = data_blob_null; + + if (!spnego_state->sub_sec_ready) { + /* somethings wrong here... */ + DBG_ERR("gensec_update not ready without output\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + if (ta->negResult != SPNEGO_ACCEPT_COMPLETED) { + /* unless of course it did not accept */ + DBG_WARNING("gensec_update ok but not accepted\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!spnego_state->needs_mic_check) { + spnego_state->state_position = SPNEGO_DONE; + return NT_STATUS_OK; + } + } + + /* 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 = sub_out; + spnego_out.negTokenTarg.mechListMIC = mech_list_mic; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_WARNING("Failed to write NEG_TOKEN_TARG\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->num_targs++; + + /* set next state */ + spnego_state->state_position = SPNEGO_CLIENT_TARG; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_client_negTokenTarg_ops = { + .name = "client_negTokenTarg", + .start_fn = gensec_spnego_client_negTokenTarg_start, + .step_fn = gensec_spnego_client_negTokenTarg_step, + .finish_fn = gensec_spnego_client_negTokenTarg_finish, +}; + +/** create a server negTokenTarg + * + * This is the case, where the client is the first one who sends data +*/ + +static NTSTATUS gensec_spnego_server_response(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; + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.responseToken = unwrapped_out; + spnego_out.negTokenTarg.mechListMIC = mech_list_mic; + spnego_out.negTokenTarg.supportedMech = NULL; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; + if (spnego_state->mic_requested) { + spnego_out.negTokenTarg.negResult = SPNEGO_REQUEST_MIC; + spnego_state->mic_requested = false; + } else { + 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_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; + spnego_state->num_targs++; + + return nt_status; +} + +static NTSTATUS gensec_spnego_server_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + bool ok; + + n->mech_idx = 0; + n->mech_types = spnego_in->negTokenInit.mechTypes; + if (n->mech_types == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = spnego_write_mech_types(spnego_state, + n->mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("Failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_server_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const char *next_mech = n->mech_types[n->mech_idx+1]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + int dbg_level = DBGLVL_WARNING; + bool allow_fallback = false; + NTSTATUS status = last_status; + size_t i; + + for (i = 0; next_mech != NULL && n->all_sec[i].op != NULL; i++) { + if (strcmp(next_mech, n->all_sec[i].oid) != 0) { + continue; + } + + next_sec = &n->all_sec[i]; + break; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) + { + allow_fallback = true; + } + + if (allow_fallback && next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } + + DBG_PREFIX(dbg_level, ( + "%s: parsing NEG_TOKEN_INIT content failed " + "(next[%s]): %s\n", cur_sec->op->name, + next, nt_errstr(status))); + + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one, based on the clients + * mech type list... + */ + n->mech_idx += 1; + } + + /* + * we always reset all_idx here, as the negotiation is + * done via mech_idx! + */ + n->all_idx = 0; + + for (; n->mech_types[n->mech_idx] != NULL; n->mech_idx++) { + const char *cur_mech = n->mech_types[n->mech_idx]; + const struct gensec_security_ops_wrapper *cur_sec = NULL; + NTSTATUS status; + DATA_BLOB sub_in = data_blob_null; + size_t i; + + for (i = 0; n->all_sec[i].op != NULL; i++) { + if (strcmp(cur_mech, n->all_sec[i].oid) != 0) { + continue; + } + + cur_sec = &n->all_sec[i]; + n->all_idx = i; + break; + } + + if (cur_sec == NULL) { + continue; + } + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + if (n->mech_idx == 0) { + /* + * We can use the optimistic token. + */ + sub_in = spnego_in->negTokenInit.mechToken; + } else { + /* + * Indicate the downgrade and request a + * mic. + */ + spnego_state->downgraded = true; + spnego_state->mic_requested = true; + } + + if (sub_in.length == 0) { + spnego_state->no_optimistic = true; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* we need some content from the mech */ + *in_next = sub_in; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + DBG_WARNING("Could not find a suitable mechtype in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_server_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + DATA_BLOB mech_list_mic = data_blob_null; + + if (spnego_state->simulate_w2k) { + /* + * Windows 2000 returns the unwrapped token + * also in the mech_list_mic field. + * + * In order to verify our client code, + * we need a way to have a server with this + * broken behaviour + */ + mech_list_mic = sub_out; + } + + return gensec_spnego_server_response(spnego_state, + out_mem_ctx, + sub_status, + sub_out, + mech_list_mic, + out); +} + +static const struct spnego_neg_ops gensec_spnego_server_negTokenInit_ops = { + .name = "server_negTokenInit", + .start_fn = gensec_spnego_server_negTokenInit_start, + .step_fn = gensec_spnego_server_negTokenInit_step, + .finish_fn = gensec_spnego_server_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_server_negTokenTarg_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + const struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + NTSTATUS status; + + spnego_state->num_targs++; + + if (spnego_state->sub_sec_security == NULL) { + DBG_ERR("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego_state->needs_mic_check) { + if (ta->responseToken.length != 0) { + DBG_WARNING("non empty response token not expected\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + 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, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + return NT_STATUS_OK; + } + + if (!spnego_state->sub_sec_ready) { + *in_next = ta->responseToken; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_server_negTokenTarg_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (GENSEC_UPDATE_IS_NTERROR(last_status)) { + DBG_NOTICE("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(last_status)); + return last_status; + } + + /* + * This should never be reached! + * The step function is only called on errors! + */ + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_spnego_server_negTokenTarg_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + DATA_BLOB mech_list_mic = data_blob_null; + NTSTATUS status; + bool have_sign = true; + bool new_spnego = false; + + status = sub_status; + + if (!spnego_state->sub_sec_ready) { + /* + * We're not yet ready to deal with signatures. + */ + goto server_response; + } + + if (spnego_state->done_mic_check) { + /* + * We already checked the mic, + * either the in last round here + * in gensec_spnego_server_negTokenTarg_finish() + * or during this round in + * gensec_spnego_server_negTokenTarg_start(). + * + * Both cases we're sure we don't have to + * call gensec_sign_packet(). + */ + goto server_response; + } + + have_sign = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_SIGN); + if (spnego_state->simulate_w2k) { + have_sign = false; + } + new_spnego = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_NEW_SPNEGO); + if (ta->mechListMIC.length > 0) { + new_spnego = true; + } + + if (have_sign && new_spnego) { + spnego_state->needs_mic_check = true; + spnego_state->needs_mic_sign = true; + } + + if (have_sign && ta->mechListMIC.length > 0) { + 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, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + } + + if (spnego_state->needs_mic_sign) { + status = gensec_sign_packet(spnego_state->sub_sec_security, + n, + 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(status)) { + DBG_WARNING("failed to sign mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_sign = false; + } + + if (spnego_state->needs_mic_check) { + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + server_response: + return gensec_spnego_server_response(spnego_state, + out_mem_ctx, + status, + sub_out, + mech_list_mic, + out); +} + +static const struct spnego_neg_ops gensec_spnego_server_negTokenTarg_ops = { + .name = "server_negTokenTarg", + .start_fn = gensec_spnego_server_negTokenTarg_start, + .step_fn = gensec_spnego_server_negTokenTarg_step, + .finish_fn = gensec_spnego_server_negTokenTarg_finish, +}; + +struct gensec_spnego_update_state { + struct tevent_context *ev; + struct gensec_security *gensec; + struct spnego_state *spnego; + + DATA_BLOB full_in; + struct spnego_data _spnego_in; + struct spnego_data *spnego_in; + + struct { + bool needed; + DATA_BLOB in; + NTSTATUS status; + DATA_BLOB out; + } sub; + + struct spnego_neg_state *n; + + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_spnego_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + + switch (req_state) { + case TEVENT_REQ_USER_ERROR: + case TEVENT_REQ_TIMED_OUT: + case TEVENT_REQ_NO_MEMORY: + /* + * A fatal error, further updates are not allowed. + */ + state->spnego->state_position = SPNEGO_DONE; + break; + default: + break; + } +} + +static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, + const DATA_BLOB in, TALLOC_CTX *mem_ctx, + DATA_BLOB *full_in); +static void gensec_spnego_update_pre(struct tevent_req *req); +static void gensec_spnego_update_done(struct tevent_req *subreq); +static void gensec_spnego_update_post(struct tevent_req *req); +static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *_out); + +static struct tevent_req *gensec_spnego_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + struct tevent_req *req = NULL; + struct gensec_spnego_update_state *state = NULL; + NTSTATUS status; + ssize_t len; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_spnego_update_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->gensec = gensec_security; + state->spnego = spnego_state; + tevent_req_set_cleanup_fn(req, gensec_spnego_update_cleanup); + + if (spnego_state->out_frag.length > 0) { + if (in.length > 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + status = gensec_spnego_update_out(gensec_security, + state, &state->out); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + state->status = status; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + status = gensec_spnego_update_in(gensec_security, in, + state, &state->full_in); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + /* Check if we got a valid SPNEGO blob... */ + + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + break; + + case SPNEGO_CLIENT_TARG: + case SPNEGO_SERVER_TARG: + if (state->full_in.length == 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + FALL_THROUGH; + case SPNEGO_CLIENT_START: + case SPNEGO_SERVER_START: + + if (state->full_in.length == 0) { + /* create_negTokenInit later */ + break; + } + + len = spnego_read_data(state, + state->full_in, + &state->_spnego_in); + if (len == -1) { + if (spnego_state->state_position != SPNEGO_SERVER_START) { + DEBUG(1, ("Invalid SPNEGO request:\n")); + dump_data(1, state->full_in.data, + state->full_in.length); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + /* + * 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) + */ + status = gensec_spnego_server_try_fallback(gensec_security, + spnego_state, + state, + state->full_in); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + /* + * We'll continue with SPNEGO_FALLBACK below... + */ + break; + } + state->spnego_in = &state->_spnego_in; + + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (state->spnego_in->type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", + state->spnego_in->type, + spnego_state->expected_packet)); + dump_data(1, state->full_in.data, + state->full_in.length); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + break; + + default: + smb_panic(__location__); + return NULL; + } + + gensec_spnego_update_pre(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + if (state->sub.needed) { + struct tevent_req *subreq = NULL; + + /* + * We may need one more roundtrip... + */ + subreq = gensec_update_send(state, state->ev, + spnego_state->sub_sec_security, + state->sub.in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + gensec_spnego_update_done, + req); + state->sub.needed = false; + return req; + } + + gensec_spnego_update_post(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, + const DATA_BLOB in, TALLOC_CTX *mem_ctx, + DATA_BLOB *full_in) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + size_t expected; + bool ok; + + *full_in = data_blob_null; + + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + *full_in = in; + spnego_state->in_needed = 0; + return NT_STATUS_OK; + + case SPNEGO_CLIENT_START: + case SPNEGO_CLIENT_TARG: + case SPNEGO_SERVER_START: + case SPNEGO_SERVER_TARG: + break; + + case SPNEGO_DONE: + default: + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego_state->in_needed == 0) { + size_t size = 0; + int ret; + + /* + * try to work out the size of the full + * input token, it might be fragmented + */ + ret = asn1_peek_full_tag(in, ASN1_APPLICATION(0), &size); + if ((ret != 0) && (ret != EAGAIN)) { + ret = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size); + } + + if ((ret == 0) || (ret == EAGAIN)) { + 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; + spnego_state->in_needed = 0; + 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; + talloc_steal(mem_ctx, full_in->data); + spnego_state->in_frag = data_blob_null; + spnego_state->in_needed = 0; + return NT_STATUS_OK; +} + +static void gensec_spnego_update_pre(struct tevent_req *req) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + const struct spnego_neg_ops *ops = NULL; + NTSTATUS status; + + state->sub.needed = false; + state->sub.in = data_blob_null; + state->sub.status = NT_STATUS_INTERNAL_ERROR; + state->sub.out = data_blob_null; + + if (spnego_state->state_position == SPNEGO_FALLBACK) { + state->sub.in = state->full_in; + state->full_in = data_blob_null; + state->sub.needed = true; + return; + } + + switch (spnego_state->state_position) { + case SPNEGO_CLIENT_START: + if (state->spnego_in == NULL) { + /* client to produce negTokenInit */ + ops = &gensec_spnego_create_negTokenInit_ops; + break; + } + + ops = &gensec_spnego_client_negTokenInit_ops; + break; + + case SPNEGO_CLIENT_TARG: + ops = &gensec_spnego_client_negTokenTarg_ops; + break; + + case SPNEGO_SERVER_START: + if (state->spnego_in == NULL) { + /* server to produce negTokenInit */ + ops = &gensec_spnego_create_negTokenInit_ops; + break; + } + + ops = &gensec_spnego_server_negTokenInit_ops; + break; + + case SPNEGO_SERVER_TARG: + ops = &gensec_spnego_server_negTokenTarg_ops; + break; + + default: + smb_panic(__location__); + return; + } + + state->n = gensec_spnego_neg_state(state, ops); + if (tevent_req_nomem(state->n, req)) { + return; + } + + status = ops->start_fn(state->gensec, spnego_state, state->n, + state->spnego_in, state, &state->sub.in); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + if (NT_STATUS_IS_OK(status)) { + /* + * Call finish_fn() with an empty + * blob and NT_STATUS_OK. + */ + state->sub.status = NT_STATUS_OK; + } else if (spnego_state->state_position == SPNEGO_CLIENT_START && + spnego_state->no_optimistic) { + /* + * Skip optimistic token per conf. + */ + state->sub.status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else if (spnego_state->state_position == SPNEGO_SERVER_START && + state->sub.in.length == 0 && spnego_state->no_optimistic) { + /* + * If we didn't like the mechanism for which the client sent us + * an optimistic token, or if he didn't send any, don't call + * the sub mechanism just yet. + */ + state->sub.status = NT_STATUS_MORE_PROCESSING_REQUIRED; + spnego_state->no_optimistic = false; + } else { + /* + * MORE_PROCESSING_REQUIRED => + * we need to call gensec_update_send(). + */ + state->sub.needed = true; + } +} + +static void gensec_spnego_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + + state->sub.status = gensec_update_recv(subreq, state, &state->sub.out); + TALLOC_FREE(subreq); + if (NT_STATUS_IS_OK(state->sub.status)) { + spnego_state->sub_sec_ready = true; + } + + gensec_spnego_update_post(req); +} + +static void gensec_spnego_update_post(struct tevent_req *req) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + const struct spnego_neg_ops *ops = NULL; + NTSTATUS status; + + state->sub.in = data_blob_null; + state->sub.needed = false; + + if (spnego_state->state_position == SPNEGO_FALLBACK) { + status = state->sub.status; + spnego_state->out_frag = state->sub.out; + talloc_steal(spnego_state, spnego_state->out_frag.data); + state->sub.out = data_blob_null; + goto respond; + } + + ops = state->n->ops; + + if (GENSEC_UPDATE_IS_NTERROR(state->sub.status)) { + + + /* + * gensec_update_recv() returned an error, + * let's see if the step_fn() want to + * handle it and negotiate something else. + */ + + status = ops->step_fn(state->gensec, + spnego_state, + state->n, + state->spnego_in, + state->sub.status, + state, + &state->sub.in); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + state->sub.out = data_blob_null; + state->sub.status = NT_STATUS_INTERNAL_ERROR; + + if (NT_STATUS_IS_OK(status)) { + /* + * Call finish_fn() with an empty + * blob and NT_STATUS_OK. + */ + state->sub.status = NT_STATUS_OK; + } else { + /* + * MORE_PROCESSING_REQUIRED... + */ + state->sub.needed = true; + } + } + + if (state->sub.needed) { + struct tevent_req *subreq = NULL; + + /* + * We may need one more roundtrip... + */ + subreq = gensec_update_send(state, state->ev, + spnego_state->sub_sec_security, + state->sub.in); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + gensec_spnego_update_done, + req); + state->sub.needed = false; + return; + } + + status = ops->finish_fn(state->gensec, + spnego_state, + state->n, + state->spnego_in, + state->sub.status, + state->sub.out, + spnego_state, + &spnego_state->out_frag); + TALLOC_FREE(state->n); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + if (NT_STATUS_IS_OK(status)) { + bool reset_full = true; + + reset_full = !spnego_state->done_mic_check; + + status = gensec_may_reset_crypto(spnego_state->sub_sec_security, + reset_full); + if (tevent_req_nterror(req, status)) { + return; + } + } + +respond: + spnego_state->out_status = status; + + status = gensec_spnego_update_out(state->gensec, + state, &state->out); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + state->status = status; + tevent_req_done(req); + return; +} + +static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *_out) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + DATA_BLOB out = data_blob_null; + bool ok; + + *_out = data_blob_null; + + if (spnego_state->out_frag.length <= spnego_state->out_max_length) { + /* + * Fast path, we can deliver everything + */ + + *_out = spnego_state->out_frag; + if (spnego_state->out_frag.length > 0) { + talloc_steal(out_mem_ctx, _out->data); + spnego_state->out_frag = data_blob_null; + } + + if (!NT_STATUS_IS_OK(spnego_state->out_status)) { + return spnego_state->out_status; + } + + /* + * We're completely done, further updates are not allowed. + */ + spnego_state->state_position = SPNEGO_DONE; + return gensec_child_ready(gensec_security, + spnego_state->sub_sec_security); + } + + 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 + */ + ok = data_blob_realloc(spnego_state, &out, + spnego_state->out_max_length); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + talloc_steal(out_mem_ctx, out.data); + *_out = out; + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static NTSTATUS gensec_spnego_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +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_send = gensec_spnego_update_send, + .update_recv = gensec_spnego_update_recv, + .seal_packet = gensec_child_seal_packet, + .sign_packet = gensec_child_sign_packet, + .sig_size = gensec_child_sig_size, + .max_wrapped_size = gensec_child_max_wrapped_size, + .max_input_size = gensec_child_max_input_size, + .check_packet = gensec_child_check_packet, + .unseal_packet = gensec_child_unseal_packet, + .wrap = gensec_child_wrap, + .unwrap = gensec_child_unwrap, + .session_key = gensec_child_session_key, + .session_info = gensec_child_session_info, + .want_feature = gensec_child_want_feature, + .have_feature = gensec_child_have_feature, + .expire_time = gensec_child_expire_time, + .final_auth_type = gensec_child_final_auth_type, + .enabled = true, + .priority = GENSEC_SPNEGO, + .glue = true, +}; + +_PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + ret = gensec_register(ctx, &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; +} diff --git a/auth/gensec/wscript_build b/auth/gensec/wscript_build new file mode 100644 index 0000000..1d8071d --- /dev/null +++ b/auth/gensec/wscript_build @@ -0,0 +1,37 @@ +#!/usr/bin/env python +bld.SAMBA_LIBRARY('gensec', + source='gensec.c gensec_start.c gensec_util.c', + autoproto='gensec_toplevel_proto.h', + public_deps='tevent-util samba-util samba-errors auth_system_session samba-modules gensec_util asn1util', + private_headers='gensec.h', + deps='com_err', + private_library=True, + ) + +bld.SAMBA_MODULE('gensec_spnego', + source='spnego.c', + subsystem='gensec', + init_function='gensec_spnego_init', + deps='asn1util samba-credentials SPNEGO_PARSE' + ) + +bld.SAMBA_MODULE('gensec_schannel', + source='schannel.c', + subsystem='gensec', + init_function='gensec_schannel_init', + deps='COMMON_SCHANNEL NDR_SCHANNEL samba-credentials auth_session GNUTLS_HELPERS' + ) + +bld.SAMBA_MODULE('gensec_ncalrpc', + source='ncalrpc.c', + subsystem='gensec', + init_function='gensec_ncalrpc_as_system_init', + deps='samba-util auth_session' + ) + +bld.SAMBA_MODULE('gensec_external', + source='external.c', + subsystem='gensec', + deps='samba-debug talloc tevent tevent-util', + init_function='gensec_external_init' + ) |