Path: blob/main/crypto/krb5/src/plugins/preauth/pkinit/pkinit_clnt.c
34923 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* COPYRIGHT (C) 2006,20073* THE REGENTS OF THE UNIVERSITY OF MICHIGAN4* ALL RIGHTS RESERVED5*6* Permission is granted to use, copy, create derivative works7* and redistribute this software and such derivative works8* for any purpose, so long as the name of The University of9* Michigan is not used in any advertising or publicity10* pertaining to the use of distribution of this software11* without specific, written prior authorization. If the12* above copyright notice or any other identification of the13* University of Michigan is included in any copy of any14* portion of this software, then the disclaimer below must15* also be included.16*17* THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION18* FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY19* PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF20* MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING21* WITHOUT LIMITATION THE IMPLIED WARRANTIES OF22* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE23* REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE24* FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR25* CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING26* OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN27* IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF28* SUCH DAMAGES.29*/3031#include "k5-int.h"32#include "pkinit.h"33#include "k5-json.h"3435#include <sys/stat.h>3637/**38* Return true if we should use ContentInfo rather than SignedData. This39* happens if we are talking to what might be an old (pre-6112) MIT KDC and40* we're using anonymous.41*/42static int43use_content_info(krb5_context context, pkinit_req_context req,44krb5_principal client)45{46if (req->rfc6112_kdc)47return 0;48if (krb5_principal_compare_any_realm(context, client,49krb5_anonymous_principal()))50return 1;51return 0;52}5354static krb5_error_code55pkinit_as_req_create(krb5_context context, pkinit_context plgctx,56pkinit_req_context reqctx, krb5_timestamp ctsec,57krb5_int32 cusec, krb5_ui_4 nonce, const krb5_data *cksum,58const krb5_pachecksum2 *cksum2, krb5_principal client,59krb5_principal server, krb5_data **as_req);6061static krb5_error_code62pkinit_as_rep_parse(krb5_context context, pkinit_context plgctx,63pkinit_req_context reqctx, krb5_preauthtype pa_type,64krb5_kdc_req *request, const krb5_data *as_rep,65krb5_keyblock *key_block, krb5_enctype etype, krb5_data *);6667static void pkinit_client_plugin_fini(krb5_context context,68krb5_clpreauth_moddata moddata);6970static krb5_error_code71pa_pkinit_gen_req(krb5_context context,72pkinit_context plgctx,73pkinit_req_context reqctx,74krb5_clpreauth_callbacks cb,75krb5_clpreauth_rock rock,76krb5_kdc_req * request,77krb5_preauthtype pa_type,78krb5_pa_data *** out_padata,79krb5_prompter_fct prompter,80void *prompter_data,81krb5_get_init_creds_opt *gic_opt)82{8384krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;85krb5_data *out_data = NULL;86krb5_timestamp ctsec = 0;87krb5_int32 cusec = 0;88krb5_ui_4 nonce = 0;89krb5_data cksum = empty_data();90krb5_pachecksum2 *cksum2 = NULL;91krb5_data *der_req = NULL;92krb5_pa_data **return_pa_data = NULL;9394memset(&cksum, 0, sizeof(cksum));95reqctx->pa_type = pa_type;9697pkiDebug("kdc_options = 0x%x till = %d\n",98request->kdc_options, request->till);99/* If we don't have a client, we're done */100if (request->client == NULL) {101pkiDebug("No request->client; aborting PKINIT\n");102return KRB5KDC_ERR_PREAUTH_FAILED;103}104105retval = pkinit_get_kdc_cert(context, plgctx->cryptoctx, reqctx->cryptoctx,106reqctx->idctx, request->server);107if (retval) {108pkiDebug("pkinit_get_kdc_cert returned %d\n", retval);109goto cleanup;110}111112/* checksum of the encoded KDC-REQ-BODY */113retval = k5int_encode_krb5_kdc_req_body(request, &der_req);114if (retval) {115pkiDebug("encode_krb5_kdc_req_body returned %d\n", (int) retval);116goto cleanup;117}118119retval = crypto_generate_checksums(context, der_req, &cksum, &cksum2);120if (retval)121goto cleanup;122TRACE_PKINIT_CLIENT_REQ_CHECKSUMS(context, &cksum, cksum2);123124retval = cb->get_preauth_time(context, rock, TRUE, &ctsec, &cusec);125if (retval)126goto cleanup;127128/* XXX PKINIT RFC says that nonce in PKAuthenticator doesn't have be the129* same as in the AS_REQ. However, if we pick a different nonce, then we130* need to remember that info when AS_REP is returned. I'm choosing to131* reuse the AS_REQ nonce.132*/133nonce = request->nonce;134135retval = pkinit_as_req_create(context, plgctx, reqctx, ctsec, cusec,136nonce, &cksum, cksum2, request->client,137request->server, &out_data);138if (retval) {139pkiDebug("error %d on pkinit_as_req_create; aborting PKINIT\n",140(int) retval);141goto cleanup;142}143144return_pa_data = k5calloc(2, sizeof(*return_pa_data), &retval);145if (return_pa_data == NULL)146goto cleanup;147148return_pa_data[0] = k5alloc(sizeof(*return_pa_data[0]), &retval);149if (return_pa_data[0] == NULL)150goto cleanup;151152return_pa_data[0]->magic = KV5M_PA_DATA;153154return_pa_data[0]->pa_type = pa_type;155return_pa_data[0]->length = out_data->length;156return_pa_data[0]->contents = (krb5_octet *) out_data->data;157*out_data = empty_data();158159*out_padata = return_pa_data;160return_pa_data = NULL;161cb->disable_fallback(context, rock);162163cleanup:164krb5_free_data(context, der_req);165krb5_free_data_contents(context, &cksum);166free_pachecksum2(context, &cksum2);167krb5_free_data(context, out_data);168krb5_free_pa_data(context, return_pa_data);169return retval;170}171172static krb5_error_code173pkinit_as_req_create(krb5_context context, pkinit_context plgctx,174pkinit_req_context reqctx, krb5_timestamp ctsec,175krb5_int32 cusec, krb5_ui_4 nonce, const krb5_data *cksum,176const krb5_pachecksum2 *cksum2, krb5_principal client,177krb5_principal server, krb5_data **as_req)178{179krb5_error_code retval = ENOMEM;180krb5_data spki = empty_data(), *coded_auth_pack = NULL;181krb5_auth_pack auth_pack;182krb5_pa_pk_as_req *req = NULL;183krb5_algorithm_identifier **cmstypes = NULL;184185pkiDebug("pkinit_as_req_create pa_type = %d\n", reqctx->pa_type);186187/* Create the authpack */188memset(&auth_pack, 0, sizeof(auth_pack));189auth_pack.pkAuthenticator.ctime = ctsec;190auth_pack.pkAuthenticator.cusec = cusec;191auth_pack.pkAuthenticator.nonce = nonce;192auth_pack.pkAuthenticator.paChecksum = *cksum;193if (!reqctx->opts->disable_freshness)194auth_pack.pkAuthenticator.freshnessToken = reqctx->freshness_token;195auth_pack.pkAuthenticator.paChecksum2 = (krb5_pachecksum2 *)cksum2;196auth_pack.clientDHNonce.length = 0;197auth_pack.supportedKDFs = (krb5_data **)supported_kdf_alg_ids;198199/* add List of CMS algorithms */200retval = create_krb5_supportedCMSTypes(context, plgctx->cryptoctx,201reqctx->cryptoctx,202reqctx->idctx, &cmstypes);203auth_pack.supportedCMSTypes = cmstypes;204if (retval)205goto cleanup;206207TRACE_PKINIT_CLIENT_REQ_DH(context);208209/* create client-side DH keys */210retval = client_create_dh(context, plgctx->cryptoctx, reqctx->cryptoctx,211reqctx->idctx, reqctx->opts->dh_size, &spki);212auth_pack.clientPublicValue = spki;213if (retval != 0) {214pkiDebug("failed to create dh parameters\n");215goto cleanup;216}217218retval = k5int_encode_krb5_auth_pack(&auth_pack, &coded_auth_pack);219if (retval) {220pkiDebug("failed to encode the AuthPack %d\n", retval);221goto cleanup;222}223#ifdef DEBUG_ASN1224print_buffer_bin((unsigned char *)coded_auth_pack->data,225coded_auth_pack->length,226"/tmp/client_auth_pack");227#endif228229/* create PKCS7 object from authpack */230init_krb5_pa_pk_as_req(&req);231if (req == NULL) {232retval = ENOMEM;233goto cleanup;234}235if (use_content_info(context, reqctx, client)) {236retval = cms_contentinfo_create(context, plgctx->cryptoctx,237reqctx->cryptoctx, reqctx->idctx,238CMS_SIGN_CLIENT,239(unsigned char *)240coded_auth_pack->data,241coded_auth_pack->length,242(unsigned char **)243&req->signedAuthPack.data,244&req->signedAuthPack.length);245} else {246retval = cms_signeddata_create(context, plgctx->cryptoctx,247reqctx->cryptoctx, reqctx->idctx,248CMS_SIGN_CLIENT,249(unsigned char *)250coded_auth_pack->data,251coded_auth_pack->length,252(unsigned char **)253&req->signedAuthPack.data,254&req->signedAuthPack.length);255}256257#ifdef DEBUG_ASN1258print_buffer_bin((unsigned char *)req->signedAuthPack.data,259req->signedAuthPack.length,260"/tmp/client_signed_data");261#endif262263krb5_free_data(context, coded_auth_pack);264if (retval) {265pkiDebug("failed to create pkcs7 signed data\n");266goto cleanup;267}268269/* create a list of trusted CAs */270retval = create_krb5_trustedCertifiers(context, plgctx->cryptoctx,271reqctx->cryptoctx, reqctx->idctx,272&req->trustedCertifiers);273if (retval)274goto cleanup;275retval = create_issuerAndSerial(context, plgctx->cryptoctx,276reqctx->cryptoctx, reqctx->idctx,277(unsigned char **)&req->kdcPkId.data,278&req->kdcPkId.length);279if (retval)280goto cleanup;281282/* Encode the as-req */283retval = k5int_encode_krb5_pa_pk_as_req(req, as_req);284285#ifdef DEBUG_ASN1286if (!retval)287print_buffer_bin((unsigned char *)(*as_req)->data, (*as_req)->length,288"/tmp/client_as_req");289#endif290291cleanup:292free_krb5_algorithm_identifiers(&cmstypes);293free_krb5_pa_pk_as_req(&req);294krb5_free_data_contents(context, &spki);295296pkiDebug("pkinit_as_req_create retval=%d\n", (int) retval);297298return retval;299}300301static krb5_error_code302pa_pkinit_parse_rep(krb5_context context,303pkinit_context plgctx,304pkinit_req_context reqctx,305krb5_kdc_req * request,306krb5_pa_data * in_padata,307krb5_enctype etype,308krb5_keyblock * as_key,309krb5_data *encoded_request)310{311krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;312krb5_data asRep = { 0, 0, NULL};313314/*315* One way or the other - success or failure - no other PA systems can316* work if the server sent us a PKINIT reply, since only we know how to317* decrypt the key.318*/319if ((in_padata == NULL) || (in_padata->length == 0)) {320pkiDebug("pa_pkinit_parse_rep: no in_padata\n");321return KRB5KDC_ERR_PREAUTH_FAILED;322}323324asRep.data = (char *) in_padata->contents;325asRep.length = in_padata->length;326327retval =328pkinit_as_rep_parse(context, plgctx, reqctx, in_padata->pa_type,329request, &asRep, as_key, etype, encoded_request);330if (retval) {331pkiDebug("pkinit_as_rep_parse returned %d (%s)\n",332retval, error_message(retval));333goto cleanup;334}335336retval = 0;337338cleanup:339340return retval;341}342343static krb5_error_code344verify_kdc_san(krb5_context context,345pkinit_context plgctx,346pkinit_req_context reqctx,347krb5_principal kdcprinc,348int *valid_san,349int *need_eku_checking)350{351krb5_error_code retval;352char **certhosts = NULL, **cfghosts = NULL, **hostptr;353krb5_principal *princs = NULL;354unsigned char ***get_dns;355size_t i, j;356357*valid_san = 0;358*need_eku_checking = 1;359360retval = pkinit_libdefault_strings(context,361krb5_princ_realm(context, kdcprinc),362KRB5_CONF_PKINIT_KDC_HOSTNAME,363&cfghosts);364if (retval || cfghosts == NULL) {365pkiDebug("%s: No pkinit_kdc_hostname values found in config file\n",366__FUNCTION__);367get_dns = NULL;368} else {369pkiDebug("%s: pkinit_kdc_hostname values found in config file\n",370__FUNCTION__);371for (hostptr = cfghosts; *hostptr != NULL; hostptr++)372TRACE_PKINIT_CLIENT_SAN_CONFIG_DNSNAME(context, *hostptr);373get_dns = (unsigned char ***)&certhosts;374}375376retval = crypto_retrieve_cert_sans(context, plgctx->cryptoctx,377reqctx->cryptoctx, reqctx->idctx,378&princs, NULL, get_dns);379if (retval) {380pkiDebug("%s: error from retrieve_certificate_sans()\n", __FUNCTION__);381TRACE_PKINIT_CLIENT_SAN_ERR(context);382retval = KRB5KDC_ERR_KDC_NAME_MISMATCH;383goto out;384}385for (i = 0; princs != NULL && princs[i] != NULL; i++)386TRACE_PKINIT_CLIENT_SAN_KDCCERT_PRINC(context, princs[i]);387if (certhosts != NULL) {388for (hostptr = certhosts; *hostptr != NULL; hostptr++)389TRACE_PKINIT_CLIENT_SAN_KDCCERT_DNSNAME(context, *hostptr);390}391392pkiDebug("%s: Checking pkinit sans\n", __FUNCTION__);393for (i = 0; princs != NULL && princs[i] != NULL; i++) {394if (krb5_principal_compare(context, princs[i], kdcprinc)) {395TRACE_PKINIT_CLIENT_SAN_MATCH_PRINC(context, kdcprinc);396pkiDebug("%s: pkinit san match found\n", __FUNCTION__);397*valid_san = 1;398*need_eku_checking = 0;399retval = 0;400goto out;401}402}403pkiDebug("%s: no pkinit san match found\n", __FUNCTION__);404405if (certhosts == NULL) {406pkiDebug("%s: no certhosts (or we wouldn't accept them anyway)\n",407__FUNCTION__);408retval = KRB5KDC_ERR_KDC_NAME_MISMATCH;409goto out;410}411412for (i = 0; certhosts[i] != NULL; i++) {413for (j = 0; cfghosts != NULL && cfghosts[j] != NULL; j++) {414pkiDebug("%s: comparing cert name '%s' with config name '%s'\n",415__FUNCTION__, certhosts[i], cfghosts[j]);416if (strcasecmp(certhosts[i], cfghosts[j]) == 0) {417TRACE_PKINIT_CLIENT_SAN_MATCH_DNSNAME(context, certhosts[i]);418pkiDebug("%s: we have a dnsName match\n", __FUNCTION__);419*valid_san = 1;420retval = 0;421goto out;422}423}424}425TRACE_PKINIT_CLIENT_SAN_MATCH_NONE(context);426pkiDebug("%s: no dnsName san match found\n", __FUNCTION__);427428/* We found no match */429retval = 0;430431out:432if (princs != NULL) {433for (i = 0; princs[i] != NULL; i++)434krb5_free_principal(context, princs[i]);435free(princs);436}437if (certhosts != NULL) {438for (i = 0; certhosts[i] != NULL; i++)439free(certhosts[i]);440free(certhosts);441}442if (cfghosts != NULL)443profile_free_list(cfghosts);444445pkiDebug("%s: returning retval %d, valid_san %d, need_eku_checking %d\n",446__FUNCTION__, retval, *valid_san, *need_eku_checking);447return retval;448}449450static krb5_error_code451verify_kdc_eku(krb5_context context,452pkinit_context plgctx,453pkinit_req_context reqctx,454int *eku_accepted)455{456krb5_error_code retval;457458*eku_accepted = 0;459460if (reqctx->opts->require_eku == 0) {461TRACE_PKINIT_CLIENT_EKU_SKIP(context);462pkiDebug("%s: configuration requests no EKU checking\n", __FUNCTION__);463*eku_accepted = 1;464retval = 0;465goto out;466}467retval = crypto_check_cert_eku(context, plgctx->cryptoctx,468reqctx->cryptoctx, reqctx->idctx,4691, /* kdc cert */470reqctx->opts->accept_secondary_eku,471eku_accepted);472if (retval) {473pkiDebug("%s: Error from crypto_check_cert_eku %d (%s)\n",474__FUNCTION__, retval, error_message(retval));475goto out;476}477478out:479if (*eku_accepted)480TRACE_PKINIT_CLIENT_EKU_ACCEPT(context);481else482TRACE_PKINIT_CLIENT_EKU_REJECT(context);483pkiDebug("%s: returning retval %d, eku_accepted %d\n",484__FUNCTION__, retval, *eku_accepted);485return retval;486}487488/*489* Parse PA-PK-AS-REP message. Optionally evaluates the message's490* certificate chain.491* Optionally returns various components.492*/493static krb5_error_code494pkinit_as_rep_parse(krb5_context context,495pkinit_context plgctx,496pkinit_req_context reqctx,497krb5_preauthtype pa_type,498krb5_kdc_req *request,499const krb5_data *as_rep,500krb5_keyblock *key_block,501krb5_enctype etype,502krb5_data *encoded_request)503{504krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;505krb5_principal kdc_princ = NULL;506krb5_pa_pk_as_rep *kdc_reply = NULL;507krb5_kdc_dh_key_info *kdc_dh = NULL;508krb5_reply_key_pack *key_pack = NULL;509krb5_data dh_data = { 0, 0, NULL };510unsigned char *client_key = NULL, *kdc_hostname = NULL;511unsigned int client_key_len = 0;512krb5_checksum cksum = {0, 0, 0, NULL};513krb5_data k5data;514krb5_data secret;515int valid_san = 0;516int valid_eku = 0;517int need_eku_checking = 1;518519assert((as_rep != NULL) && (key_block != NULL));520521#ifdef DEBUG_ASN1522print_buffer_bin((unsigned char *)as_rep->data, as_rep->length,523"/tmp/client_as_rep");524#endif525526if ((retval = k5int_decode_krb5_pa_pk_as_rep(as_rep, &kdc_reply))) {527pkiDebug("decode_krb5_as_rep failed %d\n", retval);528return retval;529}530531if (kdc_reply->choice != choice_pa_pk_as_rep_dhInfo) {532pkiDebug("unknown as_rep type %d\n", kdc_reply->choice);533retval = KRB5KDC_ERR_PREAUTH_FAILED;534goto cleanup;535}536537#ifdef DEBUG_ASN1538print_buffer_bin(kdc_reply->u.dh_Info.dhSignedData.data,539kdc_reply->u.dh_Info.dhSignedData.length,540"/tmp/client_kdc_signeddata");541#endif542retval = cms_signeddata_verify(context, plgctx->cryptoctx,543reqctx->cryptoctx, reqctx->idctx,544CMS_SIGN_SERVER,545reqctx->opts->require_crl_checking,546(unsigned char *)547kdc_reply->u.dh_Info.dhSignedData.data,548kdc_reply->u.dh_Info.dhSignedData.length,549(unsigned char **)&dh_data.data,550&dh_data.length,551NULL, NULL, NULL);552if (retval) {553pkiDebug("failed to verify pkcs7 signed data\n");554TRACE_PKINIT_CLIENT_REP_DH_FAIL(context);555goto cleanup;556}557TRACE_PKINIT_CLIENT_REP_DH(context);558559retval = krb5_build_principal_ext(context, &kdc_princ,560request->server->realm.length,561request->server->realm.data,562strlen(KRB5_TGS_NAME), KRB5_TGS_NAME,563request->server->realm.length,564request->server->realm.data,5650);566if (retval)567goto cleanup;568retval = verify_kdc_san(context, plgctx, reqctx, kdc_princ,569&valid_san, &need_eku_checking);570if (retval)571goto cleanup;572if (!valid_san) {573pkiDebug("%s: did not find an acceptable SAN in KDC certificate\n",574__FUNCTION__);575retval = KRB5KDC_ERR_KDC_NAME_MISMATCH;576goto cleanup;577}578579if (need_eku_checking) {580retval = verify_kdc_eku(context, plgctx, reqctx,581&valid_eku);582if (retval)583goto cleanup;584if (!valid_eku) {585pkiDebug("%s: did not find an acceptable EKU in KDC certificate\n",586__FUNCTION__);587retval = KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE;588goto cleanup;589}590} else591pkiDebug("%s: skipping EKU check\n", __FUNCTION__);592593OCTETDATA_TO_KRB5DATA(&dh_data, &k5data);594595#ifdef DEBUG_ASN1596print_buffer_bin(dh_data.data, dh_data.length, "/tmp/client_dh_key");597#endif598retval = k5int_decode_krb5_kdc_dh_key_info(&k5data, &kdc_dh);599if (retval) {600pkiDebug("failed to decode kdc_dh_key_info\n");601goto cleanup;602}603604/* client after KDC reply */605retval = client_process_dh(context, plgctx->cryptoctx, reqctx->cryptoctx,606reqctx->idctx,607(unsigned char *)kdc_dh->subjectPublicKey.data,608kdc_dh->subjectPublicKey.length, &client_key,609&client_key_len);610if (retval) {611pkiDebug("failed to process dh params\n");612goto cleanup;613}614615secret = make_data(client_key, client_key_len);616retval = pkinit_kdf(context, &secret, kdc_reply->u.dh_Info.kdfID,617request->client, request->server, etype,618encoded_request, as_rep, key_block);619if (retval) {620pkiDebug("pkinit_kdf failed: %s\n", error_message(retval));621goto cleanup;622}623624retval = 0;625626cleanup:627free(dh_data.data);628krb5_free_principal(context, kdc_princ);629free(client_key);630free_krb5_kdc_dh_key_info(&kdc_dh);631free_krb5_pa_pk_as_rep(&kdc_reply);632633if (key_pack != NULL) {634free_krb5_reply_key_pack(&key_pack);635free(cksum.contents);636}637638free(kdc_hostname);639640pkiDebug("pkinit_as_rep_parse returning %d (%s)\n",641retval, error_message(retval));642return retval;643}644645static void646pkinit_client_profile(krb5_context context,647pkinit_context plgctx,648pkinit_req_context reqctx,649krb5_clpreauth_callbacks cb,650krb5_clpreauth_rock rock,651const krb5_data *realm)652{653const char *configured_identity;654char *eku_string = NULL, *minbits = NULL;655656pkiDebug("pkinit_client_profile %p %p %p %p\n",657context, plgctx, reqctx, realm);658659pkinit_libdefault_boolean(context, realm,660KRB5_CONF_PKINIT_REQUIRE_CRL_CHECKING,661reqctx->opts->require_crl_checking,662&reqctx->opts->require_crl_checking);663pkinit_libdefault_string(context, realm, KRB5_CONF_PKINIT_DH_MIN_BITS,664&minbits);665reqctx->opts->dh_size = parse_dh_min_bits(context, minbits);666free(minbits);667pkinit_libdefault_string(context, realm,668KRB5_CONF_PKINIT_EKU_CHECKING,669&eku_string);670if (eku_string != NULL) {671if (strcasecmp(eku_string, "kpKDC") == 0) {672reqctx->opts->require_eku = 1;673reqctx->opts->accept_secondary_eku = 0;674} else if (strcasecmp(eku_string, "kpServerAuth") == 0) {675reqctx->opts->require_eku = 1;676reqctx->opts->accept_secondary_eku = 1;677} else if (strcasecmp(eku_string, "none") == 0) {678reqctx->opts->require_eku = 0;679reqctx->opts->accept_secondary_eku = 0;680} else {681pkiDebug("%s: Invalid value for pkinit_eku_checking: '%s'\n",682__FUNCTION__, eku_string);683}684free(eku_string);685}686687/* Only process anchors here if they were not specified on command line */688if (reqctx->idopts->anchors == NULL)689pkinit_libdefault_strings(context, realm,690KRB5_CONF_PKINIT_ANCHORS,691&reqctx->idopts->anchors);692pkinit_libdefault_strings(context, realm,693KRB5_CONF_PKINIT_POOL,694&reqctx->idopts->intermediates);695pkinit_libdefault_strings(context, realm,696KRB5_CONF_PKINIT_REVOKE,697&reqctx->idopts->crls);698pkinit_libdefault_strings(context, realm,699KRB5_CONF_PKINIT_IDENTITIES,700&reqctx->idopts->identity_alt);701reqctx->do_identity_matching = TRUE;702703/* If we had a primary identity in the stored configuration, pick it up. */704configured_identity = cb->get_cc_config(context, rock,705"X509_user_identity");706if (configured_identity != NULL) {707free(reqctx->idopts->identity);708reqctx->idopts->identity = strdup(configured_identity);709reqctx->do_identity_matching = FALSE;710}711}712713/*714* Convert a PKCS11 token flags value to the subset that we're interested in715* passing along to our API callers.716*/717static long long718pkinit_client_get_token_flags(unsigned long pkcs11_token_flags)719{720long long flags = 0;721722if (pkcs11_token_flags & CKF_USER_PIN_COUNT_LOW)723flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_COUNT_LOW;724if (pkcs11_token_flags & CKF_USER_PIN_FINAL_TRY)725flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_FINAL_TRY;726if (pkcs11_token_flags & CKF_USER_PIN_LOCKED)727flags |= KRB5_RESPONDER_PKINIT_FLAGS_TOKEN_USER_PIN_LOCKED;728return flags;729}730731/*732* Phase one of loading client identity information - call733* identity_initialize() to load any identities which we can without requiring734* help from the calling user, and use their names of those which we can't load735* to construct the challenge for the responder callback.736*/737static krb5_error_code738pkinit_client_prep_questions(krb5_context context,739krb5_clpreauth_moddata moddata,740krb5_clpreauth_modreq modreq,741krb5_get_init_creds_opt *opt,742krb5_clpreauth_callbacks cb,743krb5_clpreauth_rock rock,744krb5_kdc_req *request,745krb5_data *encoded_request_body,746krb5_data *encoded_previous_request,747krb5_pa_data *pa_data)748{749krb5_error_code retval;750pkinit_context plgctx = (pkinit_context)moddata;751pkinit_req_context reqctx = (pkinit_req_context)modreq;752size_t i, n;753const pkinit_deferred_id *deferred_ids;754const char *identity;755unsigned long ck_flags;756char *encoded;757k5_json_object jval = NULL;758k5_json_number jflag = NULL;759760/* Don't ask questions for the informational padata items or when the761* ticket is issued. */762if (pa_data->pa_type != KRB5_PADATA_PK_AS_REQ)763return 0;764765if (!reqctx->identity_initialized) {766pkinit_client_profile(context, plgctx, reqctx, cb, rock,767&request->server->realm);768retval = pkinit_identity_initialize(context, plgctx->cryptoctx,769reqctx->cryptoctx, reqctx->idopts,770reqctx->idctx, cb, rock,771request->client);772if (retval != 0) {773TRACE_PKINIT_CLIENT_NO_IDENTITY(context);774pkiDebug("pkinit_identity_initialize returned %d (%s)\n",775retval, error_message(retval));776}777778reqctx->identity_initialized = TRUE;779if (retval != 0) {780pkiDebug("%s: not asking responder question\n", __FUNCTION__);781retval = 0;782goto cleanup;783}784}785786deferred_ids = crypto_get_deferred_ids(context, reqctx->idctx);787for (i = 0; deferred_ids != NULL && deferred_ids[i] != NULL; i++)788continue;789n = i;790791/* Make sure we don't just return an empty challenge. */792if (n == 0) {793pkiDebug("%s: no questions to ask\n", __FUNCTION__);794retval = 0;795goto cleanup;796}797798/* Create the top-level object. */799retval = k5_json_object_create(&jval);800if (retval != 0)801goto cleanup;802803for (i = 0; i < n; i++) {804/* Add an entry to the top-level object for the identity. */805identity = deferred_ids[i]->identity;806ck_flags = deferred_ids[i]->ck_flags;807/* Calculate the flags value for the bits that that we care about. */808retval = k5_json_number_create(pkinit_client_get_token_flags(ck_flags),809&jflag);810if (retval != 0)811goto cleanup;812retval = k5_json_object_set(jval, identity, jflag);813if (retval != 0)814goto cleanup;815k5_json_release(jflag);816jflag = NULL;817}818819/* Encode and done. */820retval = k5_json_encode(jval, &encoded);821if (retval == 0) {822cb->ask_responder_question(context, rock,823KRB5_RESPONDER_QUESTION_PKINIT,824encoded);825pkiDebug("%s: asking question '%s'\n", __FUNCTION__, encoded);826free(encoded);827}828829cleanup:830k5_json_release(jval);831k5_json_release(jflag);832833pkiDebug("%s returning %d\n", __FUNCTION__, retval);834835return retval;836}837838/*839* Parse data supplied by the application's responder callback, saving off any840* PINs and passwords for identities which we noted needed them.841*/842struct save_one_password_data {843krb5_context context;844krb5_clpreauth_modreq modreq;845const char *caller;846};847848static void849save_one_password(void *arg, const char *key, k5_json_value val)850{851struct save_one_password_data *data = arg;852pkinit_req_context reqctx = (pkinit_req_context)data->modreq;853const char *password;854855if (k5_json_get_tid(val) == K5_JSON_TID_STRING) {856password = k5_json_string_utf8(val);857pkiDebug("%s: \"%s\": %p\n", data->caller, key, password);858crypto_set_deferred_id(data->context, reqctx->idctx, key, password);859}860}861862static krb5_error_code863pkinit_client_parse_answers(krb5_context context,864krb5_clpreauth_moddata moddata,865krb5_clpreauth_modreq modreq,866krb5_clpreauth_callbacks cb,867krb5_clpreauth_rock rock)868{869krb5_error_code retval;870const char *encoded;871k5_json_value jval;872struct save_one_password_data data;873874data.context = context;875data.modreq = modreq;876data.caller = __FUNCTION__;877878encoded = cb->get_responder_answer(context, rock,879KRB5_RESPONDER_QUESTION_PKINIT);880if (encoded == NULL)881return 0;882883pkiDebug("pkinit_client_parse_answers: %s\n", encoded);884885retval = k5_json_decode(encoded, &jval);886if (retval != 0)887goto cleanup;888889/* Expect that the top-level answer is an object. */890if (k5_json_get_tid(jval) != K5_JSON_TID_OBJECT) {891retval = EINVAL;892goto cleanup;893}894895/* Store the passed-in per-identity passwords. */896k5_json_object_iterate(jval, &save_one_password, &data);897retval = 0;898899cleanup:900if (jval != NULL)901k5_json_release(jval);902return retval;903}904905static krb5_error_code906pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata,907krb5_clpreauth_modreq modreq,908krb5_get_init_creds_opt *gic_opt,909krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,910krb5_kdc_req *request, krb5_data *encoded_request_body,911krb5_data *encoded_previous_request,912krb5_pa_data *in_padata,913krb5_prompter_fct prompter, void *prompter_data,914krb5_pa_data ***out_padata)915{916krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;917krb5_enctype enctype = -1;918int processing_request = 0;919pkinit_context plgctx = (pkinit_context)moddata;920pkinit_req_context reqctx = (pkinit_req_context)modreq;921krb5_keyblock as_key;922krb5_data d;923924pkiDebug("pkinit_client_process %p %p %p %p\n",925context, plgctx, reqctx, request);926927928if (plgctx == NULL || reqctx == NULL)929return EINVAL;930931switch ((int) in_padata->pa_type) {932case KRB5_PADATA_PKINIT_KX:933reqctx->rfc6112_kdc = 1;934return 0;935case KRB5_PADATA_AS_FRESHNESS:936TRACE_PKINIT_CLIENT_FRESHNESS_TOKEN(context);937krb5_free_data(context, reqctx->freshness_token);938reqctx->freshness_token = NULL;939d = make_data(in_padata->contents, in_padata->length);940return krb5_copy_data(context, &d, &reqctx->freshness_token);941case KRB5_PADATA_PK_AS_REQ:942pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n");943processing_request = 1;944break;945946case KRB5_PADATA_PK_AS_REP:947pkiDebug("processing KRB5_PADATA_PK_AS_REP\n");948break;949default:950pkiDebug("unrecognized patype = %d for PKINIT\n",951in_padata->pa_type);952return EINVAL;953}954955if (processing_request) {956if (reqctx->idopts->anchors == NULL) {957krb5_set_error_message(context, KRB5_PREAUTH_FAILED,958_("No pkinit_anchors supplied"));959return KRB5_PREAUTH_FAILED;960}961/* Pull in PINs and passwords for identities which we deferred962* loading earlier. */963retval = pkinit_client_parse_answers(context, moddata, modreq,964cb, rock);965if (retval) {966if (retval == KRB5KRB_ERR_GENERIC)967pkiDebug("pkinit responder answers were invalid\n");968return retval;969}970if (!reqctx->identity_prompted) {971reqctx->identity_prompted = TRUE;972/*973* Load identities (again, potentially), prompting, if we can, for974* anything for which we didn't get an answer from the responder975* callback.976*/977pkinit_identity_set_prompter(reqctx->idctx, prompter,978prompter_data);979retval = pkinit_identity_prompt(context, plgctx->cryptoctx,980reqctx->cryptoctx, reqctx->idopts,981reqctx->idctx, cb, rock,982reqctx->do_identity_matching,983request->client);984pkinit_identity_set_prompter(reqctx->idctx, NULL, NULL);985reqctx->identity_prompt_retval = retval;986if (retval) {987TRACE_PKINIT_CLIENT_NO_IDENTITY(context);988pkiDebug("pkinit_identity_prompt returned %d (%s)\n",989retval, error_message(retval));990return retval;991}992} else if (reqctx->identity_prompt_retval) {993retval = reqctx->identity_prompt_retval;994TRACE_PKINIT_CLIENT_NO_IDENTITY(context);995pkiDebug("pkinit_identity_prompt previously returned %d (%s)\n",996retval, error_message(retval));997return retval;998}999retval = pa_pkinit_gen_req(context, plgctx, reqctx, cb, rock, request,1000in_padata->pa_type, out_padata, prompter,1001prompter_data, gic_opt);1002} else {1003/*1004* Get the enctype of the reply.1005*/1006enctype = cb->get_etype(context, rock);1007retval = pa_pkinit_parse_rep(context, plgctx, reqctx, request,1008in_padata, enctype, &as_key,1009encoded_previous_request);1010if (retval == 0) {1011retval = cb->set_as_key(context, rock, &as_key);1012krb5_free_keyblock_contents(context, &as_key);1013}1014}10151016pkiDebug("pkinit_client_process: returning %d (%s)\n",1017retval, error_message(retval));1018return retval;1019}10201021static krb5_error_code1022pkinit_client_tryagain(krb5_context context, krb5_clpreauth_moddata moddata,1023krb5_clpreauth_modreq modreq,1024krb5_get_init_creds_opt *gic_opt,1025krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,1026krb5_kdc_req *request, krb5_data *encoded_request_body,1027krb5_data *encoded_previous_request,1028krb5_preauthtype pa_type, krb5_error *err_reply,1029krb5_pa_data **err_padata, krb5_prompter_fct prompter,1030void *prompter_data, krb5_pa_data ***out_padata)1031{1032krb5_error_code retval = KRB5KDC_ERR_PREAUTH_FAILED;1033pkinit_context plgctx = (pkinit_context)moddata;1034pkinit_req_context reqctx = (pkinit_req_context)modreq;1035krb5_pa_data *pa;1036krb5_data scratch;1037krb5_external_principal_identifier **certifiers = NULL;1038krb5_algorithm_identifier **algId = NULL;1039int do_again = 0;10401041pkiDebug("pkinit_client_tryagain %p %p %p %p\n",1042context, plgctx, reqctx, request);10431044if (reqctx->pa_type != pa_type || err_padata == NULL)1045return retval;10461047for (; *err_padata != NULL && !do_again; err_padata++) {1048pa = *err_padata;1049PADATA_TO_KRB5DATA(pa, &scratch);1050switch (pa->pa_type) {1051case TD_TRUSTED_CERTIFIERS:1052case TD_INVALID_CERTIFICATES:1053retval = k5int_decode_krb5_td_trusted_certifiers(&scratch,1054&certifiers);1055if (retval) {1056pkiDebug("failed to decode sequence of trusted certifiers\n");1057goto cleanup;1058}1059retval = pkinit_process_td_trusted_certifiers(context,1060plgctx->cryptoctx,1061reqctx->cryptoctx,1062reqctx->idctx,1063certifiers,1064pa->pa_type);1065if (!retval)1066do_again = 1;1067break;1068case TD_DH_PARAMETERS:1069retval = k5int_decode_krb5_td_dh_parameters(&scratch, &algId);1070if (retval) {1071pkiDebug("failed to decode td_dh_parameters\n");1072goto cleanup;1073}1074retval = pkinit_process_td_dh_params(context, plgctx->cryptoctx,1075reqctx->cryptoctx,1076reqctx->idctx, algId,1077&reqctx->opts->dh_size);1078if (!retval)1079do_again = 1;1080break;1081default:1082break;1083}1084}10851086if (do_again) {1087TRACE_PKINIT_CLIENT_TRYAGAIN(context);1088retval = pa_pkinit_gen_req(context, plgctx, reqctx, cb, rock, request,1089pa_type, out_padata, prompter,1090prompter_data, gic_opt);1091if (retval)1092goto cleanup;1093}10941095retval = 0;1096cleanup:1097if (certifiers != NULL)1098free_krb5_external_principal_identifier(&certifiers);10991100if (algId != NULL)1101free_krb5_algorithm_identifiers(&algId);11021103pkiDebug("pkinit_client_tryagain: returning %d (%s)\n",1104retval, error_message(retval));1105return retval;1106}11071108static int1109pkinit_client_get_flags(krb5_context kcontext, krb5_preauthtype patype)1110{1111if (patype == KRB5_PADATA_PKINIT_KX || patype == KRB5_PADATA_AS_FRESHNESS)1112return PA_INFO;1113return PA_REAL;1114}11151116/*1117* We want to be notified about KRB5_PADATA_PKINIT_KX in addition to the actual1118* pkinit patypes because RFC 6112 requires anonymous KDCs to send it. We use1119* that to determine whether to use the broken MIT 1.9 behavior of sending1120* ContentInfo rather than SignedData or the RFC 6112 behavior1121*/1122static krb5_preauthtype supported_client_pa_types[] = {1123KRB5_PADATA_PK_AS_REP,1124KRB5_PADATA_PK_AS_REQ,1125KRB5_PADATA_PKINIT_KX,1126KRB5_PADATA_AS_FRESHNESS,112701128};11291130static void1131pkinit_client_req_init(krb5_context context,1132krb5_clpreauth_moddata moddata,1133krb5_clpreauth_modreq *modreq_out)1134{1135krb5_error_code retval = ENOMEM;1136pkinit_req_context reqctx = NULL;1137pkinit_context plgctx = (pkinit_context)moddata;11381139*modreq_out = NULL;11401141reqctx = malloc(sizeof(*reqctx));1142if (reqctx == NULL)1143return;1144memset(reqctx, 0, sizeof(*reqctx));11451146reqctx->magic = PKINIT_REQ_CTX_MAGIC;1147reqctx->cryptoctx = NULL;1148reqctx->opts = NULL;1149reqctx->idctx = NULL;1150reqctx->idopts = NULL;1151reqctx->freshness_token = NULL;11521153retval = pkinit_init_req_opts(&reqctx->opts);1154if (retval)1155goto cleanup;11561157reqctx->opts->require_eku = plgctx->opts->require_eku;1158reqctx->opts->accept_secondary_eku = plgctx->opts->accept_secondary_eku;1159reqctx->opts->allow_upn = plgctx->opts->allow_upn;1160reqctx->opts->require_crl_checking = plgctx->opts->require_crl_checking;1161reqctx->opts->disable_freshness = plgctx->opts->disable_freshness;11621163retval = pkinit_init_req_crypto(&reqctx->cryptoctx);1164if (retval)1165goto cleanup;11661167retval = pkinit_init_identity_crypto(&reqctx->idctx);1168if (retval)1169goto cleanup;11701171retval = pkinit_dup_identity_opts(plgctx->idopts, &reqctx->idopts);1172if (retval)1173goto cleanup;11741175*modreq_out = (krb5_clpreauth_modreq)reqctx;1176pkiDebug("%s: returning reqctx at %p\n", __FUNCTION__, reqctx);11771178cleanup:1179if (retval) {1180if (reqctx->idctx != NULL)1181pkinit_fini_identity_crypto(reqctx->idctx);1182if (reqctx->cryptoctx != NULL)1183pkinit_fini_req_crypto(reqctx->cryptoctx);1184if (reqctx->opts != NULL)1185pkinit_fini_req_opts(reqctx->opts);1186if (reqctx->idopts != NULL)1187pkinit_fini_identity_opts(reqctx->idopts);1188free(reqctx);1189}11901191return;1192}11931194static void1195pkinit_client_req_fini(krb5_context context, krb5_clpreauth_moddata moddata,1196krb5_clpreauth_modreq modreq)1197{1198pkinit_req_context reqctx = (pkinit_req_context)modreq;11991200pkiDebug("%s: received reqctx at %p\n", __FUNCTION__, reqctx);1201if (reqctx == NULL)1202return;1203if (reqctx->magic != PKINIT_REQ_CTX_MAGIC) {1204pkiDebug("%s: Bad magic value (%x) in req ctx\n",1205__FUNCTION__, reqctx->magic);1206return;1207}1208if (reqctx->opts != NULL)1209pkinit_fini_req_opts(reqctx->opts);12101211if (reqctx->cryptoctx != NULL)1212pkinit_fini_req_crypto(reqctx->cryptoctx);12131214if (reqctx->idctx != NULL)1215pkinit_fini_identity_crypto(reqctx->idctx);12161217if (reqctx->idopts != NULL)1218pkinit_fini_identity_opts(reqctx->idopts);12191220krb5_free_data(context, reqctx->freshness_token);12211222free(reqctx);1223return;1224}12251226static int1227pkinit_client_plugin_init(krb5_context context,1228krb5_clpreauth_moddata *moddata_out)1229{1230krb5_error_code retval = ENOMEM;1231pkinit_context ctx = NULL;12321233ctx = calloc(1, sizeof(*ctx));1234if (ctx == NULL)1235return ENOMEM;1236memset(ctx, 0, sizeof(*ctx));1237ctx->magic = PKINIT_CTX_MAGIC;1238ctx->opts = NULL;1239ctx->cryptoctx = NULL;1240ctx->idopts = NULL;12411242retval = pkinit_accessor_init();1243if (retval)1244goto errout;12451246retval = pkinit_init_plg_opts(&ctx->opts);1247if (retval)1248goto errout;12491250retval = pkinit_init_plg_crypto(context, &ctx->cryptoctx);1251if (retval)1252goto errout;12531254retval = pkinit_init_identity_opts(&ctx->idopts);1255if (retval)1256goto errout;12571258*moddata_out = (krb5_clpreauth_moddata)ctx;12591260pkiDebug("%s: returning plgctx at %p\n", __FUNCTION__, ctx);12611262errout:1263if (retval)1264pkinit_client_plugin_fini(context, (krb5_clpreauth_moddata)ctx);12651266return retval;1267}12681269static void1270pkinit_client_plugin_fini(krb5_context context, krb5_clpreauth_moddata moddata)1271{1272pkinit_context ctx = (pkinit_context)moddata;12731274if (ctx == NULL || ctx->magic != PKINIT_CTX_MAGIC) {1275pkiDebug("pkinit_lib_fini: got bad plgctx (%p)!\n", ctx);1276return;1277}1278pkiDebug("%s: got plgctx at %p\n", __FUNCTION__, ctx);12791280pkinit_fini_identity_opts(ctx->idopts);1281pkinit_fini_plg_crypto(ctx->cryptoctx);1282pkinit_fini_plg_opts(ctx->opts);1283free(ctx);12841285}12861287static krb5_error_code1288add_string_to_array(krb5_context context, char ***array, const char *addition)1289{1290char **a = *array;1291size_t len;12921293for (len = 0; a != NULL && a[len] != NULL; len++);1294a = realloc(a, (len + 2) * sizeof(char *));1295if (a == NULL)1296return ENOMEM;1297*array = a;1298a[len] = strdup(addition);1299if (a[len] == NULL)1300return ENOMEM;1301a[len + 1] = NULL;1302return 0;1303}13041305static krb5_error_code1306handle_gic_opt(krb5_context context,1307pkinit_context plgctx,1308const char *attr,1309const char *value)1310{1311krb5_error_code retval;13121313if (strcmp(attr, "X509_user_identity") == 0) {1314if (plgctx->idopts->identity != NULL) {1315krb5_set_error_message(context, KRB5_PREAUTH_FAILED,1316"X509_user_identity can not be given twice\n");1317return KRB5_PREAUTH_FAILED;1318}1319plgctx->idopts->identity = strdup(value);1320if (plgctx->idopts->identity == NULL) {1321krb5_set_error_message(context, ENOMEM,1322"Could not duplicate X509_user_identity value\n");1323return ENOMEM;1324}1325} else if (strcmp(attr, "X509_anchors") == 0) {1326retval = add_string_to_array(context, &plgctx->idopts->anchors, value);1327if (retval)1328return retval;1329} else if (strcmp(attr, "disable_freshness") == 0) {1330if (strcmp(value, "yes") == 0)1331plgctx->opts->disable_freshness = 1;1332}1333return 0;1334}13351336static krb5_error_code1337pkinit_client_gic_opt(krb5_context context, krb5_clpreauth_moddata moddata,1338krb5_get_init_creds_opt *gic_opt,1339const char *attr,1340const char *value)1341{1342krb5_error_code retval;1343pkinit_context plgctx = (pkinit_context)moddata;13441345pkiDebug("(pkinit) received '%s' = '%s'\n", attr, value);1346retval = handle_gic_opt(context, plgctx, attr, value);1347if (retval)1348return retval;13491350return 0;1351}13521353krb5_error_code1354clpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver,1355krb5_plugin_vtable vtable);13561357krb5_error_code1358clpreauth_pkinit_initvt(krb5_context context, int maj_ver, int min_ver,1359krb5_plugin_vtable vtable)1360{1361krb5_clpreauth_vtable vt;13621363if (maj_ver != 1)1364return KRB5_PLUGIN_VER_NOTSUPP;1365vt = (krb5_clpreauth_vtable)vtable;1366vt->name = "pkinit";1367vt->pa_type_list = supported_client_pa_types;1368vt->init = pkinit_client_plugin_init;1369vt->fini = pkinit_client_plugin_fini;1370vt->flags = pkinit_client_get_flags;1371vt->request_init = pkinit_client_req_init;1372vt->prep_questions = pkinit_client_prep_questions;1373vt->request_fini = pkinit_client_req_fini;1374vt->process = pkinit_client_process;1375vt->tryagain = pkinit_client_tryagain;1376vt->gic_opts = pkinit_client_gic_opt;1377return 0;1378}137913801381