1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* plugins/preauth/spake/spake_client.c - SPAKE clpreauth module */ 3 /* 4 * Copyright (C) 2015 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 * OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include "k5-int.h" 34 #include "k5-spake.h" 35 #include "trace.h" 36 #include "util.h" 37 #include "iana.h" 38 #include "groups.h" 39 #include <krb5/clpreauth_plugin.h> 40 41 typedef struct reqstate_st { 42 krb5_pa_spake *msg; /* set in prep_questions, used in process */ 43 krb5_keyblock *initial_key; 44 krb5_data *support; 45 krb5_data thash; 46 krb5_data spakeresult; 47 } reqstate; 48 49 /* Return true if SF-NONE is present in factors. */ 50 static krb5_boolean 51 contains_sf_none(krb5_spake_factor **factors) 52 { 53 int i; 54 55 for (i = 0; factors != NULL && factors[i] != NULL; i++) { 56 if (factors[i]->type == SPAKE_SF_NONE) 57 return TRUE; 58 } 59 return FALSE; 60 } 61 62 static krb5_error_code 63 spake_init(krb5_context context, krb5_clpreauth_moddata *moddata_out) 64 { 65 krb5_error_code ret; 66 groupstate *gstate; 67 68 ret = group_init_state(context, FALSE, &gstate); 69 if (ret) 70 return ret; 71 *moddata_out = (krb5_clpreauth_moddata)gstate; 72 return 0; 73 } 74 75 static void 76 spake_fini(krb5_context context, krb5_clpreauth_moddata moddata) 77 { 78 group_free_state((groupstate *)moddata); 79 } 80 81 static void 82 spake_request_init(krb5_context context, krb5_clpreauth_moddata moddata, 83 krb5_clpreauth_modreq *modreq_out) 84 { 85 *modreq_out = calloc(1, sizeof(reqstate)); 86 } 87 88 static void 89 spake_request_fini(krb5_context context, krb5_clpreauth_moddata moddata, 90 krb5_clpreauth_modreq modreq) 91 { 92 reqstate *st = (reqstate *)modreq; 93 94 k5_free_pa_spake(context, st->msg); 95 krb5_free_keyblock(context, st->initial_key); 96 krb5_free_data(context, st->support); 97 krb5_free_data_contents(context, &st->thash); 98 zapfree(st->spakeresult.data, st->spakeresult.length); 99 free(st); 100 } 101 102 static krb5_error_code 103 spake_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata, 104 krb5_clpreauth_modreq modreq, 105 krb5_get_init_creds_opt *opt, krb5_clpreauth_callbacks cb, 106 krb5_clpreauth_rock rock, krb5_kdc_req *req, 107 krb5_data *enc_req, krb5_data *enc_prev_req, 108 krb5_pa_data *pa_data) 109 { 110 krb5_error_code ret; 111 groupstate *gstate = (groupstate *)moddata; 112 reqstate *st = (reqstate *)modreq; 113 krb5_data in_data; 114 krb5_spake_challenge *ch; 115 116 if (st == NULL) 117 return ENOMEM; 118 119 /* We don't need to ask any questions to send a support message. */ 120 if (pa_data->length == 0) 121 return 0; 122 123 /* Decode the incoming message, replacing any previous one in the request 124 * state. If we can't decode it, we have no questions to ask. */ 125 k5_free_pa_spake(context, st->msg); 126 st->msg = NULL; 127 in_data = make_data(pa_data->contents, pa_data->length); 128 ret = decode_krb5_pa_spake(&in_data, &st->msg); 129 if (ret) 130 return (ret == ENOMEM) ? ENOMEM : 0; 131 132 if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) { 133 ch = &st->msg->u.challenge; 134 if (!group_is_permitted(gstate, ch->group)) 135 return 0; 136 /* When second factor support is implemented, we should ask questions 137 * based on the factors in the challenge. */ 138 if (!contains_sf_none(ch->factors)) 139 return 0; 140 /* We will need the AS key to respond to the challenge. */ 141 cb->need_as_key(context, rock); 142 } else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) { 143 /* When second factor support is implemented, we should decrypt the 144 * encdata message and ask questions based on the factor data. */ 145 } 146 return 0; 147 } 148 149 /* 150 * Output a PA-SPAKE support message indicating which groups we support. This 151 * may be done for optimistic preauth, in response to an empty message, or in 152 * response to a challenge using a group we do not support. Save the support 153 * message in st->support. 154 */ 155 static krb5_error_code 156 send_support(krb5_context context, groupstate *gstate, reqstate *st, 157 krb5_pa_data ***pa_out) 158 { 159 krb5_error_code ret; 160 krb5_data *support; 161 krb5_pa_spake msg; 162 163 msg.choice = SPAKE_MSGTYPE_SUPPORT; 164 group_get_permitted(gstate, &msg.u.support.groups, &msg.u.support.ngroups); 165 ret = encode_krb5_pa_spake(&msg, &support); 166 if (ret) 167 return ret; 168 169 /* Save the support message for later use in the transcript hash. */ 170 ret = krb5_copy_data(context, support, &st->support); 171 if (ret) { 172 krb5_free_data(context, support); 173 return ret; 174 } 175 176 TRACE_SPAKE_SEND_SUPPORT(context); 177 return convert_to_padata(support, pa_out); 178 } 179 180 static krb5_error_code 181 process_challenge(krb5_context context, groupstate *gstate, reqstate *st, 182 krb5_spake_challenge *ch, const krb5_data *der_msg, 183 krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock, 184 krb5_prompter_fct prompter, void *prompter_data, 185 const krb5_data *der_req, krb5_pa_data ***pa_out) 186 { 187 krb5_error_code ret; 188 krb5_keyblock *k0 = NULL, *k1 = NULL, *as_key; 189 krb5_spake_factor factor; 190 krb5_pa_spake msg; 191 krb5_data *der_factor = NULL, *response; 192 krb5_data clpriv = empty_data(), clpub = empty_data(); 193 krb5_data wbytes = empty_data(); 194 krb5_enc_data enc_factor; 195 196 enc_factor.ciphertext = empty_data(); 197 198 /* Not expected if we processed a challenge and didn't reject it. */ 199 if (st->initial_key != NULL) 200 return KRB5KDC_ERR_PREAUTH_FAILED; 201 202 if (!group_is_permitted(gstate, ch->group)) { 203 TRACE_SPAKE_REJECT_CHALLENGE(context, ch->group); 204 /* No point in sending a second support message. */ 205 if (st->support != NULL) 206 return KRB5KDC_ERR_PREAUTH_FAILED; 207 return send_support(context, gstate, st, pa_out); 208 } 209 210 /* Initialize and update the transcript with the concatenation of the 211 * support message (if we sent one) and the received challenge. */ 212 ret = update_thash(context, gstate, ch->group, &st->thash, st->support, 213 der_msg); 214 if (ret) 215 return ret; 216 217 TRACE_SPAKE_RECEIVE_CHALLENGE(context, ch->group, &ch->pubkey); 218 219 /* When second factor support is implemented, we should check for a 220 * supported factor type instead of just checking for SF-NONE. */ 221 if (!contains_sf_none(ch->factors)) 222 return KRB5KDC_ERR_PREAUTH_FAILED; 223 224 ret = cb->get_as_key(context, rock, &as_key); 225 if (ret) 226 goto cleanup; 227 ret = krb5_copy_keyblock(context, as_key, &st->initial_key); 228 if (ret) 229 goto cleanup; 230 ret = derive_wbytes(context, ch->group, st->initial_key, &wbytes); 231 if (ret) 232 goto cleanup; 233 ret = group_keygen(context, gstate, ch->group, &wbytes, &clpriv, &clpub); 234 if (ret) 235 goto cleanup; 236 ret = group_result(context, gstate, ch->group, &wbytes, &clpriv, 237 &ch->pubkey, &st->spakeresult); 238 if (ret) 239 goto cleanup; 240 241 ret = update_thash(context, gstate, ch->group, &st->thash, &clpub, NULL); 242 if (ret) 243 goto cleanup; 244 TRACE_SPAKE_CLIENT_THASH(context, &st->thash); 245 246 /* Replace the reply key with K'[0]. */ 247 ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes, 248 &st->spakeresult, &st->thash, der_req, 0, &k0); 249 if (ret) 250 goto cleanup; 251 ret = cb->set_as_key(context, rock, k0); 252 if (ret) 253 goto cleanup; 254 255 /* Encrypt a SPAKESecondFactor message with K'[1]. */ 256 ret = derive_key(context, gstate, ch->group, st->initial_key, &wbytes, 257 &st->spakeresult, &st->thash, der_req, 1, &k1); 258 if (ret) 259 goto cleanup; 260 /* When second factor support is implemented, we should construct an 261 * appropriate factor here instead of hardcoding SF-NONE. */ 262 factor.type = SPAKE_SF_NONE; 263 factor.data = NULL; 264 ret = encode_krb5_spake_factor(&factor, &der_factor); 265 if (ret) 266 goto cleanup; 267 ret = krb5_encrypt_helper(context, k1, KRB5_KEYUSAGE_SPAKE, der_factor, 268 &enc_factor); 269 if (ret) 270 goto cleanup; 271 272 /* Encode and output a response message. */ 273 msg.choice = SPAKE_MSGTYPE_RESPONSE; 274 msg.u.response.pubkey = clpub; 275 msg.u.response.factor = enc_factor; 276 ret = encode_krb5_pa_spake(&msg, &response); 277 if (ret) 278 goto cleanup; 279 TRACE_SPAKE_SEND_RESPONSE(context); 280 ret = convert_to_padata(response, pa_out); 281 if (ret) 282 goto cleanup; 283 284 cb->disable_fallback(context, rock); 285 286 cleanup: 287 krb5_free_keyblock(context, k0); 288 krb5_free_keyblock(context, k1); 289 krb5_free_data_contents(context, &enc_factor.ciphertext); 290 krb5_free_data_contents(context, &clpub); 291 zapfree(clpriv.data, clpriv.length); 292 zapfree(wbytes.data, wbytes.length); 293 if (der_factor != NULL) { 294 zapfree(der_factor->data, der_factor->length); 295 free(der_factor); 296 } 297 return ret; 298 } 299 300 static krb5_error_code 301 process_encdata(krb5_context context, reqstate *st, krb5_enc_data *enc, 302 krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock, 303 krb5_prompter_fct prompter, void *prompter_data, 304 const krb5_data *der_prev_req, const krb5_data *der_req, 305 krb5_pa_data ***pa_out) 306 { 307 /* Not expected if we haven't sent a response yet. */ 308 if (st->initial_key == NULL || st->spakeresult.length == 0) 309 return KRB5KDC_ERR_PREAUTH_FAILED; 310 311 /* 312 * When second factor support is implemented, we should process encdata 313 * messages according to the factor type. We should make sure to re-derive 314 * K'[0] and replace the reply key again, in case the request has changed. 315 * We should use der_prev_req to derive K'[n] to decrypt factor from the 316 * KDC. We should use der_req to derive K'[n+1] for the next message to 317 * send to the KDC. 318 */ 319 return KRB5_PLUGIN_OP_NOTSUPP; 320 } 321 322 static krb5_error_code 323 spake_process(krb5_context context, krb5_clpreauth_moddata moddata, 324 krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt, 325 krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock, 326 krb5_kdc_req *req, krb5_data *der_req, krb5_data *der_prev_req, 327 krb5_pa_data *pa_in, krb5_prompter_fct prompter, 328 void *prompter_data, krb5_pa_data ***pa_out) 329 { 330 krb5_error_code ret; 331 groupstate *gstate = (groupstate *)moddata; 332 reqstate *st = (reqstate *)modreq; 333 krb5_data in_data; 334 335 if (st == NULL) 336 return ENOMEM; 337 338 if (pa_in->length == 0) { 339 /* Not expected if we already sent a support message. */ 340 if (st->support != NULL) 341 return KRB5KDC_ERR_PREAUTH_FAILED; 342 return send_support(context, gstate, st, pa_out); 343 } 344 345 if (st->msg == NULL) { 346 /* The message failed to decode in spake_prep_questions(). */ 347 ret = KRB5KDC_ERR_PREAUTH_FAILED; 348 } else if (st->msg->choice == SPAKE_MSGTYPE_CHALLENGE) { 349 in_data = make_data(pa_in->contents, pa_in->length); 350 ret = process_challenge(context, gstate, st, &st->msg->u.challenge, 351 &in_data, cb, rock, prompter, prompter_data, 352 der_req, pa_out); 353 } else if (st->msg->choice == SPAKE_MSGTYPE_ENCDATA) { 354 ret = process_encdata(context, st, &st->msg->u.encdata, cb, rock, 355 prompter, prompter_data, der_prev_req, der_req, 356 pa_out); 357 } else { 358 /* Unexpected message type */ 359 ret = KRB5KDC_ERR_PREAUTH_FAILED; 360 } 361 362 return ret; 363 } 364 365 krb5_error_code 366 clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver, 367 krb5_plugin_vtable vtable); 368 369 krb5_error_code 370 clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver, 371 krb5_plugin_vtable vtable) 372 { 373 krb5_clpreauth_vtable vt; 374 static krb5_preauthtype pa_types[] = { KRB5_PADATA_SPAKE, 0 }; 375 376 if (maj_ver != 1) 377 return KRB5_PLUGIN_VER_NOTSUPP; 378 vt = (krb5_clpreauth_vtable)vtable; 379 vt->name = "spake"; 380 vt->pa_type_list = pa_types; 381 vt->init = spake_init; 382 vt->fini = spake_fini; 383 vt->request_init = spake_request_init; 384 vt->request_fini = spake_request_fini; 385 vt->process = spake_process; 386 vt->prep_questions = spake_prep_questions; 387 return 0; 388 } 389