xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/gc_via_tkt.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * lib/krb5/krb/gc_via_tgt.c
3  *
4  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  *
26  *
27  * Given a tkt, and a target cred, get it.
28  * Assumes that the kdc_rep has been decrypted.
29  */
30 
31 #include "k5-int.h"
32 #include "int-proto.h"
33 
34 #define in_clock_skew(date, now) (labs((date)-(now)) < context->clockskew)
35 
36 #define IS_TGS_PRINC(c, p)				\
37     ((krb5_princ_size((c), (p)) == 2) &&		\
38      (krb5_princ_component((c), (p), 0)->length ==	\
39       KRB5_TGS_NAME_SIZE) &&				\
40      (!memcmp(krb5_princ_component((c), (p), 0)->data,	\
41 	      KRB5_TGS_NAME, KRB5_TGS_NAME_SIZE)))
42 
43 static krb5_error_code
44 krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds)
45 {
46     krb5_error_code retval;
47     krb5_data *pdata;
48 
49     if ((*ppcreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) {
50         return ENOMEM;
51     }
52 
53     memset(*ppcreds, 0, sizeof(krb5_creds));
54 
55     if ((retval = krb5_copy_principal(context, pkdcrep->client,
56                                      &(*ppcreds)->client)))
57         goto cleanup;
58 
59     if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server,
60 				      &(*ppcreds)->server)))
61         goto cleanup;
62 
63     if ((retval = krb5_copy_keyblock_contents(context,
64 					      pkdcrep->enc_part2->session,
65 					      &(*ppcreds)->keyblock)))
66         goto cleanup;
67 
68     if ((retval = krb5_copy_data(context, psectkt, &pdata)))
69 	goto cleanup;
70     (*ppcreds)->second_ticket = *pdata;
71     krb5_xfree(pdata);
72 
73     (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags;
74     (*ppcreds)->times = pkdcrep->enc_part2->times;
75     (*ppcreds)->magic = KV5M_CREDS;
76 
77     (*ppcreds)->authdata = NULL;   			/* not used */
78     (*ppcreds)->is_skey = psectkt->length != 0;
79 
80     if (pkdcrep->enc_part2->caddrs) {
81 	if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs,
82 					  &(*ppcreds)->addresses)))
83 	    goto cleanup_keyblock;
84     } else {
85 	/* no addresses in the list means we got what we had */
86 	if ((retval = krb5_copy_addresses(context, address,
87 					  &(*ppcreds)->addresses)))
88 	    goto cleanup_keyblock;
89     }
90 
91     if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata)))
92 	goto cleanup_keyblock;
93 
94     (*ppcreds)->ticket = *pdata;
95     free(pdata);
96     return 0;
97 
98 cleanup_keyblock:
99     krb5_free_keyblock(context, &(*ppcreds)->keyblock);
100 
101 cleanup:
102     free (*ppcreds);
103     return retval;
104 }
105 
106 static krb5_error_code
107 check_reply_server(krb5_context context, krb5_flags kdcoptions,
108 		   krb5_creds *in_cred, krb5_kdc_rep *dec_rep)
109 {
110 
111     if (!krb5_principal_compare(context, dec_rep->ticket->server,
112 				dec_rep->enc_part2->server))
113 	return KRB5_KDCREP_MODIFIED;
114 
115     /* Reply is self-consistent. */
116 
117     if (krb5_principal_compare(context, dec_rep->ticket->server,
118 			       in_cred->server))
119 	return 0;
120 
121     /* Server in reply differs from what we requested. */
122 
123     if (kdcoptions & KDC_OPT_CANONICALIZE) {
124 	/* in_cred server differs from ticket returned, but ticket
125 	   returned is consistent and we requested canonicalization. */
126 #if 0
127 #ifdef DEBUG_REFERRALS
128 	printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n");
129 	krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server);
130 	krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server);
131 #endif
132 #endif
133 	return 0;
134     }
135 
136     /* We didn't request canonicalization. */
137 
138     if (!IS_TGS_PRINC(context, in_cred->server) ||
139 	!IS_TGS_PRINC(context, dec_rep->ticket->server)) {
140 	/* Canonicalization not requested, and not a TGS referral. */
141 	return KRB5_KDCREP_MODIFIED;
142     }
143 #if 0
144     /*
145      * Is this check needed?  find_nxt_kdc() in gc_frm_kdc.c already
146      * effectively checks this.
147      */
148     if (krb5_realm_compare(context, in_cred->client, in_cred->server) &&
149 	in_cred->server->data[1].length == in_cred->client->realm.length &&
150 	!memcmp(in_cred->client->realm.data, in_cred->server->data[1].data,
151 		in_cred->client->realm.length)) {
152 	/* Attempted to rewrite local TGS. */
153 	return KRB5_KDCREP_MODIFIED;
154     }
155 #endif
156     return 0;
157 }
158 
159 krb5_error_code
160 krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt,
161 		       krb5_flags kdcoptions, krb5_address *const *address,
162 		       krb5_creds *in_cred, krb5_creds **out_cred)
163 {
164     krb5_error_code retval;
165     krb5_kdc_rep *dec_rep;
166     krb5_error *err_reply;
167     krb5_response tgsrep;
168     krb5_enctype *enctypes = 0;
169 
170 #ifdef DEBUG_REFERRALS
171     printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off");
172     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server);
173     krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server);
174 #endif
175 
176     /* tkt->client must be equal to in_cred->client */
177     if (!krb5_principal_compare(context, tkt->client, in_cred->client))
178 	return KRB5_PRINC_NOMATCH;
179 
180     if (!tkt->ticket.length)
181 	return KRB5_NO_TKT_SUPPLIED;
182 
183     if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) &&
184 	(!in_cred->second_ticket.length))
185         return(KRB5_NO_2ND_TKT);
186 
187 
188     /* check if we have the right TGT                    */
189     /* tkt->server must be equal to                      */
190     /* krbtgt/realmof(cred->server)@realmof(tgt->server) */
191 /*
192     {
193     krb5_principal tempprinc;
194         if (retval = krb5_tgtname(context,
195 		     krb5_princ_realm(context, in_cred->server),
196 		     krb5_princ_realm(context, tkt->server), &tempprinc))
197     	    return(retval);
198 
199         if (!krb5_principal_compare(context, tempprinc, tkt->server)) {
200             krb5_free_principal(context, tempprinc);
201 	    return (KRB5_PRINC_NOMATCH);
202         }
203     krb5_free_principal(context, tempprinc);
204     }
205 */
206 
207     if (in_cred->keyblock.enctype) {
208 	enctypes = (krb5_enctype *) malloc(sizeof(krb5_enctype)*2);
209 	if (!enctypes)
210 	    return ENOMEM;
211 	enctypes[0] = in_cred->keyblock.enctype;
212 	enctypes[1] = 0;
213     }
214 
215     retval = krb5_send_tgs(context, kdcoptions, &in_cred->times, enctypes,
216 			   in_cred->server, address, in_cred->authdata,
217 			   0,		/* no padata */
218 			   (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ?
219 			   &in_cred->second_ticket : NULL,
220 			   tkt, &tgsrep);
221     if (enctypes)
222 	free(enctypes);
223     if (retval) {
224 #ifdef DEBUG_REFERRALS
225         printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n",
226 	       error_message(retval));
227 #endif
228 	return retval;
229     }
230 
231     switch (tgsrep.message_type) {
232     case KRB5_TGS_REP:
233 	break;
234     case KRB5_ERROR:
235     default:
236 	if (krb5_is_krb_error(&tgsrep.response))
237 	    retval = decode_krb5_error(&tgsrep.response, &err_reply);
238 	else
239 	    retval = KRB5KRB_AP_ERR_MSG_TYPE;
240 
241 	if (retval)			/* neither proper reply nor error! */
242 	    goto error_4;
243 
244 	retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5;
245 	if (err_reply->text.length > 0) {
246 #if 0
247 	    const char *m;
248 #endif
249 	    switch (err_reply->error) {
250 	    case KRB_ERR_GENERIC:
251 		krb5_set_error_message(context, retval,
252 				       "KDC returned error string: %s",
253 				       err_reply->text.data);
254 		break;
255 	    default:
256 #if 0 /* We should stop the KDC from sending back this text, because
257 	 if the local language doesn't match the KDC's language, we'd
258 	 just wind up printing out the error message in two languages.
259 	 Well, when we get some localization.  Which is already
260 	 happening in KfM.  */
261 		m = error_message(retval);
262 		/* Special case: MIT KDC may return this same string
263 		   in the e-text field.  */
264 		if (strlen (m) == err_reply->text.length-1
265 		    && !strcmp(m, err_reply->text.data))
266 		    break;
267 		krb5_set_error_message(context, retval,
268 				       "%s (KDC supplied additional data: %s)",
269 				       m, err_reply->text.data);
270 #endif
271 		break;
272 	    }
273 	}
274 
275 	krb5_free_error(context, err_reply);
276 	goto error_4;
277     }
278 
279     if ((retval = krb5_decode_kdc_rep(context, &tgsrep.response,
280 				      &tkt->keyblock, &dec_rep)))
281 	goto error_4;
282 
283     if (dec_rep->msg_type != KRB5_TGS_REP) {
284 	retval = KRB5KRB_AP_ERR_MSG_TYPE;
285 	goto error_3;
286     }
287 
288     /* make sure the response hasn't been tampered with..... */
289     retval = 0;
290 
291     if (!krb5_principal_compare(context, dec_rep->client, tkt->client))
292 	retval = KRB5_KDCREP_MODIFIED;
293 
294     if (retval == 0)
295 	retval = check_reply_server(context, kdcoptions, in_cred, dec_rep);
296 
297     if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce)
298 	retval = KRB5_KDCREP_MODIFIED;
299 
300     if ((kdcoptions & KDC_OPT_POSTDATED) &&
301 	(in_cred->times.starttime != 0) &&
302     	(in_cred->times.starttime != dec_rep->enc_part2->times.starttime))
303 	retval = KRB5_KDCREP_MODIFIED;
304 
305     if ((in_cred->times.endtime != 0) &&
306 	(dec_rep->enc_part2->times.endtime > in_cred->times.endtime))
307 	retval = KRB5_KDCREP_MODIFIED;
308 
309     if ((kdcoptions & KDC_OPT_RENEWABLE) &&
310 	(in_cred->times.renew_till != 0) &&
311 	(dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till))
312 	retval = KRB5_KDCREP_MODIFIED;
313 
314     if ((kdcoptions & KDC_OPT_RENEWABLE_OK) &&
315 	(dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) &&
316 	(in_cred->times.endtime != 0) &&
317 	(dec_rep->enc_part2->times.renew_till > in_cred->times.endtime))
318  	retval = KRB5_KDCREP_MODIFIED;
319 
320     if (retval != 0)
321     	goto error_3;
322 
323     if (!in_cred->times.starttime &&
324 	!in_clock_skew(dec_rep->enc_part2->times.starttime,
325 		       tgsrep.request_time)) {
326 	retval = KRB5_KDCREP_SKEW;
327 	goto error_3;
328     }
329 
330     retval = krb5_kdcrep2creds(context, dec_rep, address,
331 			       &in_cred->second_ticket,  out_cred);
332 
333 error_3:;
334     memset(dec_rep->enc_part2->session->contents, 0,
335 	   dec_rep->enc_part2->session->length);
336     krb5_free_kdc_rep(context, dec_rep);
337 
338 error_4:;
339     free(tgsrep.response.data);
340 #ifdef DEBUG_REFERRALS
341     printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error");
342 #endif
343     return retval;
344 }
345