diff options
-rw-r--r-- | source4/libcli/auth/gensec_krb5.c | 351 | ||||
-rw-r--r-- | source4/libcli/auth/kerberos_verify.c | 49 |
2 files changed, 360 insertions, 40 deletions
diff --git a/source4/libcli/auth/gensec_krb5.c b/source4/libcli/auth/gensec_krb5.c new file mode 100644 index 0000000000..2035a5bf9a --- /dev/null +++ b/source4/libcli/auth/gensec_krb5.c @@ -0,0 +1,351 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +enum GENSEC_KRB5_STATE { + GENSEC_KRB5_SERVER_START, + GENSEC_KRB5_CLIENT_START, + GENSEC_KRB5_CLIENT_MUTUAL_AUTH, + GENSEC_KRB5_DONE +}; + +struct gensec_krb5_state { + TALLOC_CTX *mem_ctx; + DATA_BLOB session_key; + DATA_BLOB pac; + enum GENSEC_KRB5_STATE state_position; + krb5_context krb5_context; + krb5_auth_context krb5_auth_context; +}; + +static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state; + krb5_error_code ret = 0; + + TALLOC_CTX *mem_ctx = talloc_init("gensec_krb5"); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + gensec_krb5_state = talloc_p(mem_ctx, struct gensec_krb5_state); + if (!gensec_krb5_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_krb5_state->mem_ctx = mem_ctx; + + gensec_security->private_data = gensec_krb5_state; + + initialize_krb5_error_table(); + gensec_krb5_state->krb5_context = NULL; + gensec_krb5_state->krb5_auth_context = NULL; + gensec_krb5_state->session_key = data_blob(NULL, 0); + + ret = krb5_init_context(&gensec_krb5_state->krb5_context); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_init_context failed (%s)\n", error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + if (lp_realm() && *lp_realm()) { + ret = krb5_set_default_realm(gensec_krb5_state->krb5_context, lp_realm()); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_set_default_realm failed (%s)\n", error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + } + + ret = krb5_auth_con_init(gensec_krb5_state->krb5_context, &gensec_krb5_state->krb5_auth_context); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n", error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct gensec_krb5_state *gensec_krb5_state; + + nt_status = gensec_krb5_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_krb5_state = gensec_security->private_data; + gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START; + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state; + + NTSTATUS nt_status; + nt_status = gensec_krb5_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_krb5_state = gensec_security->private_data; + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START; + + return NT_STATUS_OK; +} + +static void gensec_krb5_end(struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + + if (gensec_krb5_state->krb5_auth_context) { + krb5_auth_con_free(gensec_krb5_state->krb5_context, + gensec_krb5_state->krb5_auth_context); + } + + if (gensec_krb5_state->krb5_context) { + krb5_free_context(gensec_krb5_state->krb5_context); + } + + talloc_destroy(gensec_krb5_state->mem_ctx); + gensec_security->private_data = NULL; +} + + +/** + * Next state function for the Krb5 GENSEC mechanism + * + * @param gensec_krb5_state KRB5 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. + */ + +static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + krb5_error_code ret = 0; + DATA_BLOB pac; + NTSTATUS nt_status; + + switch (gensec_krb5_state->state_position) { + case GENSEC_KRB5_CLIENT_START: + { + krb5_data packet; + krb5_ccache ccdef = NULL; + +#if 0 /* When we get some way to input the time offset */ + if (time_offset != 0) { + krb5_set_real_time(context, time(NULL) + time_offset, 0); + } +#endif + + ret = krb5_cc_default(gensec_krb5_state->krb5_context, &ccdef); + if (ret) { + DEBUG(1,("krb5_cc_default failed (%s)\n", + error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + ret = ads_krb5_mk_req(gensec_krb5_state->krb5_context, + &gensec_krb5_state->krb5_auth_context, + AP_OPTS_USE_SUBKEY +#ifdef MUTUAL_AUTH + | AP_OPTS_MUTUAL_REQUIRED +#endif + , + gensec_security->target.principal, + ccdef, &packet); + if (ret) { + DEBUG(1,("ads_krb5_mk_req (request ticket) failed (%s)\n", + error_message(ret))); + nt_status = NT_STATUS_LOGON_FAILURE; + } else { + *out = data_blob_talloc(out_mem_ctx, packet.data, packet.length); + + /* Hmm, heimdal dooesn't have this - what's the correct call? */ +#ifdef HAVE_KRB5_FREE_DATA_CONTENTS + krb5_free_data_contents(gensec_krb5_state->krb5_context, &packet); +#endif +#ifdef MUTUAL_AUTH + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH; + nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; +#else + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + nt_status = NT_STATUS_OK; +#endif + } + + /* Removed by jra. They really need to fix their kerberos so we don't leak memory. + JERRY -- disabled since it causes heimdal 0.6.1rc3 to die + SuSE 9.1 Pro + */ +#if 0 /* redisabled by gd :) at least until any official heimdal version has it fixed. */ + krb5_cc_close(context, ccdef); +#endif + return nt_status; + } + + case GENSEC_KRB5_CLIENT_MUTUAL_AUTH: + { + krb5_data inbuf; + krb5_ap_rep_enc_part *repl = NULL; + inbuf.data = in.data; + inbuf.length = in.length; + ret = krb5_rd_rep(gensec_krb5_state->krb5_context, + gensec_krb5_state->krb5_auth_context, + &inbuf, &repl); + if (ret) { + DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n", + error_message(ret))); + dump_data_pw("Mutual authentication message:\n", in.data, in.length); + nt_status = NT_STATUS_ACCESS_DENIED; + } else { + *out = data_blob(NULL, 0); + nt_status = NT_STATUS_OK; + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + } + if (repl) { + krb5_free_ap_rep_enc_part(gensec_krb5_state->krb5_context, repl); + } + return nt_status; + } + + case GENSEC_KRB5_SERVER_START: + { + char *principal; + + nt_status = ads_verify_ticket(out_mem_ctx, + gensec_krb5_state->krb5_context, + gensec_krb5_state->krb5_auth_context, + lp_realm(), &in, + &principal, &pac, out); + gensec_krb5_state->pac = data_blob_talloc_steal(out_mem_ctx, gensec_krb5_state->mem_ctx, + &pac); + /* TODO: parse the pac */ + + if (NT_STATUS_IS_OK(nt_status)) { + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + } + SAFE_FREE(principal); + return nt_status; + } + case GENSEC_KRB5_DONE: + return NT_STATUS_OK; + } + + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) +{ + struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + krb5_context context = gensec_krb5_state->krb5_context; + krb5_auth_context auth_context = gensec_krb5_state->krb5_auth_context; + krb5_keyblock *skey; + krb5_error_code err; + + if (gensec_krb5_state->session_key.data) { + *session_key = gensec_krb5_state->session_key; + return NT_STATUS_OK; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey); + break; + case GENSEC_SERVER: + err = krb5_auth_con_getremotesubkey(context, auth_context, &skey); + break; + } + if (err == 0 && skey != NULL) { + DEBUG(10, ("Got KRB5 session key of length %d\n", KRB5_KEY_LENGTH(skey))); + gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state->mem_ctx, + KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey)); + *session_key = gensec_krb5_state->session_key; + dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length); + + krb5_free_keyblock(context, skey); + return NT_STATUS_OK; + } else { + DEBUG(10, ("KRB5 error getting session key %d\n", err)); + return NT_STATUS_NO_USER_SESSION_KEY; + } +} + + +static const struct gensec_security_ops gensec_krb5_security_ops = { + .name = "krb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = OID_KERBEROS5, + .client_start = gensec_krb5_client_start, + .server_start = gensec_krb5_server_start, + .update = gensec_krb5_update, + .session_key = gensec_krb5_session_key, + .end = gensec_krb5_end +}; + +static const struct gensec_security_ops gensec_ms_krb5_security_ops = { + .name = "ms_krb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = OID_KERBEROS5_OLD, + .client_start = gensec_krb5_client_start, + .server_start = gensec_krb5_server_start, + .update = gensec_krb5_update, + .session_key = gensec_krb5_session_key, + .end = gensec_krb5_end +}; + + +NTSTATUS gensec_krb5_init(void) +{ + NTSTATUS ret; + ret = register_backend("gensec", &gensec_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_krb5_security_ops.name)); + return ret; + } + + ret = register_backend("gensec", &gensec_ms_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_krb5_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/libcli/auth/kerberos_verify.c b/source4/libcli/auth/kerberos_verify.c index e93d3aa6e8..d24244e0d9 100644 --- a/source4/libcli/auth/kerberos_verify.c +++ b/source4/libcli/auth/kerberos_verify.c @@ -202,14 +202,14 @@ static BOOL ads_secrets_verify_ticket(krb5_context context, krb5_auth_context au authorization_data if available. ***********************************************************************************/ -NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, +NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx, + krb5_context context, + krb5_auth_context auth_context, + const char *realm, const DATA_BLOB *ticket, char **principal, DATA_BLOB *auth_data, - DATA_BLOB *ap_rep, - DATA_BLOB *session_key) + DATA_BLOB *ap_rep) { NTSTATUS sret = NT_STATUS_LOGON_FAILURE; - krb5_context context = NULL; - krb5_auth_context auth_context = NULL; krb5_data packet; krb5_ticket *tkt = NULL; krb5_rcache rcache = NULL; @@ -219,38 +219,18 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, char *host_princ_s = NULL; BOOL got_replay_mutex = False; - fstring myname; + char *myname; BOOL auth_ok = False; ZERO_STRUCT(packet); ZERO_STRUCTP(auth_data); ZERO_STRUCTP(ap_rep); - ZERO_STRUCTP(session_key); - - initialize_krb5_error_table(); - ret = krb5_init_context(&context); - if (ret) { - DEBUG(1,("ads_verify_ticket: krb5_init_context failed (%s)\n", error_message(ret))); - return NT_STATUS_LOGON_FAILURE; - } - - ret = krb5_set_default_realm(context, realm); - if (ret) { - DEBUG(1,("ads_verify_ticket: krb5_set_default_realm failed (%s)\n", error_message(ret))); - goto out; - } /* This whole process is far more complex than I would like. We have to go through all this to allow us to store the secret internally, instead of using /etc/krb5.keytab */ - ret = krb5_auth_con_init(context, &auth_context); - if (ret) { - DEBUG(1,("ads_verify_ticket: krb5_auth_con_init failed (%s)\n", error_message(ret))); - goto out; - } - - name_to_fqdn(myname, global_myname()); + myname = name_to_fqdn(mem_ctx, global_myname()); strlower_m(myname); asprintf(&host_princ_s, "host/%s@%s", myname, lp_realm()); ret = krb5_parse_name(context, host_princ_s, &host_princ); @@ -309,18 +289,15 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, goto out; } - *ap_rep = data_blob(packet.data, packet.length); + *ap_rep = data_blob_talloc(mem_ctx, packet.data, packet.length); SAFE_FREE(packet.data); packet.length = 0; - get_krb5_smb_session_key(context, auth_context, session_key, True); - dump_data_pw("SMB session key (from ticket)\n", session_key->data, session_key->length); - #if 0 file_save("/tmp/ticket.dat", ticket->data, ticket->length); #endif - get_auth_data_from_tkt(auth_data, tkt); + get_auth_data_from_tkt(mem_ctx, auth_data, tkt); #if 0 if (tkt->enc_part2) { @@ -364,14 +341,6 @@ NTSTATUS ads_verify_ticket(const char *realm, const DATA_BLOB *ticket, SAFE_FREE(host_princ_s); - if (auth_context) { - krb5_auth_con_free(context, auth_context); - } - - if (context) { - krb5_free_context(context); - } - return sret; } |