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