diff options
Diffstat (limited to 'src/lib/krb5/krb/preauth_sam2.c')
| -rw-r--r-- | src/lib/krb5/krb/preauth_sam2.c | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/src/lib/krb5/krb/preauth_sam2.c b/src/lib/krb5/krb/preauth_sam2.c new file mode 100644 index 000000000000..c8a330655c4a --- /dev/null +++ b/src/lib/krb5/krb/preauth_sam2.c @@ -0,0 +1,433 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/krb/preauth_sam2.c - SAM-2 clpreauth module */ +/* + * Copyright 1995, 2003, 2008, 2012 by the Massachusetts Institute of Technology. All + * Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + */ + +#include <k5-int.h> +#include <krb5/clpreauth_plugin.h> +#include "int-proto.h" +#include "os-proto.h" +#include "init_creds_ctx.h" + +/* this macro expands to the int,ptr necessary for "%.*s" in an sprintf */ + +#define SAMDATA(kdata, str, maxsize) \ + (int)((kdata.length)? \ + ((((kdata.length)<=(maxsize))?(kdata.length):strlen(str))): \ + strlen(str)), \ + (kdata.length)? \ + ((((kdata.length)<=(maxsize))?(kdata.data):(str))):(str) +static char * +sam_challenge_banner(krb5_int32 sam_type) +{ + char *label; + + switch (sam_type) { + case PA_SAM_TYPE_ENIGMA: /* Enigma Logic */ + label = _("Challenge for Enigma Logic mechanism"); + break; + case PA_SAM_TYPE_DIGI_PATH: /* Digital Pathways */ + case PA_SAM_TYPE_DIGI_PATH_HEX: /* Digital Pathways */ + label = _("Challenge for Digital Pathways mechanism"); + break; + case PA_SAM_TYPE_ACTIVCARD_DEC: /* Digital Pathways */ + case PA_SAM_TYPE_ACTIVCARD_HEX: /* Digital Pathways */ + label = _("Challenge for Activcard mechanism"); + break; + case PA_SAM_TYPE_SKEY_K0: /* S/key where KDC has key 0 */ + label = _("Challenge for Enhanced S/Key mechanism"); + break; + case PA_SAM_TYPE_SKEY: /* Traditional S/Key */ + label = _("Challenge for Traditional S/Key mechanism"); + break; + case PA_SAM_TYPE_SECURID: /* Security Dynamics */ + label = _("Challenge for Security Dynamics mechanism"); + break; + case PA_SAM_TYPE_SECURID_PREDICT: /* predictive Security Dynamics */ + label = _("Challenge for Security Dynamics mechanism"); + break; + default: + label = _("Challenge from authentication server"); + break; + } + + return(label); +} + +static krb5_error_code +sam2_process(krb5_context context, krb5_clpreauth_moddata moddata, + krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt, + krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock, + krb5_kdc_req *request, krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, krb5_pa_data *padata, + krb5_prompter_fct prompter, void *prompter_data, + krb5_pa_data ***out_padata) +{ + krb5_init_creds_context ctx = (krb5_init_creds_context)rock; + krb5_error_code retval; + krb5_sam_challenge_2 *sc2 = NULL; + krb5_sam_challenge_2_body *sc2b = NULL; + krb5_data tmp_data; + krb5_data response_data; + char name[100], banner[100], prompt[100], response[100]; + krb5_prompt kprompt; + krb5_prompt_type prompt_type; + krb5_data defsalt, *salt; + struct gak_password *gakpw; + krb5_checksum **cksum; + krb5_data *scratch = NULL; + krb5_boolean valid_cksum = 0; + krb5_enc_sam_response_enc_2 enc_sam_response_enc_2; + krb5_sam_response_2 sr2; + size_t ciph_len; + krb5_pa_data **sam_padata; + + if (prompter == NULL) + return KRB5_LIBOS_CANTREADPWD; + + tmp_data.length = padata->length; + tmp_data.data = (char *)padata->contents; + + if ((retval = decode_krb5_sam_challenge_2(&tmp_data, &sc2))) + return(retval); + + retval = decode_krb5_sam_challenge_2_body(&sc2->sam_challenge_2_body, &sc2b); + + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + return(retval); + } + + if (!sc2->sam_cksum || ! *sc2->sam_cksum) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(KRB5_SAM_NO_CHECKSUM); + } + + if (sc2b->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(KRB5_SAM_UNSUPPORTED); + } + + if (!krb5_c_valid_enctype(sc2b->sam_etype)) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(KRB5_SAM_INVALID_ETYPE); + } + + /* All of the above error checks are KDC-specific, that is, they */ + /* assume a failure in the KDC reply. By returning anything other */ + /* than KRB5_KDC_UNREACH, KRB5_PREAUTH_FAILED, */ + /* KRB5_LIBOS_PWDINTR, or KRB5_REALM_CANT_RESOLVE, the client will */ + /* most likely go on to try the AS_REQ against master KDC */ + + if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) { + /* We will need the password to obtain the key used for */ + /* the checksum, and encryption of the sam_response. */ + /* Go ahead and get it now, preserving the ordering of */ + /* prompts for the user. */ + + salt = ctx->default_salt ? NULL : &ctx->salt; + retval = ctx->gak_fct(context, request->client, sc2b->sam_etype, + prompter, prompter_data, &ctx->salt, + &ctx->s2kparams, &ctx->as_key, + ctx->gak_data, ctx->rctx.items); + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(retval); + } + } + + snprintf(name, sizeof(name), "%.*s", + SAMDATA(sc2b->sam_type_name, _("SAM Authentication"), + sizeof(name) - 1)); + + snprintf(banner, sizeof(banner), "%.*s", + SAMDATA(sc2b->sam_challenge_label, + sam_challenge_banner(sc2b->sam_type), + sizeof(banner)-1)); + + snprintf(prompt, sizeof(prompt), "%s%.*s%s%.*s", + sc2b->sam_challenge.length?"Challenge is [":"", + SAMDATA(sc2b->sam_challenge, "", 20), + sc2b->sam_challenge.length?"], ":"", + SAMDATA(sc2b->sam_response_prompt, "passcode", 55)); + + response_data.data = response; + response_data.length = sizeof(response); + kprompt.prompt = prompt; + kprompt.hidden = 1; + kprompt.reply = &response_data; + + prompt_type = KRB5_PROMPT_TYPE_PREAUTH; + k5_set_prompt_types(context, &prompt_type); + + if ((retval = ((*prompter)(context, prompter_data, name, + banner, 1, &kprompt)))) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + k5_set_prompt_types(context, NULL); + return(retval); + } + + k5_set_prompt_types(context, NULL); + + /* Generate salt used by string_to_key() */ + if (ctx->default_salt) { + if ((retval = + krb5_principal2salt(context, request->client, &defsalt))) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(retval); + } + salt = &defsalt; + } else { + salt = &ctx->salt; + defsalt.length = 0; + } + + /* Get encryption key to be used for checksum and sam_response */ + if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) { + /* as_key = string_to_key(password) */ + + if (ctx->as_key.length) { + krb5_free_keyblock_contents(context, &ctx->as_key); + ctx->as_key.length = 0; + } + + /* generate a key using the supplied password */ + gakpw = ctx->gak_data; + retval = krb5_c_string_to_key(context, sc2b->sam_etype, + gakpw->password, salt, &ctx->as_key); + + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + if (defsalt.length) free(defsalt.data); + return(retval); + } + + if (!(sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD)) { + /* as_key = combine_key (as_key, string_to_key(SAD)) */ + krb5_keyblock tmp_kb; + + retval = krb5_c_string_to_key(context, sc2b->sam_etype, + &response_data, salt, &tmp_kb); + + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + if (defsalt.length) free(defsalt.data); + return(retval); + } + + /* This should be a call to the crypto library some day */ + /* key types should already match the sam_etype */ + retval = krb5int_c_combine_keys(context, &ctx->as_key, &tmp_kb, + &ctx->as_key); + + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + if (defsalt.length) free(defsalt.data); + return(retval); + } + krb5_free_keyblock_contents(context, &tmp_kb); + } + + if (defsalt.length) + free(defsalt.data); + + } else { + /* as_key = string_to_key(SAD) */ + + if (ctx->as_key.length) { + krb5_free_keyblock_contents(context, &ctx->as_key); + ctx->as_key.length = 0; + } + + /* generate a key using the supplied password */ + retval = krb5_c_string_to_key(context, sc2b->sam_etype, + &response_data, salt, &ctx->as_key); + + if (defsalt.length) + free(defsalt.data); + + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(retval); + } + } + + /* Now we have a key, verify the checksum on the sam_challenge */ + + cksum = sc2->sam_cksum; + + for (; *cksum; cksum++) { + if (!krb5_c_is_keyed_cksum((*cksum)->checksum_type)) + continue; + /* Check this cksum */ + retval = krb5_c_verify_checksum(context, &ctx->as_key, + KRB5_KEYUSAGE_PA_SAM_CHALLENGE_CKSUM, + &sc2->sam_challenge_2_body, + *cksum, &valid_cksum); + if (retval) { + krb5_free_data(context, scratch); + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(retval); + } + if (valid_cksum) + break; + } + + if (!valid_cksum) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + /* + * Note: We return AP_ERR_BAD_INTEGRITY so upper-level applications + * can interpret that as "password incorrect", which is probably + * the best error we can return in this situation. + */ + return(KRB5KRB_AP_ERR_BAD_INTEGRITY); + } + + /* fill in enc_sam_response_enc_2 */ + enc_sam_response_enc_2.magic = KV5M_ENC_SAM_RESPONSE_ENC_2; + enc_sam_response_enc_2.sam_nonce = sc2b->sam_nonce; + if (sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) { + enc_sam_response_enc_2.sam_sad = response_data; + } else { + enc_sam_response_enc_2.sam_sad.data = NULL; + enc_sam_response_enc_2.sam_sad.length = 0; + } + + /* encode and encrypt enc_sam_response_enc_2 with as_key */ + retval = encode_krb5_enc_sam_response_enc_2(&enc_sam_response_enc_2, + &scratch); + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + return(retval); + } + + /* Fill in sam_response_2 */ + memset(&sr2, 0, sizeof(sr2)); + sr2.sam_type = sc2b->sam_type; + sr2.sam_flags = sc2b->sam_flags; + sr2.sam_track_id = sc2b->sam_track_id; + sr2.sam_nonce = sc2b->sam_nonce; + + /* Now take care of sr2.sam_enc_nonce_or_sad by encrypting encoded */ + /* enc_sam_response_enc_2 from above */ + + retval = krb5_c_encrypt_length(context, ctx->as_key.enctype, + scratch->length, &ciph_len); + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + krb5_free_data(context, scratch); + return(retval); + } + sr2.sam_enc_nonce_or_sad.ciphertext.length = ciph_len; + + sr2.sam_enc_nonce_or_sad.ciphertext.data = + (char *)malloc(sr2.sam_enc_nonce_or_sad.ciphertext.length); + + if (!sr2.sam_enc_nonce_or_sad.ciphertext.data) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + krb5_free_data(context, scratch); + return(ENOMEM); + } + + retval = krb5_c_encrypt(context, &ctx->as_key, + KRB5_KEYUSAGE_PA_SAM_RESPONSE, NULL, scratch, + &sr2.sam_enc_nonce_or_sad); + if (retval) { + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + krb5_free_data(context, scratch); + krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext); + return(retval); + } + krb5_free_data(context, scratch); + scratch = NULL; + + /* Encode the sam_response_2 */ + retval = encode_krb5_sam_response_2(&sr2, &scratch); + krb5_free_sam_challenge_2(context, sc2); + krb5_free_sam_challenge_2_body(context, sc2b); + krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext); + + if (retval) { + return (retval); + } + + /* Almost there, just need to make padata ! */ + sam_padata = malloc(2 * sizeof(*sam_padata)); + if (sam_padata == NULL) { + krb5_free_data(context, scratch); + return(ENOMEM); + } + sam_padata[0] = malloc(sizeof(krb5_pa_data)); + if (sam_padata[0] == NULL) { + krb5_free_data(context, scratch); + free(sam_padata); + return(ENOMEM); + } + + sam_padata[0]->magic = KV5M_PA_DATA; + sam_padata[0]->pa_type = KRB5_PADATA_SAM_RESPONSE_2; + sam_padata[0]->length = scratch->length; + sam_padata[0]->contents = (krb5_octet *) scratch->data; + free(scratch); + sam_padata[1] = NULL; + + *out_padata = sam_padata; + + return(0); +} + +static krb5_preauthtype sam2_pa_types[] = { + KRB5_PADATA_SAM_CHALLENGE_2, 0}; + +krb5_error_code +clpreauth_sam2_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable) +{ + krb5_clpreauth_vtable vt; + + if (maj_ver != 1) + return KRB5_PLUGIN_VER_NOTSUPP; + vt = (krb5_clpreauth_vtable)vtable; + vt->name = "sam2"; + vt->pa_type_list = sam2_pa_types; + vt->process = sam2_process; + return 0; +} |
