krb5 commit: Refactor GSS per-message token parsing
ghudson at mit.edu
ghudson at mit.edu
Mon Aug 12 22:27:48 EDT 2024
https://github.com/krb5/krb5/commit/7ae0adcdf16687810f747e284c9fb571a561c5bd
commit 7ae0adcdf16687810f747e284c9fb571a561c5bd
Author: Greg Hudson <ghudson at mit.edu>
Date: Mon Jun 24 15:46:50 2024 -0400
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.
src/lib/gssapi/krb5/Makefile.in | 9 +-
src/lib/gssapi/krb5/gssapiP_krb5.h | 49 ++--
src/lib/gssapi/krb5/k5sealv3.c | 227 ---------------
src/lib/gssapi/krb5/k5unseal.c | 433 ----------------------------
src/lib/gssapi/krb5/process_context_token.c | 14 +-
src/lib/gssapi/krb5/unwrap.c | 414 ++++++++++++++++++++++++++
src/lib/gssapi/krb5/util_crypt.c | 74 ++++-
src/lib/gssapi/krb5/util_seqnum.c | 4 +-
src/lib/gssapi/krb5/verify_mic.c | 176 +++++++++++
src/lib/gssapi/libgssapi_krb5.exports | 1 -
10 files changed, 700 insertions(+), 701 deletions(-)
diff --git a/src/lib/gssapi/krb5/Makefile.in b/src/lib/gssapi/krb5/Makefile.in
index be4399568..10e7961f1 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 3d6aaccf7..da7c1cfac 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 d3210c110..f50beadc2 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 5e57487da..000000000
--- 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 <memory.h>
-#endif
-#include <assert.h>
-
-/* 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 67805fba7..e346d1ba2 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 000000000..5cf259f50
--- /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 84f194988..28411429b 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 a5a4d5cf8..58fc1eba1 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 000000000..9852f4991
--- /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 fd4fced8e..811365b9f 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
More information about the cvs-krb5
mailing list