* get_in_tkt.c (krb5_get_in_tkt())
[krb5.git] / src / lib / krb5 / krb / get_in_tkt.c
1 /*
2  * lib/krb5/krb/get_in_tkt.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.  M.I.T. makes no representations about the suitability of
20  * this software for any purpose.  It is provided "as is" without express
21  * or implied warranty.
22  * 
23  *
24  * krb5_get_in_tkt()
25  */
26
27
28 #include <krb5/krb5.h>
29 #include <krb5/asn1.h>
30 #include <krb5/los-proto.h>
31 #include <krb5/ext-proto.h>
32
33 /*
34  All-purpose initial ticket routine, usually called via
35  krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
36
37  Attempts to get an initial ticket for creds->client to use server
38  creds->server, (realm is taken from creds->client), with options
39  options, and using creds->times.starttime, creds->times.endtime,
40  creds->times.renew_till as from, till, and rtime.  
41  creds->times.renew_till is ignored unless the RENEWABLE option is requested.
42
43  key_proc is called to fill in the key to be used for decryption.
44  keyseed is passed on to key_proc.
45
46  decrypt_proc is called to perform the decryption of the response (the
47  encrypted part is in dec_rep->enc_part; the decrypted part should be
48  allocated and filled into dec_rep->enc_part2
49  arg is passed on to decrypt_proc.
50
51  If addrs is non-NULL, it is used for the addresses requested.  If it is
52  null, the system standard addresses are used.
53
54  A succesful call will place the ticket in the credentials cache ccache
55  and fill in creds with the ticket information used/returned..
56
57  returns system errors, encryption errors
58
59  */
60
61
62 extern krb5_deltat krb5_clockskew;
63 #define in_clock_skew(date) (abs((date)-request.nonce) < krb5_clockskew)
64
65 /* some typedef's for the function args to make things look a bit cleaner */
66
67 typedef krb5_error_code (*git_key_proc) PROTOTYPE((krb5_context,
68                                                    const krb5_keytype,
69                                                    krb5_data *,
70                                                    krb5_const_pointer,
71                                                    krb5_keyblock **));
72
73 typedef krb5_error_code (*git_decrypt_proc) PROTOTYPE((krb5_context,
74                                                        const krb5_keyblock *,
75                                                        krb5_const_pointer,
76                                                        krb5_kdc_rep * ));
77 krb5_error_code
78 krb5_get_in_tkt(context, options, addrs, etypes, ptypes, key_proc, keyseed,
79                 decrypt_proc, decryptarg, creds, ccache, ret_as_reply)
80     krb5_context context;
81     const krb5_flags options;
82     krb5_address * const * addrs;
83     krb5_enctype * etypes;
84     krb5_preauthtype * ptypes;
85     git_key_proc key_proc;
86     krb5_const_pointer keyseed;
87     git_decrypt_proc decrypt_proc;
88     krb5_const_pointer decryptarg;
89     krb5_creds * creds;
90     krb5_ccache ccache;
91     krb5_kdc_rep ** ret_as_reply;
92 {
93     krb5_keytype keytype;
94     krb5_enctype etype;
95     krb5_kdc_req request;
96     krb5_kdc_rep *as_reply = 0;
97     krb5_error *err_reply;
98     krb5_error_code retval;
99     krb5_data *packet;
100     krb5_data reply;
101     krb5_keyblock *decrypt_key = 0;
102     krb5_timestamp time_now;
103     krb5_pa_data        *padata;
104     int f_salt = 0, use_salt = 0;
105     krb5_data salt;
106     char k4_version;            /* same type as *(krb5_data::data) */
107
108     if (! krb5_realm_compare(context, creds->client, creds->server))
109         return KRB5_IN_TKT_REALM_MISMATCH;
110
111     if (ret_as_reply)
112         *ret_as_reply = 0;
113     
114     request.msg_type = KRB5_AS_REQ;
115     if (!addrs)
116         krb5_os_localaddr(&request.addresses);
117     else
118         request.addresses = (krb5_address **) addrs;
119
120     reply.data = 0;
121
122     if (1) {
123             decrypt_key = 0;
124             request.padata = 0;
125     } else {
126             /* Pre authentication is not yet supported */
127
128             /*
129              * First, we get the user's key.  We assume we will need
130              * it for the pre-authentication.  Actually, this could
131              * possibly not be the case, but it's usually true.
132              *
133              * XXX Problem here: if we're doing preauthentication,
134              * we're getting the key before we get the KDC hit as to
135              * which salting algorithm to use; hence, we're using the
136              * default.  But if we're changing salts, because of a
137              * realm renaming, or some such, this won't work.
138              */
139 /*    retval = (*key_proc)(context, keytype, &decrypt_key, keyseed, 0); */
140             if (retval)
141                     return retval;
142             request.padata = (krb5_pa_data **) malloc(sizeof(krb5_pa_data *)
143                                                       * 2);
144             if (!request.padata) {
145                 retval = ENOMEM;
146                 goto cleanup;
147             }
148             
149           /*  retval = krb5_obtain_padata(context, ptypes[0], creds->client,
150                                         request.addresses, decrypt_key,
151                                         &padata); */
152             if (retval)
153                 goto cleanup;
154 /*          request.padata[0] = padata; */
155             request.padata[1] = 0;
156     }
157     
158     request.kdc_options = options;
159     request.client = creds->client;
160     request.server = creds->server;
161
162     request.from = creds->times.starttime;
163     request.till = creds->times.endtime;
164     request.rtime = creds->times.renew_till;
165     if (retval = krb5_timeofday(context, &time_now))
166         goto cleanup;
167
168     /* XXX we know they are the same size... */
169     request.nonce = (krb5_int32) time_now;
170
171     if (etypes) 
172         request.etype = etypes;
173     else 
174         krb5_get_default_in_tkt_etypes(context, &request.etype);
175     for (request.netypes = 0;request.etype[request.netypes];request.netypes++);
176     request.authorization_data.ciphertext.length = 0;
177     request.authorization_data.ciphertext.data = 0;
178     request.unenc_authdata = 0;
179     request.second_ticket = 0;
180
181     /* encode & send to KDC */
182     if (retval = encode_krb5_as_req(&request, &packet))
183         goto cleanup;
184
185     k4_version = packet->data[0];
186     retval = krb5_sendto_kdc(context, packet, 
187                              krb5_princ_realm(context, creds->client), &reply);
188     krb5_free_data(context, packet);
189     if (retval)
190         goto cleanup;
191
192     /* now decode the reply...could be error or as_rep */
193
194     if (krb5_is_krb_error(&reply)) {
195         if (retval = decode_krb5_error(&reply, &err_reply))
196             /* some other error code--??? */        
197             goto cleanup;
198     
199         /* it was an error */
200
201         if ((err_reply->ctime != request.nonce) ||
202             !krb5_principal_compare(context, err_reply->server, request.server) ||
203             !krb5_principal_compare(context, err_reply->client, request.client))
204             retval = KRB5_KDCREP_MODIFIED;
205         else
206             retval = err_reply->error + ERROR_TABLE_BASE_krb5;
207
208         /* XXX somehow make error msg text available to application? */
209
210         krb5_free_error(context, err_reply);
211         goto cleanup;
212     }
213
214     if (!krb5_is_as_rep(&reply)) {
215 /* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
216 #define V4_KRB_PROT_VERSION     4
217 #define V4_AUTH_MSG_ERR_REPLY   (5<<1)
218         /* check here for V4 reply */
219         unsigned int t_switch;
220
221         /* From v4 g_in_tkt.c: This used to be
222            switch (pkt_msg_type(rpkt) & ~1) {
223            but SCO 3.2v4 cc compiled that incorrectly.  */
224         t_switch = reply.data[1];
225         t_switch &= ~1;
226
227         if (t_switch == V4_AUTH_MSG_ERR_REPLY
228             && (reply.data[0] == V4_KRB_PROT_VERSION
229                 || reply.data[0] == k4_version)) {
230             retval = KRB5KRB_AP_ERR_V4_REPLY;
231         } else {
232             retval = KRB5KRB_AP_ERR_MSG_TYPE;
233         }
234         goto cleanup;
235     }
236     if (retval = decode_krb5_as_rep(&reply, &as_reply))
237         /* some other error code ??? */
238         goto cleanup;
239
240     if (as_reply->msg_type != KRB5_AS_REP) {
241         retval = KRB5KRB_AP_ERR_MSG_TYPE;
242         goto cleanup;
243     }
244
245     /* Encryption type, keytype, */
246     etype = as_reply->ticket->enc_part.etype;
247     keytype = krb5_csarray[etype]->system->proto_keytype;
248
249     /* and salt */
250     if (as_reply->padata) {
251         krb5_pa_data **ptr;
252
253         for (ptr = as_reply->padata; *ptr; ptr++) {
254             if ((*ptr)->pa_type == KRB5_PADATA_PW_SALT) {
255                 /* use KDC-supplied salt, instead of default */
256                 salt.data = (char *)(*ptr)->contents;
257                 salt.length = (*ptr)->length;
258                 use_salt = 1;
259                 break;
260             }
261         }
262     } 
263     if (!use_salt) {
264         /* need to use flattened principal */
265         if (retval = krb5_principal2salt(context, creds->client, &salt))
266             return(retval);
267         f_salt = 1;
268     }
269
270     /* it was a kdc_rep--decrypt & check */
271     /* Generate the key, if we haven't done so already. */
272     if (!decrypt_key) {
273             if (retval = (*key_proc)(context, keytype, & salt, keyseed,
274                                      &decrypt_key))
275                 goto cleanup;
276     }
277     
278     if (retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply))
279         goto cleanup;
280
281     krb5_free_keyblock(context, decrypt_key);
282     decrypt_key = 0;
283     
284     /* check the contents for sanity: */
285     if (!as_reply->enc_part2->times.starttime)
286         as_reply->enc_part2->times.starttime =
287             as_reply->enc_part2->times.authtime;
288     if (!krb5_principal_compare(context, as_reply->client, request.client)
289         || !krb5_principal_compare(context, as_reply->enc_part2->server, request.server)
290         || !krb5_principal_compare(context, as_reply->ticket->server, request.server)
291         || (request.nonce != as_reply->enc_part2->nonce)
292         /* XXX check for extraneous flags */
293         /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
294         || ((request.from != 0) &&
295             (request.from != as_reply->enc_part2->times.starttime))
296         || ((request.till != 0) &&
297             (as_reply->enc_part2->times.endtime > request.till))
298         || ((request.kdc_options & KDC_OPT_RENEWABLE) &&
299             (request.rtime != 0) &&
300             (as_reply->enc_part2->times.renew_till > request.rtime))
301         || ((request.kdc_options & KDC_OPT_RENEWABLE_OK) &&
302             (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
303             (request.till != 0) &&
304             (as_reply->enc_part2->times.renew_till > request.till))
305         ) {
306         retval = KRB5_KDCREP_MODIFIED;
307         goto cleanup;
308     }
309     if ((request.from == 0) &&
310         !in_clock_skew(as_reply->enc_part2->times.starttime)) {
311         retval = KRB5_KDCREP_MODIFIED;
312         goto cleanup;
313     }
314     
315
316     /* XXX issue warning if as_reply->enc_part2->key_exp is nearby */
317         
318     /* fill in the credentials */
319     if (retval = krb5_copy_keyblock_contents(context, 
320                                              as_reply->enc_part2->session,
321                                              &creds->keyblock))
322         goto cleanup;
323     creds->keyblock.etype = as_reply->ticket->enc_part.etype;
324
325     creds->times = as_reply->enc_part2->times;
326     creds->is_skey = FALSE;             /* this is an AS_REQ, so cannot
327                                            be encrypted in skey */
328     creds->ticket_flags = as_reply->enc_part2->flags;
329     if (retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
330                                      &creds->addresses))
331         goto cred_cleanup;
332
333     creds->second_ticket.length = 0;
334     creds->second_ticket.data = 0;
335
336     if (retval = encode_krb5_ticket(as_reply->ticket, &packet))
337         goto cred_cleanup;
338
339     creds->ticket = *packet;
340     krb5_xfree(packet);
341
342     /* store it in the ccache! */
343     if (retval = krb5_cc_store_cred(context, ccache, creds))
344         goto cred_cleanup;
345
346     if (ret_as_reply) {
347         *ret_as_reply = as_reply;
348         as_reply = 0;
349     }
350
351     retval = 0;
352     
353 cleanup:
354     if (f_salt)
355         krb5_xfree(salt.data);
356     if (as_reply)
357         krb5_free_kdc_rep(context, as_reply);
358     if (reply.data)
359         free(reply.data);
360     if (decrypt_key)
361         krb5_free_keyblock(context, decrypt_key);
362     if (request.padata)
363         free(request.padata);
364     if (!addrs)
365         krb5_free_addresses(context, request.addresses);
366     return retval;
367     
368     /*
369      * Clean up left over mess in credentials structure, in case of 
370      * error
371      */
372 cred_cleanup:
373     if (creds->keyblock.contents) {
374         memset((char *)creds->keyblock.contents, 0, creds->keyblock.length);
375         krb5_xfree(creds->keyblock.contents);
376         creds->keyblock.contents = 0;
377         creds->keyblock.length = 0;
378     }
379     if (creds->ticket.data) {
380         krb5_xfree(creds->ticket.data);
381         creds->ticket.data = 0;
382     }
383     if (creds->addresses) {
384         krb5_free_addresses(context, creds->addresses);
385         creds->addresses = 0;
386     }
387     goto cleanup;
388 }
389