1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 * COPYRIGHT (C) 2006,2007
4 * THE REGENTS OF THE UNIVERSITY OF MICHIGAN
5 * ALL RIGHTS RESERVED
6 *
7 * Permission is granted to use, copy, create derivative works
8 * and redistribute this software and such derivative works
9 * for any purpose, so long as the name of The University of
10 * Michigan is not used in any advertising or publicity
11 * pertaining to the use of distribution of this software
12 * without specific, written prior authorization. If the
13 * above copyright notice or any other identification of the
14 * University of Michigan is included in any copy of any
15 * portion of this software, then the disclaimer below must
16 * also be included.
17 *
18 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
19 * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
20 * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
21 * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
22 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
23 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
24 * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
25 * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
26 * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
27 * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
28 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGES.
30 */
31
32 #include <k5-int.h>
33 #include "pkinit.h"
34 #include "krb5/certauth_plugin.h"
35
36 /* Aliases used by the built-in certauth modules */
37 struct certauth_req_opts {
38 krb5_kdcpreauth_callbacks cb;
39 krb5_kdcpreauth_rock rock;
40 pkinit_kdc_context plgctx;
41 pkinit_kdc_req_context reqctx;
42 };
43
44 typedef struct certauth_module_handle_st {
45 struct krb5_certauth_vtable_st vt;
46 krb5_certauth_moddata moddata;
47 } *certauth_handle;
48
49 struct krb5_kdcpreauth_moddata_st {
50 pkinit_kdc_context *realm_contexts;
51 certauth_handle *certauth_modules;
52 };
53
54 static krb5_error_code
55 pkinit_init_kdc_req_context(krb5_context, pkinit_kdc_req_context *blob);
56
57 static void
58 pkinit_fini_kdc_req_context(krb5_context context, void *blob);
59
60 static void
61 pkinit_server_plugin_fini_realm(krb5_context context,
62 pkinit_kdc_context plgctx);
63
64 static void
65 pkinit_server_plugin_fini(krb5_context context,
66 krb5_kdcpreauth_moddata moddata);
67
68 static pkinit_kdc_context
69 pkinit_find_realm_context(krb5_context context,
70 krb5_kdcpreauth_moddata moddata,
71 krb5_principal princ);
72
73 static void
free_realm_contexts(krb5_context context,pkinit_kdc_context * realm_contexts)74 free_realm_contexts(krb5_context context, pkinit_kdc_context *realm_contexts)
75 {
76 size_t i;
77
78 if (realm_contexts == NULL)
79 return;
80 for (i = 0; realm_contexts[i] != NULL; i++)
81 pkinit_server_plugin_fini_realm(context, realm_contexts[i]);
82 pkiDebug("%s: freeing context at %p\n", __FUNCTION__, realm_contexts);
83 free(realm_contexts);
84 }
85
86 static void
free_certauth_handles(krb5_context context,certauth_handle * list)87 free_certauth_handles(krb5_context context, certauth_handle *list)
88 {
89 size_t i;
90
91 if (list == NULL)
92 return;
93 for (i = 0; list[i] != NULL; i++) {
94 if (list[i]->vt.fini != NULL)
95 list[i]->vt.fini(context, list[i]->moddata);
96 free(list[i]);
97 }
98 free(list);
99 }
100
101 static krb5_error_code
pkinit_create_edata(krb5_context context,pkinit_plg_crypto_context plg_cryptoctx,pkinit_req_crypto_context req_cryptoctx,pkinit_identity_crypto_context id_cryptoctx,pkinit_plg_opts * opts,krb5_error_code err_code,krb5_pa_data *** e_data_out)102 pkinit_create_edata(krb5_context context,
103 pkinit_plg_crypto_context plg_cryptoctx,
104 pkinit_req_crypto_context req_cryptoctx,
105 pkinit_identity_crypto_context id_cryptoctx,
106 pkinit_plg_opts *opts,
107 krb5_error_code err_code,
108 krb5_pa_data ***e_data_out)
109 {
110 krb5_error_code retval = KRB5KRB_ERR_GENERIC;
111
112 pkiDebug("pkinit_create_edata: creating edata for error %d (%s)\n",
113 err_code, error_message(err_code));
114 switch(err_code) {
115 case KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE:
116 retval = pkinit_create_td_trusted_certifiers(context,
117 plg_cryptoctx, req_cryptoctx, id_cryptoctx, e_data_out);
118 break;
119 case KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED:
120 retval = pkinit_create_td_dh_parameters(context, plg_cryptoctx,
121 req_cryptoctx, id_cryptoctx, opts, e_data_out);
122 break;
123 case KRB5KDC_ERR_INVALID_CERTIFICATE:
124 case KRB5KDC_ERR_REVOKED_CERTIFICATE:
125 retval = pkinit_create_td_invalid_certificate(context,
126 plg_cryptoctx, req_cryptoctx, id_cryptoctx, e_data_out);
127 break;
128 default:
129 pkiDebug("no edata needed for error %d (%s)\n",
130 err_code, error_message(err_code));
131 retval = 0;
132 goto cleanup;
133 }
134
135 cleanup:
136
137 return retval;
138 }
139
140 static void
pkinit_server_get_edata(krb5_context context,krb5_kdc_req * request,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_preauthtype pa_type,krb5_kdcpreauth_edata_respond_fn respond,void * arg)141 pkinit_server_get_edata(krb5_context context,
142 krb5_kdc_req *request,
143 krb5_kdcpreauth_callbacks cb,
144 krb5_kdcpreauth_rock rock,
145 krb5_kdcpreauth_moddata moddata,
146 krb5_preauthtype pa_type,
147 krb5_kdcpreauth_edata_respond_fn respond,
148 void *arg)
149 {
150 krb5_error_code retval = 0;
151 pkinit_kdc_context plgctx = NULL;
152
153 pkiDebug("pkinit_server_get_edata: entered!\n");
154
155
156 /*
157 * If we don't have a realm context for the given realm,
158 * don't tell the client that we support pkinit!
159 */
160 plgctx = pkinit_find_realm_context(context, moddata, request->server);
161 if (plgctx == NULL)
162 retval = EINVAL;
163
164 /* Send a freshness token if the client requested one. */
165 if (!retval)
166 cb->send_freshness_token(context, rock);
167
168 (*respond)(arg, retval, NULL);
169 }
170
171 static krb5_error_code
verify_client_san(krb5_context context,pkinit_kdc_context plgctx,pkinit_kdc_req_context reqctx,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_const_principal client,int * valid_san)172 verify_client_san(krb5_context context,
173 pkinit_kdc_context plgctx,
174 pkinit_kdc_req_context reqctx,
175 krb5_kdcpreauth_callbacks cb,
176 krb5_kdcpreauth_rock rock,
177 krb5_const_principal client,
178 int *valid_san)
179 {
180 krb5_error_code retval;
181 krb5_principal *princs = NULL, upn;
182 krb5_boolean match;
183 char **upns = NULL;
184 size_t i;
185 #ifdef DEBUG_SAN_INFO
186 char *client_string = NULL, *san_string;
187 #endif
188
189 *valid_san = 0;
190 retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx,
191 reqctx->cryptoctx, plgctx->idctx,
192 &princs,
193 plgctx->opts->allow_upn ? &upns : NULL,
194 NULL);
195 if (retval) {
196 pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__);
197 retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
198 goto out;
199 }
200
201 if (princs == NULL && upns == NULL) {
202 TRACE_PKINIT_SERVER_NO_SAN(context);
203 retval = ENOENT;
204 goto out;
205 }
206
207 #ifdef DEBUG_SAN_INFO
208 krb5_unparse_name(context, client, &client_string);
209 #endif
210 pkiDebug("%s: Checking pkinit sans\n", __FUNCTION__);
211 for (i = 0; princs != NULL && princs[i] != NULL; i++) {
212 #ifdef DEBUG_SAN_INFO
213 krb5_unparse_name(context, princs[i], &san_string);
214 pkiDebug("%s: Comparing client '%s' to pkinit san value '%s'\n",
215 __FUNCTION__, client_string, san_string);
216 krb5_free_unparsed_name(context, san_string);
217 #endif
218 if (cb->match_client(context, rock, princs[i])) {
219 TRACE_PKINIT_SERVER_MATCHING_SAN_FOUND(context);
220 *valid_san = 1;
221 retval = 0;
222 goto out;
223 }
224 }
225 pkiDebug("%s: no pkinit san match found\n", __FUNCTION__);
226 /*
227 * XXX if cert has names but none match, should we
228 * be returning KRB5KDC_ERR_CLIENT_NAME_MISMATCH here?
229 */
230
231 if (upns == NULL) {
232 pkiDebug("%s: no upn sans (or we wouldn't accept them anyway)\n",
233 __FUNCTION__);
234 retval = KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
235 goto out;
236 }
237
238 pkiDebug("%s: Checking upn sans\n", __FUNCTION__);
239 for (i = 0; upns[i] != NULL; i++) {
240 #ifdef DEBUG_SAN_INFO
241 pkiDebug("%s: Comparing client '%s' to upn san value '%s'\n",
242 __FUNCTION__, client_string, upns[i]);
243 #endif
244 retval = krb5_parse_name_flags(context, upns[i],
245 KRB5_PRINCIPAL_PARSE_ENTERPRISE, &upn);
246 if (retval) {
247 TRACE_PKINIT_SERVER_UPN_PARSE_FAIL(context, upns[i], retval);
248 continue;
249 }
250 match = cb->match_client(context, rock, upn);
251 krb5_free_principal(context, upn);
252 if (match) {
253 TRACE_PKINIT_SERVER_MATCHING_UPN_FOUND(context);
254 *valid_san = 1;
255 retval = 0;
256 goto out;
257 }
258 }
259 pkiDebug("%s: no upn san match found\n", __FUNCTION__);
260
261 retval = 0;
262 out:
263 if (princs != NULL) {
264 for (i = 0; princs[i] != NULL; i++)
265 krb5_free_principal(context, princs[i]);
266 free(princs);
267 }
268 if (upns != NULL) {
269 for (i = 0; upns[i] != NULL; i++)
270 free(upns[i]);
271 free(upns);
272 }
273 #ifdef DEBUG_SAN_INFO
274 if (client_string != NULL)
275 krb5_free_unparsed_name(context, client_string);
276 #endif
277 pkiDebug("%s: returning retval %d, valid_san %d\n",
278 __FUNCTION__, retval, *valid_san);
279 return retval;
280 }
281
282 static krb5_error_code
verify_client_eku(krb5_context context,pkinit_kdc_context plgctx,pkinit_kdc_req_context reqctx,int * eku_accepted)283 verify_client_eku(krb5_context context,
284 pkinit_kdc_context plgctx,
285 pkinit_kdc_req_context reqctx,
286 int *eku_accepted)
287 {
288 krb5_error_code retval;
289
290 *eku_accepted = 0;
291
292 if (plgctx->opts->require_eku == 0) {
293 TRACE_PKINIT_SERVER_EKU_SKIP(context);
294 *eku_accepted = 1;
295 retval = 0;
296 goto out;
297 }
298
299 retval = crypto_check_cert_eku(context, plgctx->cryptoctx,
300 reqctx->cryptoctx, plgctx->idctx,
301 0, /* kdc cert */
302 plgctx->opts->accept_secondary_eku,
303 eku_accepted);
304 if (retval) {
305 pkiDebug("%s: Error from crypto_check_cert_eku %d (%s)\n",
306 __FUNCTION__, retval, error_message(retval));
307 goto out;
308 }
309
310 out:
311 pkiDebug("%s: returning retval %d, eku_accepted %d\n",
312 __FUNCTION__, retval, *eku_accepted);
313 return retval;
314 }
315
316
317 /* Run the received, verified certificate through certauth modules, to verify
318 * that it is authorized to authenticate as client. */
319 static krb5_error_code
authorize_cert(krb5_context context,certauth_handle * certauth_modules,pkinit_kdc_context plgctx,pkinit_kdc_req_context reqctx,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_principal client,krb5_boolean * hwauth_out)320 authorize_cert(krb5_context context, certauth_handle *certauth_modules,
321 pkinit_kdc_context plgctx, pkinit_kdc_req_context reqctx,
322 krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
323 krb5_principal client, krb5_boolean *hwauth_out)
324 {
325 krb5_error_code ret;
326 certauth_handle h;
327 struct certauth_req_opts opts;
328 krb5_boolean accepted = FALSE, hwauth = FALSE;
329 uint8_t *cert;
330 size_t i, cert_len;
331 void *db_ent = NULL;
332 char **ais = NULL, **ai = NULL;
333
334 /* Re-encode the received certificate into DER, which is extra work, but
335 * avoids creating an X.509 library dependency in the interface. */
336 ret = crypto_encode_der_cert(context, reqctx->cryptoctx, &cert, &cert_len);
337 if (ret)
338 goto cleanup;
339
340 /* Set options for the builtin module. */
341 opts.plgctx = plgctx;
342 opts.reqctx = reqctx;
343 opts.cb = cb;
344 opts.rock = rock;
345
346 db_ent = cb->client_entry(context, rock);
347
348 /*
349 * Check the certificate against each certauth module. For the certificate
350 * to be authorized at least one module must return 0 or
351 * KRB5_CERTAUTH_HWAUTH, and no module can return an error code other than
352 * KRB5_PLUGIN_NO_HANDLE (pass) or KRB5_CERTAUTH_HWAUTH_PASS (pass but
353 * set hw-authent). Add indicators from all modules.
354 */
355 ret = KRB5_PLUGIN_NO_HANDLE;
356 for (i = 0; certauth_modules != NULL && certauth_modules[i] != NULL; i++) {
357 h = certauth_modules[i];
358 TRACE_PKINIT_SERVER_CERT_AUTH(context, h->vt.name);
359 ret = h->vt.authorize(context, h->moddata, cert, cert_len, client,
360 &opts, db_ent, &ais);
361 if (ret == 0)
362 accepted = TRUE;
363 else if (ret == KRB5_CERTAUTH_HWAUTH)
364 accepted = hwauth = TRUE;
365 else if (ret == KRB5_CERTAUTH_HWAUTH_PASS)
366 hwauth = TRUE;
367 else if (ret != KRB5_PLUGIN_NO_HANDLE)
368 goto cleanup;
369
370 if (ais != NULL) {
371 /* Assert authentication indicators from the module. */
372 for (ai = ais; *ai != NULL; ai++) {
373 ret = cb->add_auth_indicator(context, rock, *ai);
374 if (ret)
375 goto cleanup;
376 }
377 h->vt.free_ind(context, h->moddata, ais);
378 ais = NULL;
379 }
380 }
381
382 *hwauth_out = hwauth;
383 ret = accepted ? 0 : KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
384
385 cleanup:
386 free(cert);
387 return ret;
388 }
389
390 /* Return an error if freshness tokens are required and one was not received.
391 * Log an appropriate message indicating whether a valid token was received. */
392 static krb5_error_code
check_log_freshness(krb5_context context,pkinit_kdc_context plgctx,krb5_kdc_req * request,krb5_boolean valid_freshness_token)393 check_log_freshness(krb5_context context, pkinit_kdc_context plgctx,
394 krb5_kdc_req *request, krb5_boolean valid_freshness_token)
395 {
396 krb5_error_code ret;
397 char *name = NULL;
398
399 ret = krb5_unparse_name(context, request->client, &name);
400 if (ret)
401 return ret;
402 if (plgctx->opts->require_freshness && !valid_freshness_token) {
403 com_err("", 0, _("PKINIT: no freshness token, rejecting auth from %s"),
404 name);
405 ret = KRB5KDC_ERR_PREAUTH_FAILED;
406 } else if (valid_freshness_token) {
407 com_err("", 0, _("PKINIT: freshness token received from %s"), name);
408 } else {
409 com_err("", 0, _("PKINIT: no freshness token received from %s"), name);
410 }
411 krb5_free_unparsed_name(context, name);
412 return ret;
413 }
414
415 static void
pkinit_server_verify_padata(krb5_context context,krb5_data * req_pkt,krb5_kdc_req * request,krb5_enc_tkt_part * enc_tkt_reply,krb5_pa_data * data,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_verify_respond_fn respond,void * arg)416 pkinit_server_verify_padata(krb5_context context,
417 krb5_data *req_pkt,
418 krb5_kdc_req * request,
419 krb5_enc_tkt_part * enc_tkt_reply,
420 krb5_pa_data * data,
421 krb5_kdcpreauth_callbacks cb,
422 krb5_kdcpreauth_rock rock,
423 krb5_kdcpreauth_moddata moddata,
424 krb5_kdcpreauth_verify_respond_fn respond,
425 void *arg)
426 {
427 krb5_error_code retval = 0;
428 krb5_data authp_data = {0, 0, NULL}, krb5_authz = {0, 0, NULL};
429 krb5_pa_pk_as_req *reqp = NULL;
430 krb5_auth_pack *auth_pack = NULL;
431 krb5_pk_authenticator *pka;
432 pkinit_kdc_context plgctx = NULL;
433 pkinit_kdc_req_context reqctx = NULL;
434 krb5_checksum cksum = {0, 0, 0, NULL};
435 krb5_data *der_req = NULL;
436 krb5_data k5data;
437 int is_signed = 1;
438 krb5_pa_data **e_data = NULL;
439 krb5_kdcpreauth_modreq modreq = NULL;
440 krb5_boolean valid_freshness_token = FALSE, hwauth = FALSE;
441 char **sp;
442
443 pkiDebug("pkinit_verify_padata: entered!\n");
444 if (data == NULL || data->length <= 0 || data->contents == NULL) {
445 (*respond)(arg, EINVAL, NULL, NULL, NULL);
446 return;
447 }
448
449
450 if (moddata == NULL) {
451 (*respond)(arg, EINVAL, NULL, NULL, NULL);
452 return;
453 }
454
455 plgctx = pkinit_find_realm_context(context, moddata, request->server);
456 if (plgctx == NULL) {
457 (*respond)(arg, EINVAL, NULL, NULL, NULL);
458 return;
459 }
460
461 #ifdef DEBUG_ASN1
462 print_buffer_bin(data->contents, data->length, "/tmp/kdc_as_req");
463 #endif
464 /* create a per-request context */
465 retval = pkinit_init_kdc_req_context(context, &reqctx);
466 if (retval)
467 goto cleanup;
468 reqctx->pa_type = data->pa_type;
469
470 PADATA_TO_KRB5DATA(data, &k5data);
471
472 if (data->pa_type != KRB5_PADATA_PK_AS_REQ) {
473 pkiDebug("unrecognized pa_type = %d\n", data->pa_type);
474 retval = EINVAL;
475 goto cleanup;
476 }
477
478 TRACE_PKINIT_SERVER_PADATA_VERIFY(context);
479 retval = k5int_decode_krb5_pa_pk_as_req(&k5data, &reqp);
480 if (retval) {
481 pkiDebug("decode_krb5_pa_pk_as_req failed\n");
482 goto cleanup;
483 }
484 #ifdef DEBUG_ASN1
485 print_buffer_bin(reqp->signedAuthPack.data, reqp->signedAuthPack.length,
486 "/tmp/kdc_signed_data");
487 #endif
488 retval = cms_signeddata_verify(context, plgctx->cryptoctx,
489 reqctx->cryptoctx, plgctx->idctx,
490 CMS_SIGN_CLIENT,
491 plgctx->opts->require_crl_checking,
492 (unsigned char *)reqp->signedAuthPack.data,
493 reqp->signedAuthPack.length,
494 (unsigned char **)&authp_data.data,
495 &authp_data.length,
496 (unsigned char **)&krb5_authz.data,
497 &krb5_authz.length, &is_signed);
498 if (retval) {
499 TRACE_PKINIT_SERVER_PADATA_VERIFY_FAIL(context);
500 goto cleanup;
501 }
502 if (is_signed) {
503 retval = authorize_cert(context, moddata->certauth_modules, plgctx,
504 reqctx, cb, rock, request->client, &hwauth);
505 if (retval)
506 goto cleanup;
507
508 } else { /* !is_signed */
509 if (!krb5_principal_compare(context, request->client,
510 krb5_anonymous_principal())) {
511 retval = KRB5KDC_ERR_PREAUTH_FAILED;
512 krb5_set_error_message(context, retval,
513 _("Pkinit request not signed, but client "
514 "not anonymous."));
515 goto cleanup;
516 }
517 }
518 #ifdef DEBUG_ASN1
519 print_buffer_bin(authp_data.data, authp_data.length, "/tmp/kdc_auth_pack");
520 #endif
521
522 OCTETDATA_TO_KRB5DATA(&authp_data, &k5data);
523 retval = k5int_decode_krb5_auth_pack(&k5data, &auth_pack);
524 if (retval) {
525 pkiDebug("failed to decode krb5_auth_pack\n");
526 goto cleanup;
527 }
528 pka = &auth_pack->pkAuthenticator;
529
530 retval = krb5_check_clockskew(context, pka->ctime);
531 if (retval)
532 goto cleanup;
533
534 /* check dh parameters */
535 if (auth_pack->clientPublicValue.length > 0) {
536 retval = server_check_dh(context, plgctx->cryptoctx,
537 reqctx->cryptoctx, plgctx->idctx,
538 &auth_pack->clientPublicValue,
539 plgctx->opts->dh_min_bits);
540 if (retval) {
541 pkiDebug("bad dh parameters\n");
542 goto cleanup;
543 }
544 } else if (!is_signed) {
545 /*Anonymous pkinit requires DH*/
546 retval = KRB5KDC_ERR_PREAUTH_FAILED;
547 krb5_set_error_message(context, retval,
548 _("Anonymous pkinit without DH public "
549 "value not supported."));
550 goto cleanup;
551 }
552 der_req = cb->request_body(context, rock);
553
554 retval = crypto_verify_checksums(context, der_req, &pka->paChecksum,
555 pka->paChecksum2);
556 if (retval)
557 goto cleanup;
558
559 if (pka->freshnessToken != NULL) {
560 retval = cb->check_freshness_token(context, rock, pka->freshnessToken);
561 if (retval)
562 goto cleanup;
563 valid_freshness_token = TRUE;
564 }
565
566 /* check if kdcPkId present and match KDC's subjectIdentifier */
567 if (reqp->kdcPkId.data != NULL) {
568 int valid_kdcPkId = 0;
569 retval = pkinit_check_kdc_pkid(context, plgctx->cryptoctx,
570 reqctx->cryptoctx, plgctx->idctx,
571 (unsigned char *)reqp->kdcPkId.data,
572 reqp->kdcPkId.length, &valid_kdcPkId);
573 if (retval)
574 goto cleanup;
575 if (!valid_kdcPkId) {
576 pkiDebug("kdcPkId in AS_REQ does not match KDC's cert; "
577 "RFC says to ignore and proceed\n");
578 }
579 }
580 /* remember the decoded auth_pack for verify_padata routine */
581 reqctx->rcv_auth_pack = auth_pack;
582 auth_pack = NULL;
583
584 if (is_signed) {
585 retval = check_log_freshness(context, plgctx, request,
586 valid_freshness_token);
587 if (retval)
588 goto cleanup;
589 }
590
591 if (is_signed && plgctx->auth_indicators != NULL) {
592 /* Assert configured authentication indicators. */
593 for (sp = plgctx->auth_indicators; *sp != NULL; sp++) {
594 retval = cb->add_auth_indicator(context, rock, *sp);
595 if (retval)
596 goto cleanup;
597 }
598 }
599
600 /* remember to set the PREAUTH flag in the reply */
601 enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
602 if (hwauth)
603 enc_tkt_reply->flags |= TKT_FLG_HW_AUTH;
604 modreq = (krb5_kdcpreauth_modreq)reqctx;
605 reqctx = NULL;
606
607 cleanup:
608 if (retval && data->pa_type == KRB5_PADATA_PK_AS_REQ) {
609 pkiDebug("pkinit_verify_padata failed: creating e-data\n");
610 if (pkinit_create_edata(context, plgctx->cryptoctx, reqctx->cryptoctx,
611 plgctx->idctx, plgctx->opts, retval, &e_data))
612 pkiDebug("pkinit_create_edata failed\n");
613 }
614
615 free_krb5_pa_pk_as_req(&reqp);
616 free(cksum.contents);
617 free(authp_data.data);
618 free(krb5_authz.data);
619 if (reqctx != NULL)
620 pkinit_fini_kdc_req_context(context, reqctx);
621 free_krb5_auth_pack(&auth_pack);
622
623 (*respond)(arg, retval, modreq, e_data, NULL);
624 }
625 static krb5_error_code
return_pkinit_kx(krb5_context context,krb5_kdc_req * request,krb5_kdc_rep * reply,krb5_keyblock * encrypting_key,krb5_pa_data ** out_padata)626 return_pkinit_kx(krb5_context context, krb5_kdc_req *request,
627 krb5_kdc_rep *reply, krb5_keyblock *encrypting_key,
628 krb5_pa_data **out_padata)
629 {
630 krb5_error_code ret = 0;
631 krb5_keyblock *session = reply->ticket->enc_part2->session;
632 krb5_keyblock *new_session = NULL;
633 krb5_pa_data *pa = NULL;
634 krb5_enc_data enc;
635 krb5_data *scratch = NULL;
636
637 *out_padata = NULL;
638 enc.ciphertext.data = NULL;
639 if (!krb5_principal_compare(context, request->client,
640 krb5_anonymous_principal()))
641 return 0;
642 /*
643 * The KDC contribution key needs to be a fresh key of an enctype supported
644 * by the client and server. The existing session key meets these
645 * requirements so we use it.
646 */
647 ret = krb5_c_fx_cf2_simple(context, session, "PKINIT",
648 encrypting_key, "KEYEXCHANGE",
649 &new_session);
650 if (ret)
651 goto cleanup;
652 ret = encode_krb5_encryption_key( session, &scratch);
653 if (ret)
654 goto cleanup;
655 ret = krb5_encrypt_helper(context, encrypting_key,
656 KRB5_KEYUSAGE_PA_PKINIT_KX, scratch, &enc);
657 if (ret)
658 goto cleanup;
659 memset(scratch->data, 0, scratch->length);
660 krb5_free_data(context, scratch);
661 scratch = NULL;
662 ret = encode_krb5_enc_data(&enc, &scratch);
663 if (ret)
664 goto cleanup;
665 pa = malloc(sizeof(krb5_pa_data));
666 if (pa == NULL) {
667 ret = ENOMEM;
668 goto cleanup;
669 }
670 pa->pa_type = KRB5_PADATA_PKINIT_KX;
671 pa->length = scratch->length;
672 pa->contents = (krb5_octet *) scratch->data;
673 *out_padata = pa;
674 scratch->data = NULL;
675 memset(session->contents, 0, session->length);
676 krb5_free_keyblock_contents(context, session);
677 *session = *new_session;
678 new_session->contents = NULL;
679 cleanup:
680 krb5_free_data_contents(context, &enc.ciphertext);
681 krb5_free_keyblock(context, new_session);
682 krb5_free_data(context, scratch);
683 return ret;
684 }
685
686 static krb5_error_code
pkinit_pick_kdf_alg(krb5_context context,krb5_data ** kdf_list,krb5_data ** alg_oid)687 pkinit_pick_kdf_alg(krb5_context context, krb5_data **kdf_list,
688 krb5_data **alg_oid)
689 {
690 krb5_error_code retval = 0;
691 krb5_data *req_oid = NULL;
692 const krb5_data *supp_oid = NULL;
693 krb5_data *tmp_oid = NULL;
694 size_t i, j = 0;
695
696 /* if we don't find a match, return NULL value */
697 *alg_oid = NULL;
698
699 /* for each of the OIDs that the server supports... */
700 for (i = 0; NULL != (supp_oid = supported_kdf_alg_ids[i]); i++) {
701 /* if the requested OID is in the client's list, use it. */
702 for (j = 0; NULL != (req_oid = kdf_list[j]); j++) {
703 if ((req_oid->length == supp_oid->length) &&
704 (0 == memcmp(req_oid->data, supp_oid->data, req_oid->length))) {
705 tmp_oid = k5alloc(sizeof(krb5_data), &retval);
706 if (retval)
707 goto cleanup;
708 tmp_oid->data = k5memdup(supp_oid->data, supp_oid->length,
709 &retval);
710 if (retval)
711 goto cleanup;
712 tmp_oid->length = supp_oid->length;
713 *alg_oid = tmp_oid;
714 /* don't free the OID in clean-up if we are returning it */
715 tmp_oid = NULL;
716 goto cleanup;
717 }
718 }
719 }
720 cleanup:
721 if (tmp_oid)
722 krb5_free_data(context, tmp_oid);
723 return retval;
724 }
725
726 static krb5_error_code
pkinit_server_return_padata(krb5_context context,krb5_pa_data * padata,krb5_data * req_pkt,krb5_kdc_req * request,krb5_kdc_rep * reply,krb5_keyblock * encrypting_key,krb5_pa_data ** send_pa,krb5_kdcpreauth_callbacks cb,krb5_kdcpreauth_rock rock,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_modreq modreq)727 pkinit_server_return_padata(krb5_context context,
728 krb5_pa_data * padata,
729 krb5_data *req_pkt,
730 krb5_kdc_req * request,
731 krb5_kdc_rep * reply,
732 krb5_keyblock * encrypting_key,
733 krb5_pa_data ** send_pa,
734 krb5_kdcpreauth_callbacks cb,
735 krb5_kdcpreauth_rock rock,
736 krb5_kdcpreauth_moddata moddata,
737 krb5_kdcpreauth_modreq modreq)
738 {
739 krb5_error_code retval = 0;
740 krb5_data scratch = {0, 0, NULL};
741 krb5_pa_pk_as_req *reqp = NULL;
742 int i = 0;
743
744 unsigned char *dh_pubkey = NULL, *server_key = NULL;
745 unsigned int server_key_len = 0, dh_pubkey_len = 0;
746 krb5_keyblock reply_key = { 0 };
747
748 krb5_kdc_dh_key_info dhkey_info;
749 krb5_data *encoded_dhkey_info = NULL;
750 krb5_pa_pk_as_rep *rep = NULL;
751 krb5_data *out_data = NULL;
752 krb5_data secret;
753
754 krb5_enctype enctype = -1;
755
756 krb5_reply_key_pack *key_pack = NULL;
757 krb5_data *encoded_key_pack = NULL;
758
759 pkinit_kdc_context plgctx;
760 pkinit_kdc_req_context reqctx;
761
762 *send_pa = NULL;
763 if (padata->pa_type == KRB5_PADATA_PKINIT_KX) {
764 return return_pkinit_kx(context, request, reply,
765 encrypting_key, send_pa);
766 }
767 if (padata->length <= 0 || padata->contents == NULL)
768 return 0;
769
770 if (modreq == NULL) {
771 pkiDebug("missing request context \n");
772 return EINVAL;
773 }
774
775 plgctx = pkinit_find_realm_context(context, moddata, request->server);
776 if (plgctx == NULL) {
777 pkiDebug("Unable to locate correct realm context\n");
778 return ENOENT;
779 }
780
781 TRACE_PKINIT_SERVER_RETURN_PADATA(context);
782 reqctx = (pkinit_kdc_req_context)modreq;
783
784 for(i = 0; i < request->nktypes; i++) {
785 enctype = request->ktype[i];
786 if (!krb5_c_valid_enctype(enctype))
787 continue;
788 else {
789 pkiDebug("KDC picked etype = %d\n", enctype);
790 break;
791 }
792 }
793
794 if (i == request->nktypes) {
795 retval = KRB5KDC_ERR_ETYPE_NOSUPP;
796 goto cleanup;
797 }
798
799 init_krb5_pa_pk_as_rep(&rep);
800 if (rep == NULL) {
801 retval = ENOMEM;
802 goto cleanup;
803 }
804
805 if (reqctx->rcv_auth_pack == NULL ||
806 reqctx->rcv_auth_pack->clientPublicValue.length == 0) {
807 retval = KRB5KDC_ERR_PREAUTH_FAILED;
808 k5_setmsg(context, retval, _("Unsupported PKINIT RSA request"));
809 goto cleanup;
810 }
811
812 rep->choice = choice_pa_pk_as_rep_dhInfo;
813
814 retval = server_process_dh(context, plgctx->cryptoctx, reqctx->cryptoctx,
815 plgctx->idctx, &dh_pubkey, &dh_pubkey_len,
816 &server_key, &server_key_len);
817 if (retval) {
818 pkiDebug("failed to process/create dh parameters\n");
819 goto cleanup;
820 }
821
822 dhkey_info.subjectPublicKey.length = dh_pubkey_len;
823 dhkey_info.subjectPublicKey.data = (char *)dh_pubkey;
824 dhkey_info.nonce = request->nonce;
825 dhkey_info.dhKeyExpiration = 0;
826
827 retval = k5int_encode_krb5_kdc_dh_key_info(&dhkey_info,
828 &encoded_dhkey_info);
829 if (retval) {
830 pkiDebug("encode_krb5_kdc_dh_key_info failed\n");
831 goto cleanup;
832 }
833 #ifdef DEBUG_ASN1
834 print_buffer_bin((unsigned char *)encoded_dhkey_info->data,
835 encoded_dhkey_info->length, "/tmp/kdc_dh_key_info");
836 #endif
837
838 retval = cms_signeddata_create(context, plgctx->cryptoctx,
839 reqctx->cryptoctx, plgctx->idctx,
840 CMS_SIGN_SERVER,
841 (unsigned char *)encoded_dhkey_info->data,
842 encoded_dhkey_info->length,
843 (unsigned char **)
844 &rep->u.dh_Info.dhSignedData.data,
845 &rep->u.dh_Info.dhSignedData.length);
846 if (retval) {
847 pkiDebug("failed to create pkcs7 signed data\n");
848 goto cleanup;
849 }
850
851 if (reqctx->rcv_auth_pack != NULL &&
852 reqctx->rcv_auth_pack->supportedKDFs != NULL) {
853 /* If using the alg-agility KDF, put the algorithm in the reply
854 * before encoding it.
855 */
856 if (reqctx->rcv_auth_pack != NULL &&
857 reqctx->rcv_auth_pack->supportedKDFs != NULL) {
858 retval = pkinit_pick_kdf_alg(context, reqctx->rcv_auth_pack->supportedKDFs,
859 &(rep->u.dh_Info.kdfID));
860 if (retval) {
861 pkiDebug("pkinit_pick_kdf_alg failed: %s\n",
862 error_message(retval));
863 goto cleanup;
864 }
865 }
866 }
867
868 retval = k5int_encode_krb5_pa_pk_as_rep(rep, &out_data);
869 if (retval) {
870 pkiDebug("failed to encode AS_REP\n");
871 goto cleanup;
872 }
873 #ifdef DEBUG_ASN1
874 if (out_data != NULL)
875 print_buffer_bin((unsigned char *)out_data->data, out_data->length,
876 "/tmp/kdc_as_rep");
877 #endif
878
879 secret = make_data(server_key, server_key_len);
880 retval = pkinit_kdf(context, &secret, rep->u.dh_Info.kdfID,
881 request->client, request->server, enctype, req_pkt,
882 out_data, &reply_key);
883 if (retval) {
884 pkiDebug("pkinit_kdf failed: %s\n", error_message(retval));
885 goto cleanup;
886 }
887
888 retval = cb->replace_reply_key(context, rock, &reply_key, FALSE);
889 if (retval)
890 goto cleanup;
891
892 *send_pa = malloc(sizeof(krb5_pa_data));
893 if (*send_pa == NULL) {
894 retval = ENOMEM;
895 free(out_data->data);
896 free(out_data);
897 out_data = NULL;
898 goto cleanup;
899 }
900 (*send_pa)->magic = KV5M_PA_DATA;
901 (*send_pa)->pa_type = KRB5_PADATA_PK_AS_REP;
902 (*send_pa)->length = out_data->length;
903 (*send_pa)->contents = (krb5_octet *) out_data->data;
904
905 cleanup:
906 free(scratch.data);
907 free(out_data);
908 if (encoded_dhkey_info != NULL)
909 krb5_free_data(context, encoded_dhkey_info);
910 if (encoded_key_pack != NULL)
911 krb5_free_data(context, encoded_key_pack);
912 free(dh_pubkey);
913 free(server_key);
914 free_krb5_pa_pk_as_req(&reqp);
915 free_krb5_pa_pk_as_rep(&rep);
916 free_krb5_reply_key_pack(&key_pack);
917 krb5_free_keyblock_contents(context, &reply_key);
918
919 if (retval)
920 pkiDebug("pkinit_verify_padata failure");
921
922 return retval;
923 }
924
925 static int
pkinit_server_get_flags(krb5_context kcontext,krb5_preauthtype patype)926 pkinit_server_get_flags(krb5_context kcontext, krb5_preauthtype patype)
927 {
928 if (patype == KRB5_PADATA_PKINIT_KX)
929 return PA_INFO;
930 /* PKINIT does not normally set the hw-authent ticket flag, but a
931 * certauth module can cause it to do so. */
932 return PA_SUFFICIENT | PA_REPLACES_KEY | PA_TYPED_E_DATA | PA_HARDWARE;
933 }
934
935 static krb5_preauthtype supported_server_pa_types[] = {
936 KRB5_PADATA_PK_AS_REQ,
937 KRB5_PADATA_PKINIT_KX,
938 0
939 };
940
941 static void
pkinit_fini_kdc_profile(krb5_context context,pkinit_kdc_context plgctx)942 pkinit_fini_kdc_profile(krb5_context context, pkinit_kdc_context plgctx)
943 {
944 /*
945 * There is nothing currently allocated by pkinit_init_kdc_profile()
946 * which needs to be freed here.
947 */
948 }
949
950 static krb5_error_code
pkinit_init_kdc_profile(krb5_context context,pkinit_kdc_context plgctx)951 pkinit_init_kdc_profile(krb5_context context, pkinit_kdc_context plgctx)
952 {
953 krb5_error_code retval;
954 char *eku_string = NULL, *ocsp_check = NULL, *minbits = NULL;
955
956 pkiDebug("%s: entered for realm %s\n", __FUNCTION__, plgctx->realmname);
957 retval = pkinit_kdcdefault_string(context, plgctx->realmname,
958 KRB5_CONF_PKINIT_IDENTITY,
959 &plgctx->idopts->identity);
960 if (retval != 0 || NULL == plgctx->idopts->identity) {
961 retval = EINVAL;
962 krb5_set_error_message(context, retval,
963 _("No pkinit_identity supplied for realm %s"),
964 plgctx->realmname);
965 goto errout;
966 }
967
968 retval = pkinit_kdcdefault_strings(context, plgctx->realmname,
969 KRB5_CONF_PKINIT_ANCHORS,
970 &plgctx->idopts->anchors);
971 if (retval != 0 || NULL == plgctx->idopts->anchors) {
972 retval = EINVAL;
973 krb5_set_error_message(context, retval,
974 _("No pkinit_anchors supplied for realm %s"),
975 plgctx->realmname);
976 goto errout;
977 }
978
979 pkinit_kdcdefault_strings(context, plgctx->realmname,
980 KRB5_CONF_PKINIT_POOL,
981 &plgctx->idopts->intermediates);
982
983 pkinit_kdcdefault_strings(context, plgctx->realmname,
984 KRB5_CONF_PKINIT_REVOKE,
985 &plgctx->idopts->crls);
986
987 pkinit_kdcdefault_string(context, plgctx->realmname,
988 KRB5_CONF_PKINIT_KDC_OCSP,
989 &ocsp_check);
990 if (ocsp_check != NULL) {
991 free(ocsp_check);
992 retval = ENOTSUP;
993 krb5_set_error_message(context, retval,
994 _("OCSP is not supported: (realm: %s)"),
995 plgctx->realmname);
996 goto errout;
997 }
998
999 pkinit_kdcdefault_string(context, plgctx->realmname,
1000 KRB5_CONF_PKINIT_DH_MIN_BITS, &minbits);
1001 plgctx->opts->dh_min_bits = parse_dh_min_bits(context, minbits);
1002 free(minbits);
1003
1004 pkinit_kdcdefault_boolean(context, plgctx->realmname,
1005 KRB5_CONF_PKINIT_ALLOW_UPN,
1006 0, &plgctx->opts->allow_upn);
1007
1008 pkinit_kdcdefault_boolean(context, plgctx->realmname,
1009 KRB5_CONF_PKINIT_REQUIRE_CRL_CHECKING,
1010 0, &plgctx->opts->require_crl_checking);
1011
1012 pkinit_kdcdefault_boolean(context, plgctx->realmname,
1013 KRB5_CONF_PKINIT_REQUIRE_FRESHNESS,
1014 0, &plgctx->opts->require_freshness);
1015
1016 pkinit_kdcdefault_string(context, plgctx->realmname,
1017 KRB5_CONF_PKINIT_EKU_CHECKING,
1018 &eku_string);
1019 if (eku_string != NULL) {
1020 if (strcasecmp(eku_string, "kpClientAuth") == 0) {
1021 plgctx->opts->require_eku = 1;
1022 plgctx->opts->accept_secondary_eku = 0;
1023 } else if (strcasecmp(eku_string, "scLogin") == 0) {
1024 plgctx->opts->require_eku = 1;
1025 plgctx->opts->accept_secondary_eku = 1;
1026 } else if (strcasecmp(eku_string, "none") == 0) {
1027 plgctx->opts->require_eku = 0;
1028 plgctx->opts->accept_secondary_eku = 0;
1029 } else {
1030 pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n",
1031 __FUNCTION__, eku_string);
1032 }
1033 free(eku_string);
1034 }
1035
1036 pkinit_kdcdefault_strings(context, plgctx->realmname,
1037 KRB5_CONF_PKINIT_INDICATOR,
1038 &plgctx->auth_indicators);
1039
1040 return 0;
1041 errout:
1042 pkinit_fini_kdc_profile(context, plgctx);
1043 return retval;
1044 }
1045
1046 static pkinit_kdc_context
pkinit_find_realm_context(krb5_context context,krb5_kdcpreauth_moddata moddata,krb5_principal princ)1047 pkinit_find_realm_context(krb5_context context,
1048 krb5_kdcpreauth_moddata moddata,
1049 krb5_principal princ)
1050 {
1051 size_t i;
1052 pkinit_kdc_context *realm_contexts;
1053
1054 if (moddata == NULL)
1055 return NULL;
1056
1057 realm_contexts = moddata->realm_contexts;
1058 if (realm_contexts == NULL)
1059 return NULL;
1060
1061 for (i = 0; realm_contexts[i] != NULL; i++) {
1062 pkinit_kdc_context p = realm_contexts[i];
1063
1064 if ((p->realmname_len == princ->realm.length) &&
1065 (strncmp(p->realmname, princ->realm.data, p->realmname_len) == 0)) {
1066 pkiDebug("%s: returning context at %p for realm '%s'\n",
1067 __FUNCTION__, p, p->realmname);
1068 return p;
1069 }
1070 }
1071 pkiDebug("%s: unable to find realm context for realm '%.*s'\n",
1072 __FUNCTION__, princ->realm.length, princ->realm.data);
1073 return NULL;
1074 }
1075
1076 static int
pkinit_server_plugin_init_realm(krb5_context context,const char * realmname,pkinit_kdc_context * pplgctx)1077 pkinit_server_plugin_init_realm(krb5_context context, const char *realmname,
1078 pkinit_kdc_context *pplgctx)
1079 {
1080 krb5_error_code retval = ENOMEM;
1081 pkinit_kdc_context plgctx = NULL;
1082
1083 *pplgctx = NULL;
1084
1085 plgctx = calloc(1, sizeof(*plgctx));
1086 if (plgctx == NULL)
1087 goto errout;
1088
1089 pkiDebug("%s: initializing context at %p for realm '%s'\n",
1090 __FUNCTION__, plgctx, realmname);
1091 memset(plgctx, 0, sizeof(*plgctx));
1092 plgctx->magic = PKINIT_CTX_MAGIC;
1093
1094 plgctx->realmname = strdup(realmname);
1095 if (plgctx->realmname == NULL)
1096 goto errout;
1097 plgctx->realmname_len = strlen(plgctx->realmname);
1098
1099 retval = pkinit_init_plg_crypto(context, &plgctx->cryptoctx);
1100 if (retval)
1101 goto errout;
1102
1103 retval = pkinit_init_plg_opts(&plgctx->opts);
1104 if (retval)
1105 goto errout;
1106
1107 retval = pkinit_init_identity_crypto(&plgctx->idctx);
1108 if (retval)
1109 goto errout;
1110
1111 retval = pkinit_init_identity_opts(&plgctx->idopts);
1112 if (retval)
1113 goto errout;
1114
1115 retval = pkinit_init_kdc_profile(context, plgctx);
1116 if (retval)
1117 goto errout;
1118
1119 retval = pkinit_identity_initialize(context, plgctx->cryptoctx, NULL,
1120 plgctx->idopts, plgctx->idctx,
1121 NULL, NULL, NULL);
1122 if (retval)
1123 goto errout;
1124 retval = pkinit_identity_prompt(context, plgctx->cryptoctx, NULL,
1125 plgctx->idopts, plgctx->idctx,
1126 NULL, NULL, 0, NULL);
1127 if (retval)
1128 goto errout;
1129
1130 pkiDebug("%s: returning context at %p for realm '%s'\n",
1131 __FUNCTION__, plgctx, realmname);
1132 *pplgctx = plgctx;
1133 retval = 0;
1134
1135 errout:
1136 if (retval)
1137 pkinit_server_plugin_fini_realm(context, plgctx);
1138
1139 return retval;
1140 }
1141
1142 static krb5_error_code
pkinit_san_authorize(krb5_context context,krb5_certauth_moddata moddata,const uint8_t * cert,size_t cert_len,krb5_const_principal princ,const void * opts,const struct _krb5_db_entry_new * db_entry,char *** authinds_out)1143 pkinit_san_authorize(krb5_context context, krb5_certauth_moddata moddata,
1144 const uint8_t *cert, size_t cert_len,
1145 krb5_const_principal princ, const void *opts,
1146 const struct _krb5_db_entry_new *db_entry,
1147 char ***authinds_out)
1148 {
1149 krb5_error_code ret;
1150 int valid_san;
1151 const struct certauth_req_opts *req_opts = opts;
1152
1153 *authinds_out = NULL;
1154
1155 ret = verify_client_san(context, req_opts->plgctx, req_opts->reqctx,
1156 req_opts->cb, req_opts->rock, princ, &valid_san);
1157 if (ret == ENOENT)
1158 return KRB5_PLUGIN_NO_HANDLE;
1159 else if (ret)
1160 return ret;
1161
1162 if (!valid_san) {
1163 TRACE_PKINIT_SERVER_SAN_REJECT(context);
1164 return KRB5KDC_ERR_CLIENT_NAME_MISMATCH;
1165 }
1166
1167 return 0;
1168 }
1169
1170 static krb5_error_code
pkinit_eku_authorize(krb5_context context,krb5_certauth_moddata moddata,const uint8_t * cert,size_t cert_len,krb5_const_principal princ,const void * opts,const struct _krb5_db_entry_new * db_entry,char *** authinds_out)1171 pkinit_eku_authorize(krb5_context context, krb5_certauth_moddata moddata,
1172 const uint8_t *cert, size_t cert_len,
1173 krb5_const_principal princ, const void *opts,
1174 const struct _krb5_db_entry_new *db_entry,
1175 char ***authinds_out)
1176 {
1177 krb5_error_code ret;
1178 int valid_eku;
1179 const struct certauth_req_opts *req_opts = opts;
1180
1181 *authinds_out = NULL;
1182
1183 /* Verify the client EKU. */
1184 ret = verify_client_eku(context, req_opts->plgctx, req_opts->reqctx,
1185 &valid_eku);
1186 if (ret)
1187 return ret;
1188
1189 if (!valid_eku) {
1190 TRACE_PKINIT_SERVER_EKU_REJECT(context);
1191 return KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE;
1192 }
1193
1194 return KRB5_PLUGIN_NO_HANDLE;
1195 }
1196
1197 static krb5_error_code
certauth_pkinit_san_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)1198 certauth_pkinit_san_initvt(krb5_context context, int maj_ver, int min_ver,
1199 krb5_plugin_vtable vtable)
1200 {
1201 krb5_certauth_vtable vt;
1202
1203 if (maj_ver != 1)
1204 return KRB5_PLUGIN_VER_NOTSUPP;
1205 vt = (krb5_certauth_vtable)vtable;
1206 vt->name = "pkinit_san";
1207 vt->authorize = pkinit_san_authorize;
1208 return 0;
1209 }
1210
1211 static krb5_error_code
certauth_pkinit_eku_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)1212 certauth_pkinit_eku_initvt(krb5_context context, int maj_ver, int min_ver,
1213 krb5_plugin_vtable vtable)
1214 {
1215 krb5_certauth_vtable vt;
1216
1217 if (maj_ver != 1)
1218 return KRB5_PLUGIN_VER_NOTSUPP;
1219 vt = (krb5_certauth_vtable)vtable;
1220 vt->name = "pkinit_eku";
1221 vt->authorize = pkinit_eku_authorize;
1222 return 0;
1223 }
1224
1225 /*
1226 * Do certificate auth based on a match expression in the pkinit_cert_match
1227 * attribute string. An expression should be in the same form as those used
1228 * for the pkinit_cert_match configuration option.
1229 */
1230 static krb5_error_code
dbmatch_authorize(krb5_context context,krb5_certauth_moddata moddata,const uint8_t * cert,size_t cert_len,krb5_const_principal princ,const void * opts,const struct _krb5_db_entry_new * db_entry,char *** authinds_out)1231 dbmatch_authorize(krb5_context context, krb5_certauth_moddata moddata,
1232 const uint8_t *cert, size_t cert_len,
1233 krb5_const_principal princ, const void *opts,
1234 const struct _krb5_db_entry_new *db_entry,
1235 char ***authinds_out)
1236 {
1237 krb5_error_code ret;
1238 const struct certauth_req_opts *req_opts = opts;
1239 char *pattern;
1240 krb5_boolean matched;
1241
1242 *authinds_out = NULL;
1243
1244 /* Fetch the matching pattern. Pass if it isn't specified. */
1245 ret = req_opts->cb->get_string(context, req_opts->rock,
1246 "pkinit_cert_match", &pattern);
1247 if (ret)
1248 return ret;
1249 if (pattern == NULL)
1250 return KRB5_PLUGIN_NO_HANDLE;
1251
1252 /* Check the certificate against the match expression. */
1253 ret = pkinit_client_cert_match(context, req_opts->plgctx->cryptoctx,
1254 req_opts->reqctx->cryptoctx, pattern,
1255 &matched);
1256 req_opts->cb->free_string(context, req_opts->rock, pattern);
1257 if (ret)
1258 return ret;
1259 return matched ? 0 : KRB5KDC_ERR_CERTIFICATE_MISMATCH;
1260 }
1261
1262 static krb5_error_code
certauth_dbmatch_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)1263 certauth_dbmatch_initvt(krb5_context context, int maj_ver, int min_ver,
1264 krb5_plugin_vtable vtable)
1265 {
1266 krb5_certauth_vtable vt;
1267
1268 if (maj_ver != 1)
1269 return KRB5_PLUGIN_VER_NOTSUPP;
1270 vt = (krb5_certauth_vtable)vtable;
1271 vt->name = "dbmatch";
1272 vt->authorize = dbmatch_authorize;
1273 return 0;
1274 }
1275
1276 static krb5_error_code
load_certauth_plugins(krb5_context context,const char * const * realmnames,certauth_handle ** handle_out)1277 load_certauth_plugins(krb5_context context, const char *const *realmnames,
1278 certauth_handle **handle_out)
1279 {
1280 krb5_error_code ret;
1281 krb5_plugin_initvt_fn *modules = NULL, *mod;
1282 certauth_handle *list = NULL, h;
1283 size_t count;
1284
1285 /* Register the builtin modules. */
1286 ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH,
1287 "pkinit_san", certauth_pkinit_san_initvt);
1288 if (ret)
1289 goto cleanup;
1290
1291 ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH,
1292 "pkinit_eku", certauth_pkinit_eku_initvt);
1293 if (ret)
1294 goto cleanup;
1295
1296 ret = k5_plugin_register(context, PLUGIN_INTERFACE_CERTAUTH, "dbmatch",
1297 certauth_dbmatch_initvt);
1298 if (ret)
1299 goto cleanup;
1300
1301 ret = k5_plugin_load_all(context, PLUGIN_INTERFACE_CERTAUTH, &modules);
1302 if (ret)
1303 goto cleanup;
1304
1305 /* Allocate handle list. */
1306 for (count = 0; modules[count]; count++);
1307 list = k5calloc(count + 1, sizeof(*list), &ret);
1308 if (list == NULL)
1309 goto cleanup;
1310
1311 /* Initialize each module, ignoring ones that fail. */
1312 count = 0;
1313 for (mod = modules; *mod != NULL; mod++) {
1314 h = k5calloc(1, sizeof(*h), &ret);
1315 if (h == NULL)
1316 goto cleanup;
1317
1318 ret = (*mod)(context, 1, 2, (krb5_plugin_vtable)&h->vt);
1319 if (ret) {
1320 TRACE_CERTAUTH_VTINIT_FAIL(context, ret);
1321 free(h);
1322 continue;
1323 }
1324 h->moddata = NULL;
1325 if (h->vt.init_ex != NULL)
1326 ret = h->vt.init_ex(context, realmnames, &h->moddata);
1327 else if (h->vt.init != NULL)
1328 ret = h->vt.init(context, &h->moddata);
1329 if (ret) {
1330 TRACE_CERTAUTH_INIT_FAIL(context, h->vt.name, ret);
1331 free(h);
1332 continue;
1333 }
1334 list[count++] = h;
1335 list[count] = NULL;
1336 }
1337 list[count] = NULL;
1338
1339 ret = 0;
1340 *handle_out = list;
1341 list = NULL;
1342
1343 cleanup:
1344 k5_plugin_free_modules(context, modules);
1345 free_certauth_handles(context, list);
1346 return ret;
1347 }
1348
1349 static int
pkinit_server_plugin_init(krb5_context context,krb5_kdcpreauth_moddata * moddata_out,const char ** realmnames)1350 pkinit_server_plugin_init(krb5_context context,
1351 krb5_kdcpreauth_moddata *moddata_out,
1352 const char **realmnames)
1353 {
1354 krb5_error_code retval = ENOMEM;
1355 pkinit_kdc_context plgctx, *realm_contexts = NULL;
1356 certauth_handle *certauth_modules = NULL;
1357 krb5_kdcpreauth_moddata moddata;
1358 size_t i, j;
1359 size_t numrealms;
1360
1361 retval = pkinit_accessor_init();
1362 if (retval)
1363 return retval;
1364
1365 /* Determine how many realms we may need to support */
1366 for (i = 0; realmnames[i] != NULL; i++) {};
1367 numrealms = i;
1368
1369 realm_contexts = calloc(numrealms+1, sizeof(pkinit_kdc_context));
1370 if (realm_contexts == NULL)
1371 return ENOMEM;
1372
1373 for (i = 0, j = 0; i < numrealms; i++) {
1374 TRACE_PKINIT_SERVER_INIT_REALM(context, realmnames[i]);
1375 krb5_clear_error_message(context);
1376 retval = pkinit_server_plugin_init_realm(context, realmnames[i],
1377 &plgctx);
1378 if (retval)
1379 TRACE_PKINIT_SERVER_INIT_FAIL(context, realmnames[i], retval);
1380 else
1381 realm_contexts[j++] = plgctx;
1382 }
1383
1384 if (j == 0) {
1385 if (numrealms == 1) {
1386 k5_prependmsg(context, retval, "PKINIT initialization failed");
1387 } else {
1388 retval = EINVAL;
1389 k5_setmsg(context, retval,
1390 _("No realms configured correctly for pkinit support"));
1391 }
1392 goto errout;
1393 }
1394
1395 retval = load_certauth_plugins(context, realmnames, &certauth_modules);
1396 if (retval)
1397 goto errout;
1398
1399 moddata = k5calloc(1, sizeof(*moddata), &retval);
1400 if (moddata == NULL)
1401 goto errout;
1402 moddata->realm_contexts = realm_contexts;
1403 moddata->certauth_modules = certauth_modules;
1404 *moddata_out = moddata;
1405 pkiDebug("%s: returning context at %p\n", __FUNCTION__, moddata);
1406 return 0;
1407
1408 errout:
1409 free_realm_contexts(context, realm_contexts);
1410 free_certauth_handles(context, certauth_modules);
1411 return retval;
1412 }
1413
1414 static void
pkinit_server_plugin_fini_realm(krb5_context context,pkinit_kdc_context plgctx)1415 pkinit_server_plugin_fini_realm(krb5_context context, pkinit_kdc_context plgctx)
1416 {
1417 char **sp;
1418
1419 if (plgctx == NULL)
1420 return;
1421
1422 pkinit_fini_kdc_profile(context, plgctx);
1423 pkinit_fini_identity_opts(plgctx->idopts);
1424 pkinit_fini_identity_crypto(plgctx->idctx);
1425 pkinit_fini_plg_crypto(plgctx->cryptoctx);
1426 pkinit_fini_plg_opts(plgctx->opts);
1427 for (sp = plgctx->auth_indicators; sp != NULL && *sp != NULL; sp++)
1428 free(*sp);
1429 free(plgctx->auth_indicators);
1430 free(plgctx->realmname);
1431 free(plgctx);
1432 }
1433
1434 static void
pkinit_server_plugin_fini(krb5_context context,krb5_kdcpreauth_moddata moddata)1435 pkinit_server_plugin_fini(krb5_context context,
1436 krb5_kdcpreauth_moddata moddata)
1437 {
1438 if (moddata == NULL)
1439 return;
1440 free_realm_contexts(context, moddata->realm_contexts);
1441 free_certauth_handles(context, moddata->certauth_modules);
1442 free(moddata);
1443 }
1444
1445 static krb5_error_code
pkinit_init_kdc_req_context(krb5_context context,pkinit_kdc_req_context * ctx)1446 pkinit_init_kdc_req_context(krb5_context context, pkinit_kdc_req_context *ctx)
1447 {
1448 krb5_error_code retval = ENOMEM;
1449 pkinit_kdc_req_context reqctx = NULL;
1450
1451 reqctx = malloc(sizeof(*reqctx));
1452 if (reqctx == NULL)
1453 return retval;
1454 memset(reqctx, 0, sizeof(*reqctx));
1455 reqctx->magic = PKINIT_CTX_MAGIC;
1456
1457 retval = pkinit_init_req_crypto(&reqctx->cryptoctx);
1458 if (retval)
1459 goto cleanup;
1460 reqctx->rcv_auth_pack = NULL;
1461
1462 pkiDebug("%s: returning reqctx at %p\n", __FUNCTION__, reqctx);
1463 *ctx = reqctx;
1464 retval = 0;
1465 cleanup:
1466 if (retval)
1467 pkinit_fini_kdc_req_context(context, reqctx);
1468
1469 return retval;
1470 }
1471
1472 static void
pkinit_fini_kdc_req_context(krb5_context context,void * ctx)1473 pkinit_fini_kdc_req_context(krb5_context context, void *ctx)
1474 {
1475 pkinit_kdc_req_context reqctx = (pkinit_kdc_req_context)ctx;
1476
1477 if (reqctx == NULL || reqctx->magic != PKINIT_CTX_MAGIC) {
1478 pkiDebug("pkinit_fini_kdc_req_context: got bad reqctx (%p)!\n", reqctx);
1479 return;
1480 }
1481 pkiDebug("%s: freeing reqctx at %p\n", __FUNCTION__, reqctx);
1482
1483 pkinit_fini_req_crypto(reqctx->cryptoctx);
1484 if (reqctx->rcv_auth_pack != NULL)
1485 free_krb5_auth_pack(&reqctx->rcv_auth_pack);
1486
1487 free(reqctx);
1488 }
1489
1490 static void
pkinit_free_modreq(krb5_context context,krb5_kdcpreauth_moddata moddata,krb5_kdcpreauth_modreq modreq)1491 pkinit_free_modreq(krb5_context context, krb5_kdcpreauth_moddata moddata,
1492 krb5_kdcpreauth_modreq modreq)
1493 {
1494 pkinit_fini_kdc_req_context(context, modreq);
1495 }
1496
1497 krb5_error_code
1498 kdcpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver,
1499 krb5_plugin_vtable vtable);
1500
1501 krb5_error_code
kdcpreauth_pkinit_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)1502 kdcpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver,
1503 krb5_plugin_vtable vtable)
1504 {
1505 krb5_kdcpreauth_vtable vt;
1506
1507 if (maj_ver != 1)
1508 return KRB5_PLUGIN_VER_NOTSUPP;
1509 vt = (krb5_kdcpreauth_vtable)vtable;
1510 vt->name = "pkinit";
1511 vt->pa_type_list = supported_server_pa_types;
1512 vt->init = pkinit_server_plugin_init;
1513 vt->fini = pkinit_server_plugin_fini;
1514 vt->flags = pkinit_server_get_flags;
1515 vt->edata = pkinit_server_get_edata;
1516 vt->verify = pkinit_server_verify_padata;
1517 vt->return_padata = pkinit_server_return_padata;
1518 vt->free_modreq = pkinit_free_modreq;
1519 return 0;
1520 }
1521