/* * Copyright 1995 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. * */ /* * This file contains routines for establishing, verifying, and any other * necessary functions, for utilizing the pre-authentication field of the * kerberos kdc request, with various hardware/software verification devices. */ #include "k5-int.h" typedef krb5_error_code (*pa_function)(krb5_context, krb5_kdc_req *request, krb5_pa_data *in_padata, krb5_pa_data **out_padata, krb5_data *salt, krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter_fct, void *prompter_data, krb5_gic_get_as_key_fct gak_fct, void *gak_data); typedef struct _pa_types_t { krb5_preauthtype type; pa_function fct; int flags; } pa_types_t; #define PA_REAL 0x0001 #define PA_INFO 0x0002 static krb5_error_code pa_salt(krb5_context context, krb5_kdc_req *request, krb5_pa_data *in_padata, krb5_pa_data **out_padata, krb5_data *salt, krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter, void *prompter_data, krb5_gic_get_as_key_fct gak_fct, void *gak_data) { krb5_data tmp; /* screw the abstraction. If there was a *reasonable* copy_data, I'd use it. But I'm inside the library, which is the twilight zone of source code, so I can do anything. */ tmp.length = in_padata->length; if (tmp.length) { if ((tmp.data = malloc(tmp.length)) == NULL) return ENOMEM; memcpy(tmp.data, in_padata->contents, tmp.length); } else { tmp.data = NULL; } *salt = tmp; /* assume that no other salt was allocated */ if (in_padata->pa_type == KRB5_PADATA_AFS3_SALT) salt->length = SALT_TYPE_AFS_LENGTH; return(0); } static krb5_error_code pa_enc_timestamp(krb5_context context, krb5_kdc_req *request, krb5_pa_data *in_padata, krb5_pa_data **out_padata, krb5_data *salt, krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter, void *prompter_data, krb5_gic_get_as_key_fct gak_fct, void *gak_data) { krb5_error_code ret; krb5_pa_enc_ts pa_enc; krb5_data *tmp; krb5_enc_data enc_data; krb5_pa_data *pa; if (as_key->length == 0) { #ifdef DEBUG fprintf (stderr, "%s:%d: salt len=%d", __FILE__, __LINE__, salt->length); if (salt->length > 0) fprintf (stderr, " '%*s'", salt->length, salt->data); fprintf (stderr, "; *etype=%d request->ktype[0]=%d\n", *etype, request->ktype[0]); #endif if ((ret = ((*gak_fct)(context, request->client, *etype ? *etype : request->ktype[0], prompter, prompter_data, salt, as_key, gak_data)))) return(ret); } /* now get the time of day, and encrypt it accordingly */ if ((ret = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec))) return(ret); if ((ret = encode_krb5_pa_enc_ts(&pa_enc, &tmp))) return(ret); #ifdef DEBUG fprintf (stderr, "key type %d bytes %02x %02x ...\n", as_key->enctype, as_key->contents[0], as_key->contents[1]); #endif ret = krb5_encrypt_helper(context, as_key, KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS, tmp, &enc_data); #ifdef DEBUG fprintf (stderr, "enc data { type=%d kvno=%d data=%02x %02x ... }\n", enc_data.enctype, enc_data.kvno, 0xff & enc_data.ciphertext.data[0], 0xff & enc_data.ciphertext.data[1]); #endif krb5_free_data(context, tmp); if (ret) { krb5_xfree(enc_data.ciphertext.data); return(ret); } ret = encode_krb5_enc_data(&enc_data, &tmp); krb5_xfree(enc_data.ciphertext.data); if (ret) return(ret); if ((pa = (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) { krb5_free_data(context, tmp); return(ENOMEM); } pa->magic = KV5M_PA_DATA; pa->pa_type = KRB5_PADATA_ENC_TIMESTAMP; pa->length = tmp->length; pa->contents = (krb5_octet *) tmp->data; *out_padata = pa; krb5_xfree(tmp); return(0); } static char *sam_challenge_banner(sam_type) 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); } /* 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) /* XXX Danger! This code is not in sync with the kerberos-password-02 draft. This draft cannot be implemented as written. This code is compatible with earlier versions of mit krb5 and cygnus kerbnet. */ static krb5_error_code pa_sam(krb5_context context, krb5_kdc_req *request, krb5_pa_data *in_padata, krb5_pa_data **out_padata, krb5_data *salt, krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter, void *prompter_data, krb5_gic_get_as_key_fct gak_fct, void *gak_data) { krb5_error_code ret; krb5_data tmpsam; char name[100], banner[100]; char prompt[100], response[100]; krb5_data response_data; krb5_prompt kprompt; krb5_prompt_type prompt_type; krb5_data defsalt; krb5_sam_challenge *sam_challenge = 0; krb5_sam_response sam_response; /* these two get encrypted and stuffed in to sam_response */ krb5_enc_sam_response_enc enc_sam_response_enc; krb5_data * scratch; krb5_pa_data * pa; if (prompter == NULL) return EIO; tmpsam.length = in_padata->length; tmpsam.data = (char *) in_padata->contents; if ((ret = decode_krb5_sam_challenge(&tmpsam, &sam_challenge))) return(ret); if (sam_challenge->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) { krb5_xfree(sam_challenge); return(KRB5_SAM_UNSUPPORTED); } sprintf(name, "%.*s", SAMDATA(sam_challenge->sam_type_name, "SAM Authentication", sizeof(name) - 1)); sprintf(banner, "%.*s", SAMDATA(sam_challenge->sam_challenge_label, sam_challenge_banner(sam_challenge->sam_type), sizeof(banner)-1)); /* sprintf(prompt, "Challenge is [%s], %s: ", challenge, prompt); */ sprintf(prompt, "%s%.*s%s%.*s", sam_challenge->sam_challenge.length?"Challenge is [":"", SAMDATA(sam_challenge->sam_challenge, "", 20), sam_challenge->sam_challenge.length?"], ":"", SAMDATA(sam_challenge->sam_response_prompt, "passcode", 55)); response_data.data = response; response_data.length = sizeof(response); kprompt.prompt = prompt; kprompt.hidden = sam_challenge->sam_challenge.length?0:1; kprompt.reply = &response_data; prompt_type = KRB5_PROMPT_TYPE_PREAUTH; /* PROMPTER_INVOCATION */ krb5int_set_prompt_types(context, &prompt_type); if ((ret = ((*prompter)(context, prompter_data, name, banner, 1, &kprompt)))) { krb5_xfree(sam_challenge); krb5int_set_prompt_types(context, 0); return(ret); } krb5int_set_prompt_types(context, 0); enc_sam_response_enc.sam_nonce = sam_challenge->sam_nonce; if (sam_challenge->sam_nonce == 0) { if ((ret = krb5_us_timeofday(context, &enc_sam_response_enc.sam_timestamp, &enc_sam_response_enc.sam_usec))) { krb5_xfree(sam_challenge); return(ret); } sam_response.sam_patimestamp = enc_sam_response_enc.sam_timestamp; } /* XXX What if more than one flag is set? */ if (sam_challenge->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) { if (as_key->length) { krb5_free_keyblock_contents(context, as_key); as_key->length = 0; } /* generate a salt using the requested principal */ if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) { if ((ret = krb5_principal2salt(context, request->client, &defsalt))) { krb5_xfree(sam_challenge); return(ret); } salt = &defsalt; } else { defsalt.length = 0; } /* generate a key using the supplied password */ ret = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5, (krb5_data *)gak_data, salt, as_key); if (defsalt.length) krb5_xfree(defsalt.data); if (ret) { krb5_xfree(sam_challenge); return(ret); } /* encrypt the passcode with the key from above */ enc_sam_response_enc.sam_sad = response_data; } else if (sam_challenge->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) { /* process the key as password */ if (as_key->length) { krb5_free_keyblock_contents(context, as_key); as_key->length = 0; } #if 0 if ((salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) { if (ret = krb5_principal2salt(context, request->client, &defsalt)) { krb5_xfree(sam_challenge); return(ret); } salt = &defsalt; } else { defsalt.length = 0; } #else defsalt.length = 0; salt = NULL; #endif /* XXX As of the passwords-04 draft, no enctype is specified, the server uses ENCTYPE_DES_CBC_MD5. In the future the server should send a PA-SAM-ETYPE-INFO containing the enctype. */ ret = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5, &response_data, salt, as_key); if (defsalt.length) krb5_xfree(defsalt.data); if (ret) { krb5_xfree(sam_challenge); return(ret); } enc_sam_response_enc.sam_sad.length = 0; } else { /* Eventually, combine SAD with long-term key to get encryption key. */ return KRB5_PREAUTH_BAD_TYPE; } /* copy things from the challenge */ sam_response.sam_nonce = sam_challenge->sam_nonce; sam_response.sam_flags = sam_challenge->sam_flags; sam_response.sam_track_id = sam_challenge->sam_track_id; sam_response.sam_type = sam_challenge->sam_type; sam_response.magic = KV5M_SAM_RESPONSE; krb5_xfree(sam_challenge); /* encode the encoded part of the response */ if ((ret = encode_krb5_enc_sam_response_enc(&enc_sam_response_enc, &scratch))) return(ret); ret = krb5_encrypt_data(context, as_key, 0, scratch, &sam_response.sam_enc_nonce_or_ts); krb5_free_data(context, scratch); if (ret) return(ret); /* sam_enc_key is reserved for future use */ sam_response.sam_enc_key.ciphertext.length = 0; if ((pa = malloc(sizeof(krb5_pa_data))) == NULL) return(ENOMEM); if ((ret = encode_krb5_sam_response(&sam_response, &scratch))) { free(pa); return(ret); } pa->magic = KV5M_PA_DATA; pa->pa_type = KRB5_PADATA_SAM_RESPONSE; pa->length = scratch->length; pa->contents = (krb5_octet *) scratch->data; *out_padata = pa; return(0); } static pa_types_t pa_types[] = { { KRB5_PADATA_PW_SALT, pa_salt, PA_INFO, }, { KRB5_PADATA_AFS3_SALT, pa_salt, PA_INFO, }, { KRB5_PADATA_ENC_TIMESTAMP, pa_enc_timestamp, PA_REAL, }, { KRB5_PADATA_SAM_CHALLENGE, pa_sam, PA_REAL, }, { -1, NULL, 0, }, }; krb5_error_code krb5_do_preauth(krb5_context context, krb5_kdc_req *request, krb5_pa_data **in_padata, krb5_pa_data ***out_padata, krb5_data *salt, krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter, void *prompter_data, krb5_gic_get_as_key_fct gak_fct, void *gak_data) { int h, i, j, out_pa_list_size; krb5_pa_data *out_pa, **out_pa_list; krb5_data scratch; krb5_etype_info etype_info = NULL; krb5_error_code ret; static int paorder[] = { PA_INFO, PA_REAL }; int realdone; if (in_padata == NULL) { *out_padata = NULL; return(0); } #ifdef DEBUG fprintf (stderr, "salt len=%d", salt->length); if (salt->length > 0) fprintf (stderr, " '%*s'", salt->length, salt->data); fprintf (stderr, "; preauth data types:"); for (i = 0; in_padata[i]; i++) { fprintf (stderr, " %d", in_padata[i]->pa_type); } fprintf (stderr, "\n"); #endif out_pa_list = NULL; out_pa_list_size = 0; /* first do all the informational preauths, then the first real one */ for (h=0; h<(sizeof(paorder)/sizeof(paorder[0])); h++) { realdone = 0; for (i=0; in_padata[i] && !realdone; i++) { /* * This is really gross, but is necessary to prevent * lossge when talking to a 1.0.x KDC, which returns an * erroneous PA-PW-SALT when it returns a KRB-ERROR * requiring additional preauth. */ switch (in_padata[i]->pa_type) { case KRB5_PADATA_ETYPE_INFO: if (etype_info) continue; scratch.length = in_padata[i]->length; scratch.data = (char *) in_padata[i]->contents; ret = decode_krb5_etype_info(&scratch, &etype_info); if (ret) { if (out_pa_list) { out_pa_list[out_pa_list_size++] = NULL; krb5_free_pa_data(context, out_pa_list); } return ret; } if (etype_info[0] == NULL) { krb5_free_etype_info(context, etype_info); etype_info = NULL; break; } salt->data = (char *) etype_info[0]->salt; salt->length = etype_info[0]->length; *etype = etype_info[0]->etype; #ifdef DEBUG for (j = 0; etype_info[j]; j++) { krb5_etype_info_entry *e = etype_info[j]; fprintf (stderr, "etype info %d: etype %d salt len=%d", j, e->etype, e->length); if (e->length > 0 && e->length != KRB5_ETYPE_NO_SALT) fprintf (stderr, " '%*s'", e->length, e->salt); fprintf (stderr, "\n"); } #endif break; case KRB5_PADATA_PW_SALT: case KRB5_PADATA_AFS3_SALT: if (etype_info) continue; break; default: ; } for (j=0; pa_types[j].type >= 0; j++) { if ((in_padata[i]->pa_type == pa_types[j].type) && (pa_types[j].flags & paorder[h])) { out_pa = NULL; if ((ret = ((*pa_types[j].fct)(context, request, in_padata[i], &out_pa, salt, etype, as_key, prompter, prompter_data, gak_fct, gak_data)))) { if (out_pa_list) { out_pa_list[out_pa_list_size++] = NULL; krb5_free_pa_data(context, out_pa_list); } if (etype_info) krb5_free_etype_info(context, etype_info); return(ret); } if (out_pa) { if (out_pa_list == NULL) { if ((out_pa_list = (krb5_pa_data **) malloc(2*sizeof(krb5_pa_data *))) == NULL) return(ENOMEM); } else { if ((out_pa_list = (krb5_pa_data **) realloc(out_pa_list, (out_pa_list_size+2)* sizeof(krb5_pa_data *))) == NULL) /* XXX this will leak the pointers which have already been allocated. oh well. */ return(ENOMEM); } out_pa_list[out_pa_list_size++] = out_pa; } if (paorder[h] == PA_REAL) realdone = 1; } } } } if (out_pa_list) out_pa_list[out_pa_list_size++] = NULL; *out_padata = out_pa_list; return(0); }