--- /dev/null
+/*
+ Definitions for packing and unpacking of msDS-KeyCredentialLink
+ structures, derived from [MS-ADTS] 2.2.20 Key Credential Link Structures
+
+ Note: - KeyMaterial is treated as a binary blob
+ - KEYCREDENTIALLINK_ENTRY ordering by identifier not enforced
+ - Presence of the mandatory KEYCREDENTIALLINK_ENTRYs Key_ID,
+ KeyMaterial and KeyUsage is not enforced
+*/
+
+#include "idl_types.h"
+
+[
+ pointer_default(unique),
+ helper("../librpc/ndr/ndr_keycredlink.h")
+]
+interface keycredlink
+{
+ /* Public structures. */
+
+ typedef [enum8bit, public] enum {
+ KeyID = 0x01,
+ KeyHash = 0x02,
+ KeyMaterial = 0x03,
+ KeyUsage = 0x04,
+ KeySource = 0x05,
+ DeviceId = 0x06,
+ CustomKeyInformation = 0x07,
+ KeyApproximateLastLogonTimeStamp = 0x08,
+ KeyCreationTime = 0x09
+ } KEYCREDENTIALLINK_ENTRY_Identifier;
+
+ typedef [enum8bit, public] enum {
+ KEY_USAGE_NGC = 0x01,
+ KEY_USAGE_FIDO = 0x02,
+ KEY_USAGE_FEK = 0x03
+ } KEYCREDENTIALLINK_ENTRY_KeyUsage;
+
+ typedef [enum8bit, public] enum {
+ KEY_SOURCE_AD = 0x00
+ } KEYCREDENTIALLINK_ENTRY_KeySource;
+
+ typedef [bitmap8bit, public] bitmap {
+ CUSTOM_KEY_INFO_FLAGS_ATTESTATION = 0x01,
+ /* Reserved for future use */
+ CUSTOM_KEY_INFO_FLAGS_MFA_NOT_USED = 0x02
+ /*
+ * During creation of this key, the requesting client
+ * authenticated using only a single credential.
+ */
+ } CUSTOM_KEY_INFO_Flags;
+
+ typedef [enum8bit, public] enum {
+ Unspecified = 0x00, // No volume specified
+ // defined as None in the docs but this
+ // causes issues in the python bindings
+ OSV = 0x01, // Operating system volume
+ FDV = 0x02, // Fixed data volume
+ RDV = 0x03 // Removable data volume
+ } CUSTOM_KEY_INFO_VolType;
+
+ typedef [enum8bit, public] enum {
+ Unsupported = 0x00, // Notification is not supported
+ // defined as None in the docs but this
+ // causes issues in the python bindings
+ Supported = 0x01 // Notification is supported
+ } CUSTOM_KEY_INFO_SupportsNotification;
+
+ typedef [enum8bit, public] enum {
+ Unknown = 0x00,
+ Weak = 0x01,
+ Normal = 0x02
+ } CUSTOM_KEY_INFO_KeyStrength;
+
+ /*
+ * Extended custom key information
+ */
+ typedef [public, flag(NDR_NOALIGN)] struct {
+ [value(0)] uint8 version;
+ uint8 size;
+ uint8 data[size];
+ /*
+ * A Concise Binary Object Representation (CBOR)-encoded blob
+ * whose length is specified by the Size field.
+ * CBOR is a binary data serialization format defined in
+ * [RFC7049]. The contents of this field are opaque and
+ * have no behavioural impact on the protocol.
+ */
+ } EncodedExtendedCKI;
+
+ /*
+ * This structure has two possible representations which are
+ * differentiated by the sized of the encoded data.
+ *
+ * a) only the Version and Flags fields are present;
+ * and the structure has a size of 2 bytes.
+ * b) all additional are also present
+ * - the structure's total size is variable but not 2
+ *
+ * The boolean isExtended attribute is used to indicate which version
+ * was unpacked or should be packed.
+ *
+ * Note: isExtended and count are not present in the packed binary
+ * representation
+ */
+
+ typedef [nopush, nopull] struct {
+ [value(1)] uint8 version;
+ CUSTOM_KEY_INFO_Flags flags;
+ boolean8 isExtended;
+ /*
+ * Not present in packed representation indicates
+ * that the following fields are present
+ */
+ CUSTOM_KEY_INFO_VolType volType;
+ CUSTOM_KEY_INFO_SupportsNotification supportsNotification;
+ [value(1)] uint8 fekKeyVersion;
+ CUSTOM_KEY_INFO_KeyStrength keyStrength;
+ uint8 reserved[10]; /* Reserved bytes not currently used */
+ uint32 count;
+ /* Not present in packed representation size cki array */
+ EncodedExtendedCKI cki[count];
+ } CUSTOM_KEY_INFORMATION;
+
+ typedef [switch_type(KEYCREDENTIALLINK_ENTRY_Identifier),
+ public,
+ nopull,
+ nodiscriminant,
+ gensize,
+ flag(NDR_NOALIGN)]
+ union {
+ [case(KeyID)]
+ uint8 keyId[32];
+ [case(KeyHash)]
+ uint8 keyHash[32];
+ [case(KeyUsage)]
+ KEYCREDENTIALLINK_ENTRY_KeyUsage keyUsage;
+ [case(KeySource), value(KEY_SOURCE_AD)]
+ KEYCREDENTIALLINK_ENTRY_KeySource keySource;
+ [case(KeyMaterial)] [flag(NDR_REMAINING)]
+ DATA_BLOB keyMaterial;
+ /* Currently treating Key Material as an opaque binary blob */
+ [case(DeviceId)]
+ uint8 deviceId[16];
+ [case(CustomKeyInformation)]
+ CUSTOM_KEY_INFORMATION customKeyInformation;
+ [case(KeyApproximateLastLogonTimeStamp)]
+ NTTIME lastLogon;
+ [case(KeyCreationTime)]
+ NTTIME created;
+ } KEYCREDENTIALLINK_ENTRY_Value;
+
+ typedef [public, nopull, flag(NDR_NOALIGN)] struct {
+ [value(
+ ndr_size_KEYCREDENTIALLINK_ENTRY_Value(
+ &value,identifier,ndr->flags))]
+ uint16 length;
+ KEYCREDENTIALLINK_ENTRY_Identifier identifier;
+ [switch_is(identifier)] KEYCREDENTIALLINK_ENTRY_Value value;
+ } KEYCREDENTIALLINK_ENTRY;
+
+ typedef [public, nopull, nopush, flag(NDR_NOALIGN)] struct {
+ [value(0x0200)] uint32 version;
+ uint32 count;
+ KEYCREDENTIALLINK_ENTRY entries[count];
+ } KEYCREDENTIALLINK_BLOB;
+}
--- /dev/null
+/*
+ Unix SMB/CIFS implementation.
+
+ Support routines for packing and unpacking of msDS-KeyCredentialLink
+ structures.
+
+ See [MS-ADTS] 2.2.20 Key Credential Link Structures
+
+ Copyright (C) Gary Lockyer 2025
+
+ 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/>.
+*/
+
+#include "lib/replace/replace.h"
+#include "librpc/gen_ndr/ndr_keycredlink.h"
+#include "gen_ndr/keycredlink.h"
+#include "libndr.h"
+#include <assert.h>
+
+/*
+ * The KEYCREDENTIALLINK_BLOB consists of the version and a series of variable
+ * length KEYCREDENTIALLINK_ENTRIES.
+ */
+enum ndr_err_code ndr_pull_KEYCREDENTIALLINK_BLOB(
+ struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct KEYCREDENTIALLINK_BLOB *blob)
+{
+ libndr_flags _flags_save_STRUCT = ndr->flags;
+ ndr_set_flags(&ndr->flags, LIBNDR_FLAG_NOALIGN);
+
+ NDR_CHECK(ndr_pull_uint32(ndr, ndr_flags, &blob->version));
+ if (blob->version != 0x0200) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid version of (0x%04x) "
+ "should be 0x0200, at byte %zu\n",
+ blob->version,
+ (ndr->offset - sizeof(uint32_t)));
+ }
+ blob->count = 0;
+ blob->entries = talloc_array(ndr->current_mem_ctx,
+ struct KEYCREDENTIALLINK_ENTRY,
+ blob->count);
+ if (blob->entries == NULL) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Failed to pull KEYCREDENTIALLINK_ENTRY");
+ }
+ while (ndr->offset < ndr->data_size) {
+ blob->entries = talloc_realloc(ndr->current_mem_ctx,
+ blob->entries,
+ struct KEYCREDENTIALLINK_ENTRY,
+ blob->count + 1);
+ if (blob->entries == NULL) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Failed to pull KEYCREDENTIALLINK_ENTRY");
+ }
+ NDR_CHECK(ndr_pull_KEYCREDENTIALLINK_ENTRY(
+ ndr, ndr_flags, &blob->entries[blob->count]));
+ blob->count++;
+ }
+ ndr->flags = _flags_save_STRUCT;
+ return NDR_ERR_SUCCESS;
+}
+
+enum ndr_err_code ndr_push_KEYCREDENTIALLINK_BLOB(
+ struct ndr_push *ndr,
+ ndr_flags_type ndr_flags,
+ const struct KEYCREDENTIALLINK_BLOB *blob)
+{
+ int i = 0;
+
+ if (blob->version != 0x0200) {
+ return ndr_push_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid version of (0x%04x) "
+ "should be 0x0200, at byte %zu\n",
+ blob->version,
+ (ndr->offset - sizeof(uint32_t)));
+ }
+ NDR_CHECK(ndr_push_uint32(ndr, ndr_flags, blob->version));
+
+ for (i = 0; i < blob->count; i++) {
+ NDR_CHECK(ndr_push_KEYCREDENTIALLINK_ENTRY(ndr,
+ ndr_flags,
+ &blob->entries[i]));
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/*
+ * To pull the CUSTOM_KEY_INFORMATION the length from the enclosing
+ * KEYCREDENTIALLINK_ENTRY needs to be passed in.
+ *
+ * CUSTOM_KEY_INFORMATION has two representations based on the size parameter
+ *
+ * If size is 2 only the version and flags are expected.
+ * If the size is greater than 2 then
+ * version, flags, volType, supportsNotification, fekKeyVersion,
+ * keyStrength and the reserved bytes are expected
+ * Optionally followed by a series of EncodedExtendedCKI entries
+ *
+ */
+static enum ndr_err_code pull_cki(struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct CUSTOM_KEY_INFORMATION *info,
+ uint32_t size)
+{
+ /* Calculate the end of the CUSTOM_KEY_INFORMATION in the raw bytes */
+ uint32_t end_offset = ndr->offset + size;
+
+ /*
+ * Initialise the CUSTOM_KEY_INFORMATION, in case this is the
+ * short form.
+ */
+ *info = (struct CUSTOM_KEY_INFORMATION){0};
+
+ NDR_CHECK(ndr_pull_uint8(ndr, ndr_flags, &info->version));
+ if (info->version != 0x01) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid version of (0x%02x) "
+ "should be 0x01, at byte %zu\n",
+ info->version,
+ (ndr->offset - sizeof(uint8_t)));
+ }
+ NDR_CHECK(ndr_pull_CUSTOM_KEY_INFO_Flags(ndr, ndr_flags, &info->flags));
+
+ if (size == 2) {
+ info->isExtended = false;
+ return NDR_ERR_SUCCESS;
+ }
+ info->isExtended = true;
+ NDR_CHECK(ndr_pull_CUSTOM_KEY_INFO_VolType(ndr,
+ ndr_flags,
+ &info->volType));
+ NDR_CHECK(ndr_pull_CUSTOM_KEY_INFO_SupportsNotification(
+ ndr, ndr_flags, &info->supportsNotification));
+ NDR_CHECK(ndr_pull_uint8(ndr, ndr_flags, &info->fekKeyVersion));
+ if (info->fekKeyVersion != 0x01) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid fekKeyVersion of (0x%02x) "
+ "should be 0x01, at byte %zu\n",
+ info->fekKeyVersion,
+ (ndr->offset - sizeof(uint8_t)));
+ }
+ NDR_CHECK(ndr_pull_CUSTOM_KEY_INFO_KeyStrength(ndr,
+ ndr_flags,
+ &info->keyStrength));
+ NDR_CHECK(ndr_pull_array_uint8(ndr, ndr_flags, info->reserved, 10));
+
+ /* Pull the EncodedExtendedCKI values */
+ info->count = 0;
+ info->cki = talloc_array(ndr->current_mem_ctx,
+ struct EncodedExtendedCKI,
+ info->count);
+ if (info->cki == NULL) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Failed to pull EncodedExtendCKI");
+ }
+ while (ndr->offset < end_offset) {
+ info->cki = talloc_realloc(ndr->current_mem_ctx,
+ info->cki,
+ struct EncodedExtendedCKI,
+ info->count + 1);
+ if (info->cki == NULL) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_ALLOC,
+ "Failed to pull EncodedExtendedCKI");
+ }
+ NDR_CHECK(ndr_pull_EncodedExtendedCKI(ndr,
+ ndr_flags,
+ &info->cki[info->count]));
+ info->count++;
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/*
+ * CUSTOM_KEY-INFORMATION has two representations with differing sizes
+ * the flag isExtended controls which version is written.
+ */
+enum ndr_err_code ndr_push_CUSTOM_KEY_INFORMATION(
+ struct ndr_push *ndr,
+ ndr_flags_type ndr_flags,
+ const struct CUSTOM_KEY_INFORMATION *info)
+{
+ int i = 0;
+
+ if (info->version != 0x01) {
+ return ndr_push_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid version of (0x%02x) "
+ "should be 0x01, at byte %zu\n",
+ info->version,
+ (ndr->offset - sizeof(uint8_t)));
+ }
+ NDR_CHECK(ndr_push_uint8(ndr, ndr_flags, info->version));
+ NDR_CHECK(ndr_push_CUSTOM_KEY_INFO_Flags(ndr, ndr_flags, info->flags));
+ if (!info->isExtended) {
+ return NDR_ERR_SUCCESS;
+ }
+
+ NDR_CHECK(ndr_push_CUSTOM_KEY_INFO_VolType(ndr,
+ ndr_flags,
+ info->volType));
+ NDR_CHECK(ndr_push_CUSTOM_KEY_INFO_SupportsNotification(
+ ndr, ndr_flags, info->supportsNotification));
+ if (info->fekKeyVersion != 0x01) {
+ return ndr_push_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid fekKeyVersion of (0x%02x) "
+ "should be 0x01, at byte %zu\n",
+ info->fekKeyVersion,
+ (ndr->offset - sizeof(uint8_t)));
+ }
+ NDR_CHECK(ndr_push_uint8(ndr, ndr_flags, info->fekKeyVersion));
+ NDR_CHECK(ndr_push_CUSTOM_KEY_INFO_KeyStrength(ndr,
+ ndr_flags,
+ info->keyStrength));
+ NDR_CHECK(ndr_push_array_uint8(ndr, ndr_flags, info->reserved, 10));
+
+ for (i = 0; i < info->count; i++) {
+ NDR_CHECK(ndr_push_EncodedExtendedCKI(ndr,
+ ndr_flags,
+ &info->cki[i]));
+ }
+ return NDR_ERR_SUCCESS;
+}
+
+/*
+ * To pull a KEYCREDENTIALLINK_Value the length from the enclosing
+ * KEYCREDENTIALLINK_ENTRY needs to be passed in.
+ *
+ */
+static enum ndr_err_code ndr_pull_value(struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ union KEYCREDENTIALLINK_ENTRY_Value *r,
+ uint32_t size)
+{
+ uint32_t level;
+ const size_t header_len = sizeof(uint16_t) + sizeof(uint8_t);
+ const size_t identifier_len = sizeof(uint8_t);
+ libndr_flags flags_save = ndr->flags;
+
+ /* this function should only be called if NDR_SCALARS is set */
+ assert(ndr_flags & NDR_SCALARS);
+
+ ndr_set_flags(&ndr->flags, LIBNDR_FLAG_NOALIGN);
+
+ /* This token is not used again */
+ NDR_CHECK(ndr_pull_steal_switch_value(ndr, r, &level));
+
+ switch (level) {
+ case KeyID: {
+ if (size != 32) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ARRAY_SIZE,
+ "Invalid size of (%" PRIu32
+ ") for KeyID "
+ "should be (32), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(
+ ndr_pull_array_uint8(ndr, NDR_SCALARS, r->keyId, size));
+ break;
+ }
+
+ case KeyHash: {
+ if (size != 32) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ARRAY_SIZE,
+ "Invalid size of (%" PRIu32
+ ") for KeyHash "
+ "should be (32), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(ndr_pull_array_uint8(
+ ndr, NDR_SCALARS, r->keyHash, size));
+ break;
+ }
+
+ case KeyUsage: {
+ if (size != 1) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "Invalid length of (%" PRIu32
+ ") for KeyUsage "
+ "should be (1), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(ndr_pull_KEYCREDENTIALLINK_ENTRY_KeyUsage(
+ ndr, NDR_SCALARS, &r->keyUsage));
+ break;
+ }
+
+ case KeySource: {
+ if (size != 1) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_LENGTH,
+ "Invalid length of (%" PRIu32
+ ") for KeySource "
+ "should be (1), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(ndr_pull_KEYCREDENTIALLINK_ENTRY_KeySource(
+ ndr, NDR_SCALARS, &r->keySource));
+ break;
+ }
+
+ case KeyMaterial: {
+ if (size == 0) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_LENGTH,
+ "Invalid length of (%" PRIu32
+ ") for keyMaterial "
+ "should be non zero, at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_PULL_NEED_BYTES(ndr, size);
+ r->keyMaterial = data_blob_talloc(ndr->current_mem_ctx,
+ ndr->data + ndr->offset,
+ size);
+ if (r->keyMaterial.data == NULL) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ALLOC,
+ "Failed to pull keyMaterial");
+ }
+ ndr->offset += size;
+ break;
+ }
+
+ case DeviceId: {
+ if (size != 16) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_ARRAY_SIZE,
+ "Invalid size of (%" PRIu32
+ ") for KeySource "
+ "should be (1), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(ndr_pull_array_uint8(
+ ndr, NDR_SCALARS, r->deviceId, size));
+ break;
+ }
+
+ case CustomKeyInformation: {
+ NDR_CHECK(pull_cki(
+ ndr, NDR_SCALARS, &r->customKeyInformation, size));
+ break;
+ }
+
+ case KeyApproximateLastLogonTimeStamp: {
+ if (size != 8) {
+ return ndr_pull_error(
+ ndr,
+ NDR_ERR_LENGTH,
+ "Invalid length of (%" PRIu32 ") for "
+ "KeyApproximateLastLogonTimeStamp "
+ "should be (8), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(ndr_pull_NTTIME(ndr, NDR_SCALARS, &r->lastLogon));
+ break;
+ }
+
+ case KeyCreationTime: {
+ if (size != 8) {
+ return ndr_pull_error(ndr,
+ NDR_ERR_RANGE,
+ "Invalid size of (%" PRIu32
+ ") for "
+ "KeyCreationTime "
+ "should be (8), at byte %zu\n",
+ size,
+ (ndr->offset - header_len));
+ }
+ NDR_CHECK(ndr_pull_NTTIME(ndr, NDR_SCALARS, &r->created));
+ break;
+ }
+
+ default:
+ return ndr_pull_error(ndr,
+ NDR_ERR_BAD_SWITCH,
+ "Bad switch value %02x at byte %zu",
+ level,
+ ndr->offset - identifier_len);
+ }
+ ndr->flags = flags_save;
+ return NDR_ERR_SUCCESS;
+}
+
+/*
+ * Need to pass the length element of the KEYCREDENTIALLINK_ENTRY down to
+ * ndr_pull_value, the code that pulls the KEYCREDENTIALLINK_ENTRY_Value.
+ */
+enum ndr_err_code ndr_pull_KEYCREDENTIALLINK_ENTRY(
+ struct ndr_pull *ndr,
+ ndr_flags_type ndr_flags,
+ struct KEYCREDENTIALLINK_ENTRY *r)
+{
+ libndr_flags _flags_save_STRUCT = ndr->flags;
+ ndr_set_flags(&ndr->flags, LIBNDR_FLAG_NOALIGN);
+ if (ndr_flags & NDR_SCALARS) {
+ NDR_CHECK(ndr_pull_uint16(ndr, NDR_SCALARS, &r->length));
+ NDR_CHECK(ndr_pull_KEYCREDENTIALLINK_ENTRY_Identifier(
+ ndr, NDR_SCALARS, &r->identifier));
+ NDR_CHECK(ndr_pull_set_switch_value(ndr,
+ &r->value,
+ r->identifier));
+ NDR_CHECK(
+ ndr_pull_value(ndr, NDR_SCALARS, &r->value, r->length));
+ }
+ ndr->flags = _flags_save_STRUCT;
+ return NDR_ERR_SUCCESS;
+}
--- /dev/null
+#!/usr/bin/env python3
+# Tests for NDR packing and unpacking of msDS-KeyCredentialLink structures
+#
+# See [MS-ADTS] 2.2.20 Key Credential Link Structures
+#
+# Copyright (C) Gary Lockyer 2025
+#
+# 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/>.
+#
+
+import sys
+import os
+
+sys.path.insert(0, "bin/python")
+os.environ["PYTHONUNBUFFERED"] = "1"
+
+from samba.dcerpc import keycredlink
+from samba.ndr import ndr_pack, ndr_unpack
+from samba.tests import TestCase
+
+
+class KeyCredentialLinkTests(TestCase):
+ def test_unpack_empty_key_blob(self):
+ """ensure that a minimal KEYCREDENTIALLINK_BLOB (only the version)
+ can be unpacked, then packed into an identical bytes
+ """
+ empty_key_blob = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ )
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, empty_key_blob)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 0)
+ self.assertEqual(len(blob.entries), 0)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(empty_key_blob, packed)
+
+ def test_unpack_empty_key_blob_invalid_version(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with an invalid version
+ is rejected.
+ """
+ invalid_version_key_blob = bytes.fromhex(
+ "00 03 00 00" # Version 3 value 0x00000300
+ )
+ with self.assertRaises(RuntimeError) as e:
+ ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, invalid_version_key_blob)
+
+ self.assertEqual(e.exception.args[0], 13)
+ self.assertEqual(e.exception.args[1], "Range Error")
+
+ def test_unpack_short_key_blob(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with only 3 bytes
+ is rejected.
+ """
+ short_key_blob = bytes.fromhex(
+ "00 02 00" # Version 2 value 0x00000200
+ )
+ with self.assertRaises(RuntimeError) as e:
+ ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, short_key_blob)
+
+ self.assertEqual(e.exception.args[0], 11)
+ self.assertEqual(e.exception.args[1], "Buffer Size Error")
+
+ def test_unpack_KeyId(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a keyId
+ is correctly packed and unpacked.
+ """
+ source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "20 00" # 32 bytes of data
+ "01" # a Key Id
+ "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ )
+ key_id = bytes.fromhex(
+ "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ )
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 32)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.KeyID)
+ self.assertEqual(bytes(blob.entries[0].value), key_id)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(source, packed)
+
+ def test_unpack_KeyHash(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a keyHash
+ is correctly packed and unpacked.
+ """
+ key_blob_key_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "20 00" # 32 bytes of data
+ "02" # a Key Hash
+ "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ )
+ key_hash = bytes.fromhex(
+ "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ )
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, key_blob_key_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 32)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.KeyHash)
+ self.assertEqual(bytes(blob.entries[0].value), key_hash)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(key_blob_key_source, packed)
+
+ def test_unpack_KeyUsage(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a keyUsage
+ is correctly packed and unpacked.
+ """
+ key_blob_key_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "01 00" # 1 byte of data
+ "04" # a Key Usage
+ "01" # KEY_USAGE_NGC
+ )
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, key_blob_key_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 1)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.KeyUsage)
+ self.assertEqual(blob.entries[0].value, keycredlink.KEY_USAGE_NGC)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(key_blob_key_source, packed)
+
+ def test_unpack_KeySource(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a keySource
+ is correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "01 00" # 1 byte of data
+ "05" # a Key Source
+ "00" # KEY_SOURCE_AD
+ )
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 1)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.KeySource)
+ self.assertEqual(blob.entries[0].value, keycredlink.KEY_SOURCE_AD)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_DeviceId(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a deviceId
+ is correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "10 00" # 16 bytes of data
+ "06" # a Device Id
+ "00 01 02 03 04 05 06 07"
+ "08 09 0A 0B 0C 0D 0E 0F"
+ )
+ device_id = bytes.fromhex("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
+
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 16)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.DeviceId)
+ self.assertEqual(bytes(blob.entries[0].value), device_id)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_CustomKeyInformation(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a short custom key
+ (2 bytes) is correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "02 00" # 2 bytes of data
+ "07" # Custom Key Information
+ "01" # Version 1
+ "02" # Flags MFA not used
+ )
+
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 2)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.CustomKeyInformation)
+ self.assertEqual(blob.entries[0].value.version, 1)
+ self.assertEqual(blob.entries[0].value.flags, 0x02)
+ self.assertFalse(blob.entries[0].value.isExtended)
+
+ # The remaining fields should have been set to zeros
+ zeros = bytes.fromhex("00 00 00 00 00 00 00 00 00 00")
+ self.assertEqual(blob.entries[0].value.volType, 0x00)
+ self.assertEqual(blob.entries[0].value.supportsNotification, 0x00)
+ self.assertEqual(blob.entries[0].value.fekKeyVersion, 0x00)
+ self.assertEqual(blob.entries[0].value.keyStrength, 0x00)
+ self.assertEqual(bytes(blob.entries[0].value.reserved), zeros)
+ self.assertEqual(blob.entries[0].value.count, 0)
+ self.assertEqual(len(blob.entries[0].value.cki), 0)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_CustomKeyInformationExtendedNoCki(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with long custom key
+ information (16 bytes), and no EncodedExtendedCki is
+ correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "10 00" # 16 bytes of data
+ "07" # Custom Key Information
+ "01" # Version 1
+ "03" # Flags MFA not used and attestation
+ "02" # fixed volume
+ "01" # Notification supported
+ "01" # Fek Key Version
+ "01" # weak key strength
+ "00 00 00 00 00 00 00 00 00 00" # reserved space
+ )
+ reserved = bytes.fromhex("00 00 00 00 00 00 00 00 00 00")
+
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 16)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.CustomKeyInformation)
+ self.assertEqual(blob.entries[0].value.version, 1)
+ self.assertEqual(blob.entries[0].value.flags, 0x03)
+ self.assertTrue(blob.entries[0].value.isExtended)
+ self.assertEqual(blob.entries[0].value.volType, keycredlink.FDV)
+ self.assertEqual(
+ blob.entries[0].value.supportsNotification, keycredlink.Supported
+ )
+ self.assertEqual(blob.entries[0].value.fekKeyVersion, 0x01)
+ self.assertEqual(blob.entries[0].value.keyStrength, keycredlink.Weak)
+ self.assertEqual(bytes(blob.entries[0].value.reserved), reserved)
+ self.assertEqual(blob.entries[0].value.count, 0)
+ self.assertEqual(len(blob.entries[0].value.cki), 0)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_CustomKeyInformationExtendedOneCki(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with long custom key
+ information (16 bytes), and one EncodedExtendedCki is
+ correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "14 00" # 16 byte header + 4 bytes
+ " 07" # Custom Key Information
+ " 01" # Version 1
+ " 00" # Flags MFA not used
+ " 00" # No volume type
+ " 00" # Unsupported
+ " 01" # Fek Key Version
+ " 00" # Unknown key strength
+ " 00 00 00 00 00 00 00 00 00 00" # reserved space
+ " 00 02 0D 0A"
+ )
+ reserved = bytes.fromhex("00 00 00 00 00 00 00 00 00 00")
+
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 20)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.CustomKeyInformation)
+ self.assertEqual(blob.entries[0].value.version, 1)
+ self.assertEqual(blob.entries[0].value.flags, 0x00)
+ self.assertTrue(blob.entries[0].value.isExtended)
+ self.assertEqual(blob.entries[0].value.volType, keycredlink.Unspecified)
+ self.assertEqual(
+ blob.entries[0].value.supportsNotification, keycredlink.Unsupported
+ )
+ self.assertEqual(blob.entries[0].value.fekKeyVersion, 1)
+ self.assertEqual(blob.entries[0].value.keyStrength, keycredlink.Unknown)
+ self.assertEqual(bytes(blob.entries[0].value.reserved), reserved)
+ self.assertEqual(blob.entries[0].value.count, 1)
+ self.assertEqual(len(blob.entries[0].value.cki), 1)
+ self.assertEqual(blob.entries[0].value.cki[0].size, 2)
+ self.assertEqual(blob.entries[0].value.cki[0].data, [13, 10])
+ self.assertEqual(len(blob.entries[0].value.cki[0].data), 2)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_CustomKeyInformationExtendedTwoCki(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with long custom key
+ information (16 bytes), and two EncodedExtendedCkis is
+ correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "19 00" # 16 bytes header + 9 bytes CKI info
+ "07" # Custom Key Information
+ "01" # Version 1
+ "01" # Flags Attestation
+ "03" # Removablevolume
+ "01" # Notification supported
+ "01" # Fek Key Version
+ "02" # Normal key strength
+ "00 00 00 00 00 00 00 00 00 00" # reserved space
+ "00 02 0D 0A"
+ "00 03 01 02 03"
+ )
+ reserved = bytes.fromhex("00 00 00 00 00 00 00 00 00 00")
+
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 25)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.CustomKeyInformation)
+ self.assertEqual(blob.entries[0].value.version, 1)
+ self.assertEqual(
+ blob.entries[0].value.flags, keycredlink.CUSTOM_KEY_INFO_FLAGS_ATTESTATION
+ )
+ self.assertTrue(blob.entries[0].value.isExtended)
+ self.assertEqual(blob.entries[0].value.volType, keycredlink.RDV)
+ self.assertEqual(
+ blob.entries[0].value.supportsNotification, keycredlink.Supported
+ )
+ self.assertEqual(blob.entries[0].value.fekKeyVersion, 0x01)
+ self.assertEqual(blob.entries[0].value.keyStrength, keycredlink.Normal)
+ self.assertEqual(bytes(blob.entries[0].value.reserved), reserved)
+ self.assertEqual(blob.entries[0].value.count, 2)
+ self.assertEqual(len(blob.entries[0].value.cki), 2)
+ self.assertEqual(blob.entries[0].value.cki[0].size, 2)
+ self.assertEqual(blob.entries[0].value.cki[0].data, [13, 10])
+ self.assertEqual(blob.entries[0].value.cki[1].size, 3)
+ self.assertEqual(blob.entries[0].value.cki[1].data, [1, 2, 3])
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_LastLogon(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a last logon is
+ correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "08 00" # 8 bytes of data
+ "08" # Approximate Last Logon Timestamp
+ "80 30 68 87 D0 D4 DB 01" # Wed Jun 04 2025 09:43:22 GMT+1200
+ )
+ time = 0x1DBD4D087683080 # 133934606027600000 decimal
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 8)
+ self.assertEqual(
+ blob.entries[0].identifier, keycredlink.KeyApproximateLastLogonTimeStamp
+ )
+ self.assertEqual(blob.entries[0].value, time)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_KeyCreationTime(self):
+ """ensure that a KEYCREDENTIALLINK_BLOB with a key creation time is
+ correctly packed and unpacked.
+ """
+ blob_source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "08 00" # 8 bytes of data
+ "09" # Key Creation Time
+ "80 96 26 FA DE 4D B8 01" # Sun Sep 26 1993 08:03:02 GMT+1200
+ )
+ time = 0x1B84DDEFA269680 # 123934609827600000 decimal
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, blob_source)
+
+ self.assertEqual(blob.version, 0x0200)
+ self.assertEqual(blob.count, 1)
+ self.assertEqual(len(blob.entries), 1)
+ self.assertEqual(blob.entries[0].length, 8)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.KeyCreationTime)
+ self.assertEqual(blob.entries[0].value, time)
+
+ packed = ndr_pack(blob)
+ self.assertEqual(blob_source, packed)
+
+ def test_unpack_full(self):
+ """ensure that fully populated KEYCREDENTIALLINK_BLOB is
+ correctly packed and unpacked.
+ """
+ source = bytes.fromhex(
+ "00 02 00 00" # Version 2 value 0x00000200
+ "20 00 01" # 32 bytes of data, identifier = key id
+ " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ " 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ "20 00 02" # 32 bytes of data, identifier = key hash
+ " 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "40 00 03" # 64 bytes of data, identifier = key material
+ " 43 05 A2 02 C6 8F 94 48 9B 82 C4 99 C6 F2 1A 74 "
+ " 42 D7 FE C1 F5 EE AE 52 B5 C7 59 DE 32 14 91 98 "
+ " 44 4D 95 82 75 11 38 32 EA 7B 52 E9 1E 8E D4 14 "
+ " 51 DF 93 25 39 3F E1 18 9C E5 3E 7A E6 D0 2E 77 "
+ "01 00 04 01 " # 1 byte data, identifier = key usage (KEY_USAGE_NGC)
+ "01 00 05 00 " # 1 byte data, identifier = key source (KEY_SOURCE_AD)
+ "10 00 06" # 16 bytes of data, identifier = Device Id
+ " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "19 00" # 16 bytes header + 9 bytes CKI info
+ " 07" # Custom Key Information
+ " 01" # Version 1
+ " 02" # Flags MFA not used
+ " 01" # Operating system volume
+ " 01" # Notification supported
+ " 01" # Fek Key Version
+ " 02" # Normal key strength
+ " 00 00 00 00 00 00 00 00 00 00" # reserved space
+ " 00 02 0D 0A" # two bytes custom key info
+ " 00 03 01 02 03" # three bytes custom key information
+ "08 00 08" # 8 bytes of data, identifier = Approximate Last Logon
+ " 80 30 68 87 D0 D4 DB 01" # Wed Jun 04 2025 09:43:22 GMT+1200
+ "08 00 09" # 8 bytes of data, identifier = Key Creation Time
+ " 80 96 26 FA DE 4D B8 01" # Sun Sep 26 1993 08:03:02 GMT+1200
+ )
+ blob = ndr_unpack(keycredlink.KEYCREDENTIALLINK_BLOB, source)
+ self.assertEqual(len(blob.entries), 9)
+
+ # Check the key Id
+ key_id = bytes.fromhex(
+ "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ )
+ self.assertEqual(blob.entries[0].length, 32)
+ self.assertEqual(blob.entries[0].identifier, keycredlink.KeyID)
+ self.assertEqual(bytes(blob.entries[0].value), key_id)
+
+ # Check the key hash
+ key_hash = bytes.fromhex(
+ "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"
+ "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
+ )
+ self.assertEqual(blob.entries[1].length, 32)
+ self.assertEqual(blob.entries[1].identifier, keycredlink.KeyHash)
+ self.assertEqual(bytes(blob.entries[1].value), key_hash)
+
+ # Check the key material
+ key_material = bytes.fromhex(
+ "43 05 A2 02 C6 8F 94 48 9B 82 C4 99 C6 F2 1A 74 "
+ "42 D7 FE C1 F5 EE AE 52 B5 C7 59 DE 32 14 91 98 "
+ "44 4D 95 82 75 11 38 32 EA 7B 52 E9 1E 8E D4 14 "
+ "51 DF 93 25 39 3F E1 18 9C E5 3E 7A E6 D0 2E 77 "
+ )
+ self.assertEqual(blob.entries[2].length, 64)
+ self.assertEqual(blob.entries[2].identifier, keycredlink.KeyMaterial)
+ self.assertEqual(bytes(blob.entries[2].value), key_material)
+
+ # Check the key usage
+ self.assertEqual(blob.entries[3].length, 1)
+ self.assertEqual(blob.entries[3].identifier, keycredlink.KeyUsage)
+ self.assertEqual(blob.entries[3].value, keycredlink.KEY_USAGE_NGC)
+
+ # Check the key source
+ self.assertEqual(blob.entries[4].length, 1)
+ self.assertEqual(blob.entries[4].identifier, keycredlink.KeySource)
+ self.assertEqual(blob.entries[4].value, keycredlink.KEY_SOURCE_AD)
+
+ # Check the key device id
+ device_id = bytes.fromhex("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F")
+ self.assertEqual(blob.entries[5].length, 16)
+ self.assertEqual(blob.entries[5].identifier, keycredlink.DeviceId)
+ self.assertEqual(bytes(blob.entries[5].value), device_id)
+
+ # Check custom key information
+ reserved = bytes.fromhex("00 00 00 00 00 00 00 00 00 00")
+
+ self.assertEqual(blob.entries[6].length, 25)
+ self.assertEqual(blob.entries[6].identifier, keycredlink.CustomKeyInformation)
+ self.assertEqual(blob.entries[6].value.version, 1)
+ self.assertEqual(
+ blob.entries[6].value.flags, keycredlink.CUSTOM_KEY_INFO_FLAGS_MFA_NOT_USED
+ )
+ self.assertTrue(blob.entries[6].value.isExtended)
+ self.assertEqual(blob.entries[6].value.volType, keycredlink.OSV)
+ self.assertEqual(
+ blob.entries[6].value.supportsNotification, keycredlink.Supported
+ )
+ self.assertEqual(blob.entries[6].value.fekKeyVersion, 0x01)
+ self.assertEqual(blob.entries[6].value.keyStrength, keycredlink.Normal)
+ self.assertEqual(bytes(blob.entries[6].value.reserved), reserved)
+ # Check the EncodedExtendedCKI entries
+ self.assertEqual(blob.entries[6].value.count, 2)
+ self.assertEqual(len(blob.entries[6].value.cki), 2)
+
+ self.assertEqual(blob.entries[6].value.cki[0].size, 2)
+ self.assertEqual(blob.entries[6].value.cki[0].data, [13, 10])
+ self.assertEqual(blob.entries[6].value.cki[1].size, 3)
+ self.assertEqual(blob.entries[6].value.cki[1].data, [1, 2, 3])
+
+ # check last logon
+ last_logon = 0x1DBD4D087683080 # 133934606027600000 decimal
+ self.assertEqual(blob.entries[7].length, 8)
+ self.assertEqual(
+ blob.entries[7].identifier, keycredlink.KeyApproximateLastLogonTimeStamp
+ )
+ self.assertEqual(blob.entries[7].value, last_logon)
+
+ # check key creation time
+ key_created = 0x1B84DDEFA269680 # 123934609827600000 decimal
+ self.assertEqual(blob.entries[8].length, 8)
+ self.assertEqual(blob.entries[8].identifier, keycredlink.KeyCreationTime)
+ self.assertEqual(blob.entries[8].value, key_created)
+
+ # Check that when the object is packed, the bytes generated equal
+ # the source.
+ packed = ndr_pack(blob)
+ self.assertEqual(source, packed)
+
+
+if __name__ == "__main__":
+ import unittest
+
+ unittest.main()