xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/rd_req_dec.c (revision 445f2479fe3d7435daab18bf2cdc310b86cd6738)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * lib/krb5/krb/rd_req_dec.c
10  *
11  * Copyright (c) 1994 CyberSAFE Corporation.
12  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
13  * All Rights Reserved.
14  *
15  * Export of this software from the United States of America may
16  *   require a specific license from the United States Government.
17  *   It is the responsibility of any person or organization contemplating
18  *   export to obtain such a license before exporting.
19  *
20  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
21  * distribute this software and its documentation for any purpose and
22  * without fee is hereby granted, provided that the above copyright
23  * notice appear in all copies and that both that copyright notice and
24  * this permission notice appear in supporting documentation, and that
25  * the name of M.I.T. not be used in advertising or publicity pertaining
26  * to distribution of the software without specific, written prior
27  * permission.  Furthermore if you modify this software you must label
28  * your software as modified software and not distribute it in such a
29  * fashion that it might be confused with the original M.I.T. software.
30  * Neither M.I.T., the Open Computing Security Group, nor
31  * CyberSAFE Corporation make any representations about the suitability of
32  * this software for any purpose.  It is provided "as is" without express
33  * or implied warranty.
34  *
35  *
36  * krb5_rd_req_decoded()
37  */
38 
39 #include <k5-int.h>
40 #include <auth_con.h>
41 
42 /*
43  * essentially the same as krb_rd_req, but uses a decoded AP_REQ as
44  * the input rather than an encoded input.
45  */
46 /*
47  *  Parses a KRB_AP_REQ message, returning its contents.
48  *
49  *  server specifies the expected server's name for the ticket; if NULL, then
50  *  any server will be accepted if the key can be found, and the caller should
51  *  verify that the principal is something it trusts.
52  *
53  *  rcache specifies a replay detection cache used to store authenticators and
54  *  server names
55  *
56  *  keyproc specifies a procedure to generate a decryption key for the
57  *  ticket.  If keyproc is non-NULL, keyprocarg is passed to it, and the result
58  *  used as a decryption key. If keyproc is NULL, then fetchfrom is checked;
59  *  if it is non-NULL, it specifies a parameter name from which to retrieve the
60  *  decryption key.  If fetchfrom is NULL, then the default key store is
61  *  consulted.
62  *
63  *  authdat is set to point at allocated storage structures; the caller
64  *  should free them when finished.
65  *
66  *  returns system errors, encryption errors, replay errors
67  */
68 
69 static krb5_error_code decrypt_authenticator
70 	(krb5_context, const krb5_ap_req *, krb5_authenticator **,
71 		   int);
72 
73 #define in_clock_skew(date) (labs((date)-currenttime) < context->clockskew)
74 
75 static krb5_error_code
76 krb5_rd_req_decrypt_tkt_part(
77     krb5_context          context,
78     const krb5_ap_req 	* req,
79     krb5_keytab           keytab)
80 
81 {
82     krb5_error_code 	  retval;
83     krb5_enctype 	  enctype;
84     krb5_keytab_entry 	  ktent;
85 
86     enctype = req->ticket->enc_part.enctype;
87 
88     memset(&ktent, 0, sizeof(krb5_keytab_entry));
89     retval = krb5_kt_get_entry(context, keytab, req->ticket->server,
90 			    req->ticket->enc_part.kvno, enctype, &ktent);
91     if (retval)
92 	return retval;
93 
94     /*
95      * If we get this far then we know that the enc types are similar,
96      * therefore we should change the enc type to match that of what
97      * we are decrypting.
98      */
99     ktent.key.enctype = enctype;
100 
101     retval = krb5_decrypt_tkt_part(context, &ktent.key, req->ticket);
102 
103     (void) krb5_kt_free_entry(context, &ktent);
104     return retval;
105 }
106 
107 static krb5_error_code
108 krb5_rd_req_decoded_opt(
109     krb5_context 	  context,
110     krb5_auth_context   * auth_context,
111     const krb5_ap_req 	* req,
112     krb5_const_principal  server,
113     krb5_keytab           keytab,
114     krb5_flags          * ap_req_options,
115     krb5_ticket	       ** ticket,
116     int			  check_valid_flag)
117 {
118     krb5_error_code 	  retval = 0;
119     krb5_timestamp 	  currenttime;
120 
121     if (server && !krb5_principal_compare(context, server, req->ticket->server))
122 	return KRB5KRB_AP_WRONG_PRINC;
123 
124     /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY)
125        do we need special processing here ?	*/
126 
127     /* decrypt the ticket */
128     if ((*auth_context)->keyblock) { /* User to User authentication */
129     	if ((retval = krb5_decrypt_tkt_part(context, (*auth_context)->keyblock,
130 					    req->ticket)))
131 	    return retval;
132 	krb5_free_keyblock(context, (*auth_context)->keyblock);
133 	(*auth_context)->keyblock = NULL;
134     } else {
135     	if ((retval = krb5_rd_req_decrypt_tkt_part(context, req, keytab)))
136 	    return retval;
137     }
138 
139     /* XXX this is an evil hack.  check_valid_flag is set iff the call
140        is not from inside the kdc.  we can use this to determine which
141        key usage to use */
142     if ((retval = decrypt_authenticator(context, req,
143 					&((*auth_context)->authentp),
144 					check_valid_flag)))
145 	goto cleanup;
146 
147     if (!krb5_principal_compare(context, (*auth_context)->authentp->client,
148 				req->ticket->enc_part2->client)) {
149 	retval = KRB5KRB_AP_ERR_BADMATCH;
150 	goto cleanup;
151     }
152 
153     if ((*auth_context)->remote_addr &&
154       !krb5_address_search(context, (*auth_context)->remote_addr,
155 			   req->ticket->enc_part2->caddrs)) {
156 	retval = KRB5KRB_AP_ERR_BADADDR;
157 	goto cleanup;
158     }
159 
160     /* okay, now check cross-realm policy */
161 
162 #if defined(_SINGLE_HOP_ONLY)
163 
164     /* Single hop cross-realm tickets only */
165 
166     {
167 	krb5_transited *trans = &(req->ticket->enc_part2->transited);
168 
169       	/* If the transited list is empty, then we have at most one hop */
170       	if (trans->tr_contents.data && trans->tr_contents.data[0])
171             retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
172     }
173 
174 #elif defined(_NO_CROSS_REALM)
175 
176     /* No cross-realm tickets */
177 
178     {
179 	char		* lrealm;
180       	krb5_data      	* realm;
181       	krb5_transited 	* trans;
182 
183 	realm = krb5_princ_realm(context, req->ticket->enc_part2->client);
184 	trans = &(req->ticket->enc_part2->transited);
185 
186 	/*
187       	 * If the transited list is empty, then we have at most one hop
188       	 * So we also have to check that the client's realm is the local one
189 	 */
190       	krb5_get_default_realm(context, &lrealm);
191       	if ((trans->tr_contents.data && trans->tr_contents.data[0]) ||
192           strlen(lrealm) != realm->length ||
193           memcmp(lrealm, realm->data, strlen(lrealm))) {
194             retval = KRB5KRB_AP_ERR_ILL_CR_TKT;
195       	}
196       	free(lrealm);
197     }
198 
199 #else
200 
201     /* Hierarchical Cross-Realm */
202 
203     {
204      	krb5_data	* realm;
205 	krb5_transited * trans;
206 
207 	realm = krb5_princ_realm(context, req->ticket->enc_part2->client);
208 	trans = &(req->ticket->enc_part2->transited);
209 
210 	/*
211          * If the transited list is not empty, then check that all realms
212 	 * transited are within the hierarchy between the client's realm
213 	 * and the local realm.
214 	 */
215 	if (trans->tr_contents.data && trans->tr_contents.data[0]) {
216             retval = krb5_check_transited_list(context, &(trans->tr_contents),
217                                                realm,
218                                                krb5_princ_realm (context,
219                                                                  server));
220 	}
221     }
222 
223 #endif
224 
225     if (retval)  goto cleanup;
226 
227     /* only check rcache if sender has provided one---some services
228        may not be able to use replay caches (such as datagram servers) */
229 
230     if ((*auth_context)->rcache) {
231 	krb5_donot_replay  rep;
232         krb5_tkt_authent   tktauthent;
233 
234 	tktauthent.ticket = req->ticket;
235 	tktauthent.authenticator = (*auth_context)->authentp;
236 	if (!(retval = krb5_auth_to_rep(context, &tktauthent, &rep))) {
237 	    retval = krb5_rc_store(context, (*auth_context)->rcache, &rep);
238 	    krb5_xfree(rep.server);
239 	    krb5_xfree(rep.client);
240 	}
241 
242 	if (retval)
243 	    goto cleanup;
244     }
245 
246     retval = krb5_validate_times(context, &req->ticket->enc_part2->times);
247     if (retval != 0)
248 	    goto cleanup;
249 
250     if ((retval = krb5_timeofday(context, &currenttime)))
251 	goto cleanup;
252 
253     if (!in_clock_skew((*auth_context)->authentp->ctime)) {
254 	retval = KRB5KRB_AP_ERR_SKEW;
255 	goto cleanup;
256     }
257 
258     if (check_valid_flag) {
259       if (req->ticket->enc_part2->flags & TKT_FLG_INVALID) {
260 	retval = KRB5KRB_AP_ERR_TKT_INVALID;
261 	goto cleanup;
262       }
263     }
264 
265     /* check if the various etypes are permitted */
266 
267     if ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) {
268 	/* no etype check needed */
269 	/*EMPTY*/
270 	;
271     } else if ((*auth_context)->permitted_etypes == NULL) {
272 	/* check against the default set */
273 	if ((!krb5_is_permitted_enctype(context,
274 					req->ticket->enc_part.enctype)) ||
275 	    (!krb5_is_permitted_enctype(context,
276 					req->ticket->enc_part2->session->enctype)) ||
277 	    (((*auth_context)->authentp->subkey) &&
278 	     !krb5_is_permitted_enctype(context,
279 					(*auth_context)->authentp->subkey->enctype))) {
280 	    retval = KRB5_NOPERM_ETYPE;
281 	    goto cleanup;
282 	}
283     } else {
284 	/* check against the set in the auth_context */
285 	int i;
286 
287 	for (i=0; (*auth_context)->permitted_etypes[i]; i++)
288 	    if ((*auth_context)->permitted_etypes[i] ==
289 		req->ticket->enc_part.enctype)
290 		break;
291 	if (!(*auth_context)->permitted_etypes[i]) {
292 	    retval = KRB5_NOPERM_ETYPE;
293 	    goto cleanup;
294 	}
295 
296 	for (i=0; (*auth_context)->permitted_etypes[i]; i++)
297 	    if ((*auth_context)->permitted_etypes[i] ==
298 		req->ticket->enc_part2->session->enctype)
299 		break;
300 	if (!(*auth_context)->permitted_etypes[i]) {
301 	    retval = KRB5_NOPERM_ETYPE;
302 	    goto cleanup;
303 	}
304 
305 	if ((*auth_context)->authentp->subkey) {
306 	    for (i=0; (*auth_context)->permitted_etypes[i]; i++)
307 		if ((*auth_context)->permitted_etypes[i] ==
308 		    (*auth_context)->authentp->subkey->enctype)
309 		    break;
310 	    if (!(*auth_context)->permitted_etypes[i]) {
311 		retval = KRB5_NOPERM_ETYPE;
312 		goto cleanup;
313 	    }
314 	}
315     }
316 
317     (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number;
318 
319     if ((*auth_context)->authentp->subkey) {
320 
321 	if ((*auth_context)->recv_subkey != NULL) {
322 	    krb5_free_keyblock(context, (*auth_context)->recv_subkey);
323 	    (*auth_context)->recv_subkey = NULL;
324 	}
325 
326 	if ((retval = krb5_copy_keyblock(context,
327 					 (*auth_context)->authentp->subkey,
328 					 &((*auth_context)->recv_subkey))))
329 	    goto cleanup;
330 
331 	if ((*auth_context)->send_subkey != NULL) {
332 	    krb5_free_keyblock(context, (*auth_context)->send_subkey);
333 	    (*auth_context)->send_subkey = NULL;
334 	}
335 
336 	retval = krb5_copy_keyblock(context, (*auth_context)->authentp->subkey,
337                                     &((*auth_context)->send_subkey));
338 	if (retval) {
339             krb5_free_keyblock(context, (*auth_context)->recv_subkey);
340             (*auth_context)->recv_subkey = NULL;
341             goto cleanup;
342 	}
343     } else {
344 	(*auth_context)->recv_subkey = 0;
345 	(*auth_context)->send_subkey = 0;
346     }
347 
348     if ((*auth_context)->keyblock != NULL) {
349 	krb5_free_keyblock(context, (*auth_context)->keyblock);
350 	(*auth_context)->keyblock = NULL;
351     }
352     if ((retval = krb5_copy_keyblock(context, req->ticket->enc_part2->session,
353 				     &((*auth_context)->keyblock))))
354 	goto cleanup;
355 
356     /*
357      * If not AP_OPTS_MUTUAL_REQUIRED then and sequence numbers are used
358      * then the default sequence number is the one's complement of the
359      * sequence number sent ot us.
360      */
361     if ((!(req->ap_options & AP_OPTS_MUTUAL_REQUIRED)) &&
362       (*auth_context)->remote_seq_number) {
363 	(*auth_context)->local_seq_number ^=
364 	  (*auth_context)->remote_seq_number;
365     }
366 
367     if (ticket)
368    	if ((retval = krb5_copy_ticket(context, req->ticket, ticket)))
369 	    goto cleanup;
370     if (ap_req_options)
371     	*ap_req_options = req->ap_options;
372     retval = 0;
373 
374 cleanup:
375     if (retval) {
376 	/* only free if we're erroring out...otherwise some
377 	   applications will need the output. */
378         krb5_free_enc_tkt_part(context, req->ticket->enc_part2);
379 	req->ticket->enc_part2 = NULL;
380     }
381     return retval;
382 }
383 
384 krb5_error_code
385 krb5_rd_req_decoded(
386     krb5_context 	  context,
387     krb5_auth_context   * auth_context,
388     const krb5_ap_req 	* req,
389     krb5_const_principal  server,
390     krb5_keytab           keytab,
391     krb5_flags          * ap_req_options,
392     krb5_ticket	       ** ticket)
393 {
394   krb5_error_code retval;
395   retval = krb5_rd_req_decoded_opt(context, auth_context,
396 				   req, server, keytab,
397 				   ap_req_options, ticket,
398 				   1); /* check_valid_flag */
399   return retval;
400 }
401 
402 krb5_error_code
403 krb5_rd_req_decoded_anyflag(
404     krb5_context 	  context,
405     krb5_auth_context   * auth_context,
406     const krb5_ap_req 	* req,
407     krb5_const_principal  server,
408     krb5_keytab           keytab,
409     krb5_flags          * ap_req_options,
410     krb5_ticket	       ** ticket)
411 {
412   krb5_error_code retval;
413   retval = krb5_rd_req_decoded_opt(context, auth_context,
414 				   req, server, keytab,
415 				   ap_req_options, ticket,
416 				   0); /* don't check_valid_flag */
417   return retval;
418 }
419 
420 /*ARGSUSED*/
421 static krb5_error_code
422 decrypt_authenticator(
423     krb5_context context,
424     const krb5_ap_req *request,
425     krb5_authenticator **authpp,
426     int is_ap_req)
427 {
428     krb5_authenticator *local_auth;
429     krb5_error_code retval;
430     krb5_data scratch;
431     krb5_keyblock *sesskey;
432 
433     sesskey = request->ticket->enc_part2->session;
434 
435     scratch.length = request->authenticator.ciphertext.length;
436     if (!(scratch.data = malloc(scratch.length)))
437 	return(ENOMEM);
438 
439     retval = krb5_c_decrypt(context, sesskey,
440 				 is_ap_req?KRB5_KEYUSAGE_AP_REQ_AUTH:
441 				 KRB5_KEYUSAGE_TGS_REQ_AUTH, 0,
442 				 &request->authenticator, &scratch);
443     if (retval) {
444 	free(scratch.data);
445 	return(retval);
446     }
447 
448 #define clean_scratch() {memset(scratch.data, 0, scratch.length); \
449 free(scratch.data);}
450 
451     /*  now decode the decrypted stuff */
452     if (!(retval = decode_krb5_authenticator(&scratch, &local_auth))) {
453 	*authpp = local_auth;
454     }
455     clean_scratch();
456     return retval;
457 }
458