2 Unix SMB/CIFS implementation.
4 Kerberos backend for GENSEC
6 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
7 Copyright (C) Andrew Tridgell 2001
8 Copyright (C) Luke Howard 2002-2003
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 #define DBGC_CLASS DBGC_AUTH
31 enum GENSEC_KRB5_STATE {
32 GENSEC_KRB5_SERVER_START,
33 GENSEC_KRB5_CLIENT_START,
34 GENSEC_KRB5_CLIENT_MUTUAL_AUTH,
38 struct gensec_krb5_state {
40 DATA_BLOB session_key;
42 enum GENSEC_KRB5_STATE state_position;
43 krb5_context krb5_context;
44 krb5_auth_context krb5_auth_context;
45 krb5_ccache krb5_ccache;
49 static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security)
51 struct gensec_krb5_state *gensec_krb5_state;
52 krb5_error_code ret = 0;
54 TALLOC_CTX *mem_ctx = talloc_init("gensec_krb5");
56 return NT_STATUS_NO_MEMORY;
59 gensec_krb5_state = talloc_p(mem_ctx, struct gensec_krb5_state);
60 if (!gensec_krb5_state) {
61 return NT_STATUS_NO_MEMORY;
64 gensec_krb5_state->mem_ctx = mem_ctx;
66 gensec_security->private_data = gensec_krb5_state;
68 initialize_krb5_error_table();
69 gensec_krb5_state->krb5_context = NULL;
70 gensec_krb5_state->krb5_auth_context = NULL;
71 gensec_krb5_state->krb5_ccache = NULL;
72 ZERO_STRUCT(gensec_krb5_state->ticket);
73 gensec_krb5_state->session_key = data_blob(NULL, 0);
75 ret = krb5_init_context(&gensec_krb5_state->krb5_context);
77 DEBUG(1,("gensec_krb5_start: krb5_init_context failed (%s)\n", error_message(ret)));
78 return NT_STATUS_INTERNAL_ERROR;
81 if (lp_realm() && *lp_realm()) {
82 ret = krb5_set_default_realm(gensec_krb5_state->krb5_context, lp_realm());
84 DEBUG(1,("gensec_krb5_start: krb5_set_default_realm failed (%s)\n", error_message(ret)));
85 return NT_STATUS_INTERNAL_ERROR;
89 ret = krb5_auth_con_init(gensec_krb5_state->krb5_context, &gensec_krb5_state->krb5_auth_context);
91 DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n", error_message(ret)));
92 return NT_STATUS_INTERNAL_ERROR;
98 static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security)
101 struct gensec_krb5_state *gensec_krb5_state;
103 nt_status = gensec_krb5_start(gensec_security);
104 if (!NT_STATUS_IS_OK(nt_status)) {
108 gensec_krb5_state = gensec_security->private_data;
109 gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START;
114 static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security)
116 struct gensec_krb5_state *gensec_krb5_state;
120 nt_status = gensec_krb5_start(gensec_security);
121 if (!NT_STATUS_IS_OK(nt_status)) {
125 gensec_krb5_state = gensec_security->private_data;
126 gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START;
128 /* TODO: This is effecivly a static/global variable... */
129 ret = krb5_cc_default(gensec_krb5_state->krb5_context, &gensec_krb5_state->krb5_ccache);
131 DEBUG(1,("krb5_cc_default failed (%s)\n",
132 error_message(ret)));
133 return NT_STATUS_INTERNAL_ERROR;
137 if (gensec_security->target.principal) {
138 DEBUG(5, ("Finding ticket for target [%s]\n", gensec_security->target.principal));
139 ret = ads_krb5_mk_req(gensec_krb5_state->krb5_context,
140 &gensec_krb5_state->krb5_auth_context,
141 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED,
142 gensec_security->target.principal,
143 gensec_krb5_state->krb5_ccache,
144 &gensec_krb5_state->ticket);
146 DEBUG(1,("ads_krb5_mk_req failed (%s)\n",
147 error_message(ret)));
152 const char *hostname = gensec_get_target_hostname(gensec_security);
154 DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n"));
155 return NT_STATUS_ACCESS_DENIED;
157 ret = krb5_mk_req(gensec_krb5_state->krb5_context,
158 &gensec_krb5_state->krb5_auth_context,
159 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED,
160 gensec_get_target_service(gensec_security),
162 &in_data, gensec_krb5_state->krb5_ccache,
163 &gensec_krb5_state->ticket);
165 DEBUG(1,("krb5_mk_req failed (%s)\n",
166 error_message(ret)));
173 case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
174 DEBUG(3, ("Server is not registered with our KDC: %s\n",
175 error_message(ret)));
176 return NT_STATUS_ACCESS_DENIED;
177 case KRB5KDC_ERR_PREAUTH_FAILED:
178 case KRB5KRB_AP_ERR_TKT_EXPIRED:
181 DEBUG(3, ("kerberos: %s\n",
182 error_message(ret)));
183 /* fall down to remaining code */
185 /* just don't print a message for these really ordinary messages */
186 case KRB5_FCC_NOFILE:
187 case KRB5_CC_NOTFOUND:
191 nt_status = gensec_get_password(gensec_security,
192 gensec_security->mem_ctx,
194 if (!NT_STATUS_IS_OK(nt_status)) {
198 ret = kerberos_kinit_password_cc(gensec_krb5_state->krb5_context, gensec_krb5_state->krb5_ccache,
199 gensec_get_client_principal(gensec_security, gensec_security->mem_ctx),
200 password, NULL, &kdc_time);
202 /* cope with ticket being in the future due to clock skew */
203 if ((unsigned)kdc_time > time(NULL)) {
204 time_t t = time(NULL);
205 int time_offset =(unsigned)kdc_time-t;
206 DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset));
207 krb5_set_real_time(gensec_krb5_state->krb5_context, t + time_offset + 1, 0);
211 DEBUG(1,("kinit failed (%s)\n",
212 error_message(ret)));
213 return NT_STATUS_WRONG_PASSWORD;
218 DEBUG(0, ("kerberos: %s\n",
219 error_message(ret)));
220 return NT_STATUS_UNSUCCESSFUL;
225 static void gensec_krb5_end(struct gensec_security *gensec_security)
227 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
229 if (gensec_krb5_state->ticket.length) {
230 /* Hmm, heimdal dooesn't have this - what's the correct call? */
231 #ifdef HAVE_KRB5_FREE_DATA_CONTENTS
232 krb5_free_data_contents(gensec_krb5_state->krb5_context, &gensec_krb5_state->ticket);
235 if (gensec_krb5_state->krb5_ccache) {
236 /* Removed by jra. They really need to fix their kerberos so we don't leak memory.
237 JERRY -- disabled since it causes heimdal 0.6.1rc3 to die
240 #if 0 /* redisabled by gd :) at least until any official heimdal version has it fixed. */
241 krb5_cc_close(context, gensec_krb5_state->krb5_ccache);
245 if (gensec_krb5_state->krb5_auth_context) {
246 krb5_auth_con_free(gensec_krb5_state->krb5_context,
247 gensec_krb5_state->krb5_auth_context);
250 if (gensec_krb5_state->krb5_context) {
251 krb5_free_context(gensec_krb5_state->krb5_context);
254 talloc_destroy(gensec_krb5_state->mem_ctx);
255 gensec_security->private_data = NULL;
260 * Next state function for the Krb5 GENSEC mechanism
262 * @param gensec_krb5_state KRB5 State
263 * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
264 * @param in The request, as a DATA_BLOB
265 * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
266 * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent,
267 * or NT_STATUS_OK if the user is authenticated.
270 static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
271 const DATA_BLOB in, DATA_BLOB *out)
273 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
274 krb5_error_code ret = 0;
278 switch (gensec_krb5_state->state_position) {
279 case GENSEC_KRB5_CLIENT_START:
282 DEBUG(1,("ads_krb5_mk_req (request ticket) failed (%s)\n",
283 error_message(ret)));
284 nt_status = NT_STATUS_LOGON_FAILURE;
286 DATA_BLOB unwrapped_out;
287 unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length);
289 /* wrap that up in a nice GSS-API wrapping */
290 *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ);
292 gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH;
293 nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
299 case GENSEC_KRB5_CLIENT_MUTUAL_AUTH:
302 krb5_ap_rep_enc_part *repl = NULL;
304 DATA_BLOB unwrapped_in;
306 if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
307 return NT_STATUS_INVALID_PARAMETER;
309 /* TODO: check the tok_id */
311 inbuf.data = unwrapped_in.data;
312 inbuf.length = unwrapped_in.length;
313 ret = krb5_rd_rep(gensec_krb5_state->krb5_context,
314 gensec_krb5_state->krb5_auth_context,
317 DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n",
318 error_message(ret)));
319 dump_data_pw("Mutual authentication message:\n", in.data, in.length);
320 nt_status = NT_STATUS_ACCESS_DENIED;
322 *out = data_blob(NULL, 0);
323 nt_status = NT_STATUS_OK;
324 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
327 krb5_free_ap_rep_enc_part(gensec_krb5_state->krb5_context, repl);
332 case GENSEC_KRB5_SERVER_START:
335 DATA_BLOB unwrapped_in;
336 DATA_BLOB unwrapped_out;
339 /* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */
340 if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
341 nt_status = ads_verify_ticket(out_mem_ctx,
342 gensec_krb5_state->krb5_context,
343 gensec_krb5_state->krb5_auth_context,
345 &principal, &pac, &unwrapped_out);
347 /* TODO: check the tok_id */
348 nt_status = ads_verify_ticket(out_mem_ctx,
349 gensec_krb5_state->krb5_context,
350 gensec_krb5_state->krb5_auth_context,
351 lp_realm(), &unwrapped_in,
352 &principal, &pac, &unwrapped_out);
355 gensec_krb5_state->pac = data_blob_talloc_steal(out_mem_ctx, gensec_krb5_state->mem_ctx,
357 /* TODO: parse the pac */
359 if (NT_STATUS_IS_OK(nt_status)) {
360 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
361 /* wrap that up in a nice GSS-API wrapping */
362 *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP);
364 SAFE_FREE(principal);
367 case GENSEC_KRB5_DONE:
371 return NT_STATUS_INVALID_PARAMETER;
374 static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
375 DATA_BLOB *session_key)
377 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
378 krb5_context context = gensec_krb5_state->krb5_context;
379 krb5_auth_context auth_context = gensec_krb5_state->krb5_auth_context;
383 if (gensec_krb5_state->session_key.data) {
384 *session_key = gensec_krb5_state->session_key;
388 switch (gensec_security->gensec_role) {
390 err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
393 err = krb5_auth_con_getremotesubkey(context, auth_context, &skey);
396 if (err == 0 && skey != NULL) {
397 DEBUG(10, ("Got KRB5 session key of length %d\n", KRB5_KEY_LENGTH(skey)));
398 gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state->mem_ctx,
399 KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
400 *session_key = gensec_krb5_state->session_key;
401 dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
403 krb5_free_keyblock(context, skey);
406 DEBUG(10, ("KRB5 error getting session key %d\n", err));
407 return NT_STATUS_NO_USER_SESSION_KEY;
412 static const struct gensec_security_ops gensec_krb5_security_ops = {
414 .auth_type = DCERPC_AUTH_TYPE_KRB5,
415 .oid = OID_KERBEROS5,
416 .client_start = gensec_krb5_client_start,
417 .server_start = gensec_krb5_server_start,
418 .update = gensec_krb5_update,
419 .session_key = gensec_krb5_session_key,
420 .end = gensec_krb5_end
423 static const struct gensec_security_ops gensec_ms_krb5_security_ops = {
425 .auth_type = DCERPC_AUTH_TYPE_KRB5,
426 .oid = OID_KERBEROS5_OLD,
427 .client_start = gensec_krb5_client_start,
428 .server_start = gensec_krb5_server_start,
429 .update = gensec_krb5_update,
430 .session_key = gensec_krb5_session_key,
431 .end = gensec_krb5_end
435 NTSTATUS gensec_krb5_init(void)
438 ret = register_backend("gensec", &gensec_krb5_security_ops);
439 if (!NT_STATUS_IS_OK(ret)) {
440 DEBUG(0,("Failed to register '%s' gensec backend!\n",
441 gensec_krb5_security_ops.name));
445 ret = register_backend("gensec", &gensec_ms_krb5_security_ops);
446 if (!NT_STATUS_IS_OK(ret)) {
447 DEBUG(0,("Failed to register '%s' gensec backend!\n",
448 gensec_krb5_security_ops.name));