/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* 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"
#include <stdio.h>
#include <time.h>
/* some typedefs for the function args to make things look a bit cleaner */
typedef krb5_error_code (*git_key_proc)(
krb5_context,
const krb5_enctype,
krb5_data *,
krb5_const_pointer,
krb5_keyblock **);
typedef krb5_error_code (*git_decrypt_proc)(
krb5_context,
const krb5_keyblock *,
krb5_const_pointer,
krb5_kdc_rep *);
static krb5_error_code obtain_enc_ts_padata(
krb5_context,
krb5_pa_data *,
krb5_etype_info,
krb5_keyblock *,
git_key_proc,
krb5_const_pointer,
krb5_creds *,
krb5_kdc_req *,
krb5_pa_data **);
static krb5_error_code process_pw_salt(
krb5_context,
krb5_pa_data *,
krb5_kdc_req *,
krb5_kdc_rep *,
git_key_proc,
krb5_const_pointer,
git_decrypt_proc,
krb5_keyblock **,
krb5_creds *,
krb5_int32 *,
krb5_int32 *);
static krb5_error_code obtain_sam_padata(
krb5_context,
krb5_pa_data *,
krb5_etype_info,
krb5_keyblock *,
git_key_proc,
krb5_const_pointer,
krb5_creds *,
krb5_kdc_req *,
krb5_pa_data **);
static const krb5_preauth_ops preauth_systems[] = {
{
KV5M_PREAUTH_OPS,
KRB5_PADATA_ENC_TIMESTAMP,
0,
obtain_enc_ts_padata,
0,
},
{
KV5M_PREAUTH_OPS,
KRB5_PADATA_PW_SALT,
0,
0,
process_pw_salt,
},
{
KV5M_PREAUTH_OPS,
KRB5_PADATA_AFS3_SALT,
0,
0,
process_pw_salt,
},
{
KV5M_PREAUTH_OPS,
KRB5_PADATA_SAM_CHALLENGE,
0,
obtain_sam_padata,
0,
},
{ KV5M_PREAUTH_OPS, -1 }
};
static krb5_error_code
find_pa_system(krb5_preauthtype type, const krb5_preauth_ops **Preauth_proc);
krb5_error_code krb5_obtain_padata(krb5_context context, krb5_pa_data **preauth_to_use, git_key_proc key_proc, krb5_const_pointer key_seed, krb5_creds *creds, krb5_kdc_req *request)
{
krb5_error_code retval;
krb5_etype_info etype_info = 0;
krb5_pa_data ** pa;
krb5_pa_data ** send_pa_list;
krb5_pa_data ** send_pa;
const krb5_preauth_ops *ops;
krb5_keyblock * def_enc_key = 0;
krb5_enctype enctype;
krb5_data salt;
krb5_data scratch;
int size;
int f_salt = 0;
if (preauth_to_use == NULL)
return 0;
for (pa = preauth_to_use, size=0; *pa; pa++, size++) {
if ((*pa)->pa_type == KRB5_PADATA_ETYPE_INFO) {
/* XXX use the first one. Is there another way to disambiguate? */
if (etype_info)
continue;
scratch.length = (*pa)->length;
scratch.data = (char *) (*pa)->contents;
retval = decode_krb5_etype_info(&scratch, &etype_info);
if (retval)
return retval;
if (etype_info[0] == NULL) {
krb5_free_etype_info(context, etype_info);
etype_info = NULL;
}
}
}
if ((send_pa_list = malloc((size+1) * sizeof(krb5_pa_data *))) == NULL)
return ENOMEM;
send_pa = send_pa_list;
*send_pa = 0;
enctype = request->ktype[0];
salt.data = 0;
salt.length = SALT_TYPE_NO_LENGTH;
if (etype_info) {
enctype = etype_info[0]->etype;
salt.data = (char *) etype_info[0]->salt;
if(etype_info[0]->length == KRB5_ETYPE_NO_SALT)
salt.length = SALT_TYPE_NO_LENGTH; /* XXX */
else
salt.length = etype_info[0]->length;
}
if (salt.length == SALT_TYPE_NO_LENGTH) {
/*
* This will set the salt length
*/
if ((retval = krb5_principal2salt(context, request->client, &salt)))
goto cleanup;
f_salt = 1;
}
if ((retval = (*key_proc)(context, enctype, &salt, key_seed,
&def_enc_key)))
goto cleanup;
for (pa = preauth_to_use; *pa; pa++) {
if (find_pa_system((*pa)->pa_type, &ops))
continue;
if (ops->obtain == 0)
continue;
retval = ((ops)->obtain)(context, *pa, etype_info, def_enc_key,
key_proc, key_seed, creds,
request, send_pa);
if (retval)
goto cleanup;
if (*send_pa)
send_pa++;
*send_pa = 0;
}
retval = 0;
if (send_pa_list[0]) {
request->padata = send_pa_list;
send_pa_list = 0;
}
cleanup:
if (etype_info)
krb5_free_etype_info(context, etype_info);
if (f_salt)
free(salt.data);
if (send_pa_list)
krb5_free_pa_data(context, send_pa_list);
if (def_enc_key)
krb5_free_keyblock(context, def_enc_key);
return retval;
}
krb5_error_code
krb5_process_padata(krb5_context context, krb5_kdc_req *request, krb5_kdc_rep *as_reply, git_key_proc key_proc, krb5_const_pointer keyseed, git_decrypt_proc decrypt_proc, krb5_keyblock **decrypt_key, krb5_creds *creds, krb5_int32 *do_more)
{
krb5_error_code retval = 0;
const krb5_preauth_ops * ops;
krb5_pa_data ** pa;
krb5_int32 done = 0;
*do_more = 0; /* By default, we don't need to repeat... */
if (as_reply->padata == 0)
return 0;
for (pa = as_reply->padata; *pa; pa++) {
if (find_pa_system((*pa)->pa_type, &ops))
continue;
if (ops->process == 0)
continue;
retval = ((ops)->process)(context, *pa, request, as_reply,
key_proc, keyseed, decrypt_proc,
decrypt_key, creds, do_more, &done);
if (retval)
goto cleanup;
if (done)
break;
}
cleanup:
return retval;
}
/*
* This routine is the "obtain" function for the ENC_TIMESTAMP
* preauthentication type. It take the current time and encrypts it
* in the user's key.
*/
static krb5_error_code
obtain_enc_ts_padata(krb5_context context, krb5_pa_data *in_padata, krb5_etype_info etype_info, krb5_keyblock *def_enc_key, git_key_proc key_proc, krb5_const_pointer key_seed, krb5_creds *creds, krb5_kdc_req *request, krb5_pa_data **out_padata)
{
krb5_pa_enc_ts pa_enc;
krb5_error_code retval;
krb5_data * scratch;
krb5_enc_data enc_data;
krb5_pa_data * pa;
retval = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec);
if (retval)
return retval;
if ((retval = encode_krb5_pa_enc_ts(&pa_enc, &scratch)) != 0)
return retval;
enc_data.ciphertext.data = 0;
if ((retval = krb5_encrypt_helper(context, def_enc_key,
KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS,
scratch, &enc_data)))
goto cleanup;
krb5_free_data(context, scratch);
scratch = 0;
if ((retval = encode_krb5_enc_data(&enc_data, &scratch)) != 0)
goto cleanup;
if ((pa = malloc(sizeof(krb5_pa_data))) == NULL) {
retval = ENOMEM;
goto cleanup;
}
pa->magic = KV5M_PA_DATA;
pa->pa_type = KRB5_PADATA_ENC_TIMESTAMP;
pa->length = scratch->length;
pa->contents = (krb5_octet *) scratch->data;
*out_padata = pa;
free(scratch);
scratch = 0;
retval = 0;
cleanup:
if (scratch)
krb5_free_data(context, scratch);
if (enc_data.ciphertext.data)
free(enc_data.ciphertext.data);
return retval;
}
static krb5_error_code
process_pw_salt(krb5_context context, krb5_pa_data *padata, krb5_kdc_req *request, krb5_kdc_rep *as_reply, git_key_proc key_proc, krb5_const_pointer keyseed, git_decrypt_proc decrypt_proc, krb5_keyblock **decrypt_key, krb5_creds *creds, krb5_int32 *do_more, krb5_int32 *done)
{
krb5_error_code retval;
krb5_data salt;
if (*decrypt_key != 0)
return 0;
salt.data = (char *) padata->contents;
salt.length =
(padata->pa_type == KRB5_PADATA_AFS3_SALT)?(SALT_TYPE_AFS_LENGTH):(padata->length);
if ((retval = (*key_proc)(context, as_reply->enc_part.enctype,
&salt, keyseed, decrypt_key))) {
*decrypt_key = 0;
return retval;
}
return 0;
}
static krb5_error_code
find_pa_system(krb5_preauthtype type, const krb5_preauth_ops **preauth)
{
const krb5_preauth_ops *ap = preauth_systems;
while ((ap->type != -1) && (ap->type != type))
ap++;
if (ap->type == -1)
return(KRB5_PREAUTH_BAD_TYPE);
*preauth = ap;
return 0;
}
extern const char *krb5_default_pwd_prompt1;
static krb5_error_code
sam_get_pass_from_user(krb5_context context, krb5_etype_info etype_info, git_key_proc key_proc, krb5_const_pointer key_seed, krb5_kdc_req *request, krb5_keyblock **new_enc_key, const char *prompt)
{
krb5_enctype enctype;
krb5_error_code retval;
const char *oldprompt;
/* enctype = request->ktype[0]; */
enctype = ENCTYPE_DES_CBC_MD5;
/* hack with this first! */
oldprompt = krb5_default_pwd_prompt1;
krb5_default_pwd_prompt1 = prompt;
{
krb5_data newpw;
newpw.data = 0; newpw.length = 0;
/* we don't keep the new password, just the key... */
retval = (*key_proc)(context, enctype, 0,
(krb5_const_pointer)&newpw, new_enc_key);
free(newpw.data);
}
krb5_default_pwd_prompt1 = oldprompt;
return retval;
}
static
char *handle_sam_labels(krb5_sam_challenge *sc)
{
char *label = sc->sam_challenge_label.data;
unsigned int label_len = sc->sam_challenge_label.length;
char *prompt = sc->sam_response_prompt.data;
unsigned int prompt_len = sc->sam_response_prompt.length;
char *challenge = sc->sam_challenge.data;
unsigned int challenge_len = sc->sam_challenge.length;
struct k5buf buf;
if (sc->sam_cksum.length == 0) {
/* or invalid -- but lets just handle presence now XXX */
switch (sc->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;
}
prompt = "Passcode";
label_len = strlen(label);
prompt_len = strlen(prompt);
}
/* example:
Challenge for Digital Pathways mechanism: [134591]
Passcode:
*/
krb5int_buf_init_dynamic(&buf);
if (challenge_len) {
krb5int_buf_add_len(&buf, label, label_len);
krb5int_buf_add(&buf, ": [");
krb5int_buf_add_len(&buf, challenge, challenge_len);
krb5int_buf_add(&buf, "]\n");
}
krb5int_buf_add_len(&buf, prompt, prompt_len);
krb5int_buf_add(&buf, ": ");
return krb5int_buf_data(&buf);
}
/*
* This routine is the "obtain" function for the SAM_CHALLENGE
* preauthentication type. It presents the challenge...
*/
static krb5_error_code
obtain_sam_padata(krb5_context context, krb5_pa_data *in_padata, krb5_etype_info etype_info, krb5_keyblock *def_enc_key, git_key_proc key_proc, krb5_const_pointer key_seed, krb5_creds *creds, krb5_kdc_req *request, krb5_pa_data **out_padata)
{
krb5_error_code retval;
krb5_data * scratch = 0;
krb5_data tmpsam;
krb5_pa_data * pa;
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_keyblock * sam_use_key = 0;
char *prompt = 0, *passcode = 0;
sam_response.sam_enc_nonce_or_ts.ciphertext.data = 0;
tmpsam.length = in_padata->length;
tmpsam.data = (char *) in_padata->contents;
retval = decode_krb5_sam_challenge(&tmpsam, &sam_challenge);
if (retval)
return retval;
if (sam_challenge->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) {
retval = KRB5_SAM_UNSUPPORTED;
goto cleanup;
}
enc_sam_response_enc.sam_nonce = sam_challenge->sam_nonce;
if (!sam_challenge->sam_nonce) {
retval = krb5_us_timeofday(context,
&enc_sam_response_enc.sam_timestamp,
&enc_sam_response_enc.sam_usec);
if (retval)
goto cleanup;
sam_response.sam_patimestamp = enc_sam_response_enc.sam_timestamp;
}
if (sam_challenge->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) {
/* encrypt passcode in key by stuffing it here */
unsigned int pcsize = 256;
passcode = malloc(pcsize + 1);
if (passcode == NULL) {
retval = ENOMEM;
goto cleanup;
}
prompt = handle_sam_labels(sam_challenge);
if (prompt == NULL) {
retval = ENOMEM;
goto cleanup;
}
retval = krb5_read_password(context, prompt, 0, passcode, &pcsize);
if (retval)
goto cleanup;
enc_sam_response_enc.sam_sad.data = passcode;
enc_sam_response_enc.sam_sad.length = pcsize;
} else if (sam_challenge->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) {
prompt = handle_sam_labels(sam_challenge);
if (prompt == NULL) {
retval = ENOMEM;
goto cleanup;
}
retval = sam_get_pass_from_user(context, etype_info, key_proc,
key_seed, request, &sam_use_key,
prompt);
if (retval)
goto cleanup;
enc_sam_response_enc.sam_sad.length = 0;
} else {
/* what *was* it? */
retval = KRB5_SAM_UNSUPPORTED;
goto cleanup;
}
/* so at this point, either sam_use_key is generated from the passcode
* or enc_sam_response_enc.sam_sad is set to it, and we use
* def_enc_key instead. */
/* encode the encoded part of the response */
if ((retval = encode_krb5_enc_sam_response_enc(&enc_sam_response_enc,
&scratch)) != 0)
goto cleanup;
if ((retval = krb5_encrypt_helper(context,
sam_use_key?sam_use_key:def_enc_key,
0, scratch,
&sam_response.sam_enc_nonce_or_ts)))
goto cleanup;
krb5_free_data(context, scratch);
scratch = 0;
/* sam_enc_key is reserved for future use */
sam_response.sam_enc_key.ciphertext.length = 0;
/* 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;
if ((retval = encode_krb5_sam_response(&sam_response, &scratch)) != 0)
goto cleanup;
if ((pa = malloc(sizeof(krb5_pa_data))) == NULL) {
retval = ENOMEM;
goto cleanup;
}
pa->magic = KV5M_PA_DATA;
pa->pa_type = KRB5_PADATA_SAM_RESPONSE;
pa->length = scratch->length;
pa->contents = (krb5_octet *) scratch->data;
free(scratch);
scratch = NULL;
*out_padata = pa;
retval = 0;
cleanup:
krb5_free_data(context, scratch);
krb5_free_sam_challenge(context, sam_challenge);
free(prompt);
free(passcode);
free(sam_response.sam_enc_nonce_or_ts.ciphertext.data);
return retval;
}