1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* plugins/preauth/otp/main.c - OTP kdcpreauth module definition */ 3 /* 4 * Copyright 2011 NORDUnet A/S. All rights reserved. 5 * Copyright 2013 Red Hat, Inc. 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 are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 19 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 22 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "k5-int.h" 32 #include "k5-json.h" 33 #include <krb5/preauth_plugin.h> 34 #include "otp_state.h" 35 36 #include <errno.h> 37 #include <ctype.h> 38 39 static krb5_preauthtype otp_pa_type_list[] = 40 { KRB5_PADATA_OTP_REQUEST, 0 }; 41 42 struct request_state { 43 krb5_context context; 44 krb5_kdcpreauth_verify_respond_fn respond; 45 void *arg; 46 krb5_enc_tkt_part *enc_tkt_reply; 47 krb5_kdcpreauth_callbacks preauth_cb; 48 krb5_kdcpreauth_rock rock; 49 }; 50 51 static krb5_error_code 52 decrypt_encdata(krb5_context context, krb5_keyblock *armor_key, 53 krb5_pa_otp_req *req, krb5_data *out) 54 { 55 krb5_error_code retval; 56 krb5_data plaintext; 57 58 if (req == NULL) 59 return EINVAL; 60 61 retval = alloc_data(&plaintext, req->enc_data.ciphertext.length); 62 if (retval) 63 return retval; 64 65 retval = krb5_c_decrypt(context, armor_key, KRB5_KEYUSAGE_PA_OTP_REQUEST, 66 NULL, &req->enc_data, &plaintext); 67 if (retval != 0) { 68 com_err("otp", retval, "Unable to decrypt encData in PA-OTP-REQUEST"); 69 free(plaintext.data); 70 return retval; 71 } 72 73 *out = plaintext; 74 return 0; 75 } 76 77 static krb5_error_code 78 nonce_verify(krb5_context ctx, krb5_keyblock *armor_key, 79 const krb5_data *nonce) 80 { 81 krb5_error_code retval; 82 krb5_timestamp ts; 83 krb5_data *er = NULL; 84 85 if (armor_key == NULL || nonce->data == NULL) { 86 retval = EINVAL; 87 goto out; 88 } 89 90 /* Decode the PA-OTP-ENC-REQUEST structure. */ 91 retval = decode_krb5_pa_otp_enc_req(nonce, &er); 92 if (retval != 0) 93 goto out; 94 95 /* Make sure the nonce is exactly the same size as the one generated. */ 96 if (er->length != armor_key->length + sizeof(krb5_timestamp)) 97 goto out; 98 99 /* Check to make sure the timestamp at the beginning is still valid. */ 100 ts = load_32_be(er->data); 101 retval = krb5_check_clockskew(ctx, ts); 102 103 out: 104 krb5_free_data(ctx, er); 105 return retval; 106 } 107 108 static krb5_error_code 109 timestamp_verify(krb5_context ctx, const krb5_data *nonce) 110 { 111 krb5_error_code retval = EINVAL; 112 krb5_pa_enc_ts *et = NULL; 113 114 if (nonce->data == NULL) 115 goto out; 116 117 /* Decode the PA-ENC-TS-ENC structure. */ 118 retval = decode_krb5_pa_enc_ts(nonce, &et); 119 if (retval != 0) 120 goto out; 121 122 /* Check the clockskew. */ 123 retval = krb5_check_clockskew(ctx, et->patimestamp); 124 125 out: 126 krb5_free_pa_enc_ts(ctx, et); 127 return retval; 128 } 129 130 static krb5_error_code 131 nonce_generate(krb5_context ctx, unsigned int length, krb5_data *nonce_out) 132 { 133 krb5_data nonce; 134 krb5_error_code retval; 135 krb5_timestamp now; 136 137 retval = krb5_timeofday(ctx, &now); 138 if (retval != 0) 139 return retval; 140 141 retval = alloc_data(&nonce, sizeof(now) + length); 142 if (retval != 0) 143 return retval; 144 145 retval = krb5_c_random_make_octets(ctx, &nonce); 146 if (retval != 0) { 147 free(nonce.data); 148 return retval; 149 } 150 151 store_32_be(now, nonce.data); 152 *nonce_out = nonce; 153 return 0; 154 } 155 156 static void 157 on_response(void *data, krb5_error_code retval, otp_response response, 158 char *const *indicators) 159 { 160 struct request_state rs = *(struct request_state *)data; 161 krb5_context context = rs.context; 162 krb5_keyblock *armor_key; 163 char *const *ind; 164 165 free(data); 166 167 if (retval == 0 && response != otp_response_success) 168 retval = KRB5_PREAUTH_FAILED; 169 if (retval) 170 goto done; 171 172 rs.enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; 173 armor_key = rs.preauth_cb->fast_armor(context, rs.rock); 174 if (armor_key == NULL) { 175 retval = ENOENT; 176 goto done; 177 } 178 179 retval = rs.preauth_cb->replace_reply_key(context, rs.rock, armor_key, 180 FALSE); 181 if (retval) 182 goto done; 183 184 for (ind = indicators; ind != NULL && *ind != NULL; ind++) { 185 retval = rs.preauth_cb->add_auth_indicator(context, rs.rock, *ind); 186 if (retval) 187 goto done; 188 } 189 190 done: 191 rs.respond(rs.arg, retval, NULL, NULL, NULL); 192 } 193 194 static krb5_error_code 195 otp_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out, 196 const char **realmnames) 197 { 198 krb5_error_code retval; 199 otp_state *state; 200 201 retval = otp_state_new(context, &state); 202 if (retval) 203 return retval; 204 *moddata_out = (krb5_kdcpreauth_moddata)state; 205 return 0; 206 } 207 208 static void 209 otp_fini(krb5_context context, krb5_kdcpreauth_moddata moddata) 210 { 211 otp_state_free((otp_state *)moddata); 212 } 213 214 static int 215 otp_flags(krb5_context context, krb5_preauthtype pa_type) 216 { 217 return PA_REPLACES_KEY; 218 } 219 220 static void 221 otp_edata(krb5_context context, krb5_kdc_req *request, 222 krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock, 223 krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type, 224 krb5_kdcpreauth_edata_respond_fn respond, void *arg) 225 { 226 krb5_otp_tokeninfo ti, *tis[2] = { &ti, NULL }; 227 krb5_keyblock *armor_key = NULL; 228 krb5_pa_otp_challenge chl; 229 krb5_pa_data *pa = NULL; 230 krb5_error_code retval; 231 krb5_data *encoding, nonce = empty_data(); 232 char *config; 233 234 /* Determine if otp is enabled for the user. */ 235 retval = cb->get_string(context, rock, "otp", &config); 236 if (retval == 0 && config == NULL) 237 retval = ENOENT; 238 if (retval != 0) 239 goto out; 240 cb->free_string(context, rock, config); 241 242 /* Get the armor key. This indicates the length of random data to use in 243 * the nonce. */ 244 armor_key = cb->fast_armor(context, rock); 245 if (armor_key == NULL) { 246 retval = ENOENT; 247 goto out; 248 } 249 250 /* Build the (mostly empty) challenge. */ 251 memset(&ti, 0, sizeof(ti)); 252 memset(&chl, 0, sizeof(chl)); 253 chl.tokeninfo = tis; 254 ti.format = -1; 255 ti.length = -1; 256 ti.iteration_count = -1; 257 258 /* Generate the nonce. */ 259 retval = nonce_generate(context, armor_key->length, &nonce); 260 if (retval != 0) 261 goto out; 262 chl.nonce = nonce; 263 264 /* Build the output pa-data. */ 265 retval = encode_krb5_pa_otp_challenge(&chl, &encoding); 266 if (retval != 0) 267 goto out; 268 pa = k5alloc(sizeof(krb5_pa_data), &retval); 269 if (pa == NULL) { 270 krb5_free_data(context, encoding); 271 goto out; 272 } 273 pa->pa_type = KRB5_PADATA_OTP_CHALLENGE; 274 pa->contents = (krb5_octet *)encoding->data; 275 pa->length = encoding->length; 276 free(encoding); 277 278 out: 279 krb5_free_data_contents(context, &nonce); 280 (*respond)(arg, retval, pa); 281 } 282 283 static void 284 otp_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request, 285 krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa, 286 krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock, 287 krb5_kdcpreauth_moddata moddata, 288 krb5_kdcpreauth_verify_respond_fn respond, void *arg) 289 { 290 krb5_keyblock *armor_key = NULL; 291 krb5_pa_otp_req *req = NULL; 292 struct request_state *rs; 293 krb5_error_code retval; 294 krb5_data d, plaintext; 295 char *config; 296 297 /* Get the FAST armor key. */ 298 armor_key = cb->fast_armor(context, rock); 299 if (armor_key == NULL) { 300 retval = KRB5KDC_ERR_PREAUTH_FAILED; 301 com_err("otp", retval, "No armor key found when verifying padata"); 302 goto error; 303 } 304 305 /* Decode the request. */ 306 d = make_data(pa->contents, pa->length); 307 retval = decode_krb5_pa_otp_req(&d, &req); 308 if (retval != 0) { 309 com_err("otp", retval, "Unable to decode OTP request"); 310 goto error; 311 } 312 313 /* Decrypt the nonce from the request. */ 314 retval = decrypt_encdata(context, armor_key, req, &plaintext); 315 if (retval != 0) { 316 com_err("otp", retval, "Unable to decrypt nonce"); 317 goto error; 318 } 319 320 /* Verify the nonce or timestamp. */ 321 retval = nonce_verify(context, armor_key, &plaintext); 322 if (retval != 0) 323 retval = timestamp_verify(context, &plaintext); 324 krb5_free_data_contents(context, &plaintext); 325 if (retval != 0) { 326 com_err("otp", retval, "Unable to verify nonce or timestamp"); 327 goto error; 328 } 329 330 /* Create the request state. Save the response callback, and the 331 * enc_tkt_reply pointer so we can set the TKT_FLG_PRE_AUTH flag later. */ 332 rs = k5alloc(sizeof(struct request_state), &retval); 333 if (rs == NULL) 334 goto error; 335 rs->context = context; 336 rs->arg = arg; 337 rs->respond = respond; 338 rs->enc_tkt_reply = enc_tkt_reply; 339 rs->preauth_cb = cb; 340 rs->rock = rock; 341 342 /* Get the principal's OTP configuration string. */ 343 retval = cb->get_string(context, rock, "otp", &config); 344 if (retval == 0 && config == NULL) 345 retval = KRB5_PREAUTH_FAILED; 346 if (retval != 0) { 347 free(rs); 348 goto error; 349 } 350 351 /* Send the request. */ 352 otp_state_verify((otp_state *)moddata, cb->event_context(context, rock), 353 cb->client_name(context, rock), config, req, on_response, 354 rs); 355 cb->free_string(context, rock, config); 356 357 k5_free_pa_otp_req(context, req); 358 return; 359 360 error: 361 k5_free_pa_otp_req(context, req); 362 (*respond)(arg, retval, NULL, NULL, NULL); 363 } 364 365 krb5_error_code 366 kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver, 367 krb5_plugin_vtable vtable); 368 369 krb5_error_code 370 kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver, 371 krb5_plugin_vtable vtable) 372 { 373 krb5_kdcpreauth_vtable vt; 374 375 if (maj_ver != 1) 376 return KRB5_PLUGIN_VER_NOTSUPP; 377 378 vt = (krb5_kdcpreauth_vtable)vtable; 379 vt->name = "otp"; 380 vt->pa_type_list = otp_pa_type_list; 381 vt->init = otp_init; 382 vt->fini = otp_fini; 383 vt->flags = otp_flags; 384 vt->edata = otp_edata; 385 vt->verify = otp_verify; 386 387 com_err("otp", 0, "Loaded"); 388 389 return 0; 390 } 391