From 310bffc085514f9ceba5b3501ddef15807c53809 Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Wed, 12 Oct 2022 13:55:39 +1300
Subject: [PATCH 07/15] CVE-2022-3437 s4/auth/tests: Add unit tests for
 unwrap_des3()

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15134

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
[jsutton@samba.org Adapted to lack of 'samba.unittests.auth.sam' test,
 renamed 'third_party' to 'source4' in paths, defined
 HEIMDAL_NORETURN_ATTRIBUTE and HEIMDAL_PRINTF_ATTRIBUTE to fix compiler
 error]

Conflict: with_pam = ("WITH_PAM" in config_hash)
Reference: https://download.samba.org/pub/samba/patches/security/samba-4.15.11-security-2022-10-25.patch
---
 selftest/knownfail.d/heimdal-des-overflow |    9 +
 selftest/tests.py                         |    5 +
 source4/auth/tests/heimdal_unwrap_des.c   | 1247 +++++++++++++++++++++
 source4/auth/wscript_build                |   21 +
 4 files changed, 1282 insertions(+)
 create mode 100644 selftest/knownfail.d/heimdal-des-overflow
 create mode 100644 source4/auth/tests/heimdal_unwrap_des.c

diff --git a/selftest/knownfail.d/heimdal-des-overflow b/selftest/knownfail.d/heimdal-des-overflow
new file mode 100644
index 0000000..23acbb4
--- /dev/null
+++ b/selftest/knownfail.d/heimdal-des-overflow
@@ -0,0 +1,9 @@
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_dce_style_missing_payload.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_dce_style_with_seal_missing_payload.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_missing_8_bytes.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_missing_payload.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_truncated_header_0.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_truncated_header_1.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_with_padding_truncated_0.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_with_padding_truncated_1.none
+^samba.unittests.auth.heimdal_gensec_unwrap_des.test_unwrap_with_seal_missing_payload.none
diff --git a/selftest/tests.py b/selftest/tests.py
index 7589566..dfda95a 100644
--- a/selftest/tests.py
+++ b/selftest/tests.py
@@ -46,6 +46,8 @@ have_man_pages_support = ("XSLTPROC_MANPAGES" in config_hash)
 with_pam = ("WITH_PAM" in config_hash)
 pam_wrapper_so_path = config_hash["LIBPAM_WRAPPER_SO_PATH"]
 pam_set_items_so_path = config_hash["PAM_SET_ITEMS_SO_PATH"]
+have_heimdal_support = "SAMBA4_USES_HEIMDAL" in config_hash
+using_system_gssapi = "USING_SYSTEM_GSSAPI" in config_hash
 
 planpythontestsuite("none", "samba.tests.source")
 if have_man_pages_support:
@@ -401,3 +403,6 @@ plantestsuite("samba.unittests.test_registry_regfio", "none",
               [os.path.join(bindir(), "default/source3/test_registry_regfio")])
 plantestsuite("samba.unittests.test_oLschema2ldif", "none",
               [os.path.join(bindir(), "default/source4/utils/oLschema2ldif/test_oLschema2ldif")])
+if have_heimdal_support and not using_system_gssapi:
+    plantestsuite("samba.unittests.auth.heimdal_gensec_unwrap_des", "none",
+              [valgrindify(os.path.join(bindir(), "test_heimdal_gensec_unwrap_des"))])
diff --git a/source4/auth/tests/heimdal_unwrap_des.c b/source4/auth/tests/heimdal_unwrap_des.c
new file mode 100644
index 0000000..dc31e9d
--- /dev/null
+++ b/source4/auth/tests/heimdal_unwrap_des.c
@@ -0,0 +1,1247 @@
+/*
+ * Unit tests for source4/heimdal/lib/gssapi/krb5/unwrap.c
+ *
+ * Copyright (C) Catalyst.NET Ltd 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+
+#include <cmocka.h>
+
+#include "includes.h"
+#include "replace.h"
+
+#define HEIMDAL_NORETURN_ATTRIBUTE _NORETURN_
+#define HEIMDAL_PRINTF_ATTRIBUTE(x) FORMAT_ATTRIBUTE(x)
+
+#include "../../../source4/heimdal/lib/gssapi/gssapi/gssapi.h"
+#include "gsskrb5_locl.h"
+
+/******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+
+const uint8_t *valid_range_begin;
+const uint8_t *valid_range_end;
+const uint8_t *invalid_range_end;
+
+/*
+ * 'array_len' is the size of the passed in array. 'buffer_len' is the size to
+ * report in the resulting buffer.
+ */
+static const gss_buffer_desc get_input_buffer(TALLOC_CTX *mem_ctx,
+					      const uint8_t array[],
+					      const size_t array_len,
+					      const size_t buffer_len)
+{
+	gss_buffer_desc buf;
+
+	/* Add some padding to catch invalid memory accesses. */
+	const size_t padding = 0x100;
+	const size_t padded_len = array_len + padding;
+
+	uint8_t *data = talloc_size(mem_ctx, padded_len);
+	assert_non_null(data);
+
+	memcpy(data, array, array_len);
+	memset(data + array_len, 0, padding);
+
+	assert_in_range(buffer_len, 0, array_len);
+
+	buf.value = data;
+	buf.length = buffer_len;
+
+	valid_range_begin = buf.value;
+	valid_range_end = valid_range_begin + buf.length;
+	invalid_range_end = valid_range_begin + padded_len;
+
+	return buf;
+}
+
+static void assert_mem_in_valid_range(const uint8_t *ptr, const size_t len)
+{
+	/* Ensure we've set up the range pointers properly. */
+	assert_non_null(valid_range_begin);
+	assert_non_null(valid_range_end);
+	assert_non_null(invalid_range_end);
+
+	/*
+	 * Ensure the length isn't excessively large (a symptom of integer
+	 * underflow).
+	 */
+	assert_in_range(len, 0, 0x1000);
+
+	/* Ensure the memory is in our valid range. */
+	assert_in_range(ptr, valid_range_begin, valid_range_end);
+	assert_in_range(ptr + len, valid_range_begin, valid_range_end);
+}
+
+/*
+ * This function takes a pointer to volatile to allow it to be called from the
+ * ct_memcmp() wrapper.
+ */
+static void assert_mem_outside_invalid_range(const volatile uint8_t *ptr,
+					     const size_t len)
+{
+	const LargestIntegralType _valid_range_end
+		= cast_ptr_to_largest_integral_type(valid_range_end);
+	const LargestIntegralType _invalid_range_end
+		= cast_ptr_to_largest_integral_type(invalid_range_end);
+	const LargestIntegralType _ptr = cast_ptr_to_largest_integral_type(ptr);
+	const LargestIntegralType _len = cast_to_largest_integral_type(len);
+
+	/* Ensure we've set up the range pointers properly. */
+	assert_non_null(valid_range_begin);
+	assert_non_null(valid_range_end);
+	assert_non_null(invalid_range_end);
+
+	/*
+	 * Ensure the length isn't excessively large (a symptom of integer
+	 * underflow).
+	 */
+	assert_in_range(len, 0, 0x1000);
+
+	/* Ensure the memory is outside the invalid range. */
+	if (_ptr < _invalid_range_end && _ptr + _len > _valid_range_end) {
+		fail();
+	}
+}
+
+/*****************************************************************************
+ * wrapped functions
+ *****************************************************************************/
+
+krb5_keyblock dummy_key;
+
+krb5_error_code __wrap_krb5_auth_con_getlocalsubkey(krb5_context context,
+						    krb5_auth_context auth_context,
+						    krb5_keyblock **keyblock);
+krb5_error_code __wrap_krb5_auth_con_getlocalsubkey(krb5_context context,
+						    krb5_auth_context auth_context,
+						    krb5_keyblock **keyblock)
+{
+	*keyblock = &dummy_key;
+	return 0;
+}
+
+void __wrap_krb5_free_keyblock(krb5_context context,
+			krb5_keyblock *keyblock);
+void __wrap_krb5_free_keyblock(krb5_context context,
+			krb5_keyblock *keyblock)
+{
+	assert_ptr_equal(&dummy_key, keyblock);
+}
+
+struct krb5_crypto_data dummy_crypto;
+
+krb5_error_code __wrap_krb5_crypto_init(krb5_context context,
+					const krb5_keyblock *key,
+					krb5_enctype etype,
+					krb5_crypto *crypto);
+krb5_error_code __wrap_krb5_crypto_init(krb5_context context,
+					const krb5_keyblock *key,
+					krb5_enctype etype,
+					krb5_crypto *crypto)
+{
+	static const LargestIntegralType etypes[] = {ETYPE_DES3_CBC_NONE, 0};
+
+	assert_ptr_equal(&dummy_key, key);
+	assert_in_set(etype, etypes, ARRAY_SIZE(etypes));
+
+	*crypto = &dummy_crypto;
+
+	return 0;
+}
+
+krb5_error_code __wrap_krb5_decrypt(krb5_context context,
+				    krb5_crypto crypto,
+				    unsigned usage,
+				    void *data,
+				    size_t len,
+				    krb5_data *result);
+krb5_error_code __wrap_krb5_decrypt(krb5_context context,
+				    krb5_crypto crypto,
+				    unsigned usage,
+				    void *data,
+				    size_t len,
+				    krb5_data *result)
+{
+	assert_ptr_equal(&dummy_crypto, crypto);
+	assert_int_equal(KRB5_KU_USAGE_SEAL, usage);
+
+	assert_mem_in_valid_range(data, len);
+
+	check_expected(len);
+	check_expected_ptr(data);
+
+	result->data = malloc(len);
+	assert_non_null(result->data);
+	result->length = len;
+
+	memcpy(result->data, data, len);
+
+	return 0;
+}
+
+krb5_error_code __wrap_krb5_decrypt_ivec(krb5_context context,
+					 krb5_crypto crypto,
+					 unsigned usage,
+					 void *data,
+					 size_t len,
+					 krb5_data *result,
+					 void *ivec);
+krb5_error_code __wrap_krb5_decrypt_ivec(krb5_context context,
+					 krb5_crypto crypto,
+					 unsigned usage,
+					 void *data,
+					 size_t len,
+					 krb5_data *result,
+					 void *ivec)
+{
+	assert_ptr_equal(&dummy_crypto, crypto);
+	assert_int_equal(KRB5_KU_USAGE_SEQ, usage);
+
+	assert_mem_in_valid_range(data, len);
+
+	assert_int_equal(8, len);
+	check_expected_ptr(data);
+	check_expected_ptr(ivec);
+
+	result->data = malloc(len);
+	assert_non_null(result->data);
+	result->length = len;
+
+	memcpy(result->data, data, len);
+
+	return 0;
+}
+
+krb5_error_code __wrap_krb5_verify_checksum(krb5_context context,
+					    krb5_crypto crypto,
+					    krb5_key_usage usage,
+					    void *data,
+					    size_t len,
+					    Checksum *cksum);
+krb5_error_code __wrap_krb5_verify_checksum(krb5_context context,
+					    krb5_crypto crypto,
+					    krb5_key_usage usage,
+					    void *data,
+					    size_t len,
+					    Checksum *cksum)
+{
+	assert_ptr_equal(&dummy_crypto, crypto);
+	assert_int_equal(KRB5_KU_USAGE_SIGN, usage);
+
+	assert_mem_in_valid_range(data, len);
+
+	check_expected(len);
+	check_expected_ptr(data);
+
+	assert_non_null(cksum);
+	assert_int_equal(CKSUMTYPE_HMAC_SHA1_DES3, cksum->cksumtype);
+	assert_int_equal(20, cksum->checksum.length);
+	check_expected_ptr(cksum->checksum.data);
+
+	return 0;
+}
+
+krb5_error_code __wrap_krb5_crypto_destroy(krb5_context context,
+					   krb5_crypto crypto);
+krb5_error_code __wrap_krb5_crypto_destroy(krb5_context context,
+					   krb5_crypto crypto)
+{
+	assert_ptr_equal(&dummy_crypto, crypto);
+
+	return 0;
+}
+
+
+int __wrap_der_get_length(const unsigned char *p,
+			  size_t len,
+			  size_t *val,
+			  size_t *size);
+int __real_der_get_length(const unsigned char *p,
+			  size_t len,
+			  size_t *val,
+			  size_t *size);
+int __wrap_der_get_length(const unsigned char *p,
+			  size_t len,
+			  size_t *val,
+			  size_t *size)
+{
+	assert_mem_in_valid_range(p, len);
+
+	return __real_der_get_length(p, len, val, size);
+}
+
+int __wrap_ct_memcmp(const volatile void * volatile p1,
+		     const volatile void * volatile p2,
+		     size_t len);
+int __real_ct_memcmp(const volatile void * volatile p1,
+		     const volatile void * volatile p2,
+		     size_t len);
+int __wrap_ct_memcmp(const volatile void * volatile p1,
+		     const volatile void * volatile p2,
+		     size_t len)
+{
+	assert_mem_outside_invalid_range(p1, len);
+	assert_mem_outside_invalid_range(p2, len);
+
+	return __real_ct_memcmp(p1, p2, len);
+}
+
+void *__wrap_malloc(size_t size);
+void *__real_malloc(size_t size);
+void *__wrap_malloc(size_t size)
+{
+	/*
+	 * Ensure the length isn't excessively large (a symptom of integer
+	 * underflow).
+	 */
+	assert_in_range(size, 0, 0x10000);
+
+	return __real_malloc(size);
+}
+
+/*****************************************************************************
+ * Mock implementations
+ *****************************************************************************/
+
+/*
+ * Set the globals used by the mocked functions to a known and consistent state
+ *
+ */
+static void init_mock_results(TALLOC_CTX *mem_ctx)
+{
+	dummy_key.keytype = KRB5_ENCTYPE_DES3_CBC_MD5;
+	dummy_key.keyvalue.data = NULL;
+	dummy_key.keyvalue.length = 0;
+
+	dummy_crypto = (struct krb5_crypto_data) {0};
+
+	valid_range_begin = NULL;
+	valid_range_end = NULL;
+	invalid_range_end = NULL;
+}
+
+/*****************************************************************************
+ * Unit test set up and tear down
+ *****************************************************************************/
+
+struct context {
+	gss_ctx_id_t context_handle;
+};
+
+static int setup(void **state) {
+	struct context *ctx = NULL;
+	krb5_context context = NULL;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	krb5_error_code code;
+
+	ctx = talloc_zero(NULL, struct context);
+	assert_non_null(ctx);
+
+	init_mock_results(ctx);
+
+	code = _gsskrb5_init(&context);
+	assert_int_equal(0, code);
+
+	major_status = _gsskrb5_create_ctx(&minor_status,
+					   &ctx->context_handle,
+					   context,
+					   GSS_C_NO_CHANNEL_BINDINGS,
+					   ACCEPTOR_START);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	*state = ctx;
+	return 0;
+}
+
+static int teardown(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+
+	major_status = _gsskrb5_delete_sec_context(&minor_status,
+						   &ctx->context_handle,
+						   GSS_C_NO_BUFFER);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	TALLOC_FREE(ctx);
+	return 0;
+}
+
+/*****************************************************************************
+ * _gsskrb5_unwrap unit tests
+ *****************************************************************************/
+
+static void test_unwrap_dce_style_missing_payload(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gsskrb5_ctx gss_ctx;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+	gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+	gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_dce_style_valid(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gsskrb5_ctx gss_ctx;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0xbc, 0xbd, 0xbe,
+		0x00, /* padding byte */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+	gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+	gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+	expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+	expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+		      (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+	expect_value(__wrap_krb5_verify_checksum, len, 16);
+	expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+	expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+		      (uint8_t *)input.value + 29, 20);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	assert_int_equal(0, conf_state);
+	assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+	assert_int_equal(output.length, 0);
+
+	major_status = gss_release_buffer(&minor_status, &output);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_dce_style_with_seal_missing_payload(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gsskrb5_ctx gss_ctx;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+	gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+	gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_dce_style_with_seal_valid(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gsskrb5_ctx gss_ctx;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0xbc, 0xbd, 0xbe,
+		0x00, /* padding byte */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+	gss_ctx = (gsskrb5_ctx) ctx->context_handle;
+	gss_ctx->flags |= GSS_C_DCE_STYLE;
+
+	expect_value(__wrap_krb5_decrypt, len, 8);
+	expect_value(__wrap_krb5_decrypt, data, (uint8_t *)input.value + 49);
+
+	expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+	expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+		      (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+	expect_value(__wrap_krb5_verify_checksum, len, 16);
+	expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+	expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+		      (uint8_t *)input.value + 29, 20);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	assert_int_equal(1, conf_state);
+	assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+	assert_int_equal(output.length, 0);
+
+	major_status = gss_release_buffer(&minor_status, &output);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_missing_8_bytes(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x2f, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0x00, /* padding byte */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 49);
+
+	/*
+	 * A fixed unwrap_des3() should fail before these wrappers are called,
+	 * but we want the wrappers to have access to any required values in the
+	 * event that they are called. Specifying WILL_RETURN_ONCE avoids a test
+	 * failure if these values remain unused.
+	 */
+	expect_value_count(__wrap_krb5_decrypt_ivec, data,
+			   (uint8_t *)input.value + 21,
+			   WILL_RETURN_ONCE);
+	expect_memory_count(__wrap_krb5_decrypt_ivec, ivec,
+			    (uint8_t *)input.value + 29, DES_CBLOCK_LEN,
+			    WILL_RETURN_ONCE);
+
+	expect_value_count(__wrap_krb5_verify_checksum, len, 8, WILL_RETURN_ONCE);
+	expect_value_count(__wrap_krb5_verify_checksum, data,
+			   (uint8_t *)input.value + 41,
+			   WILL_RETURN_ONCE);
+	expect_memory_count(__wrap_krb5_verify_checksum, cksum->checksum.data,
+			    (uint8_t *)input.value + 29, 20,
+			    WILL_RETURN_ONCE);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_missing_payload(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x14, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0x00, 0xa1, 0xa2, 0xa3, /* padding byte / encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_truncated_header_0(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x00, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 2);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_DEFECTIVE_TOKEN, major_status);
+}
+
+static void test_unwrap_truncated_header_1(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x02, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, /* GSS KRB5 mech */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 4);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_valid(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0xbc, 0xbd, 0xbe,
+		0x00, /* padding byte */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+	expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+	expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+		      (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+	expect_value(__wrap_krb5_verify_checksum, len, 16);
+	expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+	expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+		      (uint8_t *)input.value + 29, 20);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	assert_int_equal(0, conf_state);
+	assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+	assert_int_equal(output.length, 0);
+
+	major_status = gss_release_buffer(&minor_status, &output);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_with_padding_truncated_0(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0x04, 0x04, 0x04, 0x04, /* padding bytes */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+	/*
+	 * A fixed unwrap_des3() should fail before these wrappers are called,
+	 * but we want the wrappers to have access to any required values in the
+	 * event that they are called. Specifying WILL_RETURN_ONCE avoids a test
+	 * failure if these values remain unused.
+	 */
+	expect_value_count(__wrap_krb5_decrypt_ivec, data,
+			   (uint8_t *)input.value + 21,
+			   WILL_RETURN_ONCE);
+	expect_memory_count(__wrap_krb5_decrypt_ivec, ivec,
+			    (uint8_t *)input.value + 29, DES_CBLOCK_LEN,
+			    WILL_RETURN_ONCE);
+
+	expect_value_count(__wrap_krb5_verify_checksum, len, 16, WILL_RETURN_ONCE);
+	expect_value_count(__wrap_krb5_verify_checksum, data,
+			   (uint8_t *)input.value + 41,
+			   WILL_RETURN_ONCE);
+	expect_memory_count(__wrap_krb5_verify_checksum, cksum->checksum.data,
+			    (uint8_t *)input.value + 29, 20,
+			    WILL_RETURN_ONCE);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_with_padding_truncated_1(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0x00, 0xa1, 0xa2, 0xa3, /* padding byte / encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* padding bytes */
+		0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+	/*
+	 * A fixed unwrap_des3() should fail before these wrappers are called,
+	 * but we want the wrappers to have access to any required values in the
+	 * event that they are called. Specifying WILL_RETURN_ONCE avoids a test
+	 * failure if these values remain unused.
+	 */
+	expect_value_count(__wrap_krb5_decrypt_ivec, data,
+			   (uint8_t *)input.value + 21,
+			   WILL_RETURN_ONCE);
+	expect_memory_count(__wrap_krb5_decrypt_ivec, ivec,
+			    (uint8_t *)input.value + 29, DES_CBLOCK_LEN,
+			    WILL_RETURN_ONCE);
+
+	expect_value_count(__wrap_krb5_verify_checksum, len, 16, WILL_RETURN_ONCE);
+	expect_value_count(__wrap_krb5_verify_checksum, data,
+			   (uint8_t *)input.value + 41,
+			   WILL_RETURN_ONCE);
+	expect_memory_count(__wrap_krb5_verify_checksum, cksum->checksum.data,
+			    (uint8_t *)input.value + 29, 20,
+			    WILL_RETURN_ONCE);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_with_padding_valid(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x3f, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0xff, 0xff, /* SEAL_ALG (none) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0xbc, 0xbd, 0xbe, 0xbf,
+		/* padding bytes */
+		0x08, 0x08, 0x08, 0x08,
+		0x08, 0x08, 0x08, 0x08,
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 65);
+
+	expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+	expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+		      (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+	expect_value(__wrap_krb5_verify_checksum, len, 24);
+	expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+	expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+		      (uint8_t *)input.value + 29, 20);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	assert_int_equal(0, conf_state);
+	assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+	assert_int_equal(output.length, 0);
+
+	major_status = gss_release_buffer(&minor_status, &output);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_with_seal_empty_token_valid(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x37, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0xbc, 0xbd, 0xbe,
+		0x00, /* padding byte */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 57);
+
+	expect_value(__wrap_krb5_decrypt, len, 8);
+	expect_value(__wrap_krb5_decrypt, data, (uint8_t *)input.value + 49);
+
+	expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+	expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+		      (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+	expect_value(__wrap_krb5_verify_checksum, len, 16);
+	expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+	expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+		      (uint8_t *)input.value + 29, 20);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	assert_int_equal(1, conf_state);
+	assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+	assert_int_equal(output.length, 0);
+
+	major_status = gss_release_buffer(&minor_status, &output);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+static void test_unwrap_with_seal_missing_payload(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x14, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 22);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_BAD_MECH, major_status);
+}
+
+static void test_unwrap_with_seal_valid(void **state) {
+	struct context *ctx = *state;
+	OM_uint32 major_status;
+	OM_uint32 minor_status;
+	gss_buffer_desc input = {0};
+	gss_buffer_desc output = {0};
+	int conf_state;
+	gss_qop_t qop_state;
+
+	/* See RFC 1964 for token format. */
+	static const uint8_t data[] = {
+		0x60, /* ASN.1 Application tag */
+		0x3e, /* total length */
+		0x06, /* OBJECT IDENTIFIER */
+		0x09, /* mech length */
+		0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x01, 0x02, 0x02, /* GSS KRB5 mech */
+		0x02, 0x01, /* TOK_ID */
+		0x04, 0x00, /* SGN_ALG (HMAC SHA1 DES3-KD) */
+		0x02, 0x00, /* SEAL_ALG (DES3-KD) */
+		0xff, 0xff, /* Filler */
+		0xa0, 0xa1, 0xa2, 0xa3, /* encrypted sequence number */
+		0x00, 0x00, 0x00, 0x00, /* sequence number direction (remote) */
+		/* checksum */
+		0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+		0xa9, 0xaa, 0xab, 0xac, 0xad,
+		0xae, 0xaf, 0xb0, 0xb1, 0xb2,
+		0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+		/* unused */
+		0xb8, 0xb9, 0xba, 0xbb,
+		0xbc, 0xbd, 0xbe, 0xbf,
+		0xc0, 0xc1, 0xc2, 0xc3,
+		0xc4, 0xc5,
+		0x00, /* padding byte */
+	};
+
+	input = get_input_buffer(ctx, data, sizeof(data), 64);
+
+	expect_value(__wrap_krb5_decrypt, len, 15);
+	expect_value(__wrap_krb5_decrypt, data, (uint8_t *)input.value + 49);
+
+	expect_value(__wrap_krb5_decrypt_ivec, data, (uint8_t *)input.value + 21);
+	expect_memory(__wrap_krb5_decrypt_ivec, ivec,
+		      (uint8_t *)input.value + 29, DES_CBLOCK_LEN);
+
+	expect_value(__wrap_krb5_verify_checksum, len, 23);
+	expect_value(__wrap_krb5_verify_checksum, data, (uint8_t *)input.value + 41);
+	expect_memory(__wrap_krb5_verify_checksum, cksum->checksum.data,
+		      (uint8_t *)input.value + 29, 20);
+
+	major_status = _gsskrb5_unwrap(&minor_status,
+				       ctx->context_handle,
+				       &input,
+				       &output,
+				       &conf_state,
+				       &qop_state);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+
+	assert_int_equal(1, conf_state);
+	assert_int_equal(GSS_C_QOP_DEFAULT, qop_state);
+
+	assert_int_equal(output.length, 7);
+	assert_memory_equal((uint8_t *)input.value + 57, output.value, output.length);
+
+	major_status = gss_release_buffer(&minor_status, &output);
+	assert_int_equal(GSS_S_COMPLETE, major_status);
+}
+
+int main(int argc, const char **argv)
+{
+	static const struct CMUnitTest tests[] = {
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_dce_style_missing_payload, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_dce_style_valid, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_dce_style_with_seal_missing_payload, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_dce_style_with_seal_valid, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_missing_8_bytes, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_missing_payload, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_truncated_header_0, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_truncated_header_1, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_valid, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_with_padding_truncated_0, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_with_padding_truncated_1, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_with_padding_valid, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_with_seal_empty_token_valid, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_with_seal_missing_payload, setup, teardown),
+		cmocka_unit_test_setup_teardown(
+			test_unwrap_with_seal_valid, setup, teardown),
+	};
+
+	cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+	return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/auth/wscript_build b/source4/auth/wscript_build
index daf5c8a..4803444 100644
--- a/source4/auth/wscript_build
+++ b/source4/auth/wscript_build
@@ -49,6 +49,27 @@ bld.SAMBA_BINARY('test_kerberos',
         install=False
         )
 
+bld.SAMBA_BINARY('test_heimdal_gensec_unwrap_des',
+                 source='tests/heimdal_unwrap_des.c',
+                 deps='cmocka talloc gssapi-subsystem',
+                 local_include=False,
+                 install=False,
+                 enabled=(bld.CONFIG_SET('SAMBA4_USES_HEIMDAL') and
+                          not bld.CONFIG_SET('USING_SYSTEM_GSSAPI')),
+                 ldflags='''
+                 -Wl,--wrap,ct_memcmp
+                 -Wl,--wrap,der_get_length
+                 -Wl,--wrap,krb5_auth_con_getlocalsubkey
+                 -Wl,--wrap,krb5_crypto_destroy
+                 -Wl,--wrap,krb5_crypto_init
+                 -Wl,--wrap,krb5_decrypt
+                 -Wl,--wrap,krb5_decrypt_ivec
+                 -Wl,--wrap,krb5_free_keyblock
+                 -Wl,--wrap,krb5_verify_checksum
+                 -Wl,--wrap,malloc
+                 '''
+)
+
 pytalloc_util = bld.pyembed_libname('pytalloc-util')
 pyparam_util = bld.pyembed_libname('pyparam_util')
 pyldb_util = bld.pyembed_libname('pyldb-util')
-- 
2.27.0
