Path: blob/main/crypto/krb5/src/plugins/preauth/pkinit/pkinit_identity.c
34923 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* COPYRIGHT (C) 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 "pkinit.h"3233static void34free_list(char **list)35{36size_t i;3738if (list == NULL)39return;4041for (i = 0; list[i] != NULL; i++)42free(list[i]);43free(list);44}4546static krb5_error_code47copy_list(char ***dst, char **src)48{49size_t i;50char **newlist;5152if (dst == NULL)53return EINVAL;54*dst = NULL;5556if (src == NULL)57return 0;5859for (i = 0; src[i] != NULL; i++);6061newlist = calloc(1, (i + 1) * sizeof(*newlist));62if (newlist == NULL)63return ENOMEM;6465for (i = 0; src[i] != NULL; i++) {66newlist[i] = strdup(src[i]);67if (newlist[i] == NULL)68goto cleanup;69}70newlist[i] = NULL;71*dst = newlist;72return 0;73cleanup:74free_list(newlist);75return ENOMEM;76}7778char *79idtype2string(int idtype)80{81switch(idtype) {82case IDTYPE_FILE: return "FILE"; break;83case IDTYPE_DIR: return "DIR"; break;84case IDTYPE_PKCS11: return "PKCS11"; break;85case IDTYPE_PKCS12: return "PKCS12"; break;86case IDTYPE_ENVVAR: return "ENV"; break;87default: return "INVALID"; break;88}89}9091char *92catype2string(int catype)93{94switch(catype) {95case CATYPE_ANCHORS: return "ANCHORS"; break;96case CATYPE_INTERMEDIATES: return "INTERMEDIATES"; break;97case CATYPE_CRLS: return "CRLS"; break;98default: return "INVALID"; break;99}100}101102krb5_error_code103pkinit_init_identity_opts(pkinit_identity_opts **idopts)104{105pkinit_identity_opts *opts = NULL;106107*idopts = NULL;108opts = calloc(1, sizeof(pkinit_identity_opts));109if (opts == NULL)110return ENOMEM;111112opts->identity = NULL;113opts->anchors = NULL;114opts->intermediates = NULL;115opts->crls = NULL;116117opts->cert_filename = NULL;118opts->key_filename = NULL;119#ifndef WITHOUT_PKCS11120opts->p11_module_name = NULL;121opts->slotid = PK_NOSLOT;122opts->token_label = NULL;123opts->cert_id_string = NULL;124opts->cert_label = NULL;125#endif126127*idopts = opts;128129return 0;130}131132krb5_error_code133pkinit_dup_identity_opts(pkinit_identity_opts *src_opts,134pkinit_identity_opts **dest_opts)135{136pkinit_identity_opts *newopts;137krb5_error_code retval;138139*dest_opts = NULL;140retval = pkinit_init_identity_opts(&newopts);141if (retval)142return retval;143144retval = ENOMEM;145146if (src_opts->identity != NULL) {147newopts->identity = strdup(src_opts->identity);148if (newopts->identity == NULL)149goto cleanup;150}151152retval = copy_list(&newopts->anchors, src_opts->anchors);153if (retval)154goto cleanup;155156retval = copy_list(&newopts->intermediates,src_opts->intermediates);157if (retval)158goto cleanup;159160retval = copy_list(&newopts->crls, src_opts->crls);161if (retval)162goto cleanup;163164if (src_opts->cert_filename != NULL) {165newopts->cert_filename = strdup(src_opts->cert_filename);166if (newopts->cert_filename == NULL)167goto cleanup;168}169170if (src_opts->key_filename != NULL) {171newopts->key_filename = strdup(src_opts->key_filename);172if (newopts->key_filename == NULL)173goto cleanup;174}175176#ifndef WITHOUT_PKCS11177if (src_opts->p11_module_name != NULL) {178newopts->p11_module_name = strdup(src_opts->p11_module_name);179if (newopts->p11_module_name == NULL)180goto cleanup;181}182183newopts->slotid = src_opts->slotid;184185if (src_opts->token_label != NULL) {186newopts->token_label = strdup(src_opts->token_label);187if (newopts->token_label == NULL)188goto cleanup;189}190191if (src_opts->cert_id_string != NULL) {192newopts->cert_id_string = strdup(src_opts->cert_id_string);193if (newopts->cert_id_string == NULL)194goto cleanup;195}196197if (src_opts->cert_label != NULL) {198newopts->cert_label = strdup(src_opts->cert_label);199if (newopts->cert_label == NULL)200goto cleanup;201}202#endif203204205*dest_opts = newopts;206return 0;207cleanup:208pkinit_fini_identity_opts(newopts);209return retval;210}211212void213pkinit_fini_identity_opts(pkinit_identity_opts *idopts)214{215if (idopts == NULL)216return;217218if (idopts->identity != NULL)219free(idopts->identity);220free_list(idopts->anchors);221free_list(idopts->intermediates);222free_list(idopts->crls);223free_list(idopts->identity_alt);224225free(idopts->cert_filename);226free(idopts->key_filename);227#ifndef WITHOUT_PKCS11228free(idopts->p11_module_name);229free(idopts->token_label);230free(idopts->cert_id_string);231free(idopts->cert_label);232#endif233free(idopts);234}235236#ifndef WITHOUT_PKCS11237static krb5_error_code238parse_pkcs11_options(krb5_context context,239pkinit_identity_opts *idopts,240const char *residual)241{242char *s, *cp, *vp, *save;243krb5_error_code retval = ENOMEM;244245if (residual == NULL || residual[0] == '\0')246return 0;247248/* Split string into attr=value substrings */249s = strdup(residual);250if (s == NULL)251return retval;252253for (cp = strtok_r(s, ":", &save); cp; cp = strtok_r(NULL, ":", &save)) {254vp = strchr(cp, '=');255256/* If there is no "=", this is a pkcs11 module name */257if (vp == NULL) {258free(idopts->p11_module_name);259idopts->p11_module_name = strdup(cp);260if (idopts->p11_module_name == NULL)261goto cleanup;262continue;263}264*vp++ = '\0';265if (!strcmp(cp, "module_name")) {266free(idopts->p11_module_name);267idopts->p11_module_name = strdup(vp);268if (idopts->p11_module_name == NULL)269goto cleanup;270} else if (!strcmp(cp, "slotid")) {271long slotid = strtol(vp, NULL, 10);272if ((slotid == LONG_MIN || slotid == LONG_MAX) && errno != 0) {273retval = EINVAL;274goto cleanup;275}276if ((long) (int) slotid != slotid) {277retval = EINVAL;278goto cleanup;279}280idopts->slotid = slotid;281} else if (!strcmp(cp, "token")) {282free(idopts->token_label);283idopts->token_label = strdup(vp);284if (idopts->token_label == NULL)285goto cleanup;286} else if (!strcmp(cp, "certid")) {287free(idopts->cert_id_string);288idopts->cert_id_string = strdup(vp);289if (idopts->cert_id_string == NULL)290goto cleanup;291} else if (!strcmp(cp, "certlabel")) {292free(idopts->cert_label);293idopts->cert_label = strdup(vp);294if (idopts->cert_label == NULL)295goto cleanup;296}297}298retval = 0;299cleanup:300free(s);301return retval;302}303#endif304305static krb5_error_code306parse_fs_options(krb5_context context,307pkinit_identity_opts *idopts,308const char *residual)309{310char *certname, *keyname, *save;311char *copy = NULL, *cert_filename = NULL, *key_filename = NULL;312krb5_error_code retval = ENOMEM;313314if (residual == NULL || residual[0] == '\0' || residual[0] == ',')315return EINVAL;316317copy = strdup(residual);318if (copy == NULL)319goto cleanup;320321certname = strtok_r(copy, ",", &save);322if (certname == NULL)323goto cleanup;324keyname = strtok_r(NULL, ",", &save);325326cert_filename = strdup(certname);327if (cert_filename == NULL)328goto cleanup;329330key_filename = strdup((keyname != NULL) ? keyname : certname);331if (key_filename == NULL)332goto cleanup;333334free(idopts->cert_filename);335free(idopts->key_filename);336idopts->cert_filename = cert_filename;337idopts->key_filename = key_filename;338cert_filename = key_filename = NULL;339retval = 0;340341cleanup:342free(copy);343free(cert_filename);344free(key_filename);345return retval;346}347348static krb5_error_code349parse_pkcs12_options(krb5_context context,350pkinit_identity_opts *idopts,351const char *residual)352{353krb5_error_code retval = ENOMEM;354355if (residual == NULL || residual[0] == '\0')356return 0;357358free(idopts->cert_filename);359idopts->cert_filename = strdup(residual);360if (idopts->cert_filename == NULL)361goto cleanup;362363free(idopts->key_filename);364idopts->key_filename = strdup(residual);365if (idopts->key_filename == NULL)366goto cleanup;367368pkiDebug("%s: cert_filename '%s' key_filename '%s'\n",369__FUNCTION__, idopts->cert_filename,370idopts->key_filename);371retval = 0;372cleanup:373return retval;374}375376static krb5_error_code377process_option_identity(krb5_context context,378pkinit_plg_crypto_context plg_cryptoctx,379pkinit_req_crypto_context req_cryptoctx,380pkinit_identity_opts *idopts,381pkinit_identity_crypto_context id_cryptoctx,382krb5_principal princ, const char *value)383{384const char *residual;385int idtype;386krb5_error_code retval = 0;387388TRACE_PKINIT_IDENTITY_OPTION(context, value);389if (value == NULL)390return EINVAL;391392residual = strchr(value, ':');393if (residual != NULL) {394unsigned int typelen;395residual++; /* skip past colon */396typelen = residual - value;397if (strncmp(value, "FILE:", typelen) == 0) {398idtype = IDTYPE_FILE;399#ifndef WITHOUT_PKCS11400} else if (strncmp(value, "PKCS11:", typelen) == 0) {401idtype = IDTYPE_PKCS11;402#endif403} else if (strncmp(value, "PKCS12:", typelen) == 0) {404idtype = IDTYPE_PKCS12;405} else if (strncmp(value, "DIR:", typelen) == 0) {406idtype = IDTYPE_DIR;407} else if (strncmp(value, "ENV:", typelen) == 0) {408idtype = IDTYPE_ENVVAR;409} else {410pkiDebug("%s: Unsupported type while processing '%s'\n",411__FUNCTION__, value);412krb5_set_error_message(context, KRB5_PREAUTH_FAILED,413_("Unsupported type while processing "414"'%s'\n"), value);415return KRB5_PREAUTH_FAILED;416}417} else {418idtype = IDTYPE_FILE;419residual = value;420}421422idopts->idtype = idtype;423pkiDebug("%s: idtype is %s\n", __FUNCTION__, idtype2string(idopts->idtype));424switch (idtype) {425case IDTYPE_ENVVAR:426return process_option_identity(context, plg_cryptoctx, req_cryptoctx,427idopts, id_cryptoctx, princ,428secure_getenv(residual));429break;430case IDTYPE_FILE:431retval = parse_fs_options(context, idopts, residual);432break;433case IDTYPE_PKCS12:434retval = parse_pkcs12_options(context, idopts, residual);435break;436#ifndef WITHOUT_PKCS11437case IDTYPE_PKCS11:438retval = parse_pkcs11_options(context, idopts, residual);439break;440#endif441case IDTYPE_DIR:442free(idopts->cert_filename);443idopts->cert_filename = strdup(residual);444if (idopts->cert_filename == NULL)445retval = ENOMEM;446break;447default:448krb5_set_error_message(context, KRB5_PREAUTH_FAILED,449_("Internal error parsing "450"X509_user_identity\n"));451retval = EINVAL;452break;453}454if (retval)455return retval;456457retval = crypto_load_certs(context, plg_cryptoctx, req_cryptoctx, idopts,458id_cryptoctx, princ, TRUE);459if (retval)460return retval;461462crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx, id_cryptoctx);463return 0;464}465466static krb5_error_code467process_option_ca_crl(krb5_context context,468pkinit_plg_crypto_context plg_cryptoctx,469pkinit_req_crypto_context req_cryptoctx,470pkinit_identity_opts *idopts,471pkinit_identity_crypto_context id_cryptoctx,472const char *value,473int catype)474{475char *residual;476unsigned int typelen;477int idtype;478479pkiDebug("%s: processing catype %s, value '%s'\n",480__FUNCTION__, catype2string(catype), value);481residual = strchr(value, ':');482if (residual == NULL) {483pkiDebug("No type given for '%s'\n", value);484return EINVAL;485}486residual++; /* skip past colon */487typelen = residual - value;488if (strncmp(value, "FILE:", typelen) == 0) {489idtype = IDTYPE_FILE;490} else if (strncmp(value, "DIR:", typelen) == 0) {491idtype = IDTYPE_DIR;492} else {493return ENOTSUP;494}495return crypto_load_cas_and_crls(context,496plg_cryptoctx,497req_cryptoctx,498idopts, id_cryptoctx,499idtype, catype, residual);500}501502/*503* Load any identity information which doesn't require us to ask a controlling504* user any questions, and record the names of anything else which would505* require us to ask questions.506*/507krb5_error_code508pkinit_identity_initialize(krb5_context context,509pkinit_plg_crypto_context plg_cryptoctx,510pkinit_req_crypto_context req_cryptoctx,511pkinit_identity_opts *idopts,512pkinit_identity_crypto_context id_cryptoctx,513krb5_clpreauth_callbacks cb,514krb5_clpreauth_rock rock,515krb5_principal princ)516{517krb5_error_code retval = EINVAL;518size_t i;519520pkiDebug("%s: %p %p %p\n", __FUNCTION__, context, idopts, id_cryptoctx);521if (!(princ &&522krb5_principal_compare_any_realm(context, princ,523krb5_anonymous_principal()))) {524if (idopts == NULL || id_cryptoctx == NULL)525goto errout;526527/*528* If identity was specified, use that. (For the kdc, this529* is specified as pkinit_identity in the kdc.conf. For users,530* this is specified on the command line via X509_user_identity.)531* If a user did not specify identity on the command line,532* then we will try alternatives which may have been specified533* in the config file.534*/535if (idopts->identity != NULL) {536retval = process_option_identity(context, plg_cryptoctx,537req_cryptoctx, idopts,538id_cryptoctx, princ,539idopts->identity);540} else if (idopts->identity_alt != NULL) {541for (i = 0; retval != 0 && idopts->identity_alt[i] != NULL; i++) {542retval = process_option_identity(context, plg_cryptoctx,543req_cryptoctx, idopts,544id_cryptoctx, princ,545idopts->identity_alt[i]);546}547} else {548retval = KRB5_PREAUTH_FAILED;549krb5_set_error_message(context, retval,550_("No user identity options specified"));551pkiDebug("%s: no user identity options specified\n", __FUNCTION__);552goto errout;553}554} else {555/* We're the anonymous principal. */556retval = 0;557}558559errout:560return retval;561}562563/*564* Load identity information, including that which requires us to ask a565* controlling user any questions. If we have PIN/password values which566* correspond to a given identity, use that, otherwise, if one is available,567* we'll use the prompter callback.568*/569krb5_error_code570pkinit_identity_prompt(krb5_context context,571pkinit_plg_crypto_context plg_cryptoctx,572pkinit_req_crypto_context req_cryptoctx,573pkinit_identity_opts *idopts,574pkinit_identity_crypto_context id_cryptoctx,575krb5_clpreauth_callbacks cb,576krb5_clpreauth_rock rock,577int do_matching,578krb5_principal princ)579{580krb5_error_code retval = 0;581const char *signer_identity;582krb5_boolean valid;583size_t i;584585pkiDebug("%s: %p %p %p\n", __FUNCTION__, context, idopts, id_cryptoctx);586if (!(princ &&587krb5_principal_compare_any_realm(context, princ,588krb5_anonymous_principal()))) {589retval = crypto_load_certs(context, plg_cryptoctx, req_cryptoctx,590idopts, id_cryptoctx, princ, FALSE);591if (retval)592goto errout;593594if (do_matching) {595/*596* Try to select exactly one certificate based on matching597* criteria. Typical used for clients.598*/599retval = pkinit_cert_matching(context, plg_cryptoctx,600req_cryptoctx, id_cryptoctx, princ);601if (retval) {602crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,603id_cryptoctx);604goto errout;605}606} else {607/*608* Tell crypto code to use the "default" identity. Typically used609* for KDCs.610*/611retval = crypto_cert_select_default(context, plg_cryptoctx,612req_cryptoctx, id_cryptoctx);613if (retval) {614crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,615id_cryptoctx);616goto errout;617}618}619620if (rock != NULL && cb != NULL && retval == 0) {621/* Save the signer identity if we're the client. */622if (crypto_retrieve_signer_identity(context, id_cryptoctx,623&signer_identity) == 0) {624cb->set_cc_config(context, rock, "X509_user_identity",625signer_identity);626}627}628629retval = crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,630id_cryptoctx);631if (retval)632goto errout;633} /* Not anonymous principal */634635/* Require at least one successful anchor if any are specified. */636valid = FALSE;637for (i = 0; idopts->anchors != NULL && idopts->anchors[i] != NULL; i++) {638retval = process_option_ca_crl(context, plg_cryptoctx, req_cryptoctx,639idopts, id_cryptoctx,640idopts->anchors[i], CATYPE_ANCHORS);641if (!retval)642valid = TRUE;643}644if (retval && !valid)645goto errout;646krb5_clear_error_message(context);647retval = 0;648649/* Require at least one successful intermediate if any are specified. */650valid = FALSE;651for (i = 0; idopts->intermediates != NULL652&& idopts->intermediates[i] != NULL; i++) {653retval = process_option_ca_crl(context, plg_cryptoctx, req_cryptoctx,654idopts, id_cryptoctx,655idopts->intermediates[i],656CATYPE_INTERMEDIATES);657if (!retval)658valid = TRUE;659}660if (retval && !valid)661goto errout;662krb5_clear_error_message(context);663retval = 0;664665for (i = 0; idopts->crls != NULL && idopts->crls[i] != NULL; i++) {666retval = process_option_ca_crl(context, plg_cryptoctx, req_cryptoctx,667idopts, id_cryptoctx, idopts->crls[i],668CATYPE_CRLS);669if (retval)670goto errout;671}672673errout:674return retval;675}676677/*678* Create an entry in the passed-in list for the named identity, optionally679* with the specified token flag value and/or supplied password, replacing any680* existing entry with the same identity name.681*/682krb5_error_code683pkinit_set_deferred_id(pkinit_deferred_id **identities,684const char *identity, unsigned long ck_flags,685const char *password)686{687size_t i;688pkinit_deferred_id *out = NULL, *ids;689char *tmp;690691/* Search for an entry that's already in the list. */692ids = *identities;693for (i = 0; ids != NULL && ids[i] != NULL; i++) {694if (strcmp(ids[i]->identity, identity) == 0) {695/* Replace its password value, then we're done. */696tmp = password ? strdup(password) : NULL;697if (password != NULL && tmp == NULL)698return ENOMEM;699ids[i]->ck_flags = ck_flags;700free(ids[i]->password);701ids[i]->password = tmp;702return 0;703}704}705706/* Resize the list. */707out = realloc(ids, sizeof(*ids) * (i + 2));708if (out == NULL)709goto oom;710*identities = out;711712/* Allocate the new final entry. */713out[i] = malloc(sizeof(*(out[i])));714if (out[i] == NULL)715goto oom;716717/* Populate the new entry. */718out[i]->magic = PKINIT_DEFERRED_ID_MAGIC;719out[i]->identity = strdup(identity);720if (out[i]->identity == NULL)721goto oom;722723out[i]->ck_flags = ck_flags;724out[i]->password = password ? strdup(password) : NULL;725if (password != NULL && out[i]->password == NULL)726goto oom;727728/* Terminate the list. */729out[i + 1] = NULL;730return 0;731732oom:733if (out != NULL && out[i] != NULL) {734free(out[i]->identity);735free(out[i]);736out[i] = NULL;737}738return ENOMEM;739}740741/*742* Return a password which we've associated with the named identity, if we've743* stored one. Otherwise return NULL.744*/745const char *746pkinit_find_deferred_id(pkinit_deferred_id *identities,747const char *identity)748{749size_t i;750751for (i = 0; identities != NULL && identities[i] != NULL; i++) {752if (strcmp(identities[i]->identity, identity) == 0)753return identities[i]->password;754}755return NULL;756}757758/*759* Return the flags associated with the specified identity, or 0 if we don't760* have such an identity.761*/762unsigned long763pkinit_get_deferred_id_flags(pkinit_deferred_id *identities,764const char *identity)765{766size_t i;767768for (i = 0; identities != NULL && identities[i] != NULL; i++) {769if (strcmp(identities[i]->identity, identity) == 0)770return identities[i]->ck_flags;771}772return 0;773}774775/*776* Free a deferred_id list.777*/778void779pkinit_free_deferred_ids(pkinit_deferred_id *identities)780{781size_t i;782783for (i = 0; identities != NULL && identities[i] != NULL; i++) {784free(identities[i]->identity);785free(identities[i]->password);786free(identities[i]);787}788free(identities);789}790791792