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