xref: /freebsd/crypto/krb5/src/plugins/preauth/pkinit/pkinit_srv.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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