Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/kdc/kdc_authdata.c
34878 views
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* kdc/kdc_authdata.c - Authorization data routines for the KDC */
3
/*
4
* Copyright (C) 2007 Apple Inc. All Rights Reserved.
5
* Copyright (C) 2008, 2009 by the Massachusetts Institute of Technology.
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
#include "k5-int.h"
28
#include "kdc_util.h"
29
#include "extern.h"
30
#include <stdio.h>
31
#include "adm_proto.h"
32
33
#include <syslog.h>
34
35
#include <assert.h>
36
#include <krb5/kdcauthdata_plugin.h>
37
38
typedef struct kdcauthdata_handle_st {
39
struct krb5_kdcauthdata_vtable_st vt;
40
krb5_kdcauthdata_moddata data;
41
} kdcauthdata_handle;
42
43
static kdcauthdata_handle *authdata_modules;
44
static size_t n_authdata_modules;
45
46
/* Load authdata plugin modules. */
47
krb5_error_code
48
load_authdata_plugins(krb5_context context)
49
{
50
krb5_error_code ret;
51
krb5_plugin_initvt_fn *modules = NULL, *mod;
52
kdcauthdata_handle *list, *h;
53
size_t count;
54
55
ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_KDCAUTHDATA, &modules);
56
if (ret)
57
return ret;
58
59
/* Allocate a large enough list of handles. */
60
for (count = 0; modules[count] != NULL; count++);
61
list = calloc(count + 1, sizeof(*list));
62
if (list == NULL) {
63
k5_plugin_free_modules(context, modules);
64
return ENOMEM;
65
}
66
67
/* Initialize each module's vtable and module data. */
68
count = 0;
69
for (mod = modules; *mod != NULL; mod++) {
70
h = &list[count];
71
memset(h, 0, sizeof(*h));
72
ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&h->vt);
73
if (ret) /* Version mismatch, keep going. */
74
continue;
75
if (h->vt.init != NULL) {
76
ret = h->vt.init(context, &h->data);
77
if (ret) {
78
kdc_err(context, ret, _("while loading authdata module %s"),
79
h->vt.name);
80
continue;
81
}
82
}
83
count++;
84
}
85
86
authdata_modules = list;
87
n_authdata_modules = count;
88
k5_plugin_free_modules(context, modules);
89
return 0;
90
}
91
92
krb5_error_code
93
unload_authdata_plugins(krb5_context context)
94
{
95
kdcauthdata_handle *h;
96
size_t i;
97
98
for (i = 0; i < n_authdata_modules; i++) {
99
h = &authdata_modules[i];
100
if (h->vt.fini != NULL)
101
h->vt.fini(context, h->data);
102
}
103
free(authdata_modules);
104
authdata_modules = NULL;
105
return 0;
106
}
107
108
/* Return true if authdata should be filtered when copying from untrusted
109
* authdata. If desired_type is non-zero, look only for that type. */
110
static krb5_boolean
111
is_kdc_issued_authdatum(krb5_authdata *authdata,
112
krb5_authdatatype desired_type)
113
{
114
krb5_boolean result = FALSE;
115
krb5_authdatatype ad_type;
116
unsigned int i, count = 0;
117
krb5_authdatatype *ad_types, *containee_types = NULL;
118
119
if (authdata->ad_type == KRB5_AUTHDATA_IF_RELEVANT) {
120
if (krb5int_get_authdata_containee_types(NULL, authdata, &count,
121
&containee_types) != 0)
122
goto cleanup;
123
ad_types = containee_types;
124
} else {
125
ad_type = authdata->ad_type;
126
count = 1;
127
ad_types = &ad_type;
128
}
129
130
for (i = 0; i < count; i++) {
131
switch (ad_types[i]) {
132
case KRB5_AUTHDATA_SIGNTICKET:
133
case KRB5_AUTHDATA_KDC_ISSUED:
134
case KRB5_AUTHDATA_WIN2K_PAC:
135
case KRB5_AUTHDATA_CAMMAC:
136
case KRB5_AUTHDATA_AUTH_INDICATOR:
137
result = desired_type ? (desired_type == ad_types[i]) : TRUE;
138
break;
139
default:
140
result = FALSE;
141
break;
142
}
143
if (result)
144
break;
145
}
146
147
cleanup:
148
free(containee_types);
149
return result;
150
}
151
152
/* Return true if authdata contains any mandatory-for-KDC elements. */
153
static krb5_boolean
154
has_mandatory_for_kdc_authdata(krb5_context context, krb5_authdata **authdata)
155
{
156
int i;
157
158
if (authdata == NULL)
159
return FALSE;
160
for (i = 0; authdata[i] != NULL; i++) {
161
if (authdata[i]->ad_type == KRB5_AUTHDATA_MANDATORY_FOR_KDC)
162
return TRUE;
163
}
164
return FALSE;
165
}
166
167
/* Add elements from *new_elements to *existing_list, reallocating as
168
* necessary. On success, release *new_elements and set it to NULL. */
169
static krb5_error_code
170
merge_authdata(krb5_authdata ***existing_list, krb5_authdata ***new_elements)
171
{
172
size_t count = 0, ncount = 0;
173
krb5_authdata **list = *existing_list, **nlist = *new_elements;
174
175
if (nlist == NULL)
176
return 0;
177
178
for (count = 0; list != NULL && list[count] != NULL; count++);
179
for (ncount = 0; nlist[ncount] != NULL; ncount++);
180
181
list = realloc(list, (count + ncount + 1) * sizeof(*list));
182
if (list == NULL)
183
return ENOMEM;
184
185
memcpy(list + count, nlist, ncount * sizeof(*nlist));
186
list[count + ncount] = NULL;
187
free(nlist);
188
189
if (list[0] == NULL) {
190
free(list);
191
list = NULL;
192
}
193
194
*new_elements = NULL;
195
*existing_list = list;
196
return 0;
197
}
198
199
/* Add a copy of new_elements to *existing_list, omitting KDC-issued
200
* authdata. */
201
static krb5_error_code
202
add_filtered_authdata(krb5_authdata ***existing_list,
203
krb5_authdata **new_elements)
204
{
205
krb5_error_code ret;
206
krb5_authdata **copy;
207
size_t i, j;
208
209
if (new_elements == NULL)
210
return 0;
211
212
ret = krb5_copy_authdata(NULL, new_elements, &copy);
213
if (ret)
214
return ret;
215
216
/* Remove KDC-issued elements from copy. */
217
j = 0;
218
for (i = 0; copy[i] != NULL; i++) {
219
if (is_kdc_issued_authdatum(copy[i], 0)) {
220
free(copy[i]->contents);
221
free(copy[i]);
222
} else {
223
copy[j++] = copy[i];
224
}
225
}
226
copy[j] = NULL;
227
228
/* Destructively merge the filtered copy into existing_list. */
229
ret = merge_authdata(existing_list, &copy);
230
krb5_free_authdata(NULL, copy);
231
return ret;
232
}
233
234
/* Copy TGS-REQ authorization data into the ticket authdata. */
235
static krb5_error_code
236
copy_request_authdata(krb5_context context, krb5_keyblock *client_key,
237
krb5_kdc_req *req, krb5_enc_tkt_part *enc_tkt_req,
238
krb5_authdata ***tkt_authdata)
239
{
240
krb5_error_code ret;
241
krb5_data plaintext;
242
243
assert(enc_tkt_req != NULL);
244
245
ret = alloc_data(&plaintext, req->authorization_data.ciphertext.length);
246
if (ret)
247
return ret;
248
249
/*
250
* RFC 4120 requires authdata in the TGS body to be encrypted in the subkey
251
* with usage 5 if a subkey is present, and in the TGS session key with key
252
* usage 4 if it is not. Prior to krb5 1.7, we got this wrong, always
253
* decrypting the authorization data with the TGS session key and usage 4.
254
* For the sake of conservatism, try the decryption the old way (wrong if
255
* client_key is a subkey) first, and then try again the right way (in the
256
* case where client_key is a subkey) if the first way fails.
257
*/
258
ret = krb5_c_decrypt(context, enc_tkt_req->session,
259
KRB5_KEYUSAGE_TGS_REQ_AD_SESSKEY, 0,
260
&req->authorization_data, &plaintext);
261
if (ret) {
262
ret = krb5_c_decrypt(context, client_key,
263
KRB5_KEYUSAGE_TGS_REQ_AD_SUBKEY, 0,
264
&req->authorization_data, &plaintext);
265
}
266
if (ret)
267
goto cleanup;
268
269
/* Decode the decrypted authdata and make it available to modules in the
270
* request. */
271
ret = decode_krb5_authdata(&plaintext, &req->unenc_authdata);
272
if (ret)
273
goto cleanup;
274
275
if (has_mandatory_for_kdc_authdata(context, req->unenc_authdata)) {
276
ret = KRB5KDC_ERR_POLICY;
277
goto cleanup;
278
}
279
280
ret = add_filtered_authdata(tkt_authdata, req->unenc_authdata);
281
282
cleanup:
283
free(plaintext.data);
284
return ret;
285
}
286
287
/* Copy TGT authorization data into the ticket authdata. */
288
static krb5_error_code
289
copy_tgt_authdata(krb5_context context, krb5_kdc_req *request,
290
krb5_authdata **tgt_authdata, krb5_authdata ***tkt_authdata)
291
{
292
if (has_mandatory_for_kdc_authdata(context, tgt_authdata))
293
return KRB5KDC_ERR_POLICY;
294
295
return add_filtered_authdata(tkt_authdata, tgt_authdata);
296
}
297
298
/* Add authentication indicator authdata to enc_tkt_reply, wrapped in a CAMMAC
299
* and an IF-RELEVANT container. */
300
static krb5_error_code
301
add_auth_indicators(krb5_context context, krb5_data *const *auth_indicators,
302
krb5_keyblock *server_key, krb5_db_entry *krbtgt,
303
krb5_keyblock *krbtgt_key,
304
krb5_enc_tkt_part *enc_tkt_reply)
305
{
306
krb5_error_code ret;
307
krb5_data *der_indicators = NULL;
308
krb5_authdata ad, *list[2], **cammac = NULL;
309
310
if (auth_indicators == NULL || *auth_indicators == NULL)
311
return 0;
312
313
/* Format the authentication indicators into an authdata list. */
314
ret = encode_utf8_strings(auth_indicators, &der_indicators);
315
if (ret)
316
goto cleanup;
317
ad.ad_type = KRB5_AUTHDATA_AUTH_INDICATOR;
318
ad.length = der_indicators->length;
319
ad.contents = (uint8_t *)der_indicators->data;
320
list[0] = &ad;
321
list[1] = NULL;
322
323
/* Wrap the list in CAMMAC and IF-RELEVANT containers. */
324
ret = cammac_create(context, enc_tkt_reply, server_key, krbtgt, krbtgt_key,
325
list, &cammac);
326
if (ret)
327
goto cleanup;
328
329
/* Add the wrapped authdata to the ticket, without copying or filtering. */
330
ret = merge_authdata(&enc_tkt_reply->authorization_data, &cammac);
331
332
cleanup:
333
krb5_free_data(context, der_indicators);
334
krb5_free_authdata(context, cammac);
335
return ret;
336
}
337
338
/* Extract any properly verified authentication indicators from the authdata in
339
* enc_tkt. */
340
krb5_error_code
341
get_auth_indicators(krb5_context context, krb5_enc_tkt_part *enc_tkt,
342
krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,
343
krb5_data ***indicators_out)
344
{
345
krb5_error_code ret;
346
krb5_authdata **cammacs = NULL, **adp;
347
krb5_cammac *cammac = NULL;
348
krb5_data **indicators = NULL, der_cammac;
349
350
*indicators_out = NULL;
351
352
ret = krb5_find_authdata(context, enc_tkt->authorization_data, NULL,
353
KRB5_AUTHDATA_CAMMAC, &cammacs);
354
if (ret)
355
goto cleanup;
356
357
for (adp = cammacs; adp != NULL && *adp != NULL; adp++) {
358
der_cammac = make_data((*adp)->contents, (*adp)->length);
359
ret = decode_krb5_cammac(&der_cammac, &cammac);
360
if (ret)
361
goto cleanup;
362
if (cammac_check_kdcver(context, cammac, enc_tkt, local_tgt,
363
local_tgt_key)) {
364
ret = authind_extract(context, cammac->elements, &indicators);
365
if (ret)
366
goto cleanup;
367
}
368
k5_free_cammac(context, cammac);
369
cammac = NULL;
370
}
371
372
*indicators_out = indicators;
373
indicators = NULL;
374
375
cleanup:
376
krb5_free_authdata(context, cammacs);
377
k5_free_cammac(context, cammac);
378
k5_free_data_ptr_list(indicators);
379
return ret;
380
}
381
382
static krb5_error_code
383
update_delegation_info(krb5_context context, krb5_kdc_req *req,
384
krb5_pac old_pac, krb5_pac new_pac)
385
{
386
krb5_error_code ret;
387
krb5_data ndr_di_in = empty_data(), ndr_di_out = empty_data();
388
struct pac_s4u_delegation_info *di = NULL;
389
char *namestr = NULL;
390
391
ret = krb5_pac_get_buffer(context, old_pac, KRB5_PAC_DELEGATION_INFO,
392
&ndr_di_in);
393
if (ret && ret != ENOENT)
394
goto cleanup;
395
if (ret) {
396
/* Create new delegation info. */
397
di = k5alloc(sizeof(*di), &ret);
398
if (di == NULL)
399
goto cleanup;
400
di->transited_services = k5calloc(1, sizeof(char *), &ret);
401
if (di->transited_services == NULL)
402
goto cleanup;
403
} else {
404
/* Decode and modify old delegation info. */
405
ret = ndr_dec_delegation_info(&ndr_di_in, &di);
406
if (ret)
407
goto cleanup;
408
}
409
410
/* Set proxy_target to the requested server, without realm. */
411
ret = krb5_unparse_name_flags(context, req->server,
412
KRB5_PRINCIPAL_UNPARSE_DISPLAY |
413
KRB5_PRINCIPAL_UNPARSE_NO_REALM,
414
&namestr);
415
if (ret)
416
goto cleanup;
417
free(di->proxy_target);
418
di->proxy_target = namestr;
419
420
/* Add a transited entry for the requesting service, with realm. */
421
assert(req->second_ticket != NULL && req->second_ticket[0] != NULL);
422
ret = krb5_unparse_name(context, req->second_ticket[0]->server, &namestr);
423
if (ret)
424
goto cleanup;
425
di->transited_services[di->transited_services_length++] = namestr;
426
427
ret = ndr_enc_delegation_info(di, &ndr_di_out);
428
if (ret)
429
goto cleanup;
430
431
ret = krb5_pac_add_buffer(context, new_pac, KRB5_PAC_DELEGATION_INFO,
432
&ndr_di_out);
433
434
cleanup:
435
krb5_free_data_contents(context, &ndr_di_in);
436
krb5_free_data_contents(context, &ndr_di_out);
437
ndr_free_delegation_info(di);
438
return ret;
439
}
440
441
static krb5_error_code
442
copy_pac_buffer(krb5_context context, uint32_t buffer_type, krb5_pac old_pac,
443
krb5_pac new_pac)
444
{
445
krb5_error_code ret;
446
krb5_data data;
447
448
ret = krb5_pac_get_buffer(context, old_pac, buffer_type, &data);
449
if (ret)
450
return ret;
451
ret = krb5_pac_add_buffer(context, new_pac, buffer_type, &data);
452
krb5_free_data_contents(context, &data);
453
return ret;
454
}
455
456
/*
457
* Possibly add a signed PAC to enc_tkt_reply. Also possibly add auth
458
* indicators; these are handled here so that the KDB module's issue_pac()
459
* method can alter the auth indicator list.
460
*/
461
static krb5_error_code
462
handle_pac(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,
463
krb5_db_entry *server, krb5_db_entry *subject_server,
464
krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,
465
krb5_keyblock *server_key, krb5_keyblock *subject_key,
466
krb5_keyblock *replaced_reply_key, krb5_enc_tkt_part *subject_tkt,
467
krb5_pac subject_pac, krb5_kdc_req *req,
468
krb5_const_principal altcprinc, krb5_timestamp authtime,
469
krb5_enc_tkt_part *enc_tkt_reply, krb5_data ***auth_indicators)
470
{
471
krb5_context context = realm->realm_context;
472
krb5_error_code ret;
473
krb5_pac new_pac = NULL;
474
krb5_const_principal pac_client = NULL;
475
krb5_boolean with_realm, is_as_req = (req->msg_type == KRB5_AS_REQ);
476
krb5_db_entry *signing_tgt;
477
krb5_keyblock *privsvr_key = NULL;
478
479
/* Don't add a PAC or auth indicators if the server disables authdata. */
480
if (server->attributes & KRB5_KDB_NO_AUTH_DATA_REQUIRED)
481
return 0;
482
483
/*
484
* Don't add a PAC if the realm disables them, or to an anonymous ticket,
485
* or for an AS-REQ if the client requested not to get one, or for a
486
* TGS-REQ if the subject ticket didn't contain one.
487
*/
488
if (realm->realm_disable_pac ||
489
(enc_tkt_reply->flags & TKT_FLG_ANONYMOUS) ||
490
(is_as_req && !include_pac_p(context, req)) ||
491
(!is_as_req && subject_pac == NULL)) {
492
return add_auth_indicators(context, *auth_indicators, server_key,
493
local_tgt, local_tgt_key, enc_tkt_reply);
494
}
495
496
ret = krb5_pac_init(context, &new_pac);
497
if (ret)
498
goto cleanup;
499
500
if (subject_pac == NULL)
501
signing_tgt = NULL;
502
else if (krb5_is_tgs_principal(subject_server->princ))
503
signing_tgt = subject_server;
504
else
505
signing_tgt = local_tgt;
506
507
ret = krb5_db_issue_pac(context, flags, client, replaced_reply_key, server,
508
signing_tgt, authtime, subject_pac, new_pac,
509
auth_indicators);
510
if (ret) {
511
if (ret == KRB5_PLUGIN_OP_NOTSUPP)
512
ret = 0;
513
if (ret)
514
goto cleanup;
515
}
516
517
ret = add_auth_indicators(context, *auth_indicators, server_key,
518
local_tgt, local_tgt_key, enc_tkt_reply);
519
520
if ((flags & KRB5_KDB_FLAG_CONSTRAINED_DELEGATION) &&
521
!(flags & KRB5_KDB_FLAG_CROSS_REALM)) {
522
/* Add delegation info for the first S4U2Proxy request. */
523
ret = update_delegation_info(context, req, subject_pac, new_pac);
524
if (ret)
525
goto cleanup;
526
} else if (subject_pac != NULL) {
527
/* Copy delegation info if it was present in the subject PAC. */
528
ret = copy_pac_buffer(context, KRB5_PAC_DELEGATION_INFO, subject_pac,
529
new_pac);
530
if (ret && ret != ENOENT)
531
goto cleanup;
532
}
533
534
if ((flags & KRB5_KDB_FLAGS_S4U) &&
535
(flags & KRB5_KDB_FLAG_ISSUING_REFERRAL)) {
536
/* When issuing a referral for either kind of S4U request, add client
537
* info for the subject with realm. */
538
pac_client = altcprinc;
539
with_realm = TRUE;
540
} else if (subject_pac == NULL || (flags & KRB5_KDB_FLAGS_S4U)) {
541
/* For a new PAC or when issuing a final ticket for either kind of S4U
542
* request, add client info for the ticket client without the realm. */
543
pac_client = enc_tkt_reply->client;
544
with_realm = FALSE;
545
} else {
546
/*
547
* For regular TGS and transitive RBCD requests, copy the client info
548
* from the incoming PAC, and don't add client info during signing. We
549
* validated the incoming client info in validate_tgs_request().
550
*/
551
ret = copy_pac_buffer(context, KRB5_PAC_CLIENT_INFO, subject_pac,
552
new_pac);
553
if (ret)
554
goto cleanup;
555
pac_client = NULL;
556
with_realm = FALSE;
557
}
558
559
ret = pac_privsvr_key(context, server, local_tgt_key, &privsvr_key);
560
if (ret)
561
goto cleanup;
562
ret = krb5_kdc_sign_ticket(context, enc_tkt_reply, new_pac, server->princ,
563
pac_client, server_key, privsvr_key,
564
with_realm);
565
if (ret)
566
goto cleanup;
567
568
ret = 0;
569
570
cleanup:
571
krb5_pac_free(context, new_pac);
572
krb5_free_keyblock(context, privsvr_key);
573
return ret;
574
}
575
576
krb5_error_code
577
handle_authdata(kdc_realm_t *realm, unsigned int flags, krb5_db_entry *client,
578
krb5_db_entry *server, krb5_db_entry *subject_server,
579
krb5_db_entry *local_tgt, krb5_keyblock *local_tgt_key,
580
krb5_keyblock *client_key, krb5_keyblock *server_key,
581
krb5_keyblock *subject_key, krb5_keyblock *replaced_reply_key,
582
krb5_data *req_pkt, krb5_kdc_req *req,
583
krb5_const_principal altcprinc, krb5_pac subject_pac,
584
krb5_enc_tkt_part *enc_tkt_req, krb5_data ***auth_indicators,
585
krb5_enc_tkt_part *enc_tkt_reply)
586
{
587
krb5_context context = realm->realm_context;
588
kdcauthdata_handle *h;
589
krb5_error_code ret = 0;
590
size_t i;
591
592
if (req->msg_type == KRB5_TGS_REQ &&
593
req->authorization_data.ciphertext.data != NULL) {
594
/* Copy TGS request authdata. This must be done first so that modules
595
* have access to the unencrypted request authdata. */
596
ret = copy_request_authdata(context, client_key, req, enc_tkt_req,
597
&enc_tkt_reply->authorization_data);
598
if (ret)
599
return ret;
600
}
601
602
/* Invoke loaded module handlers. */
603
if (!isflagset(enc_tkt_reply->flags, TKT_FLG_ANONYMOUS)) {
604
for (i = 0; i < n_authdata_modules; i++) {
605
h = &authdata_modules[i];
606
ret = h->vt.handle(context, h->data, flags, client, server,
607
subject_server, client_key, server_key,
608
subject_key, req_pkt, req, altcprinc,
609
enc_tkt_req, enc_tkt_reply);
610
if (ret)
611
kdc_err(context, ret, "from authdata module %s", h->vt.name);
612
}
613
}
614
615
if (req->msg_type == KRB5_TGS_REQ) {
616
/* Copy authdata from the TGT to the issued ticket. */
617
ret = copy_tgt_authdata(context, req, enc_tkt_req->authorization_data,
618
&enc_tkt_reply->authorization_data);
619
if (ret)
620
return ret;
621
}
622
623
return handle_pac(realm, flags, client, server, subject_server, local_tgt,
624
local_tgt_key, server_key, subject_key,
625
replaced_reply_key, enc_tkt_req, subject_pac, req,
626
altcprinc, enc_tkt_reply->times.authtime, enc_tkt_reply,
627
auth_indicators);
628
}
629
630