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_INVALID_PARAMETER;
143 if (is_ipaddress(hostname)) {
144 DEBUG(2, ("Cannot do GSSAPI to a IP address"));
145 return NT_STATUS_INVALID_PARAMETER;
149 nt_status = gensec_krb5_start(gensec_security);
150 if (!NT_STATUS_IS_OK(nt_status)) {
154 gensec_krb5_state = gensec_security->private_data;
155 gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START;
157 /* TODO: This is effecivly a static/global variable...
159 TODO: If the user set a username, we should use an in-memory CCACHE (see below)
161 ret = krb5_cc_default(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->ccache);
163 DEBUG(1,("krb5_cc_default failed (%s)\n",
164 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
165 return NT_STATUS_INTERNAL_ERROR;
173 ret = krb5_mk_req(gensec_krb5_state->smb_krb5_context->krb5_context,
174 &gensec_krb5_state->auth_context,
175 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED,
176 gensec_get_target_service(gensec_security),
178 &in_data, gensec_krb5_state->ccache,
179 &gensec_krb5_state->ticket);
185 case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
186 DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n",
187 hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
188 return NT_STATUS_ACCESS_DENIED;
189 case KRB5KDC_ERR_PREAUTH_FAILED:
190 case KRB5KRB_AP_ERR_TKT_EXPIRED:
192 /* Too much clock skew - we will need to kinit to re-skew the clock */
193 case KRB5KRB_AP_ERR_SKEW:
194 case KRB5_KDCREP_SKEW:
196 DEBUG(3, ("kerberos (mk_req) failed: %s\n",
197 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
198 /* fall down to remaining code */
202 /* just don't print a message for these really ordinary messages */
203 case KRB5_FCC_NOFILE:
204 case KRB5_CC_NOTFOUND:
207 nt_status = kinit_to_ccache(gensec_krb5_state,
208 gensec_security->credentials,
209 gensec_krb5_state->smb_krb5_context,
210 &gensec_krb5_state->ccache,
213 if (!NT_STATUS_IS_OK(nt_status)) {
219 DEBUG(0, ("kerberos: %s\n",
220 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
221 return NT_STATUS_UNSUCCESSFUL;
228 * Check if the packet is one for this mechansim
230 * @param gensec_security GENSEC state
231 * @param in The request, as a DATA_BLOB
232 * @return Error, INVALID_PARAMETER if it's not a packet for us
233 * or NT_STATUS_OK if the packet is ok.
236 static NTSTATUS gensec_krb5_magic(struct gensec_security *gensec_security,
239 if (gensec_gssapi_check_oid(in, GENSEC_OID_KERBEROS5)) {
242 return NT_STATUS_INVALID_PARAMETER;
248 * Next state function for the Krb5 GENSEC mechanism
250 * @param gensec_krb5_state KRB5 State
251 * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
252 * @param in The request, as a DATA_BLOB
253 * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
254 * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent,
255 * or NT_STATUS_OK if the user is authenticated.
258 static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security,
259 TALLOC_CTX *out_mem_ctx,
260 const DATA_BLOB in, DATA_BLOB *out)
262 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
263 krb5_error_code ret = 0;
267 switch (gensec_krb5_state->state_position) {
268 case GENSEC_KRB5_CLIENT_START:
271 DEBUG(1,("ads_krb5_mk_req (request ticket) failed (%s)\n",
272 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
273 nt_status = NT_STATUS_LOGON_FAILURE;
275 DATA_BLOB unwrapped_out;
277 #ifndef GENSEC_SEND_UNWRAPPED_KRB5 /* This should be a switch for the torture code to set */
278 unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length);
280 /* wrap that up in a nice GSS-API wrapping */
281 *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ);
283 *out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length);
285 gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH;
286 nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
292 case GENSEC_KRB5_CLIENT_MUTUAL_AUTH:
295 krb5_ap_rep_enc_part *repl = NULL;
297 DATA_BLOB unwrapped_in;
299 if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
300 DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n"));
301 dump_data_pw("Mutual authentication message:\n", in.data, in.length);
302 return NT_STATUS_INVALID_PARAMETER;
304 /* TODO: check the tok_id */
306 inbuf.data = unwrapped_in.data;
307 inbuf.length = unwrapped_in.length;
308 ret = krb5_rd_rep(gensec_krb5_state->smb_krb5_context->krb5_context,
309 gensec_krb5_state->auth_context,
312 DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n",
313 smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
314 dump_data_pw("Mutual authentication message:\n", inbuf.data, inbuf.length);
315 nt_status = NT_STATUS_ACCESS_DENIED;
317 *out = data_blob(NULL, 0);
318 nt_status = NT_STATUS_OK;
319 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
322 krb5_free_ap_rep_enc_part(gensec_krb5_state->smb_krb5_context->krb5_context, repl);
327 case GENSEC_KRB5_SERVER_START:
330 DATA_BLOB unwrapped_in;
331 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
335 *out = unwrapped_out;
336 return NT_STATUS_MORE_PROCESSING_REQUIRED;
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->smb_krb5_context,
343 gensec_krb5_state->auth_context,
345 gensec_get_target_service(gensec_security), &in,
346 &principal, &pac, &unwrapped_out,
347 &gensec_krb5_state->keyblock);
349 /* TODO: check the tok_id */
350 nt_status = ads_verify_ticket(out_mem_ctx,
351 gensec_krb5_state->smb_krb5_context,
352 gensec_krb5_state->auth_context,
354 gensec_get_target_service(gensec_security),
356 &principal, &pac, &unwrapped_out,
357 &gensec_krb5_state->keyblock);
360 if (!NT_STATUS_IS_OK(nt_status)) {
365 gensec_krb5_state->pac = data_blob_talloc_reference(gensec_krb5_state, &pac);
368 if (NT_STATUS_IS_OK(nt_status)) {
369 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
370 /* wrap that up in a nice GSS-API wrapping */
371 #ifndef GENSEC_SEND_UNWRAPPED_KRB5
372 *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP);
374 *out = unwrapped_out;
376 gensec_krb5_state->peer_principal = talloc_steal(gensec_krb5_state, principal);
380 case GENSEC_KRB5_DONE:
384 return NT_STATUS_INVALID_PARAMETER;
387 static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security,
388 DATA_BLOB *session_key)
390 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
391 krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
392 krb5_auth_context auth_context = gensec_krb5_state->auth_context;
394 krb5_error_code err = -1;
396 if (gensec_krb5_state->session_key.data) {
397 *session_key = gensec_krb5_state->session_key;
401 switch (gensec_security->gensec_role) {
403 err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
406 err = krb5_auth_con_getremotesubkey(context, auth_context, &skey);
409 if (err == 0 && skey != NULL) {
410 DEBUG(10, ("Got KRB5 session key of length %d\n",
411 (int)KRB5_KEY_LENGTH(skey)));
412 gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state,
413 KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
414 *session_key = gensec_krb5_state->session_key;
415 dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
417 krb5_free_keyblock(context, skey);
420 DEBUG(10, ("KRB5 error getting session key %d\n", err));
421 return NT_STATUS_NO_USER_SESSION_KEY;
425 static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security,
426 struct auth_session_info **_session_info)
429 struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data;
430 struct auth_serversupplied_info *server_info = NULL;
431 struct auth_session_info *session_info = NULL;
432 struct PAC_LOGON_INFO *logon_info;
435 const char *account_name;
438 principal = talloc_strdup(gensec_krb5_state, gensec_krb5_state->peer_principal);
439 NT_STATUS_HAVE_NO_MEMORY(principal);
441 p = strchr(principal, '@');
449 account_name = principal;
451 /* decode and verify the pac */
452 nt_status = kerberos_pac_logon_info(gensec_krb5_state, &logon_info, gensec_krb5_state->pac,
453 gensec_krb5_state->smb_krb5_context->krb5_context,
454 NULL, gensec_krb5_state->keyblock);
456 /* IF we have the PAC - otherwise we need to get this
457 * data from elsewere - local ldb, or (TODO) lookup of some
460 * when heimdal can generate the PAC, we should fail if there's
464 if (NT_STATUS_IS_OK(nt_status)) {
465 union netr_Validation validation;
466 validation.sam3 = &logon_info->info3;
467 nt_status = make_server_info_netlogon_validation(gensec_krb5_state,
471 talloc_free(principal);
472 NT_STATUS_NOT_OK_RETURN(nt_status);
474 DATA_BLOB user_sess_key = data_blob(NULL, 0);
475 DATA_BLOB lm_sess_key = data_blob(NULL, 0);
476 /* TODO: should we pass the krb5 session key in here? */
477 nt_status = sam_get_server_info(gensec_krb5_state, account_name, realm,
478 user_sess_key, lm_sess_key,
480 talloc_free(principal);
481 NT_STATUS_NOT_OK_RETURN(nt_status);
484 /* references the server_info into the session_info */
485 nt_status = auth_generate_session_info(gensec_krb5_state, server_info, &session_info);
486 talloc_free(server_info);
487 NT_STATUS_NOT_OK_RETURN(nt_status);
489 nt_status = gensec_krb5_session_key(gensec_security, &session_info->session_key);
490 NT_STATUS_NOT_OK_RETURN(nt_status);
492 *_session_info = session_info;
497 static BOOL gensec_krb5_have_feature(struct gensec_security *gensec_security,
500 if (feature & GENSEC_FEATURE_SESSION_KEY) {
507 static const char *gensec_krb5_oids[] = {
508 GENSEC_OID_KERBEROS5,
509 GENSEC_OID_KERBEROS5_OLD,
513 static const struct gensec_security_ops gensec_krb5_security_ops = {
515 .auth_type = DCERPC_AUTH_TYPE_KRB5,
516 .oid = gensec_krb5_oids,
517 .client_start = gensec_krb5_client_start,
518 .server_start = gensec_krb5_server_start,
519 .magic = gensec_krb5_magic,
520 .update = gensec_krb5_update,
521 .session_key = gensec_krb5_session_key,
522 .session_info = gensec_krb5_session_info,
523 .have_feature = gensec_krb5_have_feature,
527 NTSTATUS gensec_krb5_init(void)
531 ret = gensec_register(&gensec_krb5_security_ops);
532 if (!NT_STATUS_IS_OK(ret)) {
533 DEBUG(0,("Failed to register '%s' gensec backend!\n",
534 gensec_krb5_security_ops.name));