1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* plugins/preauth/spake/util.c - Utility functions for SPAKE preauth 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 "trace.h" 35 #include "util.h" 36 #include "groups.h" 37 38 /* Use data to construct a single-element pa-data list of type 39 * KRB5_PADATA_SPAKE. Claim data's memory on success or failure. */ 40 krb5_error_code 41 convert_to_padata(krb5_data *data, krb5_pa_data ***pa_out) 42 { 43 krb5_pa_data *pa = NULL, **list = NULL; 44 45 list = calloc(2, sizeof(*list)); 46 if (list == NULL) 47 goto fail; 48 pa = calloc(1, sizeof(*pa)); 49 if (pa == NULL) 50 goto fail; 51 pa->magic = KV5M_PA_DATA; 52 pa->pa_type = KRB5_PADATA_SPAKE; 53 pa->length = data->length; 54 pa->contents = (uint8_t *)data->data; 55 list[0] = pa; 56 list[1] = NULL; 57 *pa_out = list; 58 free(data); 59 return 0; 60 61 fail: 62 free(list); 63 free(pa); 64 free(data->data); 65 free(data); 66 return ENOMEM; 67 } 68 69 /* 70 * Update the transcript hash thash with its current value and the 71 * concatenation of data1 and data2, using the hash function for group. Either 72 * data1 or data2 may be NULL to omit it. Allocate thash if it is empty. 73 */ 74 krb5_error_code 75 update_thash(krb5_context context, groupstate *gstate, int32_t group, 76 krb5_data *thash, const krb5_data *data1, const krb5_data *data2) 77 { 78 krb5_error_code ret; 79 size_t hashlen; 80 krb5_data dlist[3]; 81 const krb5_data empty = empty_data(); 82 83 if (thash->length == 0) { 84 /* Initialize the transcript hash to all zeros. */ 85 ret = group_hash_len(group, &hashlen); 86 if (ret) 87 return ret; 88 ret = alloc_data(thash, hashlen); 89 if (ret) 90 return ret; 91 } 92 93 /* Set up the data array and hash it with the group's hash function. */ 94 dlist[0] = *thash; 95 dlist[1] = (data1 != NULL) ? *data1 : empty; 96 dlist[2] = (data2 != NULL) ? *data2 : empty; 97 return group_hash(context, gstate, group, dlist, 3, 98 (uint8_t *)thash->data); 99 } 100 101 /* Derive a byte vector for the SPAKE w multiplier input from ikey. Place 102 * result in allocated storage in *wbytes_out. */ 103 krb5_error_code 104 derive_wbytes(krb5_context context, int32_t group, const krb5_keyblock *ikey, 105 krb5_data *wbytes_out) 106 { 107 krb5_error_code ret; 108 const char prefix[] = "SPAKEsecret"; 109 size_t mult_len, prefix_len = sizeof(prefix) - 1; 110 krb5_data prf_input = empty_data(), wbytes = empty_data(); 111 112 *wbytes_out = empty_data(); 113 114 /* Allocate space for a multiplier. */ 115 ret = group_mult_len(group, &mult_len); 116 if (ret) 117 goto cleanup; 118 ret = alloc_data(&wbytes, mult_len); 119 if (ret) 120 goto cleanup; 121 122 /* Compose the PRF input string. */ 123 ret = alloc_data(&prf_input, prefix_len + 4); 124 if (ret) 125 goto cleanup; 126 memcpy(prf_input.data, prefix, prefix_len); 127 store_32_be(group, prf_input.data + prefix_len); 128 129 /* Derive the SPAKE input from the initial reply key with PRF+. */ 130 ret = krb5_c_prfplus(context, ikey, &prf_input, &wbytes); 131 if (ret) 132 goto cleanup; 133 134 *wbytes_out = wbytes; 135 wbytes = empty_data(); 136 137 cleanup: 138 free(prf_input.data); 139 zapfree(wbytes.data, wbytes.length); 140 return ret; 141 } 142 143 /* 144 * Derive K'[n] from the group number, the initial key enctype, the initial 145 * multiplier, the SPAKE result, the transcript hash, and the encoded 146 * KDC-REQ-BODY. Place the result in allocated storage in *out. 147 */ 148 krb5_error_code 149 derive_key(krb5_context context, groupstate *gstate, int32_t group, 150 const krb5_keyblock *ikey, const krb5_data *wbytes, 151 const krb5_data *spakeresult, const krb5_data *thash, 152 const krb5_data *der_req, uint32_t n, krb5_keyblock **out) 153 { 154 krb5_error_code ret; 155 krb5_data dlist[9], seed = empty_data(), d; 156 uint8_t groupnbuf[4], etypenbuf[4], nbuf[4], bcount; 157 size_t hashlen, seedlen, keylen, nblocks, i; 158 size_t ndata = sizeof(dlist) / sizeof(*dlist); 159 krb5_keyblock *hkey = NULL; 160 161 *out = NULL; 162 163 store_32_be(group, groupnbuf); 164 store_32_be(n, nbuf); 165 store_32_be(ikey->enctype, etypenbuf); 166 dlist[0] = string2data("SPAKEkey"); 167 dlist[1] = make_data(groupnbuf, sizeof(groupnbuf)); 168 dlist[2] = make_data(etypenbuf, sizeof(etypenbuf)); 169 dlist[3] = *wbytes; 170 dlist[4] = *spakeresult; 171 dlist[5] = *thash; 172 dlist[6] = *der_req; 173 dlist[7] = make_data(nbuf, sizeof(nbuf)); 174 dlist[8] = make_data(&bcount, 1); 175 176 /* Count the number of hash blocks required (should be 1 for all current 177 * scenarios) and allocate space. */ 178 ret = group_hash_len(group, &hashlen); 179 if (ret) 180 goto cleanup; 181 ret = krb5_c_keylengths(context, ikey->enctype, &seedlen, &keylen); 182 if (ret) 183 goto cleanup; 184 nblocks = (seedlen + hashlen - 1) / hashlen; 185 ret = alloc_data(&seed, nblocks * hashlen); 186 if (ret) 187 goto cleanup; 188 189 /* Compute and concatenate hash blocks to fill the seed buffer. */ 190 for (i = 0; i < nblocks; i++) { 191 bcount = i + 1; 192 ret = group_hash(context, gstate, group, dlist, ndata, 193 (uint8_t *)seed.data + i * hashlen); 194 if (ret) 195 goto cleanup; 196 } 197 198 ret = krb5_init_keyblock(context, ikey->enctype, keylen, &hkey); 199 if (ret) 200 goto cleanup; 201 d = make_data(seed.data, seedlen); 202 ret = krb5_c_random_to_key(context, ikey->enctype, &d, hkey); 203 if (ret) 204 goto cleanup; 205 206 ret = krb5_c_fx_cf2_simple(context, ikey, "SPAKE", hkey, "keyderiv", out); 207 208 cleanup: 209 zapfree(seed.data, seed.length); 210 krb5_free_keyblock(context, hkey); 211 return ret; 212 } 213