Path: blob/main/crypto/krb5/src/plugins/preauth/spake/groups.c
34907 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* plugins/preauth/spake/groups.c - SPAKE group interfaces */2/*3* Copyright (C) 2015 by the Massachusetts Institute of Technology.4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9*10* * Redistributions of source code must retain the above copyright11* notice, this list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in15* the documentation and/or other materials provided with the16* distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS19* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT20* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS21* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE22* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,23* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES24* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR25* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)26* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,27* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)28* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED29* OF THE POSSIBILITY OF SUCH DAMAGE.30*/3132/*33* The SPAKE2 algorithm works as follows:34*35* 1. The parties agree on a group, a base element G, and constant elements M36* and N. In this mechanism, these parameters are determined by the37* registered group number.38* 2. Both parties derive a scalar value w from the initial key.39* 3. The first party (the KDC, in this mechanism) chooses a random secret40* scalar x and sends T=xG+wM.41* 4. The second party (the client, in this mechanism) chooses a random42* secret scalar y and sends S=yG+wN.43* 5. The first party computes K=x(S-wN).44* 6. The second party computes the same value as K=y(T-wM).45* 7. Both parties derive a key from a random oracle whose input incorporates46* the party identities, w, T, S, and K.47*48* We implement the algorithm using a vtable for each group, where the primary49* vtable methods are "keygen" (corresponding to step 3 or 4) and "result"50* (corresponding to step 5 or 6). We use the term "private scalar" to refer51* to x or y, and "public element" to refer to S or T.52*/5354#include "iana.h"55#include "trace.h"56#include "groups.h"5758#define DEFAULT_GROUPS_CLIENT "edwards25519"59#define DEFAULT_GROUPS_KDC ""6061typedef struct groupent_st {62const groupdef *gdef;63groupdata *gdata;64} groupent;6566struct groupstate_st {67krb5_boolean is_kdc;6869/* Permitted and groups, from configuration */70int32_t *permitted;71size_t npermitted;7273/* Optimistic challenge group, from configuration */74int32_t challenge_group;7576/* Lazily-initialized list of gdata objects. */77groupent *data;78size_t ndata;79};8081extern groupdef builtin_edwards25519;82#ifdef SPAKE_OPENSSL83extern groupdef ossl_P256;84extern groupdef ossl_P384;85extern groupdef ossl_P521;86#endif8788static const groupdef *groupdefs[] = {89&builtin_edwards25519,90#ifdef SPAKE_OPENSSL91&ossl_P256,92&ossl_P384,93&ossl_P521,94#endif95NULL96};9798/* Find a groupdef structure by group number. Return NULL on failure. */99static const groupdef *100find_gdef(int32_t group)101{102size_t i;103104for (i = 0; groupdefs[i] != NULL; i++) {105if (groupdefs[i]->reg->id == group)106return groupdefs[i];107}108109return NULL;110}111112/* Find a group number by name. Return 0 on failure. */113static int32_t114find_gnum(const char *name)115{116size_t i;117118for (i = 0; groupdefs[i] != NULL; i++) {119if (strcasecmp(name, groupdefs[i]->reg->name) == 0)120return groupdefs[i]->reg->id;121}122return 0;123}124125static krb5_boolean126in_grouplist(const int32_t *list, size_t count, int32_t group)127{128size_t i;129130for (i = 0; i < count; i++) {131if (list[i] == group)132return TRUE;133}134135return FALSE;136}137138/* Retrieve a group data object for group within gstate, lazily initializing it139* if necessary. */140static krb5_error_code141get_gdata(krb5_context context, groupstate *gstate, const groupdef *gdef,142groupdata **gdata_out)143{144krb5_error_code ret;145groupent *ent, *newptr;146147*gdata_out = NULL;148149/* Look for an existing entry. */150for (ent = gstate->data; ent < gstate->data + gstate->ndata; ent++) {151if (ent->gdef == gdef) {152*gdata_out = ent->gdata;153return 0;154}155}156157/* Make a new entry. */158newptr = realloc(gstate->data, (gstate->ndata + 1) * sizeof(groupent));159if (newptr == NULL)160return ENOMEM;161gstate->data = newptr;162ent = &gstate->data[gstate->ndata];163ent->gdef = gdef;164ent->gdata = NULL;165if (gdef->init != NULL) {166ret = gdef->init(context, gdef, &ent->gdata);167if (ret)168return ret;169}170gstate->ndata++;171*gdata_out = ent->gdata;172return 0;173}174175/* Destructively parse str into a list of group numbers. */176static krb5_error_code177parse_groups(krb5_context context, char *str, int32_t **list_out,178size_t *count_out)179{180const char *const delim = " \t\r\n,";181char *token, *save = NULL;182int32_t group, *newptr, *list = NULL;183size_t count = 0;184185*list_out = NULL;186*count_out = 0;187188/* Walk through the words in profstr. */189for (token = strtok_r(str, delim, &save); token != NULL;190token = strtok_r(NULL, delim, &save)) {191group = find_gnum(token);192if (!group) {193TRACE_SPAKE_UNKNOWN_GROUP(context, token);194continue;195}196if (in_grouplist(list, count, group))197continue;198newptr = realloc(list, (count + 1) * sizeof(*list));199if (newptr == NULL) {200free(list);201return ENOMEM;202}203list = newptr;204list[count++] = group;205}206207*list_out = list;208*count_out = count;209return 0;210}211212krb5_error_code213group_init_state(krb5_context context, krb5_boolean is_kdc,214groupstate **gstate_out)215{216krb5_error_code ret;217groupstate *gstate;218const char *defgroups;219char *profstr1 = NULL, *profstr2 = NULL;220int32_t *permitted = NULL, challenge_group = 0;221size_t npermitted;222223*gstate_out = NULL;224225defgroups = is_kdc ? DEFAULT_GROUPS_KDC : DEFAULT_GROUPS_CLIENT;226ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,227KRB5_CONF_SPAKE_PREAUTH_GROUPS, NULL, defgroups,228&profstr1);229if (ret)230goto cleanup;231ret = parse_groups(context, profstr1, &permitted, &npermitted);232if (ret)233goto cleanup;234if (npermitted == 0) {235ret = KRB5_PLUGIN_OP_NOTSUPP;236k5_setmsg(context, ret, _("No SPAKE preauth groups configured"));237goto cleanup;238}239240if (is_kdc) {241/*242* Check for a configured optimistic challenge group. If one is set,243* the KDC will send a challenge in the PREAUTH_REQUIRED method data,244* before receiving the list of supported groups.245*/246ret = profile_get_string(context->profile, KRB5_CONF_KDCDEFAULTS,247KRB5_CONF_SPAKE_PREAUTH_KDC_CHALLENGE, NULL,248NULL, &profstr2);249if (ret)250goto cleanup;251if (profstr2 != NULL) {252challenge_group = find_gnum(profstr2);253if (!in_grouplist(permitted, npermitted, challenge_group)) {254ret = KRB5_PLUGIN_OP_NOTSUPP;255k5_setmsg(context, ret,256_("SPAKE challenge group not a permitted group: %s"),257profstr2);258goto cleanup;259}260}261}262263gstate = k5alloc(sizeof(*gstate), &ret);264if (gstate == NULL)265goto cleanup;266gstate->is_kdc = is_kdc;267gstate->permitted = permitted;268gstate->npermitted = npermitted;269gstate->challenge_group = challenge_group;270permitted = NULL;271gstate->data = NULL;272gstate->ndata = 0;273*gstate_out = gstate;274275cleanup:276profile_release_string(profstr1);277profile_release_string(profstr2);278free(permitted);279return ret;280}281282283void284group_free_state(groupstate *gstate)285{286groupent *ent;287288for (ent = gstate->data; ent < gstate->data + gstate->ndata; ent++) {289if (ent->gdata != NULL && ent->gdef->fini != NULL)290ent->gdef->fini(ent->gdata);291}292293free(gstate->permitted);294free(gstate->data);295free(gstate);296}297298krb5_boolean299group_is_permitted(groupstate *gstate, int32_t group)300{301return in_grouplist(gstate->permitted, gstate->npermitted, group);302}303304void305group_get_permitted(groupstate *gstate, int32_t **list_out, int32_t *count_out)306{307*list_out = gstate->permitted;308*count_out = gstate->npermitted;309}310311krb5_int32312group_optimistic_challenge(groupstate *gstate)313{314assert(gstate->is_kdc);315return gstate->challenge_group;316}317318krb5_error_code319group_mult_len(int32_t group, size_t *len_out)320{321const groupdef *gdef;322323*len_out = 0;324gdef = find_gdef(group);325if (gdef == NULL)326return EINVAL;327*len_out = gdef->reg->mult_len;328return 0;329}330331krb5_error_code332group_keygen(krb5_context context, groupstate *gstate, int32_t group,333const krb5_data *wbytes, krb5_data *priv_out, krb5_data *pub_out)334{335krb5_error_code ret;336const groupdef *gdef;337groupdata *gdata;338uint8_t *priv = NULL, *pub = NULL;339340*priv_out = empty_data();341*pub_out = empty_data();342gdef = find_gdef(group);343if (gdef == NULL || wbytes->length != gdef->reg->mult_len)344return EINVAL;345ret = get_gdata(context, gstate, gdef, &gdata);346if (ret)347return ret;348349priv = k5alloc(gdef->reg->mult_len, &ret);350if (priv == NULL)351goto cleanup;352pub = k5alloc(gdef->reg->elem_len, &ret);353if (pub == NULL)354goto cleanup;355356ret = gdef->keygen(context, gdata, (uint8_t *)wbytes->data, gstate->is_kdc,357priv, pub);358if (ret)359goto cleanup;360361*priv_out = make_data(priv, gdef->reg->mult_len);362*pub_out = make_data(pub, gdef->reg->elem_len);363priv = pub = NULL;364TRACE_SPAKE_KEYGEN(context, pub_out);365366cleanup:367zapfree(priv, gdef->reg->mult_len);368free(pub);369return ret;370}371372krb5_error_code373group_result(krb5_context context, groupstate *gstate, int32_t group,374const krb5_data *wbytes, const krb5_data *ourpriv,375const krb5_data *theirpub, krb5_data *spakeresult_out)376{377krb5_error_code ret;378const groupdef *gdef;379groupdata *gdata;380uint8_t *spakeresult = NULL;381382*spakeresult_out = empty_data();383gdef = find_gdef(group);384if (gdef == NULL || wbytes->length != gdef->reg->mult_len)385return EINVAL;386if (ourpriv->length != gdef->reg->mult_len ||387theirpub->length != gdef->reg->elem_len)388return EINVAL;389ret = get_gdata(context, gstate, gdef, &gdata);390if (ret)391return ret;392393spakeresult = k5alloc(gdef->reg->elem_len, &ret);394if (spakeresult == NULL)395goto cleanup;396397/* Invert is_kdc here to use the other party's constant. */398ret = gdef->result(context, gdata, (uint8_t *)wbytes->data,399(uint8_t *)ourpriv->data, (uint8_t *)theirpub->data,400!gstate->is_kdc, spakeresult);401if (ret)402goto cleanup;403404*spakeresult_out = make_data(spakeresult, gdef->reg->elem_len);405spakeresult = NULL;406TRACE_SPAKE_RESULT(context, spakeresult_out);407408cleanup:409zapfree(spakeresult, gdef->reg->elem_len);410return ret;411}412413krb5_error_code414group_hash_len(int32_t group, size_t *len_out)415{416const groupdef *gdef;417418*len_out = 0;419gdef = find_gdef(group);420if (gdef == NULL)421return EINVAL;422*len_out = gdef->reg->hash_len;423return 0;424}425426krb5_error_code427group_hash(krb5_context context, groupstate *gstate, int32_t group,428const krb5_data *dlist, size_t ndata, uint8_t *result_out)429{430krb5_error_code ret;431const groupdef *gdef;432groupdata *gdata;433434gdef = find_gdef(group);435if (gdef == NULL)436return EINVAL;437ret = get_gdata(context, gstate, gdef, &gdata);438if (ret)439return ret;440return gdef->hash(context, gdata, dlist, ndata, result_out);441}442443444