xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/gc_via_tkt.c (revision f012ee0c3db17469b492c2cf757226f3d7b1ebbc)
1 /*
2  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * lib/krb5/krb/gc_via_tgt.c
6  *
7  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
8  * All Rights Reserved.
9  *
10  * Export of this software from the United States of America may
11  *   require a specific license from the United States Government.
12  *   It is the responsibility of any person or organization contemplating
13  *   export to obtain such a license before exporting.
14  *
15  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
16  * distribute this software and its documentation for any purpose and
17  * without fee is hereby granted, provided that the above copyright
18  * notice appear in all copies and that both that copyright notice and
19  * this permission notice appear in supporting documentation, and that
20  * the name of M.I.T. not be used in advertising or publicity pertaining
21  * to distribution of the software without specific, written prior
22  * permission.  Furthermore if you modify this software you must label
23  * your software as modified software and not distribute it in such a
24  * fashion that it might be confused with the original M.I.T. software.
25  * M.I.T. makes no representations about the suitability of
26  * this software for any purpose.  It is provided "as is" without express
27  * or implied warranty.
28  *
29  *
30  * Given a tkt, and a target cred, get it.
31  * Assumes that the kdc_rep has been decrypted.
32  */
33 
34 #include "k5-int.h"
35 #include "int-proto.h"
36 #include <locale.h>
37 #include <ctype.h>
38 
39 static krb5_error_code
40 krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds)
41 {
42     krb5_error_code retval;
43     krb5_data *pdata;
44 
45     if ((*ppcreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) {
46         return ENOMEM;
47     }
48 
49     memset(*ppcreds, 0, sizeof(krb5_creds));
50 
51     if ((retval = krb5_copy_principal(context, pkdcrep->client,
52                                      &(*ppcreds)->client)))
53         goto cleanup;
54 
55     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
56 				      &(*ppcreds)->server)))
57         goto cleanup;
58 
59     if ((retval = krb5_copy_keyblock_contents(context,
60 					      pkdcrep->enc_part2->session,
61 					      &(*ppcreds)->keyblock)))
62         goto cleanup;
63 
64     if ((retval = krb5_copy_data(context, psectkt, &pdata)))
65 	goto cleanup;
66     (*ppcreds)->second_ticket = *pdata;
67     krb5_xfree(pdata);
68 
69     (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
70     (*ppcreds)->times = pkdcrep->enc_part2->times;
71     (*ppcreds)->magic = KV5M_CREDS;
72 
73     (*ppcreds)->authdata = NULL;   			/* not used */
74     (*ppcreds)->is_skey = psectkt->length != 0;
75 
76     if (pkdcrep->enc_part2->caddrs) {
77 	if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
78 					  &(*ppcreds)->addresses)))
79 	    goto cleanup_keyblock;
80     } else {
81 	/* no addresses in the list means we got what we had */
82 	if ((retval = krb5_copy_addresses(context, address,
83 					  &(*ppcreds)->addresses)))
84 	    goto cleanup_keyblock;
85     }
86 
87     if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
88 	goto cleanup_keyblock;
89 
90     (*ppcreds)->ticket = *pdata;
91     free(pdata);
92     return 0;
93 
94 cleanup_keyblock:
95     krb5_free_keyblock(context, &(*ppcreds)->keyblock);
96 
97 cleanup:
98     free (*ppcreds);
99     return retval;
100 }
101 
102 static krb5_error_code
103 check_reply_server(krb5_context context, krb5_flags kdcoptions,
104 		   krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
105 {
106 
107     if (!krb5_principal_compare(context, dec_rep->ticket->server,
108 				dec_rep->enc_part2->server))
109 	return KRB5_KDCREP_MODIFIED;
110 
111     /* Reply is self-consistent. */
112 
113     if (krb5_principal_compare(context, dec_rep->ticket->server,
114 			       in_cred->server))
115 	return 0;
116 
117     /* Server in reply differs from what we requested. */
118 
119     if (kdcoptions & KDC_OPT_CANONICALIZE) {
120 	/* in_cred server differs from ticket returned, but ticket
121 	   returned is consistent and we requested canonicalization. */
122 #if 0
123 #ifdef DEBUG_REFERRALS
124 	printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
125 	krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
126 	krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
127 #endif
128 #endif
129 	return 0;
130     }
131 
132     /* We didn't request canonicalization. */
133 
134     if (!IS_TGS_PRINC(context, in_cred->server) ||
135 	!IS_TGS_PRINC(context, dec_rep->ticket->server)) {
136 	/* Canonicalization not requested, and not a TGS referral. */
137 	return KRB5_KDCREP_MODIFIED;
138     }
139 #if 0
140     /*
141      * Is this check needed?  find_nxt_kdc() in gc_frm_kdc.c already
142      * effectively checks this.
143      */
144     if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
145 	in_cred->server->data[1].length == in_cred->client->realm.length &&
146 	!memcmp(in_cred->client->realm.data, in_cred->server->data[1].data,
147 		in_cred->client->realm.length)) {
148 	/* Attempted to rewrite local TGS. */
149 	return KRB5_KDCREP_MODIFIED;
150     }
151 #endif
152     return 0;
153 }
154 
155 krb5_error_code
156 krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
157 		       krb5_flags kdcoptions, krb5_address *const *address,
158 		       krb5_creds *in_cred, krb5_creds **out_cred)
159 {
160     krb5_error_code retval;
161     krb5_kdc_rep *dec_rep;
162     krb5_error *err_reply;
163     krb5_response tgsrep;
164     krb5_enctype *enctypes = 0;
165     char *hostname_used = NULL;
166 
167 #ifdef DEBUG_REFERRALS
168     printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
169     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
170     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
171 #endif
172 
173     /* tkt->client must be equal to in_cred->client */
174     if (!krb5_principal_compare(context, tkt->client, in_cred->client)) {
175         /* Solaris Kerberos */
176         char *r_name = NULL;
177 	char *t_name = NULL;
178 	krb5_error_code r_err, t_err;
179 	t_err = krb5_unparse_name(context, tkt->client, &t_name);
180 	r_err = krb5_unparse_name(context, in_cred->client, &r_name);
181 	krb5_set_error_message(context, KRB5_PRINC_NOMATCH,
182 			    dgettext(TEXT_DOMAIN,
183 				    "Requested principal and ticket don't match:  Requested principal is '%s' and ticket is '%s'"),
184 			    r_err ? "unknown" : r_name,
185 			    t_err ? "unknown" : t_name);
186 	if (r_name)
187 	    krb5_free_unparsed_name(context, r_name);
188 	if (t_name)
189 	    krb5_free_unparsed_name(context, t_name);
190 	return KRB5_PRINC_NOMATCH;
191     }
192 
193     if (!tkt->ticket.length)
194 	return KRB5_NO_TKT_SUPPLIED;
195 
196     if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) &&
197 	(!in_cred->second_ticket.length))
198         return(KRB5_NO_2ND_TKT);
199 
200 
201     /* check if we have the right TGT                    */
202     /* tkt->server must be equal to                      */
203     /* krbtgt/realmof(cred->server)@realmof(tgt->server) */
204 /*
205     {
206     krb5_principal tempprinc;
207         if (retval = krb5_tgtname(context,
208 		     krb5_princ_realm(context, in_cred->server),
209 		     krb5_princ_realm(context, tkt->server), &tempprinc))
210     	    return(retval);
211 
212         if (!krb5_principal_compare(context, tempprinc, tkt->server)) {
213             krb5_free_principal(context, tempprinc);
214 	    return (KRB5_PRINC_NOMATCH);
215         }
216     krb5_free_principal(context, tempprinc);
217     }
218 */
219 
220     if (in_cred->keyblock.enctype) {
221 	enctypes = (krb5_enctype *) malloc(sizeof(krb5_enctype)*2);
222 	if (!enctypes)
223 	    return ENOMEM;
224 	enctypes[0] = in_cred->keyblock.enctype;
225 	enctypes[1] = 0;
226     }
227 
228     retval = krb5_send_tgs2(context, kdcoptions, &in_cred->times, enctypes,
229 			   in_cred->server, address, in_cred->authdata,
230 			   0,		/* no padata */
231 			   (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ?
232 			   &in_cred->second_ticket : NULL,
233 			    tkt, &tgsrep, &hostname_used);
234     if (enctypes)
235 	free(enctypes);
236     if (retval) {
237 #ifdef DEBUG_REFERRALS
238         printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n",
239 	       error_message(retval));
240 #endif
241 	return retval;
242     }
243 
244     switch (tgsrep.message_type) {
245     case KRB5_TGS_REP:
246 	break;
247     case KRB5_ERROR:
248     default:
249 	if (krb5_is_krb_error(&tgsrep.response))
250 	    retval = decode_krb5_error(&tgsrep.response, &err_reply);
251 	else
252 	    retval = KRB5KRB_AP_ERR_MSG_TYPE;
253 
254 	if (retval)			/* neither proper reply nor error! */
255 	    goto error_4;
256 
257 	retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
258 	if (err_reply->text.length > 0) {
259 #if 0
260 	    const char *m;
261 #endif
262 	    switch (err_reply->error) {
263 	    case KRB_ERR_GENERIC:
264 		krb5_set_error_message(context, retval,
265 				    /* Solaris Kerberos - added dgettext */
266 				    dgettext(TEXT_DOMAIN,
267 					    "KDC returned error string: %s"),
268 				    err_reply->text.data);
269 		break;
270             case KDC_ERR_S_PRINCIPAL_UNKNOWN:
271                 {
272                     char *s_name;
273                     if (krb5_unparse_name(context, in_cred->server, &s_name) ==
274 0) {
275 			/* Solaris Kerberos - added dgettext */
276                         krb5_set_error_message(context, retval,
277                                                dgettext(TEXT_DOMAIN,
278 							"Server %s not found in Kerberos database"),
279                                                s_name);
280                         krb5_free_unparsed_name(context, s_name);
281                     } else
282                         /* In case there's a stale S_PRINCIPAL_UNKNOWN
283                            report already noted.  */
284                         krb5_clear_error_message(context);
285                 }
286                 break;
287 
288 	    case KRB_AP_ERR_SKEW:
289 		/* Solaris Kerberos */
290                 {
291                     char *s_name = NULL;
292                     char *c_name = NULL;
293 		    char stimestring[17];
294 		    char ctimestring[17];
295 		    char fill = ' ';
296 		    int st_err, ct_err, serr, cerr;
297 
298 		    st_err = krb5_timestamp_to_sfstring(err_reply->stime,
299 							stimestring,
300 							sizeof (stimestring),
301 							&fill);
302 		    ct_err = krb5_timestamp_to_sfstring(err_reply->ctime,
303 							ctimestring,
304 							sizeof (ctimestring),
305 							&fill);
306                     serr = krb5_unparse_name(context, in_cred->server, &s_name);
307                     cerr = krb5_unparse_name(context, in_cred->client, &c_name);
308 		    krb5_set_error_message(context, retval,
309 					dgettext(TEXT_DOMAIN,
310 						"Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm."),
311 					cerr == 0 ? c_name : "unknown",
312 					serr == 0 ? s_name : "unknown",
313 					hostname_used ?
314 					  hostname_used : "host unknown",
315 					st_err == 0 ? stimestring : "unknown",
316 					(ct_err||st_err) ? 0 :
317 					    abs(err_reply->stime -
318 					        err_reply->ctime) / 60);
319 
320 		    if (s_name)
321 			    krb5_free_unparsed_name(context, s_name);
322 		    if (c_name)
323 			    krb5_free_unparsed_name(context, c_name);
324                 }
325 	        break;
326 	    case KRB_AP_ERR_TKT_NYV:
327 		/* Solaris Kerberos */
328                 {
329                     char *s_name = NULL;
330                     char *c_name = NULL;
331 		    char timestring[17];
332 		    char stimestring[17];
333 		    char fill = ' ';
334 		    krb5_error_code t_err, st_err, cerr, serr;
335 
336 		    t_err = krb5_timestamp_to_sfstring(tkt->times.starttime,
337 						    timestring,
338 						    sizeof (timestring),
339 						    &fill);
340 		    st_err = krb5_timestamp_to_sfstring(err_reply->stime,
341 							stimestring,
342 							sizeof (stimestring),
343 							&fill);
344                     serr = krb5_unparse_name(context, in_cred->server, &s_name);
345                     cerr = krb5_unparse_name(context, in_cred->client, &c_name);
346 		    krb5_set_error_message(context, retval,
347 					dgettext(TEXT_DOMAIN,
348 						"Ticket not yet valid: '%s' requesting ticket '%s' from '%s' (%s). TGT start time is %s"),
349 					cerr ? "unknown" : c_name,
350 					serr ? "unknown" : s_name,
351 					hostname_used ? hostname_used : "host unknown",
352 					st_err ? "unknown" : stimestring,
353 					t_err ? "unknown" : timestring);
354 		    if (s_name)
355 		        krb5_free_unparsed_name(context, s_name);
356 		    if (c_name)
357 		        krb5_free_unparsed_name(context, c_name);
358                 }
359 	        break;
360 	    default:
361 #if 0 /* We should stop the KDC from sending back this text, because
362 	 if the local language doesn't match the KDC's language, we'd
363 	 just wind up printing out the error message in two languages.
364 	 Well, when we get some localization.  Which is already
365 	 happening in KfM.  */
366 		m = error_message(retval);
367 		/* Special case: MIT KDC may return this same string
368 		   in the e-text field.  */
369 		if (strlen (m) == err_reply->text.length-1
370 		    && !strcmp(m, err_reply->text.data))
371 		    break;
372 		/* Solaris Kerberos - added dgettext */
373 		krb5_set_error_message(context, retval,
374 				    dgettext(TEXT_DOMAIN,
375 					    "%s (KDC supplied additional data: %s)"),
376 				       m, err_reply->text.data);
377 #endif
378 		break;
379 	    }
380 	}
381 
382 	krb5_free_error(context, err_reply);
383 	goto error_4;
384     }
385 
386     if ((retval = krb5_decode_kdc_rep(context, &tgsrep.response,
387 				      &tkt->keyblock, &dec_rep)))
388 	goto error_4;
389 
390     if (dec_rep->msg_type != KRB5_TGS_REP) {
391 	retval = KRB5KRB_AP_ERR_MSG_TYPE;
392 	goto error_3;
393     }
394 
395     /* make sure the response hasn't been tampered with..... */
396     retval = 0;
397 
398     if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
399 	retval = KRB5_KDCREP_MODIFIED;
400 
401     if (retval == 0)
402 	retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
403 
404     if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce)
405 	retval = KRB5_KDCREP_MODIFIED;
406 
407     if ((kdcoptions & KDC_OPT_POSTDATED) &&
408 	(in_cred->times.starttime != 0) &&
409     	(in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
410 	retval = KRB5_KDCREP_MODIFIED;
411 
412     if ((in_cred->times.endtime != 0) &&
413 	(dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
414 	retval = KRB5_KDCREP_MODIFIED;
415 
416     if ((kdcoptions & KDC_OPT_RENEWABLE) &&
417 	(in_cred->times.renew_till != 0) &&
418 	(dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
419 	retval = KRB5_KDCREP_MODIFIED;
420 
421     if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
422 	(dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
423 	(in_cred->times.endtime != 0) &&
424 	(dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
425  	retval = KRB5_KDCREP_MODIFIED;
426 
427     if (retval != 0)
428     	goto error_3;
429 
430     if (!in_cred->times.starttime &&
431 	!in_clock_skew(dec_rep->enc_part2->times.starttime,
432 		       tgsrep.request_time)) {
433 	retval = KRB5_KDCREP_SKEW;
434 	goto error_3;
435     }
436 
437     retval = krb5_kdcrep2creds(context, dec_rep, address,
438 			       &in_cred->second_ticket,  out_cred);
439 
440 error_3:;
441     memset(dec_rep->enc_part2->session->contents, 0,
442 	   dec_rep->enc_part2->session->length);
443     krb5_free_kdc_rep(context, dec_rep);
444 
445 error_4:;
446     if (hostname_used)
447         free(hostname_used);
448     free(tgsrep.response.data);
449 #ifdef DEBUG_REFERRALS
450     printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
451 #endif
452     return retval;
453 }
454