#include "k5-int.h"
#include "k5-der.h"
#include "gssapiP_krb5.h"
static OM_uint32
kg_unseal_v1_iov(krb5_context context,
OM_uint32 *minor_status,
krb5_gss_ctx_id_rec *ctx,
gss_iov_buffer_desc *iov,
int iov_count,
size_t token_wrapper_len,
int *conf_state,
gss_qop_t *qop_state,
int toktype)
{
OM_uint32 code;
gss_iov_buffer_t header;
gss_iov_buffer_t trailer;
unsigned char *ptr;
int sealalg;
int signalg;
krb5_checksum md5cksum;
size_t cksum_len = 0;
size_t conflen = 0;
int direction;
krb5_ui_4 seqnum;
OM_uint32 retval;
size_t sumlen;
krb5_keyusage sign_usage = KG_USAGE_SIGN;
md5cksum.length = 0;
md5cksum.contents = NULL;
header = kg_locate_header_iov(iov, iov_count, toktype);
assert(header != NULL);
trailer = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER);
if (trailer != NULL && trailer->buffer.length != 0) {
*minor_status = (OM_uint32)KRB5_BAD_MSIZE;
return GSS_S_DEFECTIVE_TOKEN;
}
if (ctx->seq == NULL) {
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
if (header->buffer.length < token_wrapper_len + 22) {
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
ptr = (unsigned char *)header->buffer.value + token_wrapper_len;
signalg = ptr[0];
signalg |= ptr[1] << 8;
sealalg = ptr[2];
sealalg |= ptr[3] << 8;
if (ptr[4] != 0xFF || ptr[5] != 0xFF) {
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
if (toktype != KG_TOK_WRAP_MSG && sealalg != 0xFFFF) {
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
if (toktype == KG_TOK_WRAP_MSG &&
!(sealalg == 0xFFFF || sealalg == ctx->sealalg)) {
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
if ((ctx->sealalg == SEAL_ALG_NONE && signalg > 1) ||
(ctx->sealalg == SEAL_ALG_DES3KD &&
signalg != SGN_ALG_HMAC_SHA1_DES3_KD)||
(ctx->sealalg == SEAL_ALG_MICROSOFT_RC4 &&
signalg != SGN_ALG_HMAC_MD5)) {
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
switch (signalg) {
case SGN_ALG_HMAC_MD5:
cksum_len = 8;
if (toktype != KG_TOK_WRAP_MSG)
sign_usage = 15;
break;
case SGN_ALG_HMAC_SHA1_DES3_KD:
cksum_len = 20;
break;
default:
*minor_status = 0;
return GSS_S_DEFECTIVE_TOKEN;
}
code = kg_get_seq_num(context, ctx->seq, ptr + 14, ptr + 6, &direction,
&seqnum);
if (code != 0) {
*minor_status = code;
return GSS_S_BAD_SIG;
}
if (toktype == KG_TOK_WRAP_MSG) {
if (sealalg != 0xFFFF) {
if (ctx->sealalg == SEAL_ALG_MICROSOFT_RC4) {
unsigned char bigend_seqnum[4];
krb5_keyblock *enc_key;
size_t i;
store_32_be(seqnum, bigend_seqnum);
code = krb5_k_key_keyblock(context, ctx->enc, &enc_key);
if (code != 0) {
retval = GSS_S_FAILURE;
goto cleanup;
}
assert(enc_key->length == 16);
for (i = 0; i < enc_key->length; i++)
((char *)enc_key->contents)[i] ^= 0xF0;
code = kg_arcfour_docrypt_iov(context, enc_key, 0,
&bigend_seqnum[0], 4,
iov, iov_count);
krb5_free_keyblock(context, enc_key);
} else {
code = kg_decrypt_iov(context, 0,
((ctx->gss_flags & GSS_C_DCE_STYLE) != 0),
0 , 0 ,
ctx->enc, KG_USAGE_SEAL, NULL,
iov, iov_count);
}
if (code != 0) {
retval = GSS_S_FAILURE;
goto cleanup;
}
}
conflen = kg_confounder_size(context, ctx->enc->keyblock.enctype);
}
if (header->buffer.length != token_wrapper_len + 14 + cksum_len + conflen) {
retval = GSS_S_DEFECTIVE_TOKEN;
goto cleanup;
}
switch (signalg) {
case SGN_ALG_HMAC_MD5:
md5cksum.checksum_type = CKSUMTYPE_HMAC_MD5_ARCFOUR;
break;
case SGN_ALG_HMAC_SHA1_DES3_KD:
md5cksum.checksum_type = CKSUMTYPE_HMAC_SHA1_DES3;
break;
default:
abort();
}
code = krb5_c_checksum_length(context, md5cksum.checksum_type, &sumlen);
if (code != 0) {
retval = GSS_S_FAILURE;
goto cleanup;
}
md5cksum.length = sumlen;
code = kg_make_checksum_iov_v1(context, md5cksum.checksum_type,
cksum_len, ctx->seq, ctx->enc,
sign_usage, iov, iov_count, toktype,
&md5cksum);
if (code != 0) {
retval = GSS_S_FAILURE;
goto cleanup;
}
switch (signalg) {
case SGN_ALG_HMAC_SHA1_DES3_KD:
case SGN_ALG_HMAC_MD5:
code = k5_bcmp(md5cksum.contents, ptr + 14, cksum_len);
break;
default:
code = 0;
retval = GSS_S_DEFECTIVE_TOKEN;
goto cleanup;
break;
}
if (code != 0) {
code = 0;
retval = GSS_S_BAD_SIG;
goto cleanup;
}
if (toktype == KG_TOK_WRAP_MSG &&
(ctx->gss_flags & GSS_C_DCE_STYLE) == 0) {
retval = kg_fixup_padding_iov(&code, iov, iov_count);
if (retval != GSS_S_COMPLETE)
goto cleanup;
}
if (conf_state != NULL)
*conf_state = (sealalg != 0xFFFF);
if (qop_state != NULL)
*qop_state = GSS_C_QOP_DEFAULT;
if ((ctx->initiate && direction != 0xff) ||
(!ctx->initiate && direction != 0)) {
*minor_status = (OM_uint32)G_BAD_DIRECTION;
retval = GSS_S_BAD_SIG;
goto cleanup;
}
code = 0;
retval = g_seqstate_check(ctx->seqstate, (uint64_t)seqnum);
cleanup:
krb5_free_checksum_contents(context, &md5cksum);
*minor_status = code;
return retval;
}
static OM_uint32
kg_unseal_iov_token(OM_uint32 *minor_status,
krb5_gss_ctx_id_rec *ctx,
int *conf_state,
gss_qop_t *qop_state,
gss_iov_buffer_desc *iov,
int iov_count,
int toktype)
{
krb5_error_code code;
krb5_context context = ctx->k5_context;
struct k5input in;
gss_OID_desc mech;
size_t tlen, header_tlen;
gss_iov_buffer_t header;
gss_iov_buffer_t padding;
gss_iov_buffer_t trailer;
int toktype2;
header = kg_locate_header_iov(iov, iov_count, toktype);
if (header == NULL) {
*minor_status = EINVAL;
return GSS_S_FAILURE;
}
padding = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_PADDING);
trailer = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER);
tlen = header->buffer.length;
if ((ctx->gss_flags & GSS_C_DCE_STYLE) == 0 &&
toktype == KG_TOK_WRAP_MSG) {
size_t data_length, assoc_data_length;
kg_iov_msglen(iov, iov_count, &data_length, &assoc_data_length);
tlen += data_length - assoc_data_length;
if (padding != NULL)
tlen += padding->buffer.length;
if (trailer != NULL)
tlen += trailer->buffer.length;
}
k5_input_init(&in, header->buffer.value, header->buffer.length);
if (g_get_token_header(&in, &mech, &header_tlen)) {
if (!g_OID_equal(&mech, ctx->mech_used) || header_tlen != tlen) {
*minor_status = G_BAD_TOK_HEADER;
return GSS_S_DEFECTIVE_TOKEN;
}
}
toktype2 = k5_input_get_uint16_be(&in);
switch (toktype2) {
case KG2_TOK_MIC_MSG:
case KG2_TOK_WRAP_MSG:
case KG2_TOK_DEL_CTX:
code = gss_krb5int_unseal_v3_iov(context, minor_status, ctx, iov, iov_count,
conf_state, qop_state, toktype);
break;
case KG_TOK_MIC_MSG:
case KG_TOK_WRAP_MSG:
case KG_TOK_DEL_CTX:
code = kg_unseal_v1_iov(context, minor_status, ctx, iov, iov_count,
(size_t)(in.ptr - (unsigned char *)header->buffer.value),
conf_state, qop_state, toktype);
break;
default:
*minor_status = (OM_uint32)G_BAD_TOK_HEADER;
code = GSS_S_DEFECTIVE_TOKEN;
break;
}
if (code != 0)
save_error_info(*minor_status, context);
return code;
}
static OM_uint32
kg_unseal_stream_iov(OM_uint32 *minor_status,
krb5_gss_ctx_id_rec *ctx,
int *conf_state,
gss_qop_t *qop_state,
gss_iov_buffer_desc *iov,
int iov_count,
int toktype)
{
struct k5input in;
unsigned char *ptr;
unsigned int bodysize;
OM_uint32 code = 0, major_status = GSS_S_FAILURE;
krb5_context context = ctx->k5_context;
int conf_req_flag, toktype2;
int i = 0, j;
gss_iov_buffer_desc *tiov = NULL;
gss_iov_buffer_t stream, data = NULL;
gss_iov_buffer_t theader, tdata = NULL, tpadding, ttrailer;
assert(toktype == KG_TOK_WRAP_MSG);
if (toktype != KG_TOK_WRAP_MSG || (ctx->gss_flags & GSS_C_DCE_STYLE)) {
code = EINVAL;
goto cleanup;
}
stream = kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM);
assert(stream != NULL);
ptr = (unsigned char *)stream->buffer.value;
k5_input_init(&in, stream->buffer.value, stream->buffer.length);
(void)g_verify_token_header(&in, ctx->mech_used);
toktype2 = k5_input_get_uint16_be(&in);
if (in.status) {
*minor_status = (OM_uint32)G_BAD_TOK_HEADER;
return GSS_S_DEFECTIVE_TOKEN;
}
ptr = (uint8_t *)in.ptr;
bodysize = in.len;
tiov = (gss_iov_buffer_desc *)calloc((size_t)iov_count + 2, sizeof(gss_iov_buffer_desc));
if (tiov == NULL) {
code = ENOMEM;
goto cleanup;
}
theader = &tiov[i++];
theader->type = GSS_IOV_BUFFER_TYPE_HEADER;
theader->buffer.value = stream->buffer.value;
theader->buffer.length = ptr - (unsigned char *)stream->buffer.value;
if (bodysize < 14 ||
stream->buffer.length != theader->buffer.length + bodysize) {
major_status = GSS_S_DEFECTIVE_TOKEN;
goto cleanup;
}
theader->buffer.length += 14;
for (j = 0; j < iov_count; j++) {
OM_uint32 type = GSS_IOV_BUFFER_TYPE(iov[j].type);
if (type == GSS_IOV_BUFFER_TYPE_DATA) {
if (data != NULL) {
code = EINVAL;
goto cleanup;
}
data = &iov[j];
tdata = &tiov[i];
}
if (type == GSS_IOV_BUFFER_TYPE_DATA ||
type == GSS_IOV_BUFFER_TYPE_SIGN_ONLY)
tiov[i++] = iov[j];
}
if (data == NULL) {
code = EINVAL;
goto cleanup;
}
tpadding = &tiov[i++];
tpadding->type = GSS_IOV_BUFFER_TYPE_PADDING;
tpadding->buffer.length = 0;
tpadding->buffer.value = NULL;
ttrailer = &tiov[i++];
ttrailer->type = GSS_IOV_BUFFER_TYPE_TRAILER;
switch (toktype2) {
case KG2_TOK_MIC_MSG:
case KG2_TOK_WRAP_MSG:
case KG2_TOK_DEL_CTX: {
size_t ec, rrc;
krb5_enctype enctype;
unsigned int k5_headerlen = 0;
unsigned int k5_trailerlen = 0;
if (ctx->have_acceptor_subkey)
enctype = ctx->acceptor_subkey->keyblock.enctype;
else
enctype = ctx->subkey->keyblock.enctype;
conf_req_flag = ((ptr[0] & FLAG_WRAP_CONFIDENTIAL) != 0);
ec = conf_req_flag ? load_16_be(ptr + 2) : 0;
rrc = load_16_be(ptr + 4);
if (rrc != 0) {
if (!gss_krb5int_rotate_left((unsigned char *)stream->buffer.value + 16,
stream->buffer.length - 16, rrc)) {
code = ENOMEM;
goto cleanup;
}
store_16_be(0, ptr + 4);
}
if (conf_req_flag) {
code = krb5_c_crypto_length(context, enctype, KRB5_CRYPTO_TYPE_HEADER, &k5_headerlen);
if (code != 0)
goto cleanup;
theader->buffer.length += k5_headerlen;
}
code = krb5_c_crypto_length(context, enctype,
conf_req_flag ? KRB5_CRYPTO_TYPE_TRAILER : KRB5_CRYPTO_TYPE_CHECKSUM,
&k5_trailerlen);
if (code != 0)
goto cleanup;
ttrailer->buffer.length = ec + (conf_req_flag ? 16 : 0 ) + k5_trailerlen;
ttrailer->buffer.value = (unsigned char *)stream->buffer.value +
stream->buffer.length - ttrailer->buffer.length;
break;
}
case KG_TOK_MIC_MSG:
case KG_TOK_WRAP_MSG:
case KG_TOK_DEL_CTX:
theader->buffer.length += ctx->cksum_size +
kg_confounder_size(context, ctx->enc->keyblock.enctype);
tpadding->buffer.length = 1;
tpadding->buffer.value = (unsigned char *)stream->buffer.value + stream->buffer.length - 1;
ttrailer->buffer.length = 0;
ttrailer->buffer.value = NULL;
break;
default:
code = (OM_uint32)G_BAD_TOK_HEADER;
major_status = GSS_S_DEFECTIVE_TOKEN;
goto cleanup;
break;
}
if (stream->buffer.length < theader->buffer.length +
tpadding->buffer.length +
ttrailer->buffer.length)
{
code = (OM_uint32)KRB5_BAD_MSIZE;
major_status = GSS_S_DEFECTIVE_TOKEN;
goto cleanup;
}
tdata->buffer.length = stream->buffer.length - ttrailer->buffer.length -
tpadding->buffer.length - theader->buffer.length;
assert(data != NULL);
if (data->type & GSS_IOV_BUFFER_FLAG_ALLOCATE) {
code = kg_allocate_iov(tdata, tdata->buffer.length);
if (code != 0)
goto cleanup;
memcpy(tdata->buffer.value,
(unsigned char *)stream->buffer.value + theader->buffer.length, tdata->buffer.length);
} else
tdata->buffer.value = (unsigned char *)stream->buffer.value + theader->buffer.length;
assert(i <= iov_count + 2);
major_status = kg_unseal_iov_token(&code, ctx, conf_state, qop_state,
tiov, i, toktype);
if (major_status == GSS_S_COMPLETE)
*data = *tdata;
else
kg_release_iov(tdata, 1);
cleanup:
if (tiov != NULL)
free(tiov);
*minor_status = code;
return major_status;
}
OM_uint32
kg_unseal_iov(OM_uint32 *minor_status,
gss_ctx_id_t context_handle,
int *conf_state,
gss_qop_t *qop_state,
gss_iov_buffer_desc *iov,
int iov_count,
int toktype)
{
krb5_gss_ctx_id_rec *ctx;
OM_uint32 code;
ctx = (krb5_gss_ctx_id_rec *)context_handle;
if (ctx->terminated || !ctx->established) {
*minor_status = KG_CTX_INCOMPLETE;
return GSS_S_NO_CONTEXT;
}
if (kg_locate_iov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM) != NULL) {
code = kg_unseal_stream_iov(minor_status, ctx, conf_state, qop_state,
iov, iov_count, toktype);
} else {
code = kg_unseal_iov_token(minor_status, ctx, conf_state, qop_state,
iov, iov_count, toktype);
}
return code;
}
OM_uint32 KRB5_CALLCONV
krb5_gss_unwrap_iov(OM_uint32 *minor_status,
gss_ctx_id_t context_handle,
int *conf_state,
gss_qop_t *qop_state,
gss_iov_buffer_desc *iov,
int iov_count)
{
OM_uint32 major_status;
major_status = kg_unseal_iov(minor_status, context_handle,
conf_state, qop_state,
iov, iov_count, KG_TOK_WRAP_MSG);
return major_status;
}
OM_uint32 KRB5_CALLCONV
krb5_gss_verify_mic_iov(OM_uint32 *minor_status,
gss_ctx_id_t context_handle,
gss_qop_t *qop_state,
gss_iov_buffer_desc *iov,
int iov_count)
{
OM_uint32 major_status;
major_status = kg_unseal_iov(minor_status, context_handle,
NULL, qop_state,
iov, iov_count, KG_TOK_MIC_MSG);
return major_status;
}