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
9 Copyright (C) Stefan Metzmacher 2004-2005
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include "system/kerberos.h"
29 #include "system/time.h"
30 #include "system/network.h"
31 #include "auth/kerberos/kerberos.h"
32 #include "librpc/gen_ndr/ndr_krb5pac.h"
33 #include "auth/auth.h"
35 enum GENSEC_KRB5_STATE {
36 GENSEC_KRB5_SERVER_START,
37 GENSEC_KRB5_CLIENT_START,
38 GENSEC_KRB5_CLIENT_MUTUAL_AUTH,
42 struct gensec_krb5_state {
43 DATA_BLOB session_key;
45 enum GENSEC_KRB5_STATE state_position;
46 struct smb_krb5_context *smb_krb5_context;
47 krb5_auth_context auth_context;
50 krb5_keyblock *keyblock;
54 static int gensec_krb5_destory(void *ptr)
56 struct gensec_krb5_state *gensec_krb5_state = ptr;
58 if (gensec_krb5_state->ticket.length) {
59 kerberos_free_data_contents(gensec_krb5_state->smb_krb5_context->krb5_context,
60 &gensec_krb5_state->ticket);
62 /* ccache freed in a child destructor */
64 krb5_free_keyblock(gensec_krb5_state->smb_krb5_context->krb5_context,
65 gensec_krb5_state->keyblock);
67 if (gensec_krb5_state->auth_context) {
68 krb5_auth_con_free(gensec_krb5_state->smb_krb5_context->krb5_context,
69 gensec_krb5_state->auth_context);
75 static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security)
77 struct gensec_krb5_state *gensec_krb5_state;
78 krb5_error_code ret = 0;
80 gensec_krb5_state = talloc(gensec_security, struct gensec_krb5_state);
81 if (!gensec_krb5_state) {
82 return NT_STATUS_NO_MEMORY;
85 gensec_security->private_data = gensec_krb5_state;
87 gensec_krb5_state->auth_context = NULL;
88 gensec_krb5_state->ccache = NULL;
89 ZERO_STRUCT(gensec_krb5_state->ticket);
90 ZERO_STRUCT(gensec_krb5_state->keyblock);
91 gensec_krb5_state->session_key = data_blob(NULL, 0);
92 gensec_krb5_state->pac = data_blob(NULL, 0);
94 talloc_set_destructor(gensec_krb5_state, gensec_krb5_destory);
96 ret = smb_krb5_init_context(gensec_krb5_state,
97 &gensec_krb5_state->smb_krb5_context);
99 DEBUG(1,("gensec_krb5_start: krb5_init_context failed (%s)\n",
100 error_message(ret)));
101 return NT_STATUS_INTERNAL_ERROR;
104 ret = krb5_auth_con_init(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->auth_context);
106 DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n",
107 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context,
108 ret, gensec_krb5_state)));
109 return NT_STATUS_INTERNAL_ERROR;
115 static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security)
118 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_SERVER_START;
131 static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security)
133 struct gensec_krb5_state *gensec_krb5_state;
136 const char *ccache_name;
138 const char *hostname = gensec_get_target_hostname(gensec_security);
140 DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n"));
141 return NT_STATUS_ACCESS_DENIED;
144 nt_status = gensec_krb5_start(gensec_security);
145 if (!NT_STATUS_IS_OK(nt_status)) {
149 gensec_krb5_state = gensec_security->private_data;
150 gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START;
152 /* TODO: This is effecivly a static/global variable...
154 TODO: If the user set a username, we should use an in-memory CCACHE (see below)
156 ret = krb5_cc_default(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->ccache);
158 DEBUG(1,("krb5_cc_default failed (%s)\n",
159 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
160 return NT_STATUS_INTERNAL_ERROR;
168 ret = krb5_mk_req(gensec_krb5_state->smb_krb5_context->krb5_context,
169 &gensec_krb5_state->auth_context,
170 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED,
171 gensec_get_target_service(gensec_security),
173 &in_data, gensec_krb5_state->ccache,
174 &gensec_krb5_state->ticket);
180 case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
181 DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n",
182 hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
183 return NT_STATUS_ACCESS_DENIED;
184 case KRB5KDC_ERR_PREAUTH_FAILED:
185 case KRB5KRB_AP_ERR_TKT_EXPIRED:
187 /* Too much clock skew - we will need to kinit to re-skew the clock */
188 case KRB5KRB_AP_ERR_SKEW:
189 case KRB5_KDCREP_SKEW:
191 DEBUG(3, ("kerberos (mk_req) failed: %s\n",
192 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
193 /* fall down to remaining code */
197 /* just don't print a message for these really ordinary messages */
198 case KRB5_FCC_NOFILE:
199 case KRB5_CC_NOTFOUND:
202 nt_status = kinit_to_ccache(gensec_krb5_state,
203 gensec_security->credentials,
204 gensec_krb5_state->smb_krb5_context,
205 &gensec_krb5_state->ccache,
208 if (!NT_STATUS_IS_OK(nt_status)) {
214 DEBUG(0, ("kerberos: %s\n",
215 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
216 return NT_STATUS_UNSUCCESSFUL;
223 * Check if the packet is one for this mechansim
225 * @param gensec_security GENSEC state
226 * @param in The request, as a DATA_BLOB
227 * @return Error, INVALID_PARAMETER if it's not a packet for us
228 * or NT_STATUS_OK if the packet is ok.
231 static NTSTATUS gensec_krb5_magic(struct gensec_security *gensec_security,
234 if (gensec_gssapi_check_oid(in, GENSEC_OID_KERBEROS5)) {
237 return NT_STATUS_INVALID_PARAMETER;
243 * Next state function for the Krb5 GENSEC mechanism
245 * @param gensec_krb5_state KRB5 State
246 * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
247 * @param in The request, as a DATA_BLOB
248 * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
249 * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent,
250 * or NT_STATUS_OK if the user is authenticated.
253 static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
254 TALLOC_CTX *out_mem_ctx,
255 const DATA_BLOB in, DATA_BLOB *out)
257 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
258 krb5_error_code ret = 0;
262 switch (gensec_krb5_state->state_position) {
263 case GENSEC_KRB5_CLIENT_START:
266 DEBUG(1,("ads_krb5_mk_req (request ticket) failed (%s)\n",
267 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
268 nt_status = NT_STATUS_LOGON_FAILURE;
270 DATA_BLOB unwrapped_out;
272 #ifndef GENSEC_SEND_UNWRAPPED_KRB5 /* This should be a switch for the torture code to set */
273 unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length);
275 /* wrap that up in a nice GSS-API wrapping */
276 *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ);
278 *out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length);
280 gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH;
281 nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
287 case GENSEC_KRB5_CLIENT_MUTUAL_AUTH:
290 krb5_ap_rep_enc_part *repl = NULL;
292 DATA_BLOB unwrapped_in;
294 if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
295 DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n"));
296 dump_data_pw("Mutual authentication message:\n", in.data, in.length);
297 return NT_STATUS_INVALID_PARAMETER;
299 /* TODO: check the tok_id */
301 inbuf.data = unwrapped_in.data;
302 inbuf.length = unwrapped_in.length;
303 ret = krb5_rd_rep(gensec_krb5_state->smb_krb5_context->krb5_context,
304 gensec_krb5_state->auth_context,
307 DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n",
308 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
309 dump_data_pw("Mutual authentication message:\n", inbuf.data, inbuf.length);
310 nt_status = NT_STATUS_ACCESS_DENIED;
312 *out = data_blob(NULL, 0);
313 nt_status = NT_STATUS_OK;
314 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
317 krb5_free_ap_rep_enc_part(gensec_krb5_state->smb_krb5_context->krb5_context, repl);
322 case GENSEC_KRB5_SERVER_START:
325 DATA_BLOB unwrapped_in;
326 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
330 *out = unwrapped_out;
331 return NT_STATUS_MORE_PROCESSING_REQUIRED;
334 /* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */
335 if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
336 nt_status = ads_verify_ticket(out_mem_ctx,
337 gensec_krb5_state->smb_krb5_context,
338 gensec_krb5_state->auth_context,
340 gensec_get_target_service(gensec_security), &in,
341 &principal, &pac, &unwrapped_out,
342 &gensec_krb5_state->keyblock);
344 /* TODO: check the tok_id */
345 nt_status = ads_verify_ticket(out_mem_ctx,
346 gensec_krb5_state->smb_krb5_context,
347 gensec_krb5_state->auth_context,
349 gensec_get_target_service(gensec_security),
351 &principal, &pac, &unwrapped_out,
352 &gensec_krb5_state->keyblock);
355 if (!NT_STATUS_IS_OK(nt_status)) {
360 gensec_krb5_state->pac = data_blob_talloc_reference(gensec_krb5_state, &pac);
363 if (NT_STATUS_IS_OK(nt_status)) {
364 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
365 /* wrap that up in a nice GSS-API wrapping */
366 #ifndef GENSEC_SEND_UNWRAPPED_KRB5
367 *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP);
369 *out = unwrapped_out;
371 gensec_krb5_state->peer_principal = talloc_steal(gensec_krb5_state, principal);
375 case GENSEC_KRB5_DONE:
379 return NT_STATUS_INVALID_PARAMETER;
382 static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
383 DATA_BLOB *session_key)
385 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
386 krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
387 krb5_auth_context auth_context = gensec_krb5_state->auth_context;
391 if (gensec_krb5_state->session_key.data) {
392 *session_key = gensec_krb5_state->session_key;
396 switch (gensec_security->gensec_role) {
398 err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
401 err = krb5_auth_con_getremotesubkey(context, auth_context, &skey);
404 if (err == 0 && skey != NULL) {
405 DEBUG(10, ("Got KRB5 session key of length %d\n", KRB5_KEY_LENGTH(skey)));
406 gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state,
407 KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
408 *session_key = gensec_krb5_state->session_key;
409 dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
411 krb5_free_keyblock(context, skey);
414 DEBUG(10, ("KRB5 error getting session key %d\n", err));
415 return NT_STATUS_NO_USER_SESSION_KEY;
419 static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security,
420 struct auth_session_info **_session_info)
423 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
424 struct auth_serversupplied_info *server_info = NULL;
425 struct auth_session_info *session_info = NULL;
426 struct PAC_LOGON_INFO *logon_info;
429 const char *account_name;
432 principal = talloc_strdup(gensec_krb5_state, gensec_krb5_state->peer_principal);
433 NT_STATUS_HAVE_NO_MEMORY(principal);
435 p = strchr(principal, '@');
443 account_name = principal;
445 /* decode and verify the pac */
446 nt_status = kerberos_decode_pac(gensec_krb5_state, &logon_info, gensec_krb5_state->pac,
447 gensec_krb5_state->smb_krb5_context, (gensec_krb5_state->keyblock));
449 /* IF we have the PAC - otherwise we need to get this
450 * data from elsewere - local ldb, or (TODO) lookup of some
453 * when heimdal can generate the PAC, we should fail if there's
457 if (NT_STATUS_IS_OK(nt_status)) {
458 union netr_Validation validation;
459 validation.sam3 = &logon_info->info3;
460 nt_status = make_server_info_netlogon_validation(gensec_krb5_state,
464 talloc_free(principal);
465 NT_STATUS_NOT_OK_RETURN(nt_status);
467 DATA_BLOB user_sess_key = data_blob(NULL, 0);
468 DATA_BLOB lm_sess_key = data_blob(NULL, 0);
469 /* TODO: should we pass the krb5 session key in here? */
470 nt_status = sam_get_server_info(gensec_krb5_state, account_name, realm,
471 user_sess_key, lm_sess_key,
473 talloc_free(principal);
474 NT_STATUS_NOT_OK_RETURN(nt_status);
477 /* references the server_info into the session_info */
478 nt_status = auth_generate_session_info(gensec_krb5_state, server_info, &session_info);
479 talloc_free(server_info);
480 NT_STATUS_NOT_OK_RETURN(nt_status);
482 nt_status = gensec_krb5_session_key(gensec_security, &session_info->session_key);
483 NT_STATUS_NOT_OK_RETURN(nt_status);
485 *_session_info = session_info;
490 static BOOL gensec_krb5_have_feature(struct gensec_security *gensec_security,
493 if (feature & GENSEC_FEATURE_SESSION_KEY) {
500 static const char *gensec_krb5_oids[] = {
501 GENSEC_OID_KERBEROS5,
502 GENSEC_OID_KERBEROS5_OLD,
506 static const struct gensec_security_ops gensec_krb5_security_ops = {
508 .auth_type = DCERPC_AUTH_TYPE_KRB5,
509 .oid = gensec_krb5_oids,
510 .client_start = gensec_krb5_client_start,
511 .server_start = gensec_krb5_server_start,
512 .magic = gensec_krb5_magic,
513 .update = gensec_krb5_update,
514 .session_key = gensec_krb5_session_key,
515 .session_info = gensec_krb5_session_info,
516 .have_feature = gensec_krb5_have_feature,
520 NTSTATUS gensec_krb5_init(void)
524 ret = gensec_register(&gensec_krb5_security_ops);
525 if (!NT_STATUS_IS_OK(ret)) {
526 DEBUG(0,("Failed to register '%s' gensec backend!\n",
527 gensec_krb5_security_ops.name));