Path: blob/main/crypto/krb5/src/plugins/preauth/otp/main.c
34907 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* plugins/preauth/otp/main.c - OTP kdcpreauth module definition */2/*3* Copyright 2011 NORDUnet A/S. All rights reserved.4* Copyright 2013 Red Hat, Inc. All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions are met:8*9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11*12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in14* the documentation and/or other materials provided with the15* distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS18* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED19* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A20* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER21* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,22* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,23* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR24* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF25* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING26* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS27* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.28*/2930#include "k5-int.h"31#include "k5-json.h"32#include <krb5/preauth_plugin.h>33#include "otp_state.h"3435#include <errno.h>36#include <ctype.h>3738static krb5_preauthtype otp_pa_type_list[] =39{ KRB5_PADATA_OTP_REQUEST, 0 };4041struct request_state {42krb5_context context;43krb5_kdcpreauth_verify_respond_fn respond;44void *arg;45krb5_enc_tkt_part *enc_tkt_reply;46krb5_kdcpreauth_callbacks preauth_cb;47krb5_kdcpreauth_rock rock;48};4950static krb5_error_code51decrypt_encdata(krb5_context context, krb5_keyblock *armor_key,52krb5_pa_otp_req *req, krb5_data *out)53{54krb5_error_code retval;55krb5_data plaintext;5657if (req == NULL)58return EINVAL;5960retval = alloc_data(&plaintext, req->enc_data.ciphertext.length);61if (retval)62return retval;6364retval = krb5_c_decrypt(context, armor_key, KRB5_KEYUSAGE_PA_OTP_REQUEST,65NULL, &req->enc_data, &plaintext);66if (retval != 0) {67com_err("otp", retval, "Unable to decrypt encData in PA-OTP-REQUEST");68free(plaintext.data);69return retval;70}7172*out = plaintext;73return 0;74}7576static krb5_error_code77nonce_verify(krb5_context ctx, krb5_keyblock *armor_key,78const krb5_data *nonce)79{80krb5_error_code retval;81krb5_timestamp ts;82krb5_data *er = NULL;8384if (armor_key == NULL || nonce->data == NULL) {85retval = EINVAL;86goto out;87}8889/* Decode the PA-OTP-ENC-REQUEST structure. */90retval = decode_krb5_pa_otp_enc_req(nonce, &er);91if (retval != 0)92goto out;9394/* Make sure the nonce is exactly the same size as the one generated. */95if (er->length != armor_key->length + sizeof(krb5_timestamp))96goto out;9798/* Check to make sure the timestamp at the beginning is still valid. */99ts = load_32_be(er->data);100retval = krb5_check_clockskew(ctx, ts);101102out:103krb5_free_data(ctx, er);104return retval;105}106107static krb5_error_code108timestamp_verify(krb5_context ctx, const krb5_data *nonce)109{110krb5_error_code retval = EINVAL;111krb5_pa_enc_ts *et = NULL;112113if (nonce->data == NULL)114goto out;115116/* Decode the PA-ENC-TS-ENC structure. */117retval = decode_krb5_pa_enc_ts(nonce, &et);118if (retval != 0)119goto out;120121/* Check the clockskew. */122retval = krb5_check_clockskew(ctx, et->patimestamp);123124out:125krb5_free_pa_enc_ts(ctx, et);126return retval;127}128129static krb5_error_code130nonce_generate(krb5_context ctx, unsigned int length, krb5_data *nonce_out)131{132krb5_data nonce;133krb5_error_code retval;134krb5_timestamp now;135136retval = krb5_timeofday(ctx, &now);137if (retval != 0)138return retval;139140retval = alloc_data(&nonce, sizeof(now) + length);141if (retval != 0)142return retval;143144retval = krb5_c_random_make_octets(ctx, &nonce);145if (retval != 0) {146free(nonce.data);147return retval;148}149150store_32_be(now, nonce.data);151*nonce_out = nonce;152return 0;153}154155static void156on_response(void *data, krb5_error_code retval, otp_response response,157char *const *indicators)158{159struct request_state rs = *(struct request_state *)data;160krb5_context context = rs.context;161krb5_keyblock *armor_key;162char *const *ind;163164free(data);165166if (retval == 0 && response != otp_response_success)167retval = KRB5_PREAUTH_FAILED;168if (retval)169goto done;170171rs.enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;172armor_key = rs.preauth_cb->fast_armor(context, rs.rock);173if (armor_key == NULL) {174retval = ENOENT;175goto done;176}177178retval = rs.preauth_cb->replace_reply_key(context, rs.rock, armor_key,179FALSE);180if (retval)181goto done;182183for (ind = indicators; ind != NULL && *ind != NULL; ind++) {184retval = rs.preauth_cb->add_auth_indicator(context, rs.rock, *ind);185if (retval)186goto done;187}188189done:190rs.respond(rs.arg, retval, NULL, NULL, NULL);191}192193static krb5_error_code194otp_init(krb5_context context, krb5_kdcpreauth_moddata *moddata_out,195const char **realmnames)196{197krb5_error_code retval;198otp_state *state;199200retval = otp_state_new(context, &state);201if (retval)202return retval;203*moddata_out = (krb5_kdcpreauth_moddata)state;204return 0;205}206207static void208otp_fini(krb5_context context, krb5_kdcpreauth_moddata moddata)209{210otp_state_free((otp_state *)moddata);211}212213static int214otp_flags(krb5_context context, krb5_preauthtype pa_type)215{216return PA_REPLACES_KEY;217}218219static void220otp_edata(krb5_context context, krb5_kdc_req *request,221krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,222krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,223krb5_kdcpreauth_edata_respond_fn respond, void *arg)224{225krb5_otp_tokeninfo ti, *tis[2] = { &ti, NULL };226krb5_keyblock *armor_key = NULL;227krb5_pa_otp_challenge chl;228krb5_pa_data *pa = NULL;229krb5_error_code retval;230krb5_data *encoding, nonce = empty_data();231char *config;232233/* Determine if otp is enabled for the user. */234retval = cb->get_string(context, rock, "otp", &config);235if (retval == 0 && config == NULL)236retval = ENOENT;237if (retval != 0)238goto out;239cb->free_string(context, rock, config);240241/* Get the armor key. This indicates the length of random data to use in242* the nonce. */243armor_key = cb->fast_armor(context, rock);244if (armor_key == NULL) {245retval = ENOENT;246goto out;247}248249/* Build the (mostly empty) challenge. */250memset(&ti, 0, sizeof(ti));251memset(&chl, 0, sizeof(chl));252chl.tokeninfo = tis;253ti.format = -1;254ti.length = -1;255ti.iteration_count = -1;256257/* Generate the nonce. */258retval = nonce_generate(context, armor_key->length, &nonce);259if (retval != 0)260goto out;261chl.nonce = nonce;262263/* Build the output pa-data. */264retval = encode_krb5_pa_otp_challenge(&chl, &encoding);265if (retval != 0)266goto out;267pa = k5alloc(sizeof(krb5_pa_data), &retval);268if (pa == NULL) {269krb5_free_data(context, encoding);270goto out;271}272pa->pa_type = KRB5_PADATA_OTP_CHALLENGE;273pa->contents = (krb5_octet *)encoding->data;274pa->length = encoding->length;275free(encoding);276277out:278krb5_free_data_contents(context, &nonce);279(*respond)(arg, retval, pa);280}281282static void283otp_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,284krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *pa,285krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,286krb5_kdcpreauth_moddata moddata,287krb5_kdcpreauth_verify_respond_fn respond, void *arg)288{289krb5_keyblock *armor_key = NULL;290krb5_pa_otp_req *req = NULL;291struct request_state *rs;292krb5_error_code retval;293krb5_data d, plaintext;294char *config;295296/* Get the FAST armor key. */297armor_key = cb->fast_armor(context, rock);298if (armor_key == NULL) {299retval = KRB5KDC_ERR_PREAUTH_FAILED;300com_err("otp", retval, "No armor key found when verifying padata");301goto error;302}303304/* Decode the request. */305d = make_data(pa->contents, pa->length);306retval = decode_krb5_pa_otp_req(&d, &req);307if (retval != 0) {308com_err("otp", retval, "Unable to decode OTP request");309goto error;310}311312/* Decrypt the nonce from the request. */313retval = decrypt_encdata(context, armor_key, req, &plaintext);314if (retval != 0) {315com_err("otp", retval, "Unable to decrypt nonce");316goto error;317}318319/* Verify the nonce or timestamp. */320retval = nonce_verify(context, armor_key, &plaintext);321if (retval != 0)322retval = timestamp_verify(context, &plaintext);323krb5_free_data_contents(context, &plaintext);324if (retval != 0) {325com_err("otp", retval, "Unable to verify nonce or timestamp");326goto error;327}328329/* Create the request state. Save the response callback, and the330* enc_tkt_reply pointer so we can set the TKT_FLG_PRE_AUTH flag later. */331rs = k5alloc(sizeof(struct request_state), &retval);332if (rs == NULL)333goto error;334rs->context = context;335rs->arg = arg;336rs->respond = respond;337rs->enc_tkt_reply = enc_tkt_reply;338rs->preauth_cb = cb;339rs->rock = rock;340341/* Get the principal's OTP configuration string. */342retval = cb->get_string(context, rock, "otp", &config);343if (retval == 0 && config == NULL)344retval = KRB5_PREAUTH_FAILED;345if (retval != 0) {346free(rs);347goto error;348}349350/* Send the request. */351otp_state_verify((otp_state *)moddata, cb->event_context(context, rock),352cb->client_name(context, rock), config, req, on_response,353rs);354cb->free_string(context, rock, config);355356k5_free_pa_otp_req(context, req);357return;358359error:360k5_free_pa_otp_req(context, req);361(*respond)(arg, retval, NULL, NULL, NULL);362}363364krb5_error_code365kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,366krb5_plugin_vtable vtable);367368krb5_error_code369kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,370krb5_plugin_vtable vtable)371{372krb5_kdcpreauth_vtable vt;373374if (maj_ver != 1)375return KRB5_PLUGIN_VER_NOTSUPP;376377vt = (krb5_kdcpreauth_vtable)vtable;378vt->name = "otp";379vt->pa_type_list = otp_pa_type_list;380vt->init = otp_init;381vt->fini = otp_fini;382vt->flags = otp_flags;383vt->edata = otp_edata;384vt->verify = otp_verify;385386com_err("otp", 0, "Loaded");387388return 0;389}390391392