From: Greg Hudson Date: Mon, 24 Jun 2024 19:46:50 +0000 (-0400) Subject: Refactor GSS per-message token parsing X-Git-Tag: krb5-1.22-beta1~82 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F1362%2Fhead;p=thirdparty%2Fkrb5.git Refactor GSS per-message token parsing Replace kg_unseal_v1() and gss_krb5int_unseal_token_v3() with new functions using current coding practices. Notable differences include: * The new functions use k5input for improved safety. * The new functions do not modify the input buffer. * The new functions will never try to allocate zero bytes of memory. * There are separate functions for unwrap and verify_mic, which means there is no message_buffer parameter acting conditionally as an input or output. --- diff --git a/src/lib/gssapi/krb5/Makefile.in b/src/lib/gssapi/krb5/Makefile.in index be43995686..10e7961f11 100644 --- a/src/lib/gssapi/krb5/Makefile.in +++ b/src/lib/gssapi/krb5/Makefile.in @@ -59,8 +59,9 @@ SRCS = \ $(srcdir)/k5sealiov.c \ $(srcdir)/k5sealv3.c \ $(srcdir)/k5sealv3iov.c \ - $(srcdir)/k5unseal.c \ $(srcdir)/k5unsealiov.c \ + $(srcdir)/unwrap.c \ + $(srcdir)/verify_mic.c \ $(srcdir)/krb5_gss_glue.c \ $(srcdir)/lucid_context.c \ $(srcdir)/naming_exts.c \ @@ -112,8 +113,9 @@ OBJS = \ $(OUTPRE)k5sealiov.$(OBJEXT) \ $(OUTPRE)k5sealv3.$(OBJEXT) \ $(OUTPRE)k5sealv3iov.$(OBJEXT) \ - $(OUTPRE)k5unseal.$(OBJEXT) \ $(OUTPRE)k5unsealiov.$(OBJEXT) \ + $(OUTPRE)unwrap.$(OBJEXT) \ + $(OUTPRE)verify_mic.$(OBJEXT) \ $(OUTPRE)krb5_gss_glue.$(OBJEXT) \ $(OUTPRE)lucid_context.$(OBJEXT) \ $(OUTPRE)naming_exts.$(OBJEXT) \ @@ -168,8 +170,9 @@ STLIBOBJS = \ k5sealiov.o \ k5sealv3.o \ k5sealv3iov.o \ - k5unseal.o \ k5unsealiov.o \ + unwrap.o \ + verify_mic.o \ krb5_gss_glue.o \ lucid_context.o \ naming_exts.o \ diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index 3d6aaccf72..da7c1cfac0 100644 --- a/src/lib/gssapi/krb5/gssapiP_krb5.h +++ b/src/lib/gssapi/krb5/gssapiP_krb5.h @@ -266,10 +266,9 @@ krb5_error_code kg_make_seq_num (krb5_context context, int direction, krb5_ui_4 seqnum, unsigned char *cksum, unsigned char *buf); -krb5_error_code kg_get_seq_num (krb5_context context, - krb5_key key, - unsigned char *cksum, unsigned char *buf, int *direction, - krb5_ui_4 *seqnum); +krb5_error_code kg_get_seq_num (krb5_context context, krb5_key key, + const uint8_t *cksum, const uint8_t *buf, + int *direction, krb5_ui_4 *seqnum); krb5_error_code kg_make_seed (krb5_context context, krb5_key key, @@ -320,13 +319,23 @@ kg_arcfour_docrypt_iov (krb5_context context, gss_iov_buffer_desc *iov, int iov_count); -krb5_error_code kg_decrypt (krb5_context context, - krb5_key key, int usage, - krb5_pointer iv, - krb5_const_pointer in, - krb5_pointer out, +krb5_error_code kg_decrypt (krb5_context context, krb5_key key, int usage, + const uint8_t *iv, const uint8_t *in, uint8_t *out, unsigned int length); +krb5_boolean +kg_verify_checksum_v1(krb5_context context, uint16_t signalg, krb5_key key, + krb5_keyusage usage, const uint8_t *header, + const uint8_t *data, size_t data_len, + const uint8_t *cksum, size_t cksum_len); + +krb5_boolean +kg_verify_checksum_v3(krb5_context context, krb5_key key, krb5_keyusage usage, + krb5_cksumtype cksumtype, + uint16_t toktype, uint8_t flags, uint64_t seqnum, + const uint8_t *data, size_t data_len, + const uint8_t *cksum, size_t cksum_len); + krb5_error_code kg_decrypt_iov (krb5_context context, int proto, int dce_style, size_t ec, size_t rrc, @@ -335,6 +344,11 @@ krb5_error_code kg_decrypt_iov (krb5_context context, gss_iov_buffer_desc *iov, int iov_count); +OM_uint32 +kg_verify_mic_v1(krb5_context context, OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, uint16_t exp_toktype, + struct k5input *in, gss_buffer_t message); + OM_uint32 kg_seal (OM_uint32 *minor_status, gss_ctx_id_t context_handle, int conf_req_flag, @@ -344,14 +358,6 @@ OM_uint32 kg_seal (OM_uint32 *minor_status, gss_buffer_t output_message_buffer, int toktype); -OM_uint32 kg_unseal (OM_uint32 *minor_status, - gss_ctx_id_t context_handle, - gss_buffer_t input_token_buffer, - gss_buffer_t message_buffer, - int *conf_state, - gss_qop_t *qop_state, - int toktype); - OM_uint32 kg_seal_size (OM_uint32 *minor_status, gss_ctx_id_t context_handle, int conf_req_flag, @@ -925,15 +931,6 @@ krb5_error_code gss_krb5int_make_seal_token_v3(krb5_context, gss_buffer_t, int, int); -OM_uint32 gss_krb5int_unseal_token_v3(krb5_context *contextptr, - OM_uint32 *minor_status, - krb5_gss_ctx_id_rec *ctx, - unsigned char *ptr, - unsigned int bodysize, - gss_buffer_t message_buffer, - int *conf_state, gss_qop_t *qop_state, - int toktype); - int gss_krb5int_rotate_left (void *ptr, size_t bufsiz, size_t rc); /* naming_exts.c */ diff --git a/src/lib/gssapi/krb5/k5sealv3.c b/src/lib/gssapi/krb5/k5sealv3.c index d3210c1107..f50beadc25 100644 --- a/src/lib/gssapi/krb5/k5sealv3.c +++ b/src/lib/gssapi/krb5/k5sealv3.c @@ -285,230 +285,3 @@ cleanup: gssalloc_free(outbuf); return err; } - -/* message_buffer is an input if SIGN, output if SEAL, and ignored if DEL_CTX - conf_state is only valid if SEAL. */ - -OM_uint32 -gss_krb5int_unseal_token_v3(krb5_context *contextptr, - OM_uint32 *minor_status, - krb5_gss_ctx_id_rec *ctx, - unsigned char *ptr, unsigned int bodysize, - gss_buffer_t message_buffer, - int *conf_state, gss_qop_t *qop_state, int toktype) -{ - krb5_context context = *contextptr; - krb5_data plain = empty_data(); - uint64_t seqnum; - size_t ec, rrc; - int key_usage; - unsigned char acceptor_flag; - krb5_checksum sum; - krb5_error_code err; - krb5_boolean valid; - krb5_key key; - krb5_cksumtype cksumtype; - - if (qop_state) - *qop_state = GSS_C_QOP_DEFAULT; - - acceptor_flag = ctx->initiate ? FLAG_SENDER_IS_ACCEPTOR : 0; - key_usage = (toktype == KG_TOK_WRAP_MSG - ? (!ctx->initiate - ? KG_USAGE_INITIATOR_SEAL - : KG_USAGE_ACCEPTOR_SEAL) - : (!ctx->initiate - ? KG_USAGE_INITIATOR_SIGN - : KG_USAGE_ACCEPTOR_SIGN)); - - /* Oops. I wrote this code assuming ptr would be at the start of - the token header. */ - ptr -= 2; - bodysize += 2; - - if (bodysize < 16) { - defective: - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - if ((ptr[2] & FLAG_SENDER_IS_ACCEPTOR) != acceptor_flag) { - *minor_status = (OM_uint32)G_BAD_DIRECTION; - return GSS_S_BAD_SIG; - } - - /* Two things to note here. - - First, we can't really enforce the use of the acceptor's subkey, - if we're the acceptor; the initiator may have sent messages - before getting the subkey. We could probably enforce it if - we're the initiator. - - Second, if someone tweaks the code to not set the flag telling - the krb5 library to generate a new subkey in the AP-REP - message, the MIT library may include a subkey anyways -- - namely, a copy of the AP-REQ subkey, if it was provided. So - the initiator may think we wanted a subkey, and set the flag, - even though we weren't trying to set the subkey. The "other" - key, the one not asserted by the acceptor, will have the same - value in that case, though, so we can just ignore the flag. */ - if (ctx->have_acceptor_subkey && (ptr[2] & FLAG_ACCEPTOR_SUBKEY)) { - key = ctx->acceptor_subkey; - cksumtype = ctx->acceptor_subkey_cksumtype; - } else { - key = ctx->subkey; - cksumtype = ctx->cksumtype; - } - assert(key != NULL); - - if (toktype == KG_TOK_WRAP_MSG) { - if (load_16_be(ptr) != KG2_TOK_WRAP_MSG) - goto defective; - if (ptr[3] != 0xff) - goto defective; - ec = load_16_be(ptr+4); - rrc = load_16_be(ptr+6); - seqnum = load_64_be(ptr+8); - if (!gss_krb5int_rotate_left(ptr+16, bodysize-16, rrc)) { - no_mem: - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - if (ptr[2] & FLAG_WRAP_CONFIDENTIAL) { - /* confidentiality */ - krb5_enc_data cipher; - unsigned char *althdr; - - if (conf_state) - *conf_state = 1; - /* Do we have no decrypt_size function? - - For all current cryptosystems, the ciphertext size will - be larger than the plaintext size. */ - cipher.enctype = key->keyblock.enctype; - cipher.ciphertext.length = bodysize - 16; - cipher.ciphertext.data = (char *)ptr + 16; - plain.length = bodysize - 16; - plain.data = gssalloc_malloc(plain.length); - if (plain.data == NULL) - goto no_mem; - err = krb5_k_decrypt(context, key, key_usage, 0, - &cipher, &plain); - if (err) { - gssalloc_free(plain.data); - goto error; - } - /* Don't use bodysize here! Use the fact that - cipher.ciphertext.length has been adjusted to the - correct length. */ - if (plain.length < 16 + ec) { - free(plain.data); - goto defective; - } - althdr = (unsigned char *)plain.data + plain.length - 16; - if (load_16_be(althdr) != KG2_TOK_WRAP_MSG - || althdr[2] != ptr[2] - || althdr[3] != ptr[3] - || load_16_be(althdr+4) != ec - || memcmp(althdr+8, ptr+8, 8)) { - free(plain.data); - goto defective; - } - message_buffer->value = plain.data; - message_buffer->length = plain.length - ec - 16; - if(message_buffer->length == 0) { - gssalloc_free(message_buffer->value); - message_buffer->value = NULL; - } - } else { - size_t cksumsize; - - err = krb5_c_checksum_length(context, cksumtype, &cksumsize); - if (err) - goto error; - - /* no confidentiality */ - if (conf_state) - *conf_state = 0; - if (ec + 16 < ec) - /* overflow check */ - goto defective; - if (ec + 16 > bodysize) - goto defective; - /* We have: header | msg | cksum. - We need cksum(msg | header). - Rotate the first two. */ - store_16_be(0, ptr+4); - store_16_be(0, ptr+6); - plain = make_data(ptr, bodysize - ec); - if (!gss_krb5int_rotate_left(ptr, bodysize-ec, 16)) - goto no_mem; - sum.length = ec; - if (sum.length != cksumsize) { - *minor_status = 0; - return GSS_S_BAD_SIG; - } - sum.contents = ptr+bodysize-ec; - sum.checksum_type = cksumtype; - err = krb5_k_verify_checksum(context, key, key_usage, - &plain, &sum, &valid); - if (err) - goto error; - if (!valid) { - *minor_status = 0; - return GSS_S_BAD_SIG; - } - message_buffer->length = plain.length - 16; - message_buffer->value = gssalloc_malloc(message_buffer->length); - if (message_buffer->value == NULL) - goto no_mem; - memcpy(message_buffer->value, plain.data, message_buffer->length); - } - err = g_seqstate_check(ctx->seqstate, seqnum); - *minor_status = 0; - return err; - } else if (toktype == KG_TOK_MIC_MSG) { - /* wrap token, no confidentiality */ - if (load_16_be(ptr) != KG2_TOK_MIC_MSG) - goto defective; - verify_mic_1: - if (ptr[3] != 0xff) - goto defective; - if (load_32_be(ptr+4) != 0xffffffffL) - goto defective; - seqnum = load_64_be(ptr+8); - plain.length = message_buffer->length + 16; - plain.data = malloc(plain.length); - if (plain.data == NULL) - goto no_mem; - if (message_buffer->length) - memcpy(plain.data, message_buffer->value, message_buffer->length); - memcpy(plain.data + message_buffer->length, ptr, 16); - sum.length = bodysize - 16; - sum.contents = ptr + 16; - sum.checksum_type = cksumtype; - err = krb5_k_verify_checksum(context, key, key_usage, - &plain, &sum, &valid); - free(plain.data); - plain.data = NULL; - if (err) { - error: - *minor_status = err; - save_error_info(*minor_status, context); - return GSS_S_BAD_SIG; /* XXX */ - } - if (!valid) { - *minor_status = 0; - return GSS_S_BAD_SIG; - } - err = g_seqstate_check(ctx->seqstate, seqnum); - *minor_status = 0; - return err; - } else if (toktype == KG_TOK_DEL_CTX) { - if (load_16_be(ptr) != KG2_TOK_DEL_CTX) - goto defective; - message_buffer = (gss_buffer_t)&empty_message; - goto verify_mic_1; - } else { - goto defective; - } -} diff --git a/src/lib/gssapi/krb5/k5unseal.c b/src/lib/gssapi/krb5/k5unseal.c deleted file mode 100644 index 5e57487da6..0000000000 --- a/src/lib/gssapi/krb5/k5unseal.c +++ /dev/null @@ -1,433 +0,0 @@ -/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - * Copyright 2001, 2007 by the Massachusetts Institute of Technology. - * Copyright 1993 by OpenVision Technologies, Inc. - * - * Permission to use, copy, modify, distribute, and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appears in all copies and - * that both that copyright notice and this permission notice appear in - * supporting documentation, and that the name of OpenVision not be used - * in advertising or publicity pertaining to distribution of the software - * without specific, written prior permission. OpenVision makes no - * representations about the suitability of this software for any - * purpose. It is provided "as is" without express or implied warranty. - * - * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF - * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -/* - * Copyright (C) 1998 by the FundsXpress, INC. - * - * 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 FundsXpress. not be used in advertising or publicity pertaining - * to distribution of the software without specific, written prior - * permission. FundsXpress makes no representations about the suitability of - * this software for any purpose. It is provided "as is" without express - * or implied warranty. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - */ - -#include "gssapiP_krb5.h" -#ifdef HAVE_MEMORY_H -#include -#endif -#include - -/* message_buffer is an input if SIGN, output if SEAL, and ignored if DEL_CTX - conf_state is only valid if SEAL. */ - -static OM_uint32 -kg_unseal_v1(krb5_context context, OM_uint32 *minor_status, - krb5_gss_ctx_id_rec *ctx, unsigned char *ptr, int bodysize, - gss_buffer_t message_buffer, int *conf_state, - gss_qop_t *qop_state, int toktype) -{ - krb5_error_code code; - int conflen = 0; - int signalg; - int sealalg; - int bad_pad = 0; - gss_buffer_desc token; - krb5_checksum md5cksum; - krb5_data plaind; - char *data_ptr; - unsigned char *plain; - unsigned int cksum_len = 0; - size_t plainlen; - int direction; - krb5_ui_4 seqnum; - OM_uint32 retval; - size_t sumlen; - size_t padlen; - krb5_keyusage sign_usage = KG_USAGE_SIGN; - - if (toktype == KG_TOK_SEAL_MSG) { - message_buffer->length = 0; - message_buffer->value = NULL; - } - - /* Sanity checks */ - - if (ctx->seq == NULL) { - /* ctx was established using a newer enctype, and cannot process RFC - * 1964 tokens. */ - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - - if ((bodysize < 22) || (ptr[4] != 0xff) || (ptr[5] != 0xff)) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - - signalg = ptr[0] + (ptr[1]<<8); - sealalg = ptr[2] + (ptr[3]<<8); - - if ((toktype != KG_TOK_SEAL_MSG) && - (sealalg != 0xffff)) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - - /* in the current spec, there is only one valid seal algorithm per - key type, so a simple comparison is ok */ - - if ((toktype == KG_TOK_SEAL_MSG) && - !((sealalg == 0xffff) || - (sealalg == ctx->sealalg))) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - - /* there are several mappings of seal algorithms to sign algorithms, - but few enough that we can try them all. */ - - 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_SEAL_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; - } - - if ((size_t)bodysize < 14 + cksum_len) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - - /* get the token parameters */ - - if ((code = kg_get_seq_num(context, ctx->seq, ptr+14, ptr+6, &direction, - &seqnum))) { - *minor_status = code; - return(GSS_S_BAD_SIG); - } - - /* decode the message, if SEAL */ - - if (toktype == KG_TOK_SEAL_MSG) { - size_t tmsglen = bodysize-(14+cksum_len); - if (sealalg != 0xffff) { - if ((plain = (unsigned char *) xmalloc(tmsglen)) == NULL) { - *minor_status = ENOMEM; - return(GSS_S_FAILURE); - } - if (ctx->sealalg == SEAL_ALG_MICROSOFT_RC4) { - unsigned char bigend_seqnum[4]; - krb5_keyblock *enc_key; - int i; - store_32_be(seqnum, bigend_seqnum); - code = krb5_k_key_keyblock(context, ctx->enc, &enc_key); - if (code) - { - xfree(plain); - *minor_status = code; - return(GSS_S_FAILURE); - } - - assert (enc_key->length == 16); - for (i = 0; i <= 15; i++) - ((char *) enc_key->contents)[i] ^=0xf0; - code = kg_arcfour_docrypt (enc_key, 0, - &bigend_seqnum[0], 4, - ptr+14+cksum_len, tmsglen, - plain); - krb5_free_keyblock (context, enc_key); - } else { - code = kg_decrypt(context, ctx->enc, KG_USAGE_SEAL, NULL, - ptr+14+cksum_len, plain, tmsglen); - } - if (code) { - xfree(plain); - *minor_status = code; - return(GSS_S_FAILURE); - } - } else { - plain = ptr+14+cksum_len; - } - - plainlen = tmsglen; - - conflen = kg_confounder_size(context, ctx->enc->keyblock.enctype); - if (tmsglen < (size_t)conflen) { - if (sealalg != 0xffff) - xfree(plain); - *minor_status = 0; - return(GSS_S_DEFECTIVE_TOKEN); - } - padlen = plain[tmsglen - 1]; - if (tmsglen - conflen < padlen) { - /* Don't error out yet, to avoid padding oracle attacks. We will - * treat this as a checksum failure later on. */ - padlen = 0; - bad_pad = 1; - } - token.length = tmsglen - conflen - padlen; - - if (token.length) { - if ((token.value = (void *) gssalloc_malloc(token.length)) == NULL) { - if (sealalg != 0xffff) - xfree(plain); - *minor_status = ENOMEM; - return(GSS_S_FAILURE); - } - memcpy(token.value, plain+conflen, token.length); - } else { - token.value = NULL; - } - } else if (toktype == KG_TOK_SIGN_MSG) { - token = *message_buffer; - plain = token.value; - plainlen = token.length; - } else { - token.length = 0; - token.value = NULL; - plain = token.value; - plainlen = token.length; - } - - /* compute the checksum of the message */ - - /* initialize the the cksum */ - 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) - return(code); - md5cksum.length = sumlen; - - switch (signalg) { - default: - *minor_status = 0; - return(GSS_S_DEFECTIVE_TOKEN); - - case SGN_ALG_HMAC_SHA1_DES3_KD: - case SGN_ALG_HMAC_MD5: - /* compute the checksum of the message */ - - /* 8 = bytes of token body to be checksummed according to spec */ - - if (! (data_ptr = xmalloc(8 + plainlen))) { - if (sealalg != 0xffff) - xfree(plain); - if (toktype == KG_TOK_SEAL_MSG) - gssalloc_free(token.value); - *minor_status = ENOMEM; - return(GSS_S_FAILURE); - } - - (void) memcpy(data_ptr, ptr-2, 8); - - (void) memcpy(data_ptr+8, plain, plainlen); - - plaind.length = 8 + plainlen; - plaind.data = data_ptr; - code = krb5_k_make_checksum(context, md5cksum.checksum_type, - ctx->seq, sign_usage, - &plaind, &md5cksum); - xfree(data_ptr); - - if (code) { - if (toktype == KG_TOK_SEAL_MSG) - gssalloc_free(token.value); - *minor_status = code; - return(GSS_S_FAILURE); - } - - code = k5_bcmp(md5cksum.contents, ptr + 14, cksum_len); - break; - } - - krb5_free_checksum_contents(context, &md5cksum); - if (sealalg != 0xffff) - xfree(plain); - - /* compare the computed checksum against the transmitted checksum */ - - if (code || bad_pad) { - if (toktype == KG_TOK_SEAL_MSG) - gssalloc_free(token.value); - *minor_status = 0; - return(GSS_S_BAD_SIG); - } - - - /* It got through unscathed. Make sure the context is unexpired. */ - - if (toktype == KG_TOK_SEAL_MSG) - *message_buffer = token; - - if (conf_state) - *conf_state = (sealalg != 0xffff); - - if (qop_state) - *qop_state = GSS_C_QOP_DEFAULT; - - /* do sequencing checks */ - - if ((ctx->initiate && direction != 0xff) || - (!ctx->initiate && direction != 0)) { - if (toktype == KG_TOK_SEAL_MSG) { - gssalloc_free(token.value); - message_buffer->value = NULL; - message_buffer->length = 0; - } - *minor_status = (OM_uint32)G_BAD_DIRECTION; - return(GSS_S_BAD_SIG); - } - - retval = g_seqstate_check(ctx->seqstate, (uint64_t)seqnum); - - /* success or ordering violation */ - - *minor_status = 0; - return(retval); -} - -/* message_buffer is an input if SIGN, output if SEAL, and ignored if DEL_CTX - conf_state is only valid if SEAL. */ - -OM_uint32 -kg_unseal(OM_uint32 *minor_status, gss_ctx_id_t context_handle, - gss_buffer_t input_token_buffer, gss_buffer_t message_buffer, - int *conf_state, gss_qop_t *qop_state, int toktype) -{ - krb5_gss_ctx_id_rec *ctx; - int toktype2; - OM_uint32 ret; - struct k5input in; - - ctx = (krb5_gss_ctx_id_rec *) context_handle; - - if (ctx->terminated || !ctx->established) { - *minor_status = KG_CTX_INCOMPLETE; - return(GSS_S_NO_CONTEXT); - } - - /* parse the token, leave the data in message_buffer, setting conf_state */ - - /* verify the header */ - - k5_input_init(&in, input_token_buffer->value, input_token_buffer->length); - (void)g_verify_token_header(&in, ctx->mech_used); - toktype2 = k5_input_get_uint16_be(&in); - - switch (toktype2) { - case KG2_TOK_MIC_MSG: - case KG2_TOK_WRAP_MSG: - case KG2_TOK_DEL_CTX: - ret = gss_krb5int_unseal_token_v3(&ctx->k5_context, minor_status, ctx, - (uint8_t *)in.ptr, in.len, - message_buffer, conf_state, - qop_state, toktype); - break; - case KG_TOK_MIC_MSG: - case KG_TOK_WRAP_MSG: - case KG_TOK_DEL_CTX: - ret = kg_unseal_v1(ctx->k5_context, minor_status, ctx, - (uint8_t *)in.ptr, in.len, message_buffer, - conf_state, qop_state, toktype); - break; - default: - *minor_status = (OM_uint32)G_BAD_TOK_HEADER; - ret = GSS_S_DEFECTIVE_TOKEN; - break; - } - - if (ret != 0) - save_error_info (*minor_status, ctx->k5_context); - - return ret; -} - -OM_uint32 KRB5_CALLCONV -krb5_gss_unwrap(OM_uint32 *minor_status, gss_ctx_id_t context_handle, - gss_buffer_t input_message_buffer, - gss_buffer_t output_message_buffer, int *conf_state, - gss_qop_t *qop_state) -{ - OM_uint32 rstat; - - rstat = kg_unseal(minor_status, context_handle, - input_message_buffer, output_message_buffer, - conf_state, qop_state, KG_TOK_WRAP_MSG); - return(rstat); -} - -OM_uint32 KRB5_CALLCONV -krb5_gss_verify_mic(OM_uint32 *minor_status, gss_ctx_id_t context_handle, - gss_buffer_t message_buffer, gss_buffer_t token_buffer, - gss_qop_t *qop_state) -{ - OM_uint32 rstat; - - rstat = kg_unseal(minor_status, context_handle, - token_buffer, message_buffer, - NULL, qop_state, KG_TOK_MIC_MSG); - return(rstat); -} diff --git a/src/lib/gssapi/krb5/process_context_token.c b/src/lib/gssapi/krb5/process_context_token.c index 67805fba78..e346d1ba2b 100644 --- a/src/lib/gssapi/krb5/process_context_token.c +++ b/src/lib/gssapi/krb5/process_context_token.c @@ -34,6 +34,8 @@ krb5_gss_process_context_token(OM_uint32 *minor_status, { krb5_gss_ctx_id_rec *ctx; OM_uint32 majerr; + struct k5input in; + gss_buffer_desc empty = { 0 }; ctx = (krb5_gss_ctx_id_t) context_handle; @@ -49,13 +51,15 @@ krb5_gss_process_context_token(OM_uint32 *minor_status, return(GSS_S_DEFECTIVE_TOKEN); } - /* "unseal" the token */ + k5_input_init(&in, token_buffer->value, token_buffer->length); + (void)g_verify_token_header(&in, ctx->mech_used); - if (GSS_ERROR(majerr = kg_unseal(minor_status, context_handle, - token_buffer, - GSS_C_NO_BUFFER, NULL, NULL, - KG_TOK_DEL_CTX))) + majerr = kg_verify_mic_v1(ctx->k5_context, minor_status, ctx, + KG_TOK_DEL_CTX, &in, &empty); + if (GSS_ERROR(majerr)) { + save_error_info(*minor_status, ctx->k5_context); return(majerr); + } /* Mark the context as terminated, but do not delete it (as that would * leave the caller with a dangling context handle). */ diff --git a/src/lib/gssapi/krb5/unwrap.c b/src/lib/gssapi/krb5/unwrap.c new file mode 100644 index 0000000000..5cf259f50d --- /dev/null +++ b/src/lib/gssapi/krb5/unwrap.c @@ -0,0 +1,414 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/gssapi/krb5/unwrap.c - krb5 gss_unwrap() implementation */ +/* + * Copyright (C) 2024 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gssapiP_krb5.h" + +/* The RFC 1964 token format is only used with DES3 and RC4, both of which use + * an 8-byte confounder. */ +#define V1_CONFOUNDER_LEN 8 + +#define V3_HEADER_LEN 16 + +/* Perform raw decryption (unauthenticated, no change in length) from in into + * *out. For RC4, use seqnum to derive the encryption key. */ +static krb5_error_code +decrypt_v1(krb5_context context, uint16_t sealalg, krb5_key key, + uint32_t seqnum, const uint8_t *in, size_t len, uint8_t **out) +{ + krb5_error_code ret; + uint8_t bigend_seqnum[4], *plain = NULL; + krb5_keyblock *enc_key = NULL; + unsigned int i; + + *out = NULL; + + plain = malloc(len); + if (plain == NULL) + return ENOMEM; + + if (sealalg != SEAL_ALG_MICROSOFT_RC4) { + ret = kg_decrypt(context, key, KG_USAGE_SEAL, NULL, in, plain, len); + if (ret) + goto cleanup; + } else { + store_32_be(seqnum, bigend_seqnum); + ret = krb5_k_key_keyblock(context, key, &enc_key); + if (ret) + goto cleanup; + for (i = 0; i < enc_key->length; i++) + enc_key->contents[i] ^= 0xF0; + ret = kg_arcfour_docrypt(enc_key, 0, bigend_seqnum, 4, in, len, plain); + if (ret) + goto cleanup; + } + + *out = plain; + plain = NULL; + +cleanup: + free(plain); + krb5_free_keyblock(context, enc_key); + return ret; +} + +static OM_uint32 +unwrap_v1(krb5_context context, OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, struct k5input *in, + gss_buffer_t output_message, int *conf_state) +{ + krb5_error_code ret = 0; + OM_uint32 major; + uint8_t *decrypted = NULL; + const uint8_t *plain, *header, *seqbytes, *cksum; + int direction, bad_pad = 0; + size_t plainlen, cksum_len; + uint32_t seqnum; + uint16_t toktype, signalg, sealalg, filler; + uint8_t padlen; + + if (ctx->seq == NULL) { + /* ctx was established using a newer enctype, and cannot process RFC + * 1964 tokens. */ + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + /* Parse the header fields and fetch the checksum. */ + header = in->ptr; + toktype = k5_input_get_uint16_be(in); + signalg = k5_input_get_uint16_le(in); + sealalg = k5_input_get_uint16_le(in); + filler = k5_input_get_uint16_le(in); + seqbytes = k5_input_get_bytes(in, 8); + cksum_len = (signalg == SGN_ALG_HMAC_SHA1_DES3_KD) ? 20 : 8; + cksum = k5_input_get_bytes(in, cksum_len); + + /* Validate the header fields, and ensure that there are enough bytes + * remaining for a confounder and padding length byte. */ + if (in->status || in->len < V1_CONFOUNDER_LEN + 1 || + toktype != KG_TOK_WRAP_MSG || filler != 0xFFFF || + signalg != ctx->signalg || + (sealalg != SEAL_ALG_NONE && sealalg != ctx->sealalg)) { + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + ret = kg_get_seq_num(context, ctx->seq, cksum, seqbytes, &direction, + &seqnum); + if (ret) { + major = GSS_S_BAD_SIG; + goto cleanup; + } + + /* Decrypt the ciphertext, or just accept the remaining bytes as the + * plaintext (still with a confounder and padding length byte). */ + plain = in->ptr; + plainlen = in->len; + if (sealalg != SEAL_ALG_NONE) { + ret = decrypt_v1(context, sealalg, ctx->enc, seqnum, in->ptr, in->len, + &decrypted); + if (ret) { + major = GSS_S_FAILURE; + goto cleanup; + } + plain = decrypted; + } + + padlen = plain[plainlen - 1]; + if (plainlen - V1_CONFOUNDER_LEN < padlen) { + /* Don't error out yet, to avoid padding oracle attacks. We will + * treat this as a checksum failure later on. */ + padlen = 0; + bad_pad = 1; + } + + if (!kg_verify_checksum_v1(context, signalg, ctx->seq, KG_USAGE_SIGN, + header, plain, plainlen, cksum, cksum_len) || + bad_pad) { + major = GSS_S_BAD_SIG; + goto cleanup; + } + + if ((ctx->initiate && direction != 0xff) || + (!ctx->initiate && direction != 0)) { + *minor_status = (OM_uint32)G_BAD_DIRECTION; + major = GSS_S_BAD_SIG; + goto cleanup; + } + + output_message->length = plainlen - V1_CONFOUNDER_LEN - padlen; + if (output_message->length > 0) { + output_message->value = gssalloc_malloc(output_message->length); + if (output_message->value == NULL) { + ret = ENOMEM; + major = GSS_S_FAILURE; + goto cleanup; + } + memcpy(output_message->value, plain + V1_CONFOUNDER_LEN, + output_message->length); + } + + if (conf_state != NULL) + *conf_state = (sealalg != SEAL_ALG_NONE); + + major = g_seqstate_check(ctx->seqstate, seqnum); + +cleanup: + free(decrypted); + *minor_status = ret; + return major; +} + +/* Return true if plain ends with an RFC 4121 header with the provided fields, + * and that plain contains at least ec additional bytes of padding. */ +static krb5_boolean +verify_enc_header(krb5_data *plain, uint8_t flags, size_t ec, uint64_t seqnum) +{ + uint8_t *h; + + if (plain->length < V3_HEADER_LEN + ec) + return FALSE; + h = (uint8_t *)plain->data + plain->length - V3_HEADER_LEN; + return load_16_be(h) == KG2_TOK_WRAP_MSG && h[2] == flags && + h[3] == 0xFF && load_16_be(h + 4) == ec && + load_64_be(h + 8) == seqnum; +} + +/* Decrypt ctext, verify the encrypted header, and return the appropriately + * truncated plaintext in out, allocated with gssalloc_malloc(). */ +static OM_uint32 +decrypt_v3(krb5_context context, OM_uint32 *minor_status, + krb5_key key, krb5_keyusage usage, const uint8_t *ctext, size_t len, + uint8_t flags, size_t ec, uint64_t seqnum, gss_buffer_t out) +{ + OM_uint32 major; + krb5_error_code ret; + krb5_enc_data cipher; + krb5_data plain; + uint8_t *buf = NULL; + + buf = gssalloc_malloc(len); + if (buf == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + + cipher.enctype = key->keyblock.enctype; + cipher.ciphertext = make_data((uint8_t *)ctext, len); + plain = make_data(buf, len); + ret = krb5_k_decrypt(context, key, usage, NULL, &cipher, &plain); + if (ret) { + *minor_status = ret; + major = GSS_S_FAILURE; + goto cleanup; + } + + if (!verify_enc_header(&plain, flags, ec, seqnum)) { + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + out->length = plain.length - ec - 16; + out->value = buf; + buf = NULL; + if (out->length == 0) { + gssalloc_free(out->value); + out->value = NULL; + } + + major = GSS_S_COMPLETE; + +cleanup: + gssalloc_free(buf); + return major; +} + +/* Place a rotated copy of data in *storage and return it, or return data if no + * rotation is required. Return null on allocation failure. */ +static const uint8_t * +rotate_left(const uint8_t *data, size_t len, size_t rc, uint8_t **storage) +{ + if (len == 0 || rc % len == 0) + return data; + rc %= len; + + *storage = malloc(len); + if (*storage == NULL) + return NULL; + memcpy(*storage, data + rc, len - rc); + memcpy(*storage + len - rc, data, rc); + return *storage; +} + +static OM_uint32 +unwrap_v3(krb5_context context, OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, struct k5input *in, + gss_buffer_t output_message, int *conf_state) +{ + OM_uint32 major; + krb5_error_code ret = 0; + krb5_keyusage usage; + krb5_key key; + krb5_cksumtype cksumtype; + size_t ec, rrc, cksumsize, plen, data_len; + uint64_t seqnum; + uint16_t toktype; + uint8_t flags, filler, *rotated = NULL; + const uint8_t *payload; + + toktype = k5_input_get_uint16_be(in); + flags = k5_input_get_byte(in); + filler = k5_input_get_byte(in); + ec = k5_input_get_uint16_be(in); + rrc = k5_input_get_uint16_be(in); + seqnum = k5_input_get_uint64_be(in); + + if (in->status || toktype != KG2_TOK_WRAP_MSG || filler != 0xFF) { + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + if (!!(flags & FLAG_SENDER_IS_ACCEPTOR) != ctx->initiate) { + major = GSS_S_BAD_SIG; + *minor_status = (OM_uint32)G_BAD_DIRECTION; + goto cleanup; + } + + usage = ctx->initiate ? KG_USAGE_ACCEPTOR_SEAL : KG_USAGE_INITIATOR_SEAL; + if (ctx->have_acceptor_subkey && (flags & FLAG_ACCEPTOR_SUBKEY)) { + key = ctx->acceptor_subkey; + cksumtype = ctx->acceptor_subkey_cksumtype; + } else { + key = ctx->subkey; + cksumtype = ctx->cksumtype; + } + assert(key != NULL); + + payload = rotate_left(in->ptr, in->len, rrc, &rotated); + plen = in->len; + if (payload == NULL) { + major = GSS_S_FAILURE; + *minor_status = ENOMEM; + goto cleanup; + } + if (flags & FLAG_WRAP_CONFIDENTIAL) { + major = decrypt_v3(context, minor_status, key, usage, payload, plen, + flags, ec, seqnum, output_message); + if (major != GSS_S_COMPLETE) + goto cleanup; + } else { + /* Divide the payload into data and checksum. */ + ret = krb5_c_checksum_length(context, cksumtype, &cksumsize); + if (ret) { + major = GSS_S_FAILURE; + *minor_status = ret; + goto cleanup; + } + if (cksumsize > plen || ec != cksumsize) { + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + data_len = plen - cksumsize; + + if (!kg_verify_checksum_v3(context, key, usage, cksumtype, + KG2_TOK_WRAP_MSG, flags, seqnum, + payload, data_len, + payload + data_len, cksumsize)) { + major = GSS_S_BAD_SIG; + goto cleanup; + } + + output_message->length = data_len; + if (data_len > 0) { + output_message->value = gssalloc_malloc(data_len); + if (output_message->value == NULL) { + major = GSS_S_FAILURE; + *minor_status = ENOMEM; + goto cleanup; + } + memcpy(output_message->value, payload, data_len); + } + output_message->length = data_len; + } + + if (conf_state != NULL) + *conf_state = !!(flags & FLAG_WRAP_CONFIDENTIAL); + + major = g_seqstate_check(ctx->seqstate, seqnum); + +cleanup: + free(rotated); + return major; +} + +OM_uint32 KRB5_CALLCONV +krb5_gss_unwrap(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_buffer_t input_message, gss_buffer_t output_message, + int *conf_state, gss_qop_t *qop_state) +{ + krb5_gss_ctx_id_rec *ctx = (krb5_gss_ctx_id_rec *)context_handle; + uint16_t toktype; + OM_uint32 major; + struct k5input in, unwrapped; + + *minor_status = 0; + output_message->value = NULL; + output_message->length = 0; + if (qop_state != NULL) + *qop_state = GSS_C_QOP_DEFAULT; + + if (ctx->terminated || !ctx->established) { + *minor_status = KG_CTX_INCOMPLETE; + return GSS_S_NO_CONTEXT; + } + + k5_input_init(&in, input_message->value, input_message->length); + (void)g_verify_token_header(&in, ctx->mech_used); + unwrapped = in; + + toktype = k5_input_get_uint16_be(&in); + if (toktype == KG_TOK_WRAP_MSG) { + major = unwrap_v1(ctx->k5_context, minor_status, ctx, &unwrapped, + output_message, conf_state); + } else if (toktype == KG2_TOK_WRAP_MSG) { + major = unwrap_v3(ctx->k5_context, minor_status, ctx, &unwrapped, + output_message, conf_state); + } else { + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + major = GSS_S_DEFECTIVE_TOKEN; + } + + if (major) + save_error_info(*minor_status, ctx->k5_context); + + return major; +} diff --git a/src/lib/gssapi/krb5/util_crypt.c b/src/lib/gssapi/krb5/util_crypt.c index 84f1949887..28411429bf 100644 --- a/src/lib/gssapi/krb5/util_crypt.c +++ b/src/lib/gssapi/krb5/util_crypt.c @@ -163,7 +163,7 @@ kg_make_confounder(krb5_context context, krb5_enctype enctype, /* Set *data_out to a krb5_data structure containing iv, or to NULL if iv is * NULL. */ static krb5_error_code -iv_to_state(krb5_context context, krb5_key key, krb5_pointer iv, +iv_to_state(krb5_context context, krb5_key key, const uint8_t *iv, krb5_data **data_out) { krb5_error_code code; @@ -236,8 +236,8 @@ kg_encrypt_inplace(krb5_context context, krb5_key key, int usage, /* length is the length of the cleartext. */ krb5_error_code -kg_decrypt(krb5_context context, krb5_key key, int usage, krb5_pointer iv, - krb5_const_pointer in, krb5_pointer out, unsigned int length) +kg_decrypt(krb5_context context, krb5_key key, int usage, const uint8_t *iv, + const uint8_t *in, uint8_t *out, unsigned int length) { krb5_error_code code; krb5_data *state, outputd; @@ -252,7 +252,7 @@ kg_decrypt(krb5_context context, krb5_key key, int usage, krb5_pointer iv, inputd.ciphertext.data = (char *)in; outputd.length = length; - outputd.data = out; + outputd.data = (char *)out; code = krb5_k_decrypt(context, key, usage, state, &inputd, &outputd); krb5_free_data(context, state); @@ -274,6 +274,72 @@ kg_arcfour_docrypt(const krb5_keyblock *keyblock, int usage, return krb5int_arcfour_gsscrypt(keyblock, usage, &kd, &kiov, 1); } +/* Return true if cksum contains a valid checksum for header (implicitly of + * length 8) and data, in the RFC 1964 token format. */ +krb5_boolean +kg_verify_checksum_v1(krb5_context context, uint16_t signalg, krb5_key key, + krb5_keyusage usage, const uint8_t *header, + const uint8_t *data, size_t data_len, + const uint8_t *cksum, size_t cksum_len) +{ + krb5_error_code ret; + krb5_cksumtype type; + krb5_crypto_iov iov[3]; + uint8_t ckbuf[20]; + + if (signalg == SGN_ALG_HMAC_MD5) + type = CKSUMTYPE_HMAC_MD5_ARCFOUR; + else if (signalg == SGN_ALG_HMAC_SHA1_DES3_KD) + type = CKSUMTYPE_HMAC_SHA1_DES3; + else + abort(); + + iov[0].flags = iov[1].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + iov[0].data = make_data((uint8_t *)header, 8); + iov[1].data = make_data((uint8_t *)data, data_len); + iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM; + iov[2].data = make_data(ckbuf, sizeof(ckbuf)); + + /* For RC4 the GSS checksum is only the first eight bytes of the HMAC-MD5 + * result, so we must compute a checksum and compare. */ + ret = krb5_k_make_checksum_iov(context, type, key, usage, iov, 3); + if (ret) + return FALSE; + assert(iov[2].data.length >= cksum_len); + return k5_bcmp(iov[2].data.data, cksum, cksum_len) == 0; +} + +/* Return true if cksum contains a valid checksum for data and the provided + * header fields, in the RFC 4121 token format. */ +krb5_boolean +kg_verify_checksum_v3(krb5_context context, krb5_key key, krb5_keyusage usage, + krb5_cksumtype cksumtype, + uint16_t toktype, uint8_t flags, uint64_t seqnum, + const uint8_t *data, size_t data_len, + const uint8_t *cksum, size_t cksum_len) +{ + krb5_crypto_iov iov[3]; + uint8_t ckhdr[16]; + krb5_boolean valid; + + /* Compose an RFC 4121 token header with EC and RRC set to 0. */ + store_16_be(toktype, ckhdr); + ckhdr[2] = flags; + ckhdr[3] = 0xFF; + store_16_be(0, ckhdr + 4); + store_16_be(0, ckhdr + 6); + store_64_be(seqnum, ckhdr + 8); + + /* Verify the checksum over the data and composed header. */ + iov[0].flags = iov[1].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + iov[0].data = make_data((uint8_t *)data, data_len); + iov[1].data = make_data(ckhdr, 16); + iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM; + iov[2].data = make_data((uint8_t *)cksum, cksum_len); + return krb5_k_verify_checksum_iov(context, cksumtype, key, usage, iov, 3, + &valid) == 0 && valid; +} + /* AEAD */ static krb5_error_code kg_translate_iov_v1(krb5_context context, krb5_enctype enctype, diff --git a/src/lib/gssapi/krb5/util_seqnum.c b/src/lib/gssapi/krb5/util_seqnum.c index a5a4d5cf80..58fc1eba1a 100644 --- a/src/lib/gssapi/krb5/util_seqnum.c +++ b/src/lib/gssapi/krb5/util_seqnum.c @@ -55,8 +55,8 @@ kg_make_seq_num(krb5_context context, krb5_key key, int direction, } krb5_error_code -kg_get_seq_num(krb5_context context, krb5_key key, unsigned char *cksum, - unsigned char *buf, int *direction, krb5_ui_4 *seqnum) +kg_get_seq_num(krb5_context context, krb5_key key, const uint8_t *cksum, + const uint8_t *buf, int *direction, krb5_ui_4 *seqnum) { krb5_error_code code; unsigned char plain[8]; diff --git a/src/lib/gssapi/krb5/verify_mic.c b/src/lib/gssapi/krb5/verify_mic.c new file mode 100644 index 0000000000..9852f49912 --- /dev/null +++ b/src/lib/gssapi/krb5/verify_mic.c @@ -0,0 +1,176 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/gssapi/krb5/verify_mic.c - krb5 gss_verify_mic() implementation */ +/* + * Copyright (C) 2024 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "gssapiP_krb5.h" + +OM_uint32 +kg_verify_mic_v1(krb5_context context, OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, uint16_t exp_toktype, + struct k5input *in, gss_buffer_t message) +{ + krb5_error_code ret = 0; + krb5_keyusage usage; + const uint8_t *header, *seqbytes, *cksum; + int direction; + size_t cksum_len; + uint32_t seqnum, filler; + uint16_t toktype, signalg; + + if (ctx->seq == NULL) { + /* ctx was established using a newer enctype, and cannot process RFC + * 1964 tokens. */ + return GSS_S_DEFECTIVE_TOKEN; + } + + header = in->ptr; + toktype = k5_input_get_uint16_be(in); + signalg = k5_input_get_uint16_le(in); + filler = k5_input_get_uint32_le(in); + seqbytes = k5_input_get_bytes(in, 8); + cksum_len = (signalg == SGN_ALG_HMAC_SHA1_DES3_KD) ? 20 : 8; + cksum = k5_input_get_bytes(in, cksum_len); + + if (in->status || in->len != 0 || toktype != exp_toktype || + filler != 0xFFFFFFFF || signalg != ctx->signalg) + return GSS_S_DEFECTIVE_TOKEN; + usage = (signalg == SGN_ALG_HMAC_MD5) ? 15 : KG_USAGE_SIGN; + + ret = kg_get_seq_num(context, ctx->seq, cksum, seqbytes, &direction, + &seqnum); + if (ret) { + *minor_status = ret; + return GSS_S_BAD_SIG; + } + + if (!kg_verify_checksum_v1(context, signalg, ctx->seq, usage, header, + message->value, message->length, + cksum, cksum_len)) + return GSS_S_BAD_SIG; + + if ((ctx->initiate && direction != 0xff) || + (!ctx->initiate && direction != 0)) { + *minor_status = (OM_uint32)G_BAD_DIRECTION; + return GSS_S_BAD_SIG; + } + + return g_seqstate_check(ctx->seqstate, seqnum); +} + +static OM_uint32 +verify_mic_v3(krb5_context context, OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, struct k5input *in, + gss_buffer_t message) +{ + OM_uint32 status; + krb5_keyusage usage; + krb5_key key; + krb5_cksumtype cksumtype; + uint64_t seqnum; + uint32_t filler2; + uint16_t toktype; + uint8_t flags, filler1; + + toktype = k5_input_get_uint16_be(in); + flags = k5_input_get_byte(in); + filler1 = k5_input_get_byte(in); + filler2 = k5_input_get_uint32_be(in); + seqnum = k5_input_get_uint64_be(in); + + if (in->status || toktype != KG2_TOK_MIC_MSG || filler1 != 0xFF || + filler2 != 0xFFFFFFFF) + return GSS_S_DEFECTIVE_TOKEN; + + if (!!(flags & FLAG_SENDER_IS_ACCEPTOR) != ctx->initiate) { + *minor_status = (OM_uint32)G_BAD_DIRECTION; + return GSS_S_BAD_SIG; + } + + usage = ctx->initiate ? KG_USAGE_ACCEPTOR_SIGN : KG_USAGE_INITIATOR_SIGN; + if (ctx->have_acceptor_subkey && (flags & FLAG_ACCEPTOR_SUBKEY)) { + key = ctx->acceptor_subkey; + cksumtype = ctx->acceptor_subkey_cksumtype; + } else { + key = ctx->subkey; + cksumtype = ctx->cksumtype; + } + assert(key != NULL); + + status = kg_verify_checksum_v3(context, key, usage, cksumtype, + KG2_TOK_MIC_MSG, flags, seqnum, + message->value, message->length, + in->ptr, in->len); + if (status != GSS_S_COMPLETE) + return status; + + return g_seqstate_check(ctx->seqstate, seqnum); +} + +OM_uint32 KRB5_CALLCONV +krb5_gss_verify_mic(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_buffer_t message, gss_buffer_t token, + gss_qop_t *qop_state) +{ + krb5_gss_ctx_id_rec *ctx = (krb5_gss_ctx_id_rec *)context_handle; + uint16_t toktype; + OM_uint32 major; + struct k5input in, unwrapped; + + *minor_status = 0; + if (qop_state != NULL) + *qop_state = GSS_C_QOP_DEFAULT; + + if (ctx->terminated || !ctx->established) { + *minor_status = KG_CTX_INCOMPLETE; + return GSS_S_NO_CONTEXT; + } + + k5_input_init(&in, token->value, token->length); + (void)g_verify_token_header(&in, ctx->mech_used); + unwrapped = in; + + toktype = k5_input_get_uint16_be(&in); + if (toktype == KG_TOK_MIC_MSG) { + major = kg_verify_mic_v1(ctx->k5_context, minor_status, ctx, toktype, + &unwrapped, message); + } else if (toktype == KG2_TOK_MIC_MSG) { + major = verify_mic_v3(ctx->k5_context, minor_status, ctx, &unwrapped, + message); + } else { + *minor_status = (OM_uint32)G_BAD_TOK_HEADER; + major = GSS_S_DEFECTIVE_TOKEN; + } + + if (major) + save_error_info(*minor_status, ctx->k5_context); + + return major; +} diff --git a/src/lib/gssapi/libgssapi_krb5.exports b/src/lib/gssapi/libgssapi_krb5.exports index fd4fced8e9..811365b9fa 100644 --- a/src/lib/gssapi/libgssapi_krb5.exports +++ b/src/lib/gssapi/libgssapi_krb5.exports @@ -102,7 +102,6 @@ gss_krb5_import_cred gss_krb5_set_allowable_enctypes gss_krb5_set_cred_rcache gss_krb5int_make_seal_token_v3 -gss_krb5int_unseal_token_v3 gsskrb5_extract_authtime_from_sec_context gsskrb5_extract_authz_data_from_sec_context gss_localname