1 /* client.c --- Experimental SASL mechanism KERBEROS_V5, client side.
2  * Copyright (C) 2003-2021 Simon Josefsson
3  *
4  * This file is part of GNU SASL Library.
5  *
6  * GNU SASL Library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; either version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * GNU SASL Library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with GNU SASL Library; if not, write to the Free
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  * NB!  Shishi is licensed under GPL, so linking GSASL with it require
22  * that you follow the GPL for GSASL as well.
23  *
24  */
25 
26 #include "kerberos_v5.h"
27 
28 #include "shared.h"
29 
30 struct _Gsasl_kerberos_v5_client_state
31 {
32   int step;
33   char serverhello[BITMAP_LEN + MAXBUF_LEN + RANDOM_LEN];
34   int serverqops;
35   int clientqop;
36   int servermutual;
37   uint32_t servermaxbuf;
38   uint32_t clientmaxbuf;
39   Shishi *sh;
40   Shishi_tkt *tkt;
41   Shishi_as *as;
42   Shishi_ap *ap;
43   Shishi_key *sessionkey;
44   Shishi_safe *safe;
45 };
46 
47 int
_gsasl_kerberos_v5_client_init(Gsasl_ctx * ctx)48 _gsasl_kerberos_v5_client_init (Gsasl_ctx * ctx)
49 {
50   if (!shishi_check_version (SHISHI_VERSION))
51     return GSASL_UNKNOWN_MECHANISM;
52 
53   return GSASL_OK;
54 }
55 
56 int
_gsasl_kerberos_v5_client_start(Gsasl_session * sctx,void ** mech_data)57 _gsasl_kerberos_v5_client_start (Gsasl_session * sctx, void **mech_data)
58 {
59   struct _Gsasl_kerberos_v5_client_state *state;
60   Gsasl_ctx *ctx;
61   int err;
62 
63   state = malloc (sizeof (*state));
64   if (state == NULL)
65     return GSASL_MALLOC_ERROR;
66 
67   memset (state, 0, sizeof (*state));
68 
69   err = shishi_init (&state->sh);
70   if (err)
71     return GSASL_KERBEROS_V5_INIT_ERROR;
72 
73   state->step = 0;
74   state->clientqop = GSASL_QOP_AUTH_INT;
75 
76   *mech_data = state;
77 
78   return GSASL_OK;
79 }
80 
81 #define STEP_FIRST 0
82 #define STEP_NONINFRA_SEND_ASREQ 1
83 #define STEP_NONINFRA_WAIT_ASREP 2
84 #define STEP_NONINFRA_SEND_APREQ 3
85 #define STEP_NONINFRA_WAIT_APREP 4
86 #define STEP_SUCCESS 5
87 
88 int
_gsasl_kerberos_v5_client_step(Gsasl_session * sctx,void * mech_data,const char * input,size_t input_len,char * output,size_t * output_len)89 _gsasl_kerberos_v5_client_step (Gsasl_session * sctx,
90 				void *mech_data,
91 				const char *input,
92 				size_t input_len,
93 				char *output, size_t *output_len)
94 {
95   struct _Gsasl_kerberos_v5_client_state *state = mech_data;
96   Gsasl_client_callback_authentication_id cb_authentication_id;
97   Gsasl_client_callback_authorization_id cb_authorization_id;
98   Gsasl_client_callback_qop cb_qop;
99   Gsasl_client_callback_realm cb_realm;
100   Gsasl_client_callback_password cb_password;
101   Gsasl_client_callback_service cb_service;
102   Gsasl_client_callback_maxbuf cb_maxbuf;
103   Gsasl_ctx *ctx;
104   int res;
105   int len;
106 
107   ctx = gsasl_client_ctx_get (sctx);
108   if (ctx == NULL)
109     return GSASL_CANNOT_GET_CTX;
110 
111   /* These are optional */
112   cb_realm = gsasl_client_callback_realm_get (ctx);
113   cb_service = gsasl_client_callback_service_get (ctx);
114   cb_authentication_id = gsasl_client_callback_authentication_id_get (ctx);
115   cb_authorization_id = gsasl_client_callback_authorization_id_get (ctx);
116   cb_qop = gsasl_client_callback_qop_get (ctx);
117   cb_maxbuf = gsasl_client_callback_maxbuf_get (ctx);
118 
119   /* Only optionally needed in infrastructure mode */
120   cb_password = gsasl_client_callback_password_get (ctx);
121   if (cb_password == NULL)
122     return GSASL_NEED_CLIENT_PASSWORD_CALLBACK;
123 
124   /* I think we really need this one */
125   cb_service = gsasl_client_callback_service_get (ctx);
126   if (cb_service == NULL)
127     return GSASL_NEED_CLIENT_SERVICE_CALLBACK;
128 
129   switch (state->step)
130     {
131     case STEP_FIRST:
132       if (input == NULL)
133 	{
134 	  *output_len = 0;
135 	  return GSASL_NEEDS_MORE;
136 	}
137 
138       if (input_len != SERVER_HELLO_LEN)
139 	return GSASL_MECHANISM_PARSE_ERROR;
140 
141       memcpy (state->serverhello, input, input_len);
142 
143       {
144 	unsigned char serverbitmap;
145 
146 	memcpy (&serverbitmap, input, BITMAP_LEN);
147 	state->serverqops = 0;
148 	if (serverbitmap & GSASL_QOP_AUTH)
149 	  state->serverqops |= GSASL_QOP_AUTH;
150 	if (serverbitmap & GSASL_QOP_AUTH_INT)
151 	  state->serverqops |= GSASL_QOP_AUTH_INT;
152 	if (serverbitmap & GSASL_QOP_AUTH_CONF)
153 	  state->serverqops |= GSASL_QOP_AUTH_CONF;
154 	if (serverbitmap & MUTUAL)
155 	  state->servermutual = 1;
156       }
157       memcpy (&state->servermaxbuf, &input[BITMAP_LEN], MAXBUF_LEN);
158       state->servermaxbuf = ntohl (state->servermaxbuf);
159 
160       if (cb_qop)
161 	state->clientqop = cb_qop (sctx, state->serverqops);
162 
163       if (!(state->serverqops & state->clientqop &
164 	    (GSASL_QOP_AUTH | GSASL_QOP_AUTH_INT | GSASL_QOP_AUTH_CONF)))
165 	return GSASL_AUTHENTICATION_ERROR;
166 
167       /* XXX for now we require server authentication */
168       if (!state->servermutual)
169 	return GSASL_AUTHENTICATION_ERROR;
170 
171       /* Decide policy here: non-infrastructure, infrastructure or proxy.
172        *
173        * A callback to decide should be added, but without the default
174        * should be:
175        *
176        * IF shishi_tktset_get_for_server() THEN
177        *    INFRASTRUCTURE MODE
178        * ELSE IF shishi_realm_for_server(server) THEN
179        *    PROXY INFRASTRUCTURE (then fallback to NIM?)
180        * ELSE
181        *    NON-INFRASTRUCTURE MODE
182        */
183       state->step = STEP_NONINFRA_SEND_APREQ;	/* only NIM for now.. */
184       /* fall through */
185 
186     case STEP_NONINFRA_SEND_ASREQ:
187       res = shishi_as (state->sh, &state->as);
188       if (res)
189 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
190 
191       if (cb_authentication_id)	/* Shishi defaults to one otherwise */
192 	{
193 	  len = *output_len - 1;
194 	  res = cb_authentication_id (sctx, output, &len);
195 	  if (res != GSASL_OK)
196 	    return res;
197 	  output[len] = '\0';
198 
199 	  res = shishi_kdcreq_set_cname (state->sh, shishi_as_req (state->as),
200 					 SHISHI_NT_UNKNOWN, output);
201 	  if (res != GSASL_OK)
202 	    return res;
203 	}
204 
205       if (cb_realm)
206 	{
207 	  len = *output_len - 1;
208 	  res = cb_realm (sctx, output, &len);
209 	  if (res != GSASL_OK)
210 	    return res;
211 	}
212       else
213 	len = 0;
214 
215       output[len] = '\0';
216       res = shishi_kdcreq_set_realm (state->sh, shishi_as_req (state->as),
217 				     output);
218       if (res != GSASL_OK)
219 	return res;
220 
221       if (cb_service)
222 	{
223 	  char *sname[3];
224 	  size_t servicelen = 0;
225 	  size_t hostnamelen = 0;
226 
227 	  res = cb_service (sctx, NULL, &servicelen, NULL, &hostnamelen,
228 			    /* XXX support servicename a'la DIGEST-MD5 too? */
229 			    NULL, NULL);
230 	  if (res != GSASL_OK)
231 	    return res;
232 
233 	  if (*output_len < servicelen + 1 + hostnamelen + 1)
234 	    return GSASL_TOO_SMALL_BUFFER;
235 
236 	  sname[0] = &output[0];
237 	  sname[1] = &output[servicelen + 2];
238 	  sname[2] = NULL;
239 
240 	  res = cb_service (sctx, sname[0], &servicelen,
241 			    sname[1], &hostnamelen, NULL, NULL);
242 	  if (res != GSASL_OK)
243 	    return res;
244 
245 	  sname[0][servicelen] = '\0';
246 	  sname[1][hostnamelen] = '\0';
247 
248 	  res = shishi_kdcreq_set_sname (state->sh, shishi_as_req (state->as),
249 					 SHISHI_NT_UNKNOWN, sname);
250 	  if (res != GSASL_OK)
251 	    return res;
252 	}
253 
254       /* XXX query application for encryption types and set the etype
255          field?  Already configured by shishi though... */
256 
257       res = shishi_a2d (state->sh, shishi_as_req (state->as),
258 			output, output_len);
259       if (res != SHISHI_OK)
260 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
261 
262       state->step = STEP_NONINFRA_WAIT_ASREP;
263 
264       res = GSASL_NEEDS_MORE;
265       break;
266 
267     case STEP_NONINFRA_WAIT_ASREP:
268       if (shishi_as_rep_der_set (state->as, input, input_len) != SHISHI_OK)
269 	return GSASL_MECHANISM_PARSE_ERROR;
270 
271       /* XXX? password stored in callee's output buffer */
272       len = *output_len - 1;
273       res = cb_password (sctx, output, &len);
274       if (res != GSASL_OK && res != GSASL_NEEDS_MORE)
275 	return res;
276       output[len] = '\0';
277 
278       res = shishi_as_rep_process (state->as, NULL, output);
279       if (res != SHISHI_OK)
280 	return GSASL_AUTHENTICATION_ERROR;
281 
282       state->step = STEP_NONINFRA_SEND_APREQ;
283       /* fall through */
284 
285     case STEP_NONINFRA_SEND_APREQ:
286       if (*output_len <= CLIENT_HELLO_LEN + SERVER_HELLO_LEN)
287 	return GSASL_TOO_SMALL_BUFFER;
288 
289       if (!(state->clientqop & ~GSASL_QOP_AUTH))
290 	state->clientmaxbuf = 0;
291       else if (cb_maxbuf)
292 	state->clientmaxbuf = cb_maxbuf (sctx, state->servermaxbuf);
293       else
294 	state->clientmaxbuf = MAXBUF_DEFAULT;
295 
296       /* XXX for now we require server authentication */
297       output[0] = state->clientqop | MUTUAL;
298       {
299 	uint32_t tmp;
300 
301 	tmp = ntohl (state->clientmaxbuf);
302 	memcpy (&output[BITMAP_LEN], &tmp, MAXBUF_LEN);
303       }
304       memcpy (&output[CLIENT_HELLO_LEN], state->serverhello,
305 	      SERVER_HELLO_LEN);
306 
307       if (cb_authorization_id)
308 	{
309 	  len = *output_len - CLIENT_HELLO_LEN + SERVER_HELLO_LEN;
310 	  res = cb_authorization_id (sctx, &output[CLIENT_HELLO_LEN +
311 						   SERVER_HELLO_LEN], &len);
312 	}
313       else
314 	len = 0;
315 
316       len += CLIENT_HELLO_LEN + SERVER_HELLO_LEN;
317       res = shishi_ap_tktoptionsdata (state->sh,
318 				      &state->ap,
319 				      shishi_as_tkt (state->as),
320 				      SHISHI_APOPTIONS_MUTUAL_REQUIRED,
321 				      output, len);
322       if (res != SHISHI_OK)
323 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
324 
325       res = shishi_authenticator_add_authorizationdata
326 	(state->sh, shishi_ap_authenticator (state->ap), -1, output, len);
327       if (res != SHISHI_OK)
328 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
329 
330       /* XXX set realm in AP-REQ and Authenticator */
331 
332       res = shishi_ap_req_der (state->ap, output, output_len);
333       if (res != SHISHI_OK)
334 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
335 
336       state->step = STEP_NONINFRA_WAIT_APREP;
337 
338       res = GSASL_NEEDS_MORE;
339       break;
340 
341     case STEP_NONINFRA_WAIT_APREP:
342       if (shishi_ap_rep_der_set (state->ap, input, input_len) != SHISHI_OK)
343 	return GSASL_MECHANISM_PARSE_ERROR;
344 
345       res = shishi_ap_rep_verify (state->ap);
346       if (res != SHISHI_OK)
347 	return GSASL_AUTHENTICATION_ERROR;
348 
349       state->step = STEP_SUCCESS;
350 
351       /* XXX support AP session keys */
352       state->sessionkey = shishi_tkt_key (shishi_as_tkt (state->as));
353 
354       *output_len = 0;
355       res = GSASL_OK;
356       break;
357 
358     default:
359       res = GSASL_MECHANISM_CALLED_TOO_MANY_TIMES;
360       break;
361     }
362 
363   return res;
364 }
365 
366 int
_gsasl_kerberos_v5_client_encode(Gsasl_session * sctx,void * mech_data,const char * input,size_t input_len,char ** output,size_t * output_len)367 _gsasl_kerberos_v5_client_encode (Gsasl_session * sctx,
368 				  void *mech_data,
369 				  const char *input,
370 				  size_t input_len,
371 				  char **output, size_t *output_len)
372 {
373   struct _Gsasl_kerberos_v5_client_state *state = mech_data;
374   int res;
375 
376   if (state && state->sessionkey && state->clientqop & GSASL_QOP_AUTH_CONF)
377     {
378       return GSASL_INTEGRITY_ERROR;
379     }
380   else if (state && state->sessionkey
381 	   && state->clientqop & GSASL_QOP_AUTH_INT)
382     {
383       res = shishi_safe (state->sh, &state->safe);
384       if (res != SHISHI_OK)
385 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
386 
387       res = shishi_safe_set_user_data (state->sh,
388 				       shishi_safe_safe (state->safe),
389 				       input, input_len);
390       if (res != SHISHI_OK)
391 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
392 
393       res = shishi_safe_build (state->safe, state->sessionkey);
394       if (res != SHISHI_OK)
395 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
396 
397       res = shishi_safe_safe_der (state->safe, output, output_len);
398       if (res != SHISHI_OK)
399 	return GSASL_KERBEROS_V5_INTERNAL_ERROR;
400     }
401   else
402     {
403       *output_len = input_len;
404       *output = malloc (input_len);
405       if (!*output)
406 	return GSASL_MALLOC_ERROR;
407       memcpy (*output, input, input_len);
408     }
409 
410   return GSASL_OK;
411 }
412 
413 int
_gsasl_kerberos_v5_client_decode(Gsasl_session * sctx,void * mech_data,const char * input,size_t input_len,char * output,size_t * output_len)414 _gsasl_kerberos_v5_client_decode (Gsasl_session * sctx,
415 				  void *mech_data,
416 				  const char *input,
417 				  size_t input_len,
418 				  char *output, size_t *output_len)
419 {
420   struct _Gsasl_kerberos_v5_client_state *state = mech_data;
421 
422   if (state && state->sessionkey && state->clientqop & GSASL_QOP_AUTH_CONF)
423     {
424       return GSASL_INTEGRITY_ERROR;
425     }
426   else if (state && state->sessionkey
427 	   && state->clientqop & GSASL_QOP_AUTH_INT)
428     {
429       return GSASL_INTEGRITY_ERROR;
430     }
431   else
432     {
433       *output_len = input_len;
434       *output = malloc (input_len);
435       if (!*output)
436 	return GSASL_MALLOC_ERROR;
437       memcpy (*output, input, input_len);
438     }
439 
440   return GSASL_OK;
441 }
442 
443 int
_gsasl_kerberos_v5_client_finish(Gsasl_session * sctx,void * mech_data)444 _gsasl_kerberos_v5_client_finish (Gsasl_session * sctx, void *mech_data)
445 {
446   struct _Gsasl_kerberos_v5_client_state *state = mech_data;
447 
448   shishi_done (state->sh);
449   free (state);
450 
451   return GSASL_OK;
452 }
453