2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * plugins/preauth/encrypted_challenge/encrypted_challenge.c
2N/A *
2N/A * Copyright (C) 2009 by the Massachusetts Institute of Technology.
2N/A * All rights reserved.
2N/A *
2N/A * Export of this software from the United States of America may
2N/A * require a specific license from the United States Government.
2N/A * It is the responsibility of any person or organization contemplating
2N/A * export to obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of M.I.T. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. Furthermore if you modify this software you must label
2N/A * your software as modified software and not distribute it in such a
2N/A * fashion that it might be confused with the original M.I.T. software.
2N/A * M.I.T. makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A *
2N/A *
2N/A * Implement Encrypted Challenge fast factor from
2N/A * draft-ietf-krb-wg-preauth-framework
2N/A */
2N/A
2N/A#include <k5-int.h>
2N/A#include "../fast_factor.h"
2N/A
2N/A/* Solaris Kerberos */
2N/A#include <preauth_plugin.h>
2N/A
2N/A/* Solaris Kerberos */
2N/A#include <libintl.h>
2N/A
2N/Astatic int
2N/Apreauth_flags(krb5_context context, krb5_preauthtype pa_type)
2N/A{
2N/A return PA_REAL;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Aprocess_preauth(krb5_context context, void *plugin_context,
2N/A void *request_context, krb5_get_init_creds_opt *opt,
2N/A preauth_get_client_data_proc get_data_proc,
2N/A struct _krb5_preauth_client_rock *rock, krb5_kdc_req *request,
2N/A krb5_data *encoded_request_body,
2N/A krb5_data *encoded_previous_request, krb5_pa_data *padata,
2N/A krb5_prompter_fct prompter, void *prompter_data,
2N/A preauth_get_as_key_proc gak_fct, void *gak_data,
2N/A krb5_data *salt, krb5_data *s2kparams, krb5_keyblock *as_key,
2N/A krb5_pa_data ***out_padata)
2N/A{
2N/A krb5_error_code retval = 0;
2N/A krb5_enctype enctype = 0;
2N/A krb5_keyblock *challenge_key = NULL, *armor_key = NULL;
2N/A krb5_data *etype_data = NULL;
2N/A krb5int_access kaccess;
2N/A
2N/A if (krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION) != 0)
2N/A return 0;
2N/A retval = fast_get_armor_key(context, get_data_proc, rock, &armor_key);
2N/A if (retval || armor_key == NULL)
2N/A return 0;
2N/A retval = get_data_proc(context, rock, krb5plugin_preauth_client_get_etype, &etype_data);
2N/A if (retval == 0) {
2N/A enctype = *((krb5_enctype *)etype_data->data);
2N/A if (as_key->length == 0 ||as_key->enctype != enctype)
2N/A retval = gak_fct(context, request->client,
2N/A enctype, prompter, prompter_data,
2N/A salt, s2kparams,
2N/A as_key, gak_data);
2N/A }
2N/A if (padata->length) {
2N/A krb5_enc_data *enc = NULL;
2N/A krb5_data scratch;
2N/A scratch.length = padata->length;
2N/A scratch.data = (char *) padata->contents;
2N/A if (retval == 0)
2N/A retval = krb5_c_fx_cf2_simple(context,armor_key, "kdcchallengearmor",
2N/A as_key, "challengelongterm", &challenge_key);
2N/A if (retval == 0)
2N/A retval =kaccess.decode_enc_data(&scratch, &enc);
2N/A scratch.data = NULL;
2N/A if (retval == 0) {
2N/A scratch.data = malloc(enc->ciphertext.length);
2N/A scratch.length = enc->ciphertext.length;
2N/A if (scratch.data == NULL)
2N/A retval = ENOMEM;
2N/A }
2N/A if (retval == 0)
2N/A retval = krb5_c_decrypt(context, challenge_key,
2N/A KRB5_KEYUSAGE_ENC_CHALLENGE_KDC, NULL,
2N/A enc, &scratch);
2N/A /*
2N/A * Per draft 11 of the preauth framework, the client MAY but is not
2N/A * required to actually check the timestamp from the KDC other than to
2N/A * confirm it decrypts. This code does not perform that check.
2N/A */
2N/A if (scratch.data)
2N/A krb5_free_data_contents(context, &scratch);
2N/A if (retval == 0)
2N/A fast_set_kdc_verified(context, get_data_proc, rock);
2N/A if (enc)
2N/A kaccess.free_enc_data(context, enc);
2N/A } else { /*No padata; we send*/
2N/A krb5_enc_data enc;
2N/A krb5_pa_data *pa = NULL;
2N/A krb5_pa_data **pa_array = NULL;
2N/A krb5_data *encoded_ts = NULL;
2N/A krb5_pa_enc_ts ts;
2N/A enc.ciphertext.data = NULL;
2N/A if (retval == 0)
2N/A retval = krb5_us_timeofday(context, &ts.patimestamp, &ts.pausec);
2N/A if (retval == 0)
2N/A retval = kaccess.encode_enc_ts(&ts, &encoded_ts);
2N/A if (retval == 0)
2N/A retval = krb5_c_fx_cf2_simple(context,
2N/A armor_key, "clientchallengearmor",
2N/A as_key, "challengelongterm",
2N/A &challenge_key);
2N/A if (retval == 0)
2N/A retval = kaccess.encrypt_helper(context, challenge_key,
2N/A KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT,
2N/A encoded_ts, &enc);
2N/A if (encoded_ts)
2N/A krb5_free_data(context, encoded_ts);
2N/A encoded_ts = NULL;
2N/A if (retval == 0) {
2N/A retval = kaccess.encode_enc_data(&enc, &encoded_ts);
2N/A krb5_free_data_contents(context, &enc.ciphertext);
2N/A }
2N/A if (retval == 0) {
2N/A pa = calloc(1, sizeof(krb5_pa_data));
2N/A if (pa == NULL)
2N/A retval = ENOMEM;
2N/A }
2N/A if (retval == 0) {
2N/A pa_array = calloc(2, sizeof(krb5_pa_data *));
2N/A if (pa_array == NULL)
2N/A retval = ENOMEM;
2N/A }
2N/A if (retval == 0) {
2N/A pa->length = encoded_ts->length;
2N/A pa->contents = (unsigned char *) encoded_ts->data;
2N/A pa->pa_type = KRB5_PADATA_ENCRYPTED_CHALLENGE;
2N/A free(encoded_ts);
2N/A encoded_ts = NULL;
2N/A pa_array[0] = pa;
2N/A pa = NULL;
2N/A *out_padata = pa_array;
2N/A pa_array = NULL;
2N/A }
2N/A if (pa)
2N/A free(pa);
2N/A if (encoded_ts)
2N/A krb5_free_data(context, encoded_ts);
2N/A if (pa_array)
2N/A free(pa_array);
2N/A }
2N/A if (challenge_key)
2N/A krb5_free_keyblock(context, challenge_key);
2N/A if (armor_key)
2N/A krb5_free_keyblock(context, armor_key);
2N/A if (etype_data != NULL)
2N/A get_data_proc(context, rock, krb5plugin_preauth_client_free_etype,
2N/A &etype_data);
2N/A return retval;
2N/A}
2N/A
2N/A
2N/Astatic krb5_error_code
2N/Akdc_include_padata(krb5_context context, krb5_kdc_req *request,
2N/A struct _krb5_db_entry_new *client,
2N/A struct _krb5_db_entry_new *server,
2N/A preauth_get_entry_data_proc get_entry_proc,
2N/A void *pa_module_context, krb5_pa_data *data)
2N/A{
2N/A krb5_error_code retval = 0;
2N/A krb5_keyblock *armor_key = NULL;
2N/A retval = fast_kdc_get_armor_key(context, get_entry_proc, request, client, &armor_key);
2N/A if (retval)
2N/A return retval;
2N/A if (armor_key == 0)
2N/A return ENOENT;
2N/A krb5_free_keyblock(context, armor_key);
2N/A return 0;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Akdc_verify_preauth(krb5_context context, struct _krb5_db_entry_new *client,
2N/A krb5_data *req_pkt, krb5_kdc_req *request,
2N/A krb5_enc_tkt_part *enc_tkt_reply, krb5_pa_data *data,
2N/A preauth_get_entry_data_proc get_entry_proc,
2N/A void *pa_module_context, void **pa_request_context,
2N/A krb5_data **e_data, krb5_authdata ***authz_data)
2N/A{
2N/A krb5_error_code retval = 0;
2N/A krb5_timestamp now;
2N/A krb5_enc_data *enc = NULL;
2N/A krb5_data scratch, plain;
2N/A krb5_keyblock *armor_key = NULL;
2N/A krb5_pa_enc_ts *ts = NULL;
2N/A krb5int_access kaccess;
2N/A krb5_keyblock *client_keys = NULL;
2N/A krb5_data *client_data = NULL;
2N/A krb5_keyblock *challenge_key = NULL;
2N/A int i = 0;
2N/A
2N/A plain.data = NULL;
2N/A if (krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION) != 0)
2N/A return 0;
2N/A
2N/A retval = fast_kdc_get_armor_key(context, get_entry_proc, request, client, &armor_key);
2N/A if (retval == 0 &&armor_key == NULL) {
2N/A retval = ENOENT;
2N/A /* Solaris Kerberos */
2N/A krb5_set_error_message(context, ENOENT, gettext("Encrypted Challenge used outside of FAST tunnel"));
2N/A }
2N/A scratch.data = (char *) data->contents;
2N/A scratch.length = data->length;
2N/A if (retval == 0)
2N/A retval = kaccess.decode_enc_data(&scratch, &enc);
2N/A if (retval == 0) {
2N/A plain.data = malloc(enc->ciphertext.length);
2N/A plain.length = enc->ciphertext.length;
2N/A if (plain.data == NULL)
2N/A retval = ENOMEM;
2N/A }
2N/A if (retval == 0)
2N/A retval = get_entry_proc(context, request, client,
2N/A krb5plugin_preauth_keys, &client_data);
2N/A if (retval == 0) {
2N/A client_keys = (krb5_keyblock *) client_data->data;
2N/A for (i = 0; client_keys[i].enctype&& (retval == 0); i++ ) {
2N/A retval = krb5_c_fx_cf2_simple(context,
2N/A armor_key, "clientchallengearmor",
2N/A &client_keys[i], "challengelongterm",
2N/A &challenge_key);
2N/A if (retval == 0)
2N/A retval = krb5_c_decrypt(context, challenge_key,
2N/A KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT,
2N/A NULL, enc, &plain);
2N/A if (challenge_key)
2N/A krb5_free_keyblock(context, challenge_key);
2N/A challenge_key = NULL;
2N/A if (retval == 0)
2N/A break;
2N/A /*We failed to decrypt. Try next key*/
2N/A retval = 0;
2N/A krb5_free_keyblock_contents(context, &client_keys[i]);
2N/A }
2N/A if (client_keys[i].enctype == 0) {
2N/A retval = KRB5KDC_ERR_PREAUTH_FAILED;
2N/A /* Solaris Kerberos */
2N/A krb5_set_error_message(context, retval, gettext("Incorrect password in encrypted challenge"));
2N/A } else { /*not run out of keys*/
2N/A int j;
2N/A assert (retval == 0);
2N/A for (j = i+1; client_keys[j].enctype; j++)
2N/A krb5_free_keyblock_contents(context, &client_keys[j]);
2N/A }
2N/A
2N/A }
2N/A if (retval == 0)
2N/A retval = kaccess.decode_enc_ts(&plain, &ts);
2N/A if (retval == 0)
2N/A retval = krb5_timeofday(context, &now);
2N/A if (retval == 0) {
2N/A if (labs(now-ts->patimestamp) < context->clockskew) {
2N/A enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
2N/A /*
2N/A * If this fails, we won't generate a reply to the client. That
2N/A * may cause the client to fail, but at this point the KDC has
2N/A * considered this a success, so the return value is ignored.
2N/A */
2N/A fast_kdc_replace_reply_key(context, get_entry_proc, request);
2N/A krb5_c_fx_cf2_simple(context, armor_key, "kdcchallengearmor",
2N/A &client_keys[i], "challengelongterm",
2N/A (krb5_keyblock **) pa_request_context);
2N/A } else { /*skew*/
2N/A retval = KRB5KRB_AP_ERR_SKEW;
2N/A }
2N/A }
2N/A if (client_keys) {
2N/A if (client_keys[i].enctype)
2N/A krb5_free_keyblock_contents(context, &client_keys[i]);
2N/A krb5_free_data(context, client_data);
2N/A }
2N/A if (armor_key)
2N/A krb5_free_keyblock(context, armor_key);
2N/A if (plain.data)
2N/A free(plain.data);
2N/A if (enc)
2N/A kaccess.free_enc_data(context, enc);
2N/A if (ts)
2N/A kaccess.free_enc_ts(context, ts);
2N/A return retval;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Akdc_return_preauth(krb5_context context, krb5_pa_data *padata,
2N/A struct _krb5_db_entry_new *client, krb5_data *req_pkt,
2N/A krb5_kdc_req *request, krb5_kdc_rep *reply,
2N/A struct _krb5_key_data *client_keys,
2N/A krb5_keyblock *encrypting_key, krb5_pa_data **send_pa,
2N/A preauth_get_entry_data_proc get_entry_proc,
2N/A void *pa_module_context, void **pa_request_context)
2N/A{
2N/A krb5_error_code retval = 0;
2N/A krb5_keyblock *challenge_key = *pa_request_context;
2N/A krb5_pa_enc_ts ts;
2N/A krb5_data *plain = NULL;
2N/A krb5_enc_data enc;
2N/A krb5_data *encoded = NULL;
2N/A krb5_pa_data *pa = NULL;
2N/A krb5int_access kaccess;
2N/A
2N/A if (krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION) != 0)
2N/A return 0;
2N/A if (challenge_key == NULL)
2N/A return 0;
2N/A * pa_request_context = NULL; /*this function will free the
2N/A * challenge key*/
2N/A enc.ciphertext.data = NULL; /* In case of error pass through */
2N/A
2N/A retval = krb5_us_timeofday(context, &ts.patimestamp, &ts.pausec);
2N/A if (retval == 0)
2N/A retval = kaccess.encode_enc_ts(&ts, &plain);
2N/A if (retval == 0)
2N/A retval = kaccess.encrypt_helper(context, challenge_key,
2N/A KRB5_KEYUSAGE_ENC_CHALLENGE_KDC,
2N/A plain, &enc);
2N/A if (retval == 0)
2N/A retval = kaccess.encode_enc_data(&enc, &encoded);
2N/A if (retval == 0) {
2N/A pa = calloc(1, sizeof(krb5_pa_data));
2N/A if (pa == NULL)
2N/A retval = ENOMEM;
2N/A }
2N/A if (retval == 0) {
2N/A pa->pa_type = KRB5_PADATA_ENCRYPTED_CHALLENGE;
2N/A pa->contents = (unsigned char *) encoded->data;
2N/A pa->length = encoded->length;
2N/A encoded->data = NULL;
2N/A *send_pa = pa;
2N/A pa = NULL;
2N/A }
2N/A if (challenge_key)
2N/A krb5_free_keyblock(context, challenge_key);
2N/A if (encoded)
2N/A krb5_free_data(context, encoded);
2N/A if (plain)
2N/A krb5_free_data(context, plain);
2N/A if (enc.ciphertext.data)
2N/A krb5_free_data_contents(context, &enc.ciphertext);
2N/A return retval;
2N/A}
2N/A
2N/Astatic int
2N/Akdc_preauth_flags(krb5_context context, krb5_preauthtype patype)
2N/A{
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_preauthtype supported_pa_types[] = {
2N/A KRB5_PADATA_ENCRYPTED_CHALLENGE, 0};
2N/A
2N/Astruct krb5plugin_preauth_server_ftable_v1 preauthentication_server_1 = {
2N/A "Encrypted challenge",
2N/A &supported_pa_types[0],
2N/A NULL,
2N/A NULL,
2N/A kdc_preauth_flags,
2N/A kdc_include_padata,
2N/A kdc_verify_preauth,
2N/A kdc_return_preauth,
2N/A NULL
2N/A};
2N/A
2N/Astruct krb5plugin_preauth_client_ftable_v1 preauthentication_client_1 = {
2N/A "Encrypted Challenge", /* name */
2N/A &supported_pa_types[0], /* pa_type_list */
2N/A NULL, /* enctype_list */
2N/A NULL, /* plugin init function */
2N/A NULL, /* plugin fini function */
2N/A preauth_flags, /* get flags function */
2N/A NULL, /* request init function */
2N/A NULL, /* request fini function */
2N/A process_preauth, /* process function */
2N/A NULL, /* try_again function */
2N/A NULL /* get init creds opt function */
2N/A};