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