xref: /freebsd/crypto/krb5/src/plugins/preauth/pkinit/pkinit_clnt.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 "k5-json.h"
35 
36 #include <sys/stat.h>
37 
38 /**
39  * Return true if we should use ContentInfo rather than SignedData. This
40  * happens if we are talking to what might be an old (pre-6112) MIT KDC and
41  * we're using anonymous.
42  */
43 static int
use_content_info(krb5_context context,pkinit_req_context req,krb5_principal client)44 use_content_info(krb5_context context, pkinit_req_context req,
45                  krb5_principal client)
46 {
47     if (req->rfc6112_kdc)
48         return 0;
49     if (krb5_principal_compare_any_realm(context, client,
50                                          krb5_anonymous_principal()))
51         return 1;
52     return 0;
53 }
54 
55 static krb5_error_code
56 pkinit_as_req_create(krb5_context context, pkinit_context plgctx,
57                      pkinit_req_context reqctx, krb5_timestamp ctsec,
58                      krb5_int32 cusec, krb5_ui_4 nonce, const krb5_data *cksum,
59                      const krb5_pachecksum2 *cksum2, krb5_principal client,
60                      krb5_principal server, krb5_data **as_req);
61 
62 static krb5_error_code
63 pkinit_as_rep_parse(krb5_context context, pkinit_context plgctx,
64                     pkinit_req_context reqctx, krb5_preauthtype pa_type,
65                     krb5_kdc_req *request, const krb5_data *as_rep,
66                     krb5_keyblock *key_block, krb5_enctype etype, krb5_data *);
67 
68 static void pkinit_client_plugin_fini(krb5_context context,
69                                       krb5_clpreauth_moddata moddata);
70 
71 static krb5_error_code
pa_pkinit_gen_req(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,krb5_kdc_req * request,krb5_preauthtype pa_type,krb5_pa_data *** out_padata,krb5_prompter_fct prompter,void * prompter_data,krb5_get_init_creds_opt * gic_opt)72 pa_pkinit_gen_req(krb5_context context,
73                   pkinit_context plgctx,
74                   pkinit_req_context reqctx,
75                   krb5_clpreauth_callbacks cb,
76                   krb5_clpreauth_rock rock,
77                   krb5_kdc_req * request,
78                   krb5_preauthtype pa_type,
79                   krb5_pa_data *** out_padata,
80                   krb5_prompter_fct prompter,
81                   void *prompter_data,
82                   krb5_get_init_creds_opt *gic_opt)
83 {
84 
85     krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;
86     krb5_data *out_data = NULL;
87     krb5_timestamp ctsec = 0;
88     krb5_int32 cusec = 0;
89     krb5_ui_4 nonce = 0;
90     krb5_data cksum = empty_data();
91     krb5_pachecksum2 *cksum2 = NULL;
92     krb5_data *der_req = NULL;
93     krb5_pa_data **return_pa_data = NULL;
94 
95     memset(&cksum, 0, sizeof(cksum));
96     reqctx->pa_type = pa_type;
97 
98     pkiDebug("kdc_options = 0x%x  till = %d\n",
99              request->kdc_options, request->till);
100     /* If we don't have a client, we're done */
101     if (request->client == NULL) {
102         pkiDebug("No request->client; aborting PKINIT\n");
103         return KRB5KDC_ERR_PREAUTH_FAILED;
104     }
105 
106     retval = pkinit_get_kdc_cert(context, plgctx->cryptoctx, reqctx->cryptoctx,
107                                  reqctx->idctx, request->server);
108     if (retval) {
109         pkiDebug("pkinit_get_kdc_cert returned %d\n", retval);
110         goto cleanup;
111     }
112 
113     /* checksum of the encoded KDC-REQ-BODY */
114     retval = k5int_encode_krb5_kdc_req_body(request, &der_req);
115     if (retval) {
116         pkiDebug("encode_krb5_kdc_req_body returned %d\n", (int) retval);
117         goto cleanup;
118     }
119 
120     retval = crypto_generate_checksums(context, der_req, &cksum, &cksum2);
121     if (retval)
122         goto cleanup;
123     TRACE_PKINIT_CLIENT_REQ_CHECKSUMS(context, &cksum, cksum2);
124 
125     retval = cb->get_preauth_time(context, rock, TRUE, &ctsec, &cusec);
126     if (retval)
127         goto cleanup;
128 
129     /* XXX PKINIT RFC says that nonce in PKAuthenticator doesn't have be the
130      * same as in the AS_REQ. However, if we pick a different nonce, then we
131      * need to remember that info when AS_REP is returned. I'm choosing to
132      * reuse the AS_REQ nonce.
133      */
134     nonce = request->nonce;
135 
136     retval = pkinit_as_req_create(context, plgctx, reqctx, ctsec, cusec,
137                                   nonce, &cksum, cksum2, request->client,
138                                   request->server, &out_data);
139     if (retval) {
140         pkiDebug("error %d on pkinit_as_req_create; aborting PKINIT\n",
141                  (int) retval);
142         goto cleanup;
143     }
144 
145     return_pa_data = k5calloc(2, sizeof(*return_pa_data), &retval);
146     if (return_pa_data == NULL)
147         goto cleanup;
148 
149     return_pa_data[0] = k5alloc(sizeof(*return_pa_data[0]), &retval);
150     if (return_pa_data[0] == NULL)
151         goto cleanup;
152 
153     return_pa_data[0]->magic = KV5M_PA_DATA;
154 
155     return_pa_data[0]->pa_type = pa_type;
156     return_pa_data[0]->length = out_data->length;
157     return_pa_data[0]->contents = (krb5_octet *) out_data->data;
158     *out_data = empty_data();
159 
160     *out_padata = return_pa_data;
161     return_pa_data = NULL;
162     cb->disable_fallback(context, rock);
163 
164 cleanup:
165     krb5_free_data(context, der_req);
166     krb5_free_data_contents(context, &cksum);
167     free_pachecksum2(context, &cksum2);
168     krb5_free_data(context, out_data);
169     krb5_free_pa_data(context, return_pa_data);
170     return retval;
171 }
172 
173 static krb5_error_code
pkinit_as_req_create(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,krb5_timestamp ctsec,krb5_int32 cusec,krb5_ui_4 nonce,const krb5_data * cksum,const krb5_pachecksum2 * cksum2,krb5_principal client,krb5_principal server,krb5_data ** as_req)174 pkinit_as_req_create(krb5_context context, pkinit_context plgctx,
175                      pkinit_req_context reqctx, krb5_timestamp ctsec,
176                      krb5_int32 cusec, krb5_ui_4 nonce, const krb5_data *cksum,
177                      const krb5_pachecksum2 *cksum2, krb5_principal client,
178                      krb5_principal server, krb5_data **as_req)
179 {
180     krb5_error_code retval = ENOMEM;
181     krb5_data spki = empty_data(), *coded_auth_pack = NULL;
182     krb5_auth_pack auth_pack;
183     krb5_pa_pk_as_req *req = NULL;
184     krb5_algorithm_identifier **cmstypes = NULL;
185 
186     pkiDebug("pkinit_as_req_create pa_type = %d\n", reqctx->pa_type);
187 
188     /* Create the authpack */
189     memset(&auth_pack, 0, sizeof(auth_pack));
190     auth_pack.pkAuthenticator.ctime = ctsec;
191     auth_pack.pkAuthenticator.cusec = cusec;
192     auth_pack.pkAuthenticator.nonce = nonce;
193     auth_pack.pkAuthenticator.paChecksum = *cksum;
194     if (!reqctx->opts->disable_freshness)
195         auth_pack.pkAuthenticator.freshnessToken = reqctx->freshness_token;
196     auth_pack.pkAuthenticator.paChecksum2 = (krb5_pachecksum2 *)cksum2;
197     auth_pack.clientDHNonce.length = 0;
198     auth_pack.supportedKDFs = (krb5_data **)supported_kdf_alg_ids;
199 
200     /* add List of CMS algorithms */
201     retval = create_krb5_supportedCMSTypes(context, plgctx->cryptoctx,
202                                            reqctx->cryptoctx,
203                                            reqctx->idctx, &cmstypes);
204     auth_pack.supportedCMSTypes = cmstypes;
205     if (retval)
206         goto cleanup;
207 
208     TRACE_PKINIT_CLIENT_REQ_DH(context);
209 
210     /* create client-side DH keys */
211     retval = client_create_dh(context, plgctx->cryptoctx, reqctx->cryptoctx,
212                               reqctx->idctx, reqctx->opts->dh_size, &spki);
213     auth_pack.clientPublicValue = spki;
214     if (retval != 0) {
215         pkiDebug("failed to create dh parameters\n");
216         goto cleanup;
217     }
218 
219     retval = k5int_encode_krb5_auth_pack(&auth_pack, &coded_auth_pack);
220     if (retval) {
221         pkiDebug("failed to encode the AuthPack %d\n", retval);
222         goto cleanup;
223     }
224 #ifdef DEBUG_ASN1
225     print_buffer_bin((unsigned char *)coded_auth_pack->data,
226                      coded_auth_pack->length,
227                      "/tmp/client_auth_pack");
228 #endif
229 
230     /* create PKCS7 object from authpack */
231     init_krb5_pa_pk_as_req(&req);
232     if (req == NULL) {
233         retval = ENOMEM;
234         goto cleanup;
235     }
236     if (use_content_info(context, reqctx, client)) {
237         retval = cms_contentinfo_create(context, plgctx->cryptoctx,
238                                         reqctx->cryptoctx, reqctx->idctx,
239                                         CMS_SIGN_CLIENT,
240                                         (unsigned char *)
241                                         coded_auth_pack->data,
242                                         coded_auth_pack->length,
243                                         (unsigned char **)
244                                         &req->signedAuthPack.data,
245                                         &req->signedAuthPack.length);
246     } else {
247         retval = cms_signeddata_create(context, plgctx->cryptoctx,
248                                        reqctx->cryptoctx, reqctx->idctx,
249                                        CMS_SIGN_CLIENT,
250                                        (unsigned char *)
251                                        coded_auth_pack->data,
252                                        coded_auth_pack->length,
253                                        (unsigned char **)
254                                        &req->signedAuthPack.data,
255                                        &req->signedAuthPack.length);
256     }
257 
258 #ifdef DEBUG_ASN1
259     print_buffer_bin((unsigned char *)req->signedAuthPack.data,
260                      req->signedAuthPack.length,
261                      "/tmp/client_signed_data");
262 #endif
263 
264     krb5_free_data(context, coded_auth_pack);
265     if (retval) {
266         pkiDebug("failed to create pkcs7 signed data\n");
267         goto cleanup;
268     }
269 
270     /* create a list of trusted CAs */
271     retval = create_krb5_trustedCertifiers(context, plgctx->cryptoctx,
272                                            reqctx->cryptoctx, reqctx->idctx,
273                                            &req->trustedCertifiers);
274     if (retval)
275         goto cleanup;
276     retval = create_issuerAndSerial(context, plgctx->cryptoctx,
277                                     reqctx->cryptoctx, reqctx->idctx,
278                                     (unsigned char **)&req->kdcPkId.data,
279                                     &req->kdcPkId.length);
280     if (retval)
281         goto cleanup;
282 
283     /* Encode the as-req */
284     retval = k5int_encode_krb5_pa_pk_as_req(req, as_req);
285 
286 #ifdef DEBUG_ASN1
287     if (!retval)
288         print_buffer_bin((unsigned char *)(*as_req)->data, (*as_req)->length,
289                          "/tmp/client_as_req");
290 #endif
291 
292 cleanup:
293     free_krb5_algorithm_identifiers(&cmstypes);
294     free_krb5_pa_pk_as_req(&req);
295     krb5_free_data_contents(context, &spki);
296 
297     pkiDebug("pkinit_as_req_create retval=%d\n", (int) retval);
298 
299     return retval;
300 }
301 
302 static krb5_error_code
pa_pkinit_parse_rep(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,krb5_kdc_req * request,krb5_pa_data * in_padata,krb5_enctype etype,krb5_keyblock * as_key,krb5_data * encoded_request)303 pa_pkinit_parse_rep(krb5_context context,
304                     pkinit_context plgctx,
305                     pkinit_req_context reqctx,
306                     krb5_kdc_req * request,
307                     krb5_pa_data * in_padata,
308                     krb5_enctype etype,
309                     krb5_keyblock * as_key,
310                     krb5_data *encoded_request)
311 {
312     krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;
313     krb5_data asRep = { 0, 0, NULL};
314 
315     /*
316      * One way or the other - success or failure - no other PA systems can
317      * work if the server sent us a PKINIT reply, since only we know how to
318      * decrypt the key.
319      */
320     if ((in_padata == NULL) || (in_padata->length == 0)) {
321         pkiDebug("pa_pkinit_parse_rep: no in_padata\n");
322         return KRB5KDC_ERR_PREAUTH_FAILED;
323     }
324 
325     asRep.data = (char *) in_padata->contents;
326     asRep.length = in_padata->length;
327 
328     retval =
329         pkinit_as_rep_parse(context, plgctx, reqctx, in_padata->pa_type,
330                             request, &asRep, as_key, etype, encoded_request);
331     if (retval) {
332         pkiDebug("pkinit_as_rep_parse returned %d (%s)\n",
333                  retval, error_message(retval));
334         goto cleanup;
335     }
336 
337     retval = 0;
338 
339 cleanup:
340 
341     return retval;
342 }
343 
344 static krb5_error_code
verify_kdc_san(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,krb5_principal kdcprinc,int * valid_san,int * need_eku_checking)345 verify_kdc_san(krb5_context context,
346                pkinit_context plgctx,
347                pkinit_req_context reqctx,
348                krb5_principal kdcprinc,
349                int *valid_san,
350                int *need_eku_checking)
351 {
352     krb5_error_code retval;
353     char **certhosts = NULL, **cfghosts = NULL, **hostptr;
354     krb5_principal *princs = NULL;
355     unsigned char ***get_dns;
356     size_t i, j;
357 
358     *valid_san = 0;
359     *need_eku_checking = 1;
360 
361     retval = pkinit_libdefault_strings(context,
362                                        krb5_princ_realm(context, kdcprinc),
363                                        KRB5_CONF_PKINIT_KDC_HOSTNAME,
364                                        &cfghosts);
365     if (retval || cfghosts == NULL) {
366         pkiDebug("%s: No pkinit_kdc_hostname values found in config file\n",
367                  __FUNCTION__);
368         get_dns = NULL;
369     } else {
370         pkiDebug("%s: pkinit_kdc_hostname values found in config file\n",
371                  __FUNCTION__);
372         for (hostptr = cfghosts; *hostptr != NULL; hostptr++)
373             TRACE_PKINIT_CLIENT_SAN_CONFIG_DNSNAME(context, *hostptr);
374         get_dns = (unsigned char ***)&certhosts;
375     }
376 
377     retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx,
378                                        reqctx->cryptoctx, reqctx->idctx,
379                                        &princs, NULL, get_dns);
380     if (retval) {
381         pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__);
382         TRACE_PKINIT_CLIENT_SAN_ERR(context);
383         retval = KRB5KDC_ERR_KDC_NAME_MISMATCH;
384         goto out;
385     }
386     for (i = 0; princs != NULL && princs[i] != NULL; i++)
387         TRACE_PKINIT_CLIENT_SAN_KDCCERT_PRINC(context, princs[i]);
388     if (certhosts != NULL) {
389         for (hostptr = certhosts; *hostptr != NULL; hostptr++)
390             TRACE_PKINIT_CLIENT_SAN_KDCCERT_DNSNAME(context, *hostptr);
391     }
392 
393     pkiDebug("%s: Checking pkinit sans\n", __FUNCTION__);
394     for (i = 0; princs != NULL && princs[i] != NULL; i++) {
395         if (krb5_principal_compare(context, princs[i], kdcprinc)) {
396             TRACE_PKINIT_CLIENT_SAN_MATCH_PRINC(context, kdcprinc);
397             pkiDebug("%s: pkinit san match found\n", __FUNCTION__);
398             *valid_san = 1;
399             *need_eku_checking = 0;
400             retval = 0;
401             goto out;
402         }
403     }
404     pkiDebug("%s: no pkinit san match found\n", __FUNCTION__);
405 
406     if (certhosts == NULL) {
407         pkiDebug("%s: no certhosts (or we wouldn't accept them anyway)\n",
408                  __FUNCTION__);
409         retval = KRB5KDC_ERR_KDC_NAME_MISMATCH;
410         goto out;
411     }
412 
413     for (i = 0; certhosts[i] != NULL; i++) {
414         for (j = 0; cfghosts != NULL && cfghosts[j] != NULL; j++) {
415             pkiDebug("%s: comparing cert name '%s' with config name '%s'\n",
416                      __FUNCTION__, certhosts[i], cfghosts[j]);
417             if (strcasecmp(certhosts[i], cfghosts[j]) == 0) {
418                 TRACE_PKINIT_CLIENT_SAN_MATCH_DNSNAME(context, certhosts[i]);
419                 pkiDebug("%s: we have a dnsName match\n", __FUNCTION__);
420                 *valid_san = 1;
421                 retval = 0;
422                 goto out;
423             }
424         }
425     }
426     TRACE_PKINIT_CLIENT_SAN_MATCH_NONE(context);
427     pkiDebug("%s: no dnsName san match found\n", __FUNCTION__);
428 
429     /* We found no match */
430     retval = 0;
431 
432 out:
433     if (princs != NULL) {
434         for (i = 0; princs[i] != NULL; i++)
435             krb5_free_principal(context, princs[i]);
436         free(princs);
437     }
438     if (certhosts != NULL) {
439         for (i = 0; certhosts[i] != NULL; i++)
440             free(certhosts[i]);
441         free(certhosts);
442     }
443     if (cfghosts != NULL)
444         profile_free_list(cfghosts);
445 
446     pkiDebug("%s: returning retval %d, valid_san %d, need_eku_checking %d\n",
447              __FUNCTION__, retval, *valid_san, *need_eku_checking);
448     return retval;
449 }
450 
451 static krb5_error_code
verify_kdc_eku(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,int * eku_accepted)452 verify_kdc_eku(krb5_context context,
453                pkinit_context plgctx,
454                pkinit_req_context reqctx,
455                int *eku_accepted)
456 {
457     krb5_error_code retval;
458 
459     *eku_accepted = 0;
460 
461     if (reqctx->opts->require_eku == 0) {
462         TRACE_PKINIT_CLIENT_EKU_SKIP(context);
463         pkiDebug("%s: configuration requests no EKU checking\n", __FUNCTION__);
464         *eku_accepted = 1;
465         retval = 0;
466         goto out;
467     }
468     retval = crypto_check_cert_eku(context, plgctx->cryptoctx,
469                                    reqctx->cryptoctx, reqctx->idctx,
470                                    1, /* kdc cert */
471                                    reqctx->opts->accept_secondary_eku,
472                                    eku_accepted);
473     if (retval) {
474         pkiDebug("%s: Error from crypto_check_cert_eku %d (%s)\n",
475                  __FUNCTION__, retval, error_message(retval));
476         goto out;
477     }
478 
479 out:
480     if (*eku_accepted)
481         TRACE_PKINIT_CLIENT_EKU_ACCEPT(context);
482     else
483         TRACE_PKINIT_CLIENT_EKU_REJECT(context);
484     pkiDebug("%s: returning retval %d, eku_accepted %d\n",
485              __FUNCTION__, retval, *eku_accepted);
486     return retval;
487 }
488 
489 /*
490  * Parse PA-PK-AS-REP message. Optionally evaluates the message's
491  * certificate chain.
492  * Optionally returns various components.
493  */
494 static krb5_error_code
pkinit_as_rep_parse(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,krb5_preauthtype pa_type,krb5_kdc_req * request,const krb5_data * as_rep,krb5_keyblock * key_block,krb5_enctype etype,krb5_data * encoded_request)495 pkinit_as_rep_parse(krb5_context context,
496                     pkinit_context plgctx,
497                     pkinit_req_context reqctx,
498                     krb5_preauthtype pa_type,
499                     krb5_kdc_req *request,
500                     const krb5_data *as_rep,
501                     krb5_keyblock *key_block,
502                     krb5_enctype etype,
503                     krb5_data *encoded_request)
504 {
505     krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;
506     krb5_principal kdc_princ = NULL;
507     krb5_pa_pk_as_rep *kdc_reply = NULL;
508     krb5_kdc_dh_key_info *kdc_dh = NULL;
509     krb5_reply_key_pack *key_pack = NULL;
510     krb5_data dh_data = { 0, 0, NULL };
511     unsigned char *client_key = NULL, *kdc_hostname = NULL;
512     unsigned int client_key_len = 0;
513     krb5_checksum cksum = {0, 0, 0, NULL};
514     krb5_data k5data;
515     krb5_data secret;
516     int valid_san = 0;
517     int valid_eku = 0;
518     int need_eku_checking = 1;
519 
520     assert((as_rep != NULL) && (key_block != NULL));
521 
522 #ifdef DEBUG_ASN1
523     print_buffer_bin((unsigned char *)as_rep->data, as_rep->length,
524                      "/tmp/client_as_rep");
525 #endif
526 
527     if ((retval = k5int_decode_krb5_pa_pk_as_rep(as_rep, &kdc_reply))) {
528         pkiDebug("decode_krb5_as_rep failed %d\n", retval);
529         return retval;
530     }
531 
532     if (kdc_reply->choice != choice_pa_pk_as_rep_dhInfo) {
533         pkiDebug("unknown as_rep type %d\n", kdc_reply->choice);
534         retval = KRB5KDC_ERR_PREAUTH_FAILED;
535         goto cleanup;
536     }
537 
538 #ifdef DEBUG_ASN1
539     print_buffer_bin(kdc_reply->u.dh_Info.dhSignedData.data,
540                      kdc_reply->u.dh_Info.dhSignedData.length,
541                      "/tmp/client_kdc_signeddata");
542 #endif
543     retval = cms_signeddata_verify(context, plgctx->cryptoctx,
544                                    reqctx->cryptoctx, reqctx->idctx,
545                                    CMS_SIGN_SERVER,
546                                    reqctx->opts->require_crl_checking,
547                                    (unsigned char *)
548                                    kdc_reply->u.dh_Info.dhSignedData.data,
549                                    kdc_reply->u.dh_Info.dhSignedData.length,
550                                    (unsigned char **)&dh_data.data,
551                                    &dh_data.length,
552                                    NULL, NULL, NULL);
553     if (retval) {
554         pkiDebug("failed to verify pkcs7 signed data\n");
555         TRACE_PKINIT_CLIENT_REP_DH_FAIL(context);
556         goto cleanup;
557     }
558     TRACE_PKINIT_CLIENT_REP_DH(context);
559 
560     retval = krb5_build_principal_ext(context, &kdc_princ,
561                                       request->server->realm.length,
562                                       request->server->realm.data,
563                                       strlen(KRB5_TGS_NAME), KRB5_TGS_NAME,
564                                       request->server->realm.length,
565                                       request->server->realm.data,
566                                       0);
567     if (retval)
568         goto cleanup;
569     retval = verify_kdc_san(context, plgctx, reqctx, kdc_princ,
570                             &valid_san, &need_eku_checking);
571     if (retval)
572         goto cleanup;
573     if (!valid_san) {
574         pkiDebug("%s: did not find an acceptable SAN in KDC certificate\n",
575                  __FUNCTION__);
576         retval = KRB5KDC_ERR_KDC_NAME_MISMATCH;
577         goto cleanup;
578     }
579 
580     if (need_eku_checking) {
581         retval = verify_kdc_eku(context, plgctx, reqctx,
582                                 &valid_eku);
583         if (retval)
584             goto cleanup;
585         if (!valid_eku) {
586             pkiDebug("%s: did not find an acceptable EKU in KDC certificate\n",
587                      __FUNCTION__);
588             retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE;
589             goto cleanup;
590         }
591     } else
592         pkiDebug("%s: skipping EKU check\n", __FUNCTION__);
593 
594     OCTETDATA_TO_KRB5DATA(&dh_data, &k5data);
595 
596 #ifdef DEBUG_ASN1
597     print_buffer_bin(dh_data.data, dh_data.length, "/tmp/client_dh_key");
598 #endif
599     retval = k5int_decode_krb5_kdc_dh_key_info(&k5data, &kdc_dh);
600     if (retval) {
601         pkiDebug("failed to decode kdc_dh_key_info\n");
602         goto cleanup;
603     }
604 
605     /* client after KDC reply */
606     retval = client_process_dh(context, plgctx->cryptoctx, reqctx->cryptoctx,
607                                reqctx->idctx,
608                                (unsigned char *)kdc_dh->subjectPublicKey.data,
609                                kdc_dh->subjectPublicKey.length, &client_key,
610                                &client_key_len);
611     if (retval) {
612         pkiDebug("failed to process dh params\n");
613         goto cleanup;
614     }
615 
616     secret = make_data(client_key, client_key_len);
617     retval = pkinit_kdf(context, &secret, kdc_reply->u.dh_Info.kdfID,
618                         request->client, request->server, etype,
619                         encoded_request, as_rep, key_block);
620     if (retval) {
621         pkiDebug("pkinit_kdf failed: %s\n", error_message(retval));
622         goto cleanup;
623     }
624 
625     retval = 0;
626 
627 cleanup:
628     free(dh_data.data);
629     krb5_free_principal(context, kdc_princ);
630     free(client_key);
631     free_krb5_kdc_dh_key_info(&kdc_dh);
632     free_krb5_pa_pk_as_rep(&kdc_reply);
633 
634     if (key_pack != NULL) {
635         free_krb5_reply_key_pack(&key_pack);
636         free(cksum.contents);
637     }
638 
639     free(kdc_hostname);
640 
641     pkiDebug("pkinit_as_rep_parse returning %d (%s)\n",
642              retval, error_message(retval));
643     return retval;
644 }
645 
646 static void
pkinit_client_profile(krb5_context context,pkinit_context plgctx,pkinit_req_context reqctx,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,const krb5_data * realm)647 pkinit_client_profile(krb5_context context,
648                       pkinit_context plgctx,
649                       pkinit_req_context reqctx,
650                       krb5_clpreauth_callbacks cb,
651                       krb5_clpreauth_rock rock,
652                       const krb5_data *realm)
653 {
654     const char *configured_identity;
655     char *eku_string = NULL, *minbits = NULL;
656 
657     pkiDebug("pkinit_client_profile %p %p %p %p\n",
658              context, plgctx, reqctx, realm);
659 
660     pkinit_libdefault_boolean(context, realm,
661                               KRB5_CONF_PKINIT_REQUIRE_CRL_CHECKING,
662                               reqctx->opts->require_crl_checking,
663                               &reqctx->opts->require_crl_checking);
664     pkinit_libdefault_string(context, realm, KRB5_CONF_PKINIT_DH_MIN_BITS,
665                              &minbits);
666     reqctx->opts->dh_size = parse_dh_min_bits(context, minbits);
667     free(minbits);
668     pkinit_libdefault_string(context, realm,
669                              KRB5_CONF_PKINIT_EKU_CHECKING,
670                              &eku_string);
671     if (eku_string != NULL) {
672         if (strcasecmp(eku_string, "kpKDC") == 0) {
673             reqctx->opts->require_eku = 1;
674             reqctx->opts->accept_secondary_eku = 0;
675         } else if (strcasecmp(eku_string, "kpServerAuth") == 0) {
676             reqctx->opts->require_eku = 1;
677             reqctx->opts->accept_secondary_eku = 1;
678         } else if (strcasecmp(eku_string, "none") == 0) {
679             reqctx->opts->require_eku = 0;
680             reqctx->opts->accept_secondary_eku = 0;
681         } else {
682             pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n",
683                      __FUNCTION__, eku_string);
684         }
685         free(eku_string);
686     }
687 
688     /* Only process anchors here if they were not specified on command line */
689     if (reqctx->idopts->anchors == NULL)
690         pkinit_libdefault_strings(context, realm,
691                                   KRB5_CONF_PKINIT_ANCHORS,
692                                   &reqctx->idopts->anchors);
693     pkinit_libdefault_strings(context, realm,
694                               KRB5_CONF_PKINIT_POOL,
695                               &reqctx->idopts->intermediates);
696     pkinit_libdefault_strings(context, realm,
697                               KRB5_CONF_PKINIT_REVOKE,
698                               &reqctx->idopts->crls);
699     pkinit_libdefault_strings(context, realm,
700                               KRB5_CONF_PKINIT_IDENTITIES,
701                               &reqctx->idopts->identity_alt);
702     reqctx->do_identity_matching = TRUE;
703 
704     /* If we had a primary identity in the stored configuration, pick it up. */
705     configured_identity = cb->get_cc_config(context, rock,
706                                             "X509_user_identity");
707     if (configured_identity != NULL) {
708         free(reqctx->idopts->identity);
709         reqctx->idopts->identity = strdup(configured_identity);
710         reqctx->do_identity_matching = FALSE;
711     }
712 }
713 
714 /*
715  * Convert a PKCS11 token flags value to the subset that we're interested in
716  * passing along to our API callers.
717  */
718 static long long
pkinit_client_get_token_flags(unsigned long pkcs11_token_flags)719 pkinit_client_get_token_flags(unsigned long pkcs11_token_flags)
720 {
721     long long flags = 0;
722 
723     if (pkcs11_token_flags & CKF_USER_PIN_COUNT_LOW)
724         flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_COUNT_LOW;
725     if (pkcs11_token_flags & CKF_USER_PIN_FINAL_TRY)
726         flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_FINAL_TRY;
727     if (pkcs11_token_flags & CKF_USER_PIN_LOCKED)
728         flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_LOCKED;
729     return flags;
730 }
731 
732 /*
733  * Phase one of loading client identity information - call
734  * identity_initialize() to load any identities which we can without requiring
735  * help from the calling user, and use their names of those which we can't load
736  * to construct the challenge for the responder callback.
737  */
738 static krb5_error_code
pkinit_client_prep_questions(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq,krb5_get_init_creds_opt * opt,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,krb5_kdc_req * request,krb5_data * encoded_request_body,krb5_data * encoded_previous_request,krb5_pa_data * pa_data)739 pkinit_client_prep_questions(krb5_context context,
740                              krb5_clpreauth_moddata moddata,
741                              krb5_clpreauth_modreq modreq,
742                              krb5_get_init_creds_opt *opt,
743                              krb5_clpreauth_callbacks cb,
744                              krb5_clpreauth_rock rock,
745                              krb5_kdc_req *request,
746                              krb5_data *encoded_request_body,
747                              krb5_data *encoded_previous_request,
748                              krb5_pa_data *pa_data)
749 {
750     krb5_error_code retval;
751     pkinit_context plgctx = (pkinit_context)moddata;
752     pkinit_req_context reqctx = (pkinit_req_context)modreq;
753     size_t i, n;
754     const pkinit_deferred_id *deferred_ids;
755     const char *identity;
756     unsigned long ck_flags;
757     char *encoded;
758     k5_json_object jval = NULL;
759     k5_json_number jflag = NULL;
760 
761     /* Don't ask questions for the informational padata items or when the
762      * ticket is issued. */
763     if (pa_data->pa_type != KRB5_PADATA_PK_AS_REQ)
764         return 0;
765 
766     if (!reqctx->identity_initialized) {
767         pkinit_client_profile(context, plgctx, reqctx, cb, rock,
768                               &request->server->realm);
769         retval = pkinit_identity_initialize(context, plgctx->cryptoctx,
770                                             reqctx->cryptoctx, reqctx->idopts,
771                                             reqctx->idctx, cb, rock,
772                                             request->client);
773         if (retval != 0) {
774             TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
775             pkiDebug("pkinit_identity_initialize returned %d (%s)\n",
776                      retval, error_message(retval));
777         }
778 
779         reqctx->identity_initialized = TRUE;
780         if (retval != 0) {
781             pkiDebug("%s: not asking responder question\n", __FUNCTION__);
782             retval = 0;
783             goto cleanup;
784         }
785     }
786 
787     deferred_ids = crypto_get_deferred_ids(context, reqctx->idctx);
788     for (i = 0; deferred_ids != NULL && deferred_ids[i] != NULL; i++)
789         continue;
790     n = i;
791 
792     /* Make sure we don't just return an empty challenge. */
793     if (n == 0) {
794         pkiDebug("%s: no questions to ask\n", __FUNCTION__);
795         retval = 0;
796         goto cleanup;
797     }
798 
799     /* Create the top-level object. */
800     retval = k5_json_object_create(&jval);
801     if (retval != 0)
802         goto cleanup;
803 
804     for (i = 0; i < n; i++) {
805         /* Add an entry to the top-level object for the identity. */
806         identity = deferred_ids[i]->identity;
807         ck_flags = deferred_ids[i]->ck_flags;
808         /* Calculate the flags value for the bits that that we care about. */
809         retval = k5_json_number_create(pkinit_client_get_token_flags(ck_flags),
810                                        &jflag);
811         if (retval != 0)
812             goto cleanup;
813         retval = k5_json_object_set(jval, identity, jflag);
814         if (retval != 0)
815             goto cleanup;
816         k5_json_release(jflag);
817         jflag = NULL;
818     }
819 
820     /* Encode and done. */
821     retval = k5_json_encode(jval, &encoded);
822     if (retval == 0) {
823         cb->ask_responder_question(context, rock,
824                                    KRB5_RESPONDER_QUESTION_PKINIT,
825                                    encoded);
826         pkiDebug("%s: asking question '%s'\n", __FUNCTION__, encoded);
827         free(encoded);
828     }
829 
830 cleanup:
831     k5_json_release(jval);
832     k5_json_release(jflag);
833 
834     pkiDebug("%s returning %d\n", __FUNCTION__, retval);
835 
836     return retval;
837 }
838 
839 /*
840  * Parse data supplied by the application's responder callback, saving off any
841  * PINs and passwords for identities which we noted needed them.
842  */
843 struct save_one_password_data {
844     krb5_context context;
845     krb5_clpreauth_modreq modreq;
846     const char *caller;
847 };
848 
849 static void
save_one_password(void * arg,const char * key,k5_json_value val)850 save_one_password(void *arg, const char *key, k5_json_value val)
851 {
852     struct save_one_password_data *data = arg;
853     pkinit_req_context reqctx = (pkinit_req_context)data->modreq;
854     const char *password;
855 
856     if (k5_json_get_tid(val) == K5_JSON_TID_STRING) {
857         password = k5_json_string_utf8(val);
858         pkiDebug("%s: \"%s\": %p\n", data->caller, key, password);
859         crypto_set_deferred_id(data->context, reqctx->idctx, key, password);
860     }
861 }
862 
863 static krb5_error_code
pkinit_client_parse_answers(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock)864 pkinit_client_parse_answers(krb5_context context,
865                             krb5_clpreauth_moddata moddata,
866                             krb5_clpreauth_modreq modreq,
867                             krb5_clpreauth_callbacks cb,
868                             krb5_clpreauth_rock rock)
869 {
870     krb5_error_code retval;
871     const char *encoded;
872     k5_json_value jval;
873     struct save_one_password_data data;
874 
875     data.context = context;
876     data.modreq = modreq;
877     data.caller = __FUNCTION__;
878 
879     encoded = cb->get_responder_answer(context, rock,
880                                        KRB5_RESPONDER_QUESTION_PKINIT);
881     if (encoded == NULL)
882         return 0;
883 
884     pkiDebug("pkinit_client_parse_answers: %s\n", encoded);
885 
886     retval = k5_json_decode(encoded, &jval);
887     if (retval != 0)
888         goto cleanup;
889 
890     /* Expect that the top-level answer is an object. */
891     if (k5_json_get_tid(jval) != K5_JSON_TID_OBJECT) {
892         retval = EINVAL;
893         goto cleanup;
894     }
895 
896     /* Store the passed-in per-identity passwords. */
897     k5_json_object_iterate(jval, &save_one_password, &data);
898     retval = 0;
899 
900 cleanup:
901     if (jval != NULL)
902         k5_json_release(jval);
903     return retval;
904 }
905 
906 static krb5_error_code
pkinit_client_process(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq,krb5_get_init_creds_opt * gic_opt,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,krb5_kdc_req * request,krb5_data * encoded_request_body,krb5_data * encoded_previous_request,krb5_pa_data * in_padata,krb5_prompter_fct prompter,void * prompter_data,krb5_pa_data *** out_padata)907 pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
908                       krb5_clpreauth_modreq modreq,
909                       krb5_get_init_creds_opt *gic_opt,
910                       krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
911                       krb5_kdc_req *request, krb5_data *encoded_request_body,
912                       krb5_data *encoded_previous_request,
913                       krb5_pa_data *in_padata,
914                       krb5_prompter_fct prompter, void *prompter_data,
915                       krb5_pa_data ***out_padata)
916 {
917     krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;
918     krb5_enctype enctype = -1;
919     int processing_request = 0;
920     pkinit_context plgctx = (pkinit_context)moddata;
921     pkinit_req_context reqctx = (pkinit_req_context)modreq;
922     krb5_keyblock as_key;
923     krb5_data d;
924 
925     pkiDebug("pkinit_client_process %p %p %p %p\n",
926              context, plgctx, reqctx, request);
927 
928 
929     if (plgctx == NULL || reqctx == NULL)
930         return EINVAL;
931 
932     switch ((int) in_padata->pa_type) {
933     case KRB5_PADATA_PKINIT_KX:
934         reqctx->rfc6112_kdc = 1;
935         return 0;
936     case KRB5_PADATA_AS_FRESHNESS:
937         TRACE_PKINIT_CLIENT_FRESHNESS_TOKEN(context);
938         krb5_free_data(context, reqctx->freshness_token);
939         reqctx->freshness_token = NULL;
940         d = make_data(in_padata->contents, in_padata->length);
941         return krb5_copy_data(context, &d, &reqctx->freshness_token);
942     case KRB5_PADATA_PK_AS_REQ:
943         pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n");
944         processing_request = 1;
945         break;
946 
947     case KRB5_PADATA_PK_AS_REP:
948         pkiDebug("processing KRB5_PADATA_PK_AS_REP\n");
949         break;
950     default:
951         pkiDebug("unrecognized patype = %d for PKINIT\n",
952                  in_padata->pa_type);
953         return EINVAL;
954     }
955 
956     if (processing_request) {
957         if (reqctx->idopts->anchors == NULL) {
958             krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
959                                    _("No pkinit_anchors supplied"));
960             return KRB5_PREAUTH_FAILED;
961         }
962         /* Pull in PINs and passwords for identities which we deferred
963          * loading earlier. */
964         retval = pkinit_client_parse_answers(context, moddata, modreq,
965                                              cb, rock);
966         if (retval) {
967             if (retval == KRB5KRB_ERR_GENERIC)
968                 pkiDebug("pkinit responder answers were invalid\n");
969             return retval;
970         }
971         if (!reqctx->identity_prompted) {
972             reqctx->identity_prompted = TRUE;
973             /*
974              * Load identities (again, potentially), prompting, if we can, for
975              * anything for which we didn't get an answer from the responder
976              * callback.
977              */
978             pkinit_identity_set_prompter(reqctx->idctx, prompter,
979                                          prompter_data);
980             retval = pkinit_identity_prompt(context, plgctx->cryptoctx,
981                                             reqctx->cryptoctx, reqctx->idopts,
982                                             reqctx->idctx, cb, rock,
983                                             reqctx->do_identity_matching,
984                                             request->client);
985             pkinit_identity_set_prompter(reqctx->idctx, NULL, NULL);
986             reqctx->identity_prompt_retval = retval;
987             if (retval) {
988                 TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
989                 pkiDebug("pkinit_identity_prompt returned %d (%s)\n",
990                          retval, error_message(retval));
991                 return retval;
992             }
993         } else if (reqctx->identity_prompt_retval) {
994             retval = reqctx->identity_prompt_retval;
995             TRACE_PKINIT_CLIENT_NO_IDENTITY(context);
996             pkiDebug("pkinit_identity_prompt previously returned %d (%s)\n",
997                      retval, error_message(retval));
998             return retval;
999         }
1000         retval = pa_pkinit_gen_req(context, plgctx, reqctx, cb, rock, request,
1001                                    in_padata->pa_type, out_padata, prompter,
1002                                    prompter_data, gic_opt);
1003     } else {
1004         /*
1005          * Get the enctype of the reply.
1006          */
1007         enctype = cb->get_etype(context, rock);
1008         retval = pa_pkinit_parse_rep(context, plgctx, reqctx, request,
1009                                      in_padata, enctype, &as_key,
1010                                      encoded_previous_request);
1011         if (retval == 0) {
1012             retval = cb->set_as_key(context, rock, &as_key);
1013             krb5_free_keyblock_contents(context, &as_key);
1014         }
1015     }
1016 
1017     pkiDebug("pkinit_client_process: returning %d (%s)\n",
1018              retval, error_message(retval));
1019     return retval;
1020 }
1021 
1022 static krb5_error_code
pkinit_client_tryagain(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq,krb5_get_init_creds_opt * gic_opt,krb5_clpreauth_callbacks cb,krb5_clpreauth_rock rock,krb5_kdc_req * request,krb5_data * encoded_request_body,krb5_data * encoded_previous_request,krb5_preauthtype pa_type,krb5_error * err_reply,krb5_pa_data ** err_padata,krb5_prompter_fct prompter,void * prompter_data,krb5_pa_data *** out_padata)1023 pkinit_client_tryagain(krb5_context context, krb5_clpreauth_moddata moddata,
1024                        krb5_clpreauth_modreq modreq,
1025                        krb5_get_init_creds_opt *gic_opt,
1026                        krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
1027                        krb5_kdc_req *request, krb5_data *encoded_request_body,
1028                        krb5_data *encoded_previous_request,
1029                        krb5_preauthtype pa_type, krb5_error *err_reply,
1030                        krb5_pa_data **err_padata, krb5_prompter_fct prompter,
1031                        void *prompter_data, krb5_pa_data ***out_padata)
1032 {
1033     krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;
1034     pkinit_context plgctx = (pkinit_context)moddata;
1035     pkinit_req_context reqctx = (pkinit_req_context)modreq;
1036     krb5_pa_data *pa;
1037     krb5_data scratch;
1038     krb5_external_principal_identifier **certifiers = NULL;
1039     krb5_algorithm_identifier **algId = NULL;
1040     int do_again = 0;
1041 
1042     pkiDebug("pkinit_client_tryagain %p %p %p %p\n",
1043              context, plgctx, reqctx, request);
1044 
1045     if (reqctx->pa_type != pa_type || err_padata == NULL)
1046         return retval;
1047 
1048     for (; *err_padata != NULL && !do_again; err_padata++) {
1049         pa = *err_padata;
1050         PADATA_TO_KRB5DATA(pa, &scratch);
1051         switch (pa->pa_type) {
1052         case TD_TRUSTED_CERTIFIERS:
1053         case TD_INVALID_CERTIFICATES:
1054             retval = k5int_decode_krb5_td_trusted_certifiers(&scratch,
1055                                                              &certifiers);
1056             if (retval) {
1057                 pkiDebug("failed to decode sequence of trusted certifiers\n");
1058                 goto cleanup;
1059             }
1060             retval = pkinit_process_td_trusted_certifiers(context,
1061                                                           plgctx->cryptoctx,
1062                                                           reqctx->cryptoctx,
1063                                                           reqctx->idctx,
1064                                                           certifiers,
1065                                                           pa->pa_type);
1066             if (!retval)
1067                 do_again = 1;
1068             break;
1069         case TD_DH_PARAMETERS:
1070             retval = k5int_decode_krb5_td_dh_parameters(&scratch, &algId);
1071             if (retval) {
1072                 pkiDebug("failed to decode td_dh_parameters\n");
1073                 goto cleanup;
1074             }
1075             retval = pkinit_process_td_dh_params(context, plgctx->cryptoctx,
1076                                                  reqctx->cryptoctx,
1077                                                  reqctx->idctx, algId,
1078                                                  &reqctx->opts->dh_size);
1079             if (!retval)
1080                 do_again = 1;
1081             break;
1082         default:
1083             break;
1084         }
1085     }
1086 
1087     if (do_again) {
1088         TRACE_PKINIT_CLIENT_TRYAGAIN(context);
1089         retval = pa_pkinit_gen_req(context, plgctx, reqctx, cb, rock, request,
1090                                    pa_type, out_padata, prompter,
1091                                    prompter_data, gic_opt);
1092         if (retval)
1093             goto cleanup;
1094     }
1095 
1096     retval = 0;
1097 cleanup:
1098     if (certifiers != NULL)
1099         free_krb5_external_principal_identifier(&certifiers);
1100 
1101     if (algId != NULL)
1102         free_krb5_algorithm_identifiers(&algId);
1103 
1104     pkiDebug("pkinit_client_tryagain: returning %d (%s)\n",
1105              retval, error_message(retval));
1106     return retval;
1107 }
1108 
1109 static int
pkinit_client_get_flags(krb5_context kcontext,krb5_preauthtype patype)1110 pkinit_client_get_flags(krb5_context kcontext, krb5_preauthtype patype)
1111 {
1112     if (patype == KRB5_PADATA_PKINIT_KX || patype == KRB5_PADATA_AS_FRESHNESS)
1113         return PA_INFO;
1114     return PA_REAL;
1115 }
1116 
1117 /*
1118  * We want to be notified about KRB5_PADATA_PKINIT_KX in addition to the actual
1119  * pkinit patypes because RFC 6112 requires anonymous KDCs to send it. We use
1120  * that to determine whether to use the broken MIT 1.9 behavior of sending
1121  * ContentInfo rather than SignedData or the RFC 6112 behavior
1122  */
1123 static krb5_preauthtype supported_client_pa_types[] = {
1124     KRB5_PADATA_PK_AS_REP,
1125     KRB5_PADATA_PK_AS_REQ,
1126     KRB5_PADATA_PKINIT_KX,
1127     KRB5_PADATA_AS_FRESHNESS,
1128     0
1129 };
1130 
1131 static void
pkinit_client_req_init(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq * modreq_out)1132 pkinit_client_req_init(krb5_context context,
1133                        krb5_clpreauth_moddata moddata,
1134                        krb5_clpreauth_modreq *modreq_out)
1135 {
1136     krb5_error_code retval = ENOMEM;
1137     pkinit_req_context reqctx = NULL;
1138     pkinit_context plgctx = (pkinit_context)moddata;
1139 
1140     *modreq_out = NULL;
1141 
1142     reqctx = malloc(sizeof(*reqctx));
1143     if (reqctx == NULL)
1144         return;
1145     memset(reqctx, 0, sizeof(*reqctx));
1146 
1147     reqctx->magic = PKINIT_REQ_CTX_MAGIC;
1148     reqctx->cryptoctx = NULL;
1149     reqctx->opts = NULL;
1150     reqctx->idctx = NULL;
1151     reqctx->idopts = NULL;
1152     reqctx->freshness_token = NULL;
1153 
1154     retval = pkinit_init_req_opts(&reqctx->opts);
1155     if (retval)
1156         goto cleanup;
1157 
1158     reqctx->opts->require_eku = plgctx->opts->require_eku;
1159     reqctx->opts->accept_secondary_eku = plgctx->opts->accept_secondary_eku;
1160     reqctx->opts->allow_upn = plgctx->opts->allow_upn;
1161     reqctx->opts->require_crl_checking = plgctx->opts->require_crl_checking;
1162     reqctx->opts->disable_freshness = plgctx->opts->disable_freshness;
1163 
1164     retval = pkinit_init_req_crypto(&reqctx->cryptoctx);
1165     if (retval)
1166         goto cleanup;
1167 
1168     retval = pkinit_init_identity_crypto(&reqctx->idctx);
1169     if (retval)
1170         goto cleanup;
1171 
1172     retval = pkinit_dup_identity_opts(plgctx->idopts, &reqctx->idopts);
1173     if (retval)
1174         goto cleanup;
1175 
1176     *modreq_out = (krb5_clpreauth_modreq)reqctx;
1177     pkiDebug("%s: returning reqctx at %p\n", __FUNCTION__, reqctx);
1178 
1179 cleanup:
1180     if (retval) {
1181         if (reqctx->idctx != NULL)
1182             pkinit_fini_identity_crypto(reqctx->idctx);
1183         if (reqctx->cryptoctx != NULL)
1184             pkinit_fini_req_crypto(reqctx->cryptoctx);
1185         if (reqctx->opts != NULL)
1186             pkinit_fini_req_opts(reqctx->opts);
1187         if (reqctx->idopts != NULL)
1188             pkinit_fini_identity_opts(reqctx->idopts);
1189         free(reqctx);
1190     }
1191 
1192     return;
1193 }
1194 
1195 static void
pkinit_client_req_fini(krb5_context context,krb5_clpreauth_moddata moddata,krb5_clpreauth_modreq modreq)1196 pkinit_client_req_fini(krb5_context context, krb5_clpreauth_moddata moddata,
1197                        krb5_clpreauth_modreq modreq)
1198 {
1199     pkinit_req_context reqctx = (pkinit_req_context)modreq;
1200 
1201     pkiDebug("%s: received reqctx at %p\n", __FUNCTION__, reqctx);
1202     if (reqctx == NULL)
1203         return;
1204     if (reqctx->magic != PKINIT_REQ_CTX_MAGIC) {
1205         pkiDebug("%s: Bad magic value (%x) in req ctx\n",
1206                  __FUNCTION__, reqctx->magic);
1207         return;
1208     }
1209     if (reqctx->opts != NULL)
1210         pkinit_fini_req_opts(reqctx->opts);
1211 
1212     if (reqctx->cryptoctx != NULL)
1213         pkinit_fini_req_crypto(reqctx->cryptoctx);
1214 
1215     if (reqctx->idctx != NULL)
1216         pkinit_fini_identity_crypto(reqctx->idctx);
1217 
1218     if (reqctx->idopts != NULL)
1219         pkinit_fini_identity_opts(reqctx->idopts);
1220 
1221     krb5_free_data(context, reqctx->freshness_token);
1222 
1223     free(reqctx);
1224     return;
1225 }
1226 
1227 static int
pkinit_client_plugin_init(krb5_context context,krb5_clpreauth_moddata * moddata_out)1228 pkinit_client_plugin_init(krb5_context context,
1229                           krb5_clpreauth_moddata *moddata_out)
1230 {
1231     krb5_error_code retval = ENOMEM;
1232     pkinit_context ctx = NULL;
1233 
1234     ctx = calloc(1, sizeof(*ctx));
1235     if (ctx == NULL)
1236         return ENOMEM;
1237     memset(ctx, 0, sizeof(*ctx));
1238     ctx->magic = PKINIT_CTX_MAGIC;
1239     ctx->opts = NULL;
1240     ctx->cryptoctx = NULL;
1241     ctx->idopts = NULL;
1242 
1243     retval = pkinit_accessor_init();
1244     if (retval)
1245         goto errout;
1246 
1247     retval = pkinit_init_plg_opts(&ctx->opts);
1248     if (retval)
1249         goto errout;
1250 
1251     retval = pkinit_init_plg_crypto(context, &ctx->cryptoctx);
1252     if (retval)
1253         goto errout;
1254 
1255     retval = pkinit_init_identity_opts(&ctx->idopts);
1256     if (retval)
1257         goto errout;
1258 
1259     *moddata_out = (krb5_clpreauth_moddata)ctx;
1260 
1261     pkiDebug("%s: returning plgctx at %p\n", __FUNCTION__, ctx);
1262 
1263 errout:
1264     if (retval)
1265         pkinit_client_plugin_fini(context, (krb5_clpreauth_moddata)ctx);
1266 
1267     return retval;
1268 }
1269 
1270 static void
pkinit_client_plugin_fini(krb5_context context,krb5_clpreauth_moddata moddata)1271 pkinit_client_plugin_fini(krb5_context context, krb5_clpreauth_moddata moddata)
1272 {
1273     pkinit_context ctx = (pkinit_context)moddata;
1274 
1275     if (ctx == NULL || ctx->magic != PKINIT_CTX_MAGIC) {
1276         pkiDebug("pkinit_lib_fini: got bad plgctx (%p)!\n", ctx);
1277         return;
1278     }
1279     pkiDebug("%s: got plgctx at %p\n", __FUNCTION__, ctx);
1280 
1281     pkinit_fini_identity_opts(ctx->idopts);
1282     pkinit_fini_plg_crypto(ctx->cryptoctx);
1283     pkinit_fini_plg_opts(ctx->opts);
1284     free(ctx);
1285 
1286 }
1287 
1288 static krb5_error_code
add_string_to_array(krb5_context context,char *** array,const char * addition)1289 add_string_to_array(krb5_context context, char ***array, const char *addition)
1290 {
1291     char **a = *array;
1292     size_t len;
1293 
1294     for (len = 0; a != NULL && a[len] != NULL; len++);
1295     a = realloc(a, (len + 2) * sizeof(char *));
1296     if (a == NULL)
1297         return ENOMEM;
1298     *array = a;
1299     a[len] = strdup(addition);
1300     if (a[len] == NULL)
1301         return ENOMEM;
1302     a[len + 1] = NULL;
1303     return 0;
1304 }
1305 
1306 static krb5_error_code
handle_gic_opt(krb5_context context,pkinit_context plgctx,const char * attr,const char * value)1307 handle_gic_opt(krb5_context context,
1308                pkinit_context plgctx,
1309                const char *attr,
1310                const char *value)
1311 {
1312     krb5_error_code retval;
1313 
1314     if (strcmp(attr, "X509_user_identity") == 0) {
1315         if (plgctx->idopts->identity != NULL) {
1316             krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
1317                                    "X509_user_identity can not be given twice\n");
1318             return KRB5_PREAUTH_FAILED;
1319         }
1320         plgctx->idopts->identity = strdup(value);
1321         if (plgctx->idopts->identity == NULL) {
1322             krb5_set_error_message(context, ENOMEM,
1323                                    "Could not duplicate X509_user_identity value\n");
1324             return ENOMEM;
1325         }
1326     } else if (strcmp(attr, "X509_anchors") == 0) {
1327         retval = add_string_to_array(context, &plgctx->idopts->anchors, value);
1328         if (retval)
1329             return retval;
1330     } else if (strcmp(attr, "disable_freshness") == 0) {
1331         if (strcmp(value, "yes") == 0)
1332             plgctx->opts->disable_freshness = 1;
1333     }
1334     return 0;
1335 }
1336 
1337 static krb5_error_code
pkinit_client_gic_opt(krb5_context context,krb5_clpreauth_moddata moddata,krb5_get_init_creds_opt * gic_opt,const char * attr,const char * value)1338 pkinit_client_gic_opt(krb5_context context, krb5_clpreauth_moddata moddata,
1339                       krb5_get_init_creds_opt *gic_opt,
1340                       const char *attr,
1341                       const char *value)
1342 {
1343     krb5_error_code retval;
1344     pkinit_context plgctx = (pkinit_context)moddata;
1345 
1346     pkiDebug("(pkinit) received '%s' = '%s'\n", attr, value);
1347     retval = handle_gic_opt(context, plgctx, attr, value);
1348     if (retval)
1349         return retval;
1350 
1351     return 0;
1352 }
1353 
1354 krb5_error_code
1355 clpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver,
1356                         krb5_plugin_vtable vtable);
1357 
1358 krb5_error_code
clpreauth_pkinit_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)1359 clpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver,
1360                         krb5_plugin_vtable vtable)
1361 {
1362     krb5_clpreauth_vtable vt;
1363 
1364     if (maj_ver != 1)
1365         return KRB5_PLUGIN_VER_NOTSUPP;
1366     vt = (krb5_clpreauth_vtable)vtable;
1367     vt->name = "pkinit";
1368     vt->pa_type_list = supported_client_pa_types;
1369     vt->init = pkinit_client_plugin_init;
1370     vt->fini = pkinit_client_plugin_fini;
1371     vt->flags = pkinit_client_get_flags;
1372     vt->request_init = pkinit_client_req_init;
1373     vt->prep_questions = pkinit_client_prep_questions;
1374     vt->request_fini = pkinit_client_req_fini;
1375     vt->process = pkinit_client_process;
1376     vt->tryagain = pkinit_client_tryagain;
1377     vt->gic_opts = pkinit_client_gic_opt;
1378     return 0;
1379 }
1380