xref: /illumos-gate/usr/src/cmd/krb5/krb5kdc/do_as_req.c (revision 2983dda76a6d296fdb560c88114fe41caad1b84f)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 
7 /*
8  * kdc/do_as_req.c
9  *
10  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
11  * All Rights Reserved.
12  *
13  * Export of this software from the United States of America may
14  *   require a specific license from the United States Government.
15  *   It is the responsibility of any person or organization contemplating
16  *   export to obtain such a license before exporting.
17  *
18  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
19  * distribute this software and its documentation for any purpose and
20  * without fee is hereby granted, provided that the above copyright
21  * notice appear in all copies and that both that copyright notice and
22  * this permission notice appear in supporting documentation, and that
23  * the name of M.I.T. not be used in advertising or publicity pertaining
24  * to distribution of the software without specific, written prior
25  * permission.  Furthermore if you modify this software you must label
26  * your software as modified software and not distribute it in such a
27  * fashion that it might be confused with the original M.I.T. software.
28  * M.I.T. makes no representations about the suitability of
29  * this software for any purpose.  It is provided "as is" without express
30  * or implied warranty.
31  *
32  *
33  * KDC Routines to deal with AS_REQ's
34  */
35 
36 #define NEED_SOCKETS
37 #include "k5-int.h"
38 #include "com_err.h"
39 
40 #include <syslog.h>
41 #ifdef HAVE_NETINET_IN_H
42 #include <sys/types.h>
43 #include <netinet/in.h>
44 #ifndef hpux
45 #include <arpa/inet.h>
46 #endif	/* hpux */
47 #endif /* HAVE_NETINET_IN_H */
48 
49 #include "kdc_util.h"
50 #include "policy.h"
51 #include "adm.h"
52 #include "adm_proto.h"
53 #include "extern.h"
54 
55 static krb5_error_code prepare_error_as (krb5_kdc_req *, int, krb5_data *,
56 					 krb5_data **, const char *);
57 
58 /*ARGSUSED*/
59 krb5_error_code
60 process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
61 	       const krb5_fulladdr *from, krb5_data **response)
62 {
63     krb5_db_entry client, server;
64     krb5_kdc_rep reply;
65     krb5_enc_kdc_rep_part reply_encpart;
66     krb5_ticket ticket_reply;
67     krb5_enc_tkt_part enc_tkt_reply;
68     krb5_error_code errcode;
69     int c_nprincs = 0, s_nprincs = 0;
70     krb5_boolean more;
71     krb5_timestamp kdc_time, authtime, etime = 0;
72     krb5_keyblock session_key;
73     krb5_keyblock encrypting_key;
74     const char *status;
75     krb5_key_data  *server_key, *client_key;
76     krb5_enctype useenctype;
77 #ifdef	KRBCONF_KDC_MODIFIES_KDB
78     krb5_boolean update_client = 0;
79 #endif	/* KRBCONF_KDC_MODIFIES_KDB */
80     krb5_data e_data;
81     register int i;
82     krb5_timestamp until, rtime;
83     long long tmp_client_times, tmp_server_times, tmp_realm_times;
84     char *cname = 0, *sname = 0;
85     const char *fromstring = 0;
86     char ktypestr[128];
87     char rep_etypestr[128];
88     char fromstringbuf[70];
89     void *pa_context = NULL;
90     struct in_addr from_in4;	/* IPv4 address of sender */
91 
92     ticket_reply.enc_part.ciphertext.data = 0;
93     e_data.data = 0;
94     (void) memset(&encrypting_key, 0, sizeof(krb5_keyblock));
95     reply.padata = 0; /* avoid bogus free in error_out */
96     (void) memset(&session_key, 0, sizeof(krb5_keyblock));
97     enc_tkt_reply.authorization_data = NULL;
98 
99     ktypes2str(ktypestr, sizeof(ktypestr),
100 	       request->nktypes, request->ktype);
101 
102 	(void) memcpy(&from_in4, from->address->contents, /* SUNW */
103 		    sizeof (struct in_addr));
104 
105     fromstring = inet_ntop(ADDRTYPE2FAMILY (from->address->addrtype),
106 			   &from_in4,
107 			   fromstringbuf, sizeof(fromstringbuf));
108     if (!fromstring)
109 	fromstring = "<unknown>";
110 
111     if (!request->client) {
112 	status = "NULL_CLIENT";
113 	errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
114 	goto errout;
115     }
116     if ((errcode = krb5_unparse_name(kdc_context, request->client, &cname))) {
117 	status = "UNPARSING_CLIENT";
118 	goto errout;
119     }
120     limit_string(cname);
121     if (!request->server) {
122 	status = "NULL_SERVER";
123 	errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
124 	goto errout;
125     }
126     if ((errcode = krb5_unparse_name(kdc_context, request->server, &sname))) {
127 	status = "UNPARSING_SERVER";
128 	goto errout;
129     }
130     limit_string(sname);
131 
132     c_nprincs = 1;
133     if ((errcode = krb5_db_get_principal(kdc_context, request->client,
134 					 &client, &c_nprincs, &more))) {
135 	status = "LOOKING_UP_CLIENT";
136 	c_nprincs = 0;
137 	goto errout;
138     }
139     if (more) {
140 	status = "NON-UNIQUE_CLIENT";
141 	errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE;
142 	goto errout;
143     } else if (c_nprincs != 1) {
144 	status = "CLIENT_NOT_FOUND";
145 #ifdef KRBCONF_VAGUE_ERRORS
146 	errcode = KRB5KRB_ERR_GENERIC;
147 #else
148 	errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
149 #endif
150 	goto errout;
151     }
152 
153     s_nprincs = 1;
154     if ((errcode = krb5_db_get_principal(kdc_context, request->server, &server,
155 					 &s_nprincs, &more))) {
156 	status = "LOOKING_UP_SERVER";
157 	goto errout;
158     }
159     if (more) {
160 	status = "NON-UNIQUE_SERVER";
161 	errcode = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE;
162 	goto errout;
163     } else if (s_nprincs != 1) {
164 	status = "SERVER_NOT_FOUND";
165 	errcode = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
166 	goto errout;
167     }
168 
169     if ((errcode = krb5_timeofday(kdc_context, &kdc_time))) {
170 	status = "TIMEOFDAY";
171 	goto errout;
172     }
173 
174     if ((errcode = validate_as_request(request, client, server,
175 				      kdc_time, &status))) {
176 	if (!status)
177 	    status = "UNKNOWN_REASON";
178 	errcode += ERROR_TABLE_BASE_krb5;
179 	goto errout;
180     }
181 
182     /*
183      * Select the keytype for the ticket session key.
184      */
185     if ((useenctype = select_session_keytype(kdc_context, &server,
186 					     request->nktypes,
187 					     request->ktype)) == 0) {
188 	/* unsupported ktype */
189 	status = "BAD_ENCRYPTION_TYPE";
190 	errcode = KRB5KDC_ERR_ETYPE_NOSUPP;
191 	goto errout;
192     }
193 
194     if ((errcode = krb5_c_make_random_key(kdc_context, useenctype,
195 					  &session_key))) {
196 	/* random key failed */
197 	status = "RANDOM_KEY_FAILED";
198 	goto errout;
199     }
200 
201     ticket_reply.server = request->server;
202 
203     enc_tkt_reply.flags = 0;
204     setflag(enc_tkt_reply.flags, TKT_FLG_INITIAL);
205 
206     	/* It should be noted that local policy may affect the  */
207         /* processing of any of these flags.  For example, some */
208         /* realms may refuse to issue renewable tickets         */
209 
210     if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE))
211 	setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE);
212 
213     if (isflagset(request->kdc_options, KDC_OPT_PROXIABLE))
214 	    setflag(enc_tkt_reply.flags, TKT_FLG_PROXIABLE);
215 
216     if (isflagset(request->kdc_options, KDC_OPT_ALLOW_POSTDATE))
217 	    setflag(enc_tkt_reply.flags, TKT_FLG_MAY_POSTDATE);
218 
219     enc_tkt_reply.session = &session_key;
220     enc_tkt_reply.client = request->client;
221     enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
222     enc_tkt_reply.transited.tr_contents = empty_string; /* equivalent of "" */
223 
224     enc_tkt_reply.times.authtime = kdc_time;
225 
226     if (isflagset(request->kdc_options, KDC_OPT_POSTDATED)) {
227 	setflag(enc_tkt_reply.flags, TKT_FLG_POSTDATED);
228 	setflag(enc_tkt_reply.flags, TKT_FLG_INVALID);
229 	enc_tkt_reply.times.starttime = request->from;
230     } else
231 	enc_tkt_reply.times.starttime = kdc_time;
232 
233     until = (request->till == 0) ? kdc_infinity : request->till;
234 	/* These numbers could easily be large
235 	 * use long long variables to ensure that they don't
236 	 * result in negative values when added.
237 	*/
238 
239     tmp_client_times = (long long) enc_tkt_reply.times.starttime + client.max_life;
240 
241     tmp_server_times = (long long) enc_tkt_reply.times.starttime + server.max_life;
242 
243     tmp_realm_times = (long long) enc_tkt_reply.times.starttime + max_life_for_realm;
244 
245     enc_tkt_reply.times.endtime =
246 	min(until,
247 	    min(tmp_client_times,
248 		min(tmp_server_times,
249 			min(tmp_realm_times,KRB5_KDB_EXPIRATION))));
250 
251     if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE_OK) &&
252 	!isflagset(client.attributes, KRB5_KDB_DISALLOW_RENEWABLE) &&
253 	(enc_tkt_reply.times.endtime < request->till)) {
254 
255 	/* we set the RENEWABLE option for later processing */
256 
257 	setflag(request->kdc_options, KDC_OPT_RENEWABLE);
258 	request->rtime = request->till;
259     }
260     rtime = (request->rtime == 0) ? kdc_infinity : request->rtime;
261 
262     if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE)) {
263 	/*
264 	 * XXX Should we squelch the output renew_till to be no
265 	 * earlier than the endtime of the ticket?
266 	 */
267 	setflag(enc_tkt_reply.flags, TKT_FLG_RENEWABLE);
268 	tmp_client_times = (double) enc_tkt_reply.times.starttime + client.max_renewable_life;
269 
270     	tmp_server_times = (double) enc_tkt_reply.times.starttime + server.max_renewable_life;
271 
272     	tmp_realm_times = (double) enc_tkt_reply.times.starttime + max_renewable_life_for_realm;
273 
274 	enc_tkt_reply.times.renew_till =
275 	    min(rtime, min(tmp_client_times,
276 		       min(tmp_server_times,
277 			   min(tmp_realm_times,KRB5_KDB_EXPIRATION))));
278     } else
279 	enc_tkt_reply.times.renew_till = 0; /* XXX */
280 
281     /* starttime is optional, and treated as authtime if not present.
282        so we can nuke it if it matches */
283     if (enc_tkt_reply.times.starttime == enc_tkt_reply.times.authtime)
284 	enc_tkt_reply.times.starttime = 0;
285 
286     enc_tkt_reply.caddrs = request->addresses;
287     enc_tkt_reply.authorization_data = 0;
288 
289     /*
290      * Check the preauthentication if it is there.
291      */
292     if (request->padata) {
293 	errcode = check_padata(kdc_context, &client, req_pkt, request,
294 			       &enc_tkt_reply, &pa_context, &e_data);
295 	if (errcode) {
296 #ifdef KRBCONF_KDC_MODIFIES_KDB
297 	    /*
298 	     * Note: this doesn't work if you're using slave servers!!!
299 	     * It also causes the database to be modified (and thus
300 	     * need to be locked) frequently.
301 	     */
302 	    if (client.fail_auth_count < KRB5_MAX_FAIL_COUNT) {
303 		client.fail_auth_count = client.fail_auth_count + 1;
304 		if (client.fail_auth_count == KRB5_MAX_FAIL_COUNT) {
305 		    client.attributes |= KRB5_KDB_DISALLOW_ALL_TIX;
306 		}
307 	    }
308 	    client.last_failed = kdc_time;
309 	    update_client = 1;
310 #endif
311 	    status = "PREAUTH_FAILED";
312 #ifdef KRBCONF_VAGUE_ERRORS
313 	    errcode = KRB5KRB_ERR_GENERIC;
314 #endif
315 	    goto errout;
316 	}
317     }
318 
319     /*
320      * Final check before handing out ticket: If the client requires
321      * preauthentication, verify that the proper kind of
322      * preauthentication was carried out.
323      */
324     status = missing_required_preauth(&client, &server, &enc_tkt_reply);
325     if (status) {
326 	errcode = KRB5KDC_ERR_PREAUTH_REQUIRED;
327 	get_preauth_hint_list(request, &client, &server, &e_data);
328 	goto errout;
329     }
330 
331     ticket_reply.enc_part2 = &enc_tkt_reply;
332 
333     /*
334      * Find the server key
335      */
336     if ((errcode = krb5_dbe_find_enctype(kdc_context, &server,
337 					 -1, /* ignore keytype */
338 					 -1,		/* Ignore salttype */
339 					 0,		/* Get highest kvno */
340 					 &server_key))) {
341 	status = "FINDING_SERVER_KEY";
342 	goto errout;
343     }
344 
345     /* convert server.key into a real key (it may be encrypted
346        in the database) */
347     if ((errcode = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock,
348 					       server_key, &encrypting_key,
349 					       NULL))) {
350 	status = "DECRYPT_SERVER_KEY";
351 	goto errout;
352     }
353 
354     errcode = krb5_encrypt_tkt_part(kdc_context, &encrypting_key, &ticket_reply);
355     krb5_free_keyblock_contents(kdc_context, &encrypting_key);
356     encrypting_key.contents = 0;
357     if (errcode) {
358 	status = "ENCRYPTING_TICKET";
359 	goto errout;
360     }
361     ticket_reply.enc_part.kvno = server_key->key_data_kvno;
362 
363     /*
364      * Find the appropriate client key.  We search in the order specified
365      * by request keytype list.
366      */
367     client_key = (krb5_key_data *) NULL;
368     for (i = 0; i < request->nktypes; i++) {
369 	useenctype = request->ktype[i];
370 	if (!krb5_c_valid_enctype(useenctype))
371 	    continue;
372 
373 	if (!krb5_dbe_find_enctype(kdc_context, &client, useenctype, -1,
374 				   0, &client_key))
375 	    break;
376     }
377     if (!(client_key)) {
378 	/* Cannot find an appropriate key */
379 	status = "CANT_FIND_CLIENT_KEY";
380 	errcode = KRB5KDC_ERR_ETYPE_NOSUPP;
381 	goto errout;
382     }
383 
384     /* convert client.key_data into a real key */
385     if ((errcode = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock,
386 					       client_key, &encrypting_key,
387 					       NULL))) {
388 	status = "DECRYPT_CLIENT_KEY";
389 	goto errout;
390     }
391     encrypting_key.enctype = useenctype;
392 
393     /* Start assembling the response */
394     reply.msg_type = KRB5_AS_REP;
395     reply.client = request->client;
396     reply.ticket = &ticket_reply;
397     reply_encpart.session = &session_key;
398     if ((errcode = fetch_last_req_info(&client, &reply_encpart.last_req))) {
399 	status = "FETCH_LAST_REQ";
400 	goto errout;
401     }
402     reply_encpart.nonce = request->nonce;
403 
404     /*
405      * Take the minimum of expiration or pw_expiration if not zero.
406      */
407     if (client.expiration != 0 && client.pw_expiration != 0)
408     	etime = min(client.expiration, client.pw_expiration);
409     else
410 	etime = client.expiration ? client.expiration : client.pw_expiration;
411 
412     reply_encpart.key_exp = etime;
413     reply_encpart.flags = enc_tkt_reply.flags;
414     reply_encpart.server = ticket_reply.server;
415 
416     /* copy the time fields EXCEPT for authtime; it's location
417        is used for ktime */
418     reply_encpart.times = enc_tkt_reply.times;
419     reply_encpart.times.authtime = authtime = kdc_time;
420 
421     reply_encpart.caddrs = enc_tkt_reply.caddrs;
422 
423     /* Fetch the padata info to be returned */
424     errcode = return_padata(kdc_context, &client, req_pkt, request,
425 			    &reply, client_key, &encrypting_key, &pa_context);
426     if (errcode) {
427 	status = "KDC_RETURN_PADATA";
428 	goto errout;
429     }
430 
431     /* now encode/encrypt the response */
432 
433     reply.enc_part.enctype = encrypting_key.enctype;
434 
435     errcode = krb5_encode_kdc_rep(kdc_context, KRB5_AS_REP, &reply_encpart,
436 				  0, &encrypting_key,  &reply, response);
437     krb5_free_keyblock_contents(kdc_context, &encrypting_key);
438     encrypting_key.contents = 0;
439     reply.enc_part.kvno = client_key->key_data_kvno;
440 
441     if (errcode) {
442 	status = "ENCODE_KDC_REP";
443 	goto errout;
444     }
445 
446     /* these parts are left on as a courtesy from krb5_encode_kdc_rep so we
447        can use them in raw form if needed.  But, we don't... */
448     memset(reply.enc_part.ciphertext.data, 0, reply.enc_part.ciphertext.length);
449     free(reply.enc_part.ciphertext.data);
450 
451     /* SUNW14resync:
452      * The third argument to audit_krb5kdc_as_req() is zero as the local
453      * portnumber is no longer passed to process_as_req().
454      */
455     audit_krb5kdc_as_req(&from_in4, (in_port_t)from->port, 0,
456                         cname, sname, 0);
457     rep_etypes2str(rep_etypestr, sizeof(rep_etypestr), &reply);
458     krb5_klog_syslog(LOG_INFO,
459 		     "AS_REQ (%s) %s: ISSUE: authtime %d, "
460 		     "%s, %s for %s",
461 		     ktypestr,
462 	             fromstring, authtime,
463 		     rep_etypestr,
464 		     cname, sname);
465 
466 #ifdef	KRBCONF_KDC_MODIFIES_KDB
467     /*
468      * If we get this far, we successfully did the AS_REQ.
469      */
470     client.last_success = kdc_time;
471     client.fail_auth_count = 0;
472     update_client = 1;
473 #endif	/* KRBCONF_KDC_MODIFIES_KDB */
474 
475 errout:
476     if (pa_context)
477 	free_padata_context(kdc_context, &pa_context);
478 
479     if (status) {
480 	const char * emsg = 0;
481 	if (errcode)
482 	    emsg = krb5_get_error_message (kdc_context, errcode);
483 
484 	    audit_krb5kdc_as_req(&from_in4, (in_port_t)from->port,
485 				0, cname, sname, errcode);
486         krb5_klog_syslog(LOG_INFO, "AS_REQ (%s) %s: %s: %s for %s%s%s",
487 			 ktypestr,
488 	       fromstring, status,
489 	       cname ? cname : "<unknown client>",
490 	       sname ? sname : "<unknown server>",
491 	       errcode ? ", " : "",
492 	       errcode ? emsg : "");
493 	if (errcode)
494 	    krb5_free_error_message (kdc_context, emsg);
495     }
496     if (errcode) {
497         int got_err = 0;
498 	if (status == 0) {
499 	    status = krb5_get_error_message (kdc_context, errcode);
500 	    got_err = 1;
501 	}
502 	errcode -= ERROR_TABLE_BASE_krb5;
503 	if (errcode < 0 || errcode > 128)
504 	    errcode = KRB_ERR_GENERIC;
505 
506 	errcode = prepare_error_as(request, errcode, &e_data, response,
507 				   status);
508 	if (got_err) {
509 	    krb5_free_error_message (kdc_context, status);
510 	    status = 0;
511 	}
512     }
513 
514     if (enc_tkt_reply.authorization_data != NULL)
515 	krb5_free_authdata(kdc_context, enc_tkt_reply.authorization_data);
516     if (encrypting_key.contents)
517 	krb5_free_keyblock_contents(kdc_context, &encrypting_key);
518     if (reply.padata)
519 	krb5_free_pa_data(kdc_context, reply.padata);
520 
521     if (cname)
522 	    free(cname);
523     if (sname)
524 	    free(sname);
525     if (c_nprincs) {
526 #ifdef	KRBCONF_KDC_MODIFIES_KDB
527 	if (update_client) {
528 	    krb5_db_put_principal(kdc_context, &client, &c_nprincs);
529 	    /*
530 	     * ptooey.  We want krb5_db_sync() or something like that.
531 	     */
532 	    krb5_db_fini(kdc_context);
533 	    if (kdc_active_realm->realm_dbname)
534 		krb5_db_set_name(kdc_active_realm->realm_context,
535 				 kdc_active_realm->realm_dbname);
536 	    krb5_db_init(kdc_context);
537 	    /* Reset master key */
538 	    krb5_db_set_mkey(kdc_context, &kdc_active_realm->realm_mkey);
539 	}
540 #endif	/* KRBCONF_KDC_MODIFIES_KDB */
541 	krb5_db_free_principal(kdc_context, &client, c_nprincs);
542     }
543     if (s_nprincs)
544 	krb5_db_free_principal(kdc_context, &server, s_nprincs);
545     if (session_key.contents)
546 	krb5_free_keyblock_contents(kdc_context, &session_key);
547     if (ticket_reply.enc_part.ciphertext.data) {
548 	memset(ticket_reply.enc_part.ciphertext.data , 0,
549 	       ticket_reply.enc_part.ciphertext.length);
550 	free(ticket_reply.enc_part.ciphertext.data);
551     }
552 
553     krb5_free_data_contents(kdc_context, &e_data);
554 
555     return errcode;
556 }
557 
558 static krb5_error_code
559 prepare_error_as (krb5_kdc_req *request, int error, krb5_data *e_data,
560 		  krb5_data **response, const char *status)
561 {
562     krb5_error errpkt;
563     krb5_error_code retval;
564     krb5_data *scratch;
565 
566     errpkt.ctime = request->nonce;
567     errpkt.cusec = 0;
568 
569     if ((retval = krb5_us_timeofday(kdc_context, &errpkt.stime,
570 				    &errpkt.susec)))
571 	return(retval);
572     errpkt.error = error;
573     errpkt.server = request->server;
574     errpkt.client = request->client;
575     errpkt.text.length = strlen(status)+1;
576     if (!(errpkt.text.data = malloc(errpkt.text.length)))
577 	return ENOMEM;
578     (void) strcpy(errpkt.text.data, status);
579 
580     if (!(scratch = (krb5_data *)malloc(sizeof(*scratch)))) {
581 	free(errpkt.text.data);
582 	return ENOMEM;
583     }
584     if (e_data && e_data->data) {
585 	errpkt.e_data = *e_data;
586     } else {
587 	errpkt.e_data.length = 0;
588 	errpkt.e_data.data = 0;
589     }
590 
591     retval = krb5_mk_error(kdc_context, &errpkt, scratch);
592     free(errpkt.text.data);
593     if (retval)
594 	free(scratch);
595     else
596 	*response = scratch;
597 
598     return retval;
599 }
600