4 * Methods for using Kerberos authentication and signing with SIPE,
5 * implemented with reference to
6 * - MS-SIP: http://msdn.microsoft.com/en-us/library/cc431510.aspx
8 * Authentication is known to be working, but the signing does not work at
13 * Copyright (C) 2008 Novell, Inc.
14 * Copyright (C) 2008 Andrew Rechenberg
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
38 #include <et/com_err.h>
45 #include <gssapi/gssapi_generic.h>
46 #include <gssapi/gssapi_krb5.h>
51 void log_krb5_error(krb5_context ctx
, krb5_error_code err
, char * msg
)
53 const char * err_msg
= krb5_get_error_message(ctx
, err
);
54 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "%s; error: %s\n", msg
, err_msg
);
55 krb5_free_error_message(ctx
, err_msg
);
58 /* Taken from krb5's src/tests/gss-threads/gss-misc.c */
60 static void display_status_1(m
, code
, type
)
65 OM_uint32 maj_stat
, min_stat
;
71 maj_stat
= gss_display_status(&min_stat
, code
,
74 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "GSS-API error %s: %s\n", m
, (char *)msg
.value
);
75 (void) gss_release_buffer(&min_stat
, &msg
);
83 * Function: display_status
85 * Purpose: displays GSS-API messages
89 * msg a string to be displayed with the message
90 * maj_stat the GSS-API major status code
91 * min_stat the GSS-API minor status code
95 * The GSS-API messages associated with maj_stat and min_stat are
96 * displayed on stderr, each preceeded by "GSS-API error <msg>: " and
97 * followed by a newline.
99 void display_status(msg
, maj_stat
, min_stat
)
104 display_status_1(msg
, maj_stat
, GSS_C_GSS_CODE
);
105 display_status_1(msg
, min_stat
, GSS_C_MECH_CODE
);
108 /* End taken from krb5's src/tests/gss-threads/gss-misc.c */
111 purple_krb5_init_auth(struct sipe_krb5_auth
* auth
,
112 const char *authuser
,
115 const char *hostname
,
118 auth
->authuser
= authuser
;
120 auth
->password
= password
;
121 auth
->hostname
= hostname
;
122 auth
->service
= service
;
125 auth
->base64_token
= NULL
;
126 auth
->gss_context
= NULL
;
128 purple_krb5_gen_auth_token(auth
);
132 purple_krb5_gen_auth_token(struct sipe_krb5_auth
* auth
)
135 * Ideally we will check to see if a Kerberos ticket already exists in the
136 * default Kerberos credential cache. Right now we re-do everything all
139 * XXX FIXME - Check for ticket already and create a KRB_AP_REQ from creds
143 krb5_context context
;
144 krb5_principal principal
;
145 krb5_creds credentials
;
147 krb5_error_code retval
;
150 memset(&credentials
, 0, sizeof(krb5_creds
));
152 // Initialize the KRB context
153 if (retval
= krb5_init_context(&context
)) {
154 log_krb5_error(context
, retval
, "krb5_init_context");
158 // Build a Kerberos principal and get a TGT if there isn't one already
159 if (retval
= krb5_build_principal(context
, &principal
, strlen(auth
->realm
), auth
->realm
, auth
->authuser
, NULL
)) {
160 log_krb5_error(context
, retval
, "krb5_build_principal");
164 if (retval
= krb5_get_init_creds_password(context
, &credentials
, principal
, auth
->password
, NULL
, NULL
, 0, NULL
, NULL
)) {
165 log_krb5_error(context
, retval
, "krb5_get_init_creds_password");
169 // Initialize default credentials cache
170 if (retval
= krb5_cc_default(context
, &ccdef
)) {
171 log_krb5_error(context
, retval
, "krb5_cc_default");
175 if (credentials
.client
== NULL
) {
176 log_krb5_error(context
, retval
, "credentials.client == NULL");
180 if (retval
= krb5_cc_initialize(context
, ccdef
, credentials
.client
)) {
181 log_krb5_error(context
, retval
, "krb5_cc_initialize");
186 if (retval
= krb5_cc_store_cred(context
, ccdef
, &credentials
)) {
187 log_krb5_error(context
, retval
, "krb5_cc_store_cred");
191 // Prepare the AP-REQ
192 krb5_data inbuf
, ap_req
;
193 krb5_auth_context auth_context
= NULL
;
195 inbuf
.data
= (char *)auth
->hostname
;
196 inbuf
.length
= strlen((char *)auth
->hostname
);
198 if ((retval
= krb5_mk_req(context
, &auth_context
, AP_OPTS_MUTUAL_REQUIRED
, (char *)auth
->service
, (char *)auth
->hostname
, &inbuf
, ccdef
, &ap_req
))) {
199 log_krb5_error(context
, retval
, "krb5_mk_req");
203 auth
->token
= (char *)ap_req
.data
;
204 auth
->base64_token
= purple_base64_encode(auth
->token
, ap_req
.length
);
206 // Initialize the GSS layer
207 initialize_gss(auth
, &(credentials
.server
));
208 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "generated krb5 auth token and initialized GSS context\n");
212 krb5_free_principal(context
, principal
);
215 krb5_free_context(context
);
218 int initialize_gss(struct sipe_krb5_auth
* auth
, krb5_principal
*principal
)
220 OM_uint32
* ret_flags
;
221 gss_buffer_desc send_tok
, recv_tok
, *token_ptr
;
222 gss_name_t target_name
;
223 OM_uint32 maj_stat
, min_stat
, init_sec_min_stat
;
227 * Import the name into target_name. Use send_tok to save
228 * local variable space.
230 //char * service_name = "sip";
231 //char * service_name = "sip/ocs1.ocs.provo.novell.com";
232 char * service_name
= "sip@ocs1.ocs.provo.novell.com";
233 send_tok
.value
= service_name
;
234 send_tok
.length
= strlen(service_name
);
235 //maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NT_USER_NAME, &target_name);
236 maj_stat
= gss_import_name(&min_stat
, &send_tok
, GSS_C_NT_HOSTBASED_SERVICE
, &target_name
);
238 if (maj_stat
!= GSS_S_COMPLETE
) {
239 display_status("parsing name", maj_stat
, min_stat
);
244 if (send_token(s, TOKEN_NOOP|TOKEN_CONTEXT_NEXT, empty_token) < 0) {
245 (void) gss_release_name(&min_stat, &target_name);
251 * Perform the context-establishement loop.
253 * On each pass through the loop, token_ptr points to the token
254 * to send to the server (or GSS_C_NO_BUFFER on the first pass).
255 * Every generated token is stored in send_tok which is then
256 * transmitted to the server; every received token is stored in
257 * recv_tok, which token_ptr is then set to, to be processed by
258 * the next call to gss_init_sec_context.
260 * GSS-API guarantees that send_tok's length will be non-zero
261 * if and only if the server is expecting another token from us,
262 * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if
263 * and only if the server has another token to send us.
266 token_ptr
= GSS_C_NO_BUFFER
;
267 gss_ctx_id_t gss_no_context
= GSS_C_NO_CONTEXT
;
268 auth
->gss_context
= &gss_no_context
;
269 //OM_uint32 req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG;
270 //OM_uint32 req_flags = 0; //GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;// | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG;
271 OM_uint32 req_flags
= GSS_C_MUTUAL_FLAG
| GSS_C_REPLAY_FLAG
;// | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG;
274 maj_stat
= gss_init_sec_context(&init_sec_min_stat
,
280 0, // 0 = default = 2 hrs
281 GSS_C_NO_CHANNEL_BINDINGS
,
283 NULL
, /* ignore mech type */
285 ret_flags
, /* output */
286 NULL
); /* ignore time_rec */
288 if (token_ptr
!= GSS_C_NO_BUFFER
) {
289 free(recv_tok
.value
);
292 if (send_tok
.length
!= 0) {
293 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "send_tok.length != 0, but not sending..\n");
295 printf("Sending init_sec_context token (size=%d)...",
296 (int) send_tok.length);
298 /*if (send_token(s, v1_format?0:TOKEN_CONTEXT, &send_tok) < 0) {
299 (void) gss_release_buffer(&min_stat, &send_tok);
300 (void) gss_release_name(&min_stat, &target_name);
301 if (*gss_context != GSS_C_NO_CONTEXT) {
302 gss_delete_sec_context(&min_stat, gss_context,
304 *gss_context = GSS_C_NO_CONTEXT;
309 //purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "send_tok.length == 0, nothing to send\n");
312 (void) gss_release_buffer(&min_stat
, &send_tok
);
314 if (maj_stat
== GSS_S_COMPLETE
&& ret_flags
!= NULL
) {
315 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "gss_init_sec_context req_flags = %d ret_flags = %d\n", req_flags
, *ret_flags
);
318 if (maj_stat
!= GSS_S_COMPLETE
&& maj_stat
!= GSS_S_CONTINUE_NEEDED
) {
319 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED; maj_stat = %d\n", maj_stat
);
320 display_status("initializing context", maj_stat
, init_sec_min_stat
);
321 (void) gss_release_name(&min_stat
, &target_name
);
323 if (*(auth
->gss_context
) != GSS_C_NO_CONTEXT
) {
324 gss_delete_sec_context(&min_stat
, auth
->gss_context
, GSS_C_NO_BUFFER
);
329 if (maj_stat
== GSS_S_CONTINUE_NEEDED
) {
330 purple_debug(PURPLE_DEBUG_MISC
, "sipkrb5", "maj_stat == GSS_S_CONTINUE_NEEDED\n");
332 printf("continue needed...");
334 if (recv_token(s, &token_flags, &recv_tok) < 0) {
335 (void) gss_release_name(&min_stat, &target_name);
338 token_ptr = &recv_tok;*/
344 } while (maj_stat
== GSS_S_CONTINUE_NEEDED
);
346 (void) gss_release_name(&min_stat
, &target_name
);
353 purple_krb5_get_mic(struct sipe_krb5_auth * auth, char * msg)
355 if (auth == NULL || auth->gss_context == NULL) {
356 purple_debug(PURPLE_DEBUG_MISC, "sip_krb5", "gss_get_mic auth or auth->gss_context is null\n");
361 gss_buffer_desc msg_buf;
363 msg_buf.length = strlen(msg_buf.value) + 1;
365 gss_buffer_desc mic_buf;
366 OM_uint32 major, minor;
368 if (major = gss_get_mic(&minor, *(auth->gss_context), GSS_C_QOP_DEFAULT, &msg_buf, &mic_buf)) {
369 purple_debug(PURPLE_DEBUG_MISC, "gss_get_mic", "major is %d, minor is %d\n", major, minor);
373 if (mic_buf.length > 0) {
374 gchar mic_val [mic_buf.length * 2];
375 guint8 * mic_int_ary = (guint8 *) mic_buf.value;
377 for (i = 0, j = 0; i < mic_buf.length; i++, j+=2) {
378 g_sprintf(&mic_val[j], "%02x", mic_int_ary[i]);
380 mic = g_strdup(mic_val);
382 purple_debug(PURPLE_DEBUG_MISC, "sipe", "gss_get_mic MIC is empty\n");
385 gss_release_buffer(&minor, &mic_buf);
391 purple_krb5_get_mic_for_msg_breakdown(struct sipe_krb5_auth * auth, struct sipmsg_breakdown * msgbd)
393 if (msgbd->realm == empty_string || msgbd->realm == NULL) {
394 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "realm NULL, so returning NULL MIC\n");
398 gchar * response_str = msgbd->msg->response != 0 ? g_strdup_printf("<%d>", msgbd->msg->response) : empty_string;
399 gchar * msg = g_strdup_printf(
400 "<%s><%s><%s><%s><%s><%s><%s><%s><%s><%s><%s>" // 1 - 11
402 "Kerberos", msgbd->rand, msgbd->num, msgbd->realm, msgbd->target_name, msgbd->call_id, msgbd->cseq,
403 msgbd->msg->method, msgbd->from_url, msgbd->from_tag, msgbd->to_tag,
404 msgbd->expires ? msgbd->expires : empty_string, response_str
407 gchar * mic = purple_krb5_get_mic(auth, msg);
410 if (response_str != empty_string) {
411 g_free(response_str);
418 purple_krb5_get_mic_for_sipmsg(struct sipe_krb5_auth * auth, struct sipmsg * msg)
420 struct sipmsg_breakdown sipbd;
422 sipmsg_breakdown_parse(&sipbd);
424 gchar * mic = purple_krb5_get_mic_for_msg_breakdown(auth, &sipbd);
426 sipmsg_breakdown_free(&sipbd);
430 #endif /*USE_KERBEROS*/