From: Gary Lockyer Date: Tue, 10 Jun 2025 21:04:01 +0000 (+1200) Subject: librpc/idl: Add idl for msDS-KeyCredentialLink X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=da98534911435daf2ec9a098e8a5120dfc94fa53;p=thirdparty%2Fsamba.git librpc/idl: Add idl for msDS-KeyCredentialLink Idl and supporting helpers for msDS-KeyCredentialLinks. See [MS-ADTS] 2.2.20 Key Credential Link Structures Currently the KeyMaterial is treated as a binary blob The naming and casing of the variable names is close as is possible to those in the specification. Signed-off-by: Gary Lockyer Reviewed-by: Douglas Bagnall Autobuild-User(master): Douglas Bagnall Autobuild-Date(master): Thu Jun 19 00:08:31 UTC 2025 on atb-devel-224 --- diff --git a/librpc/idl/keycredlink.idl b/librpc/idl/keycredlink.idl new file mode 100644 index 00000000000..6492df522a1 --- /dev/null +++ b/librpc/idl/keycredlink.idl @@ -0,0 +1,167 @@ +/* + 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; +} diff --git a/librpc/idl/wscript_build b/librpc/idl/wscript_build index c7d6413b47f..8a8f97d8592 100644 --- a/librpc/idl/wscript_build +++ b/librpc/idl/wscript_build @@ -132,6 +132,7 @@ bld.SAMBA_PIDL_LIST('PIDL', drsblobs.idl gmsa.idl idmap.idl + keycredlink.idl krb5pac.idl krb5ccache.idl schannel.idl diff --git a/librpc/ndr/ndr_keycredlink.c b/librpc/ndr/ndr_keycredlink.c new file mode 100644 index 00000000000..967202978b6 --- /dev/null +++ b/librpc/ndr/ndr_keycredlink.c @@ -0,0 +1,441 @@ +/* + 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 . +*/ + +#include "lib/replace/replace.h" +#include "librpc/gen_ndr/ndr_keycredlink.h" +#include "gen_ndr/keycredlink.h" +#include "libndr.h" +#include + +/* + * 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; +} diff --git a/librpc/ndr/ndr_keycredlink.h b/librpc/ndr/ndr_keycredlink.h new file mode 100644 index 00000000000..48baafa5d2e --- /dev/null +++ b/librpc/ndr/ndr_keycredlink.h @@ -0,0 +1,24 @@ +/* + 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 . +*/ diff --git a/librpc/wscript_build b/librpc/wscript_build index dedba157d9f..f8a2a28cff1 100644 --- a/librpc/wscript_build +++ b/librpc/wscript_build @@ -647,7 +647,8 @@ bld.SAMBA_LIBRARY('ndr-samba', NDR_DNSSERVER NDR_EPMAPPER NDR_XATTR NDR_UNIXINFO NDR_NAMED_PIPE_AUTH NDR_NTPRINTING NDR_FSRVP NDR_WITNESS NDR_MDSSVC NDR_OPEN_FILES NDR_SMBXSRV NDR_SMB3POSIX NDR_RPCD_WITNESS - NDR_KRB5CCACHE NDR_WSP NDR_GKDI NDR_GMSA''', + NDR_KRB5CCACHE NDR_WSP NDR_GKDI NDR_GMSA + NDR_KEYCREDLINK''', private_library=True, grouping_library=True ) @@ -755,6 +756,11 @@ bld.SAMBA_SUBSYSTEM('NDR_FSRVP_STATE', source='gen_ndr/ndr_fsrvp_state.c', public_deps='ndr' ) + +bld.SAMBA_SUBSYSTEM('NDR_KEYCREDLINK', + source='ndr/ndr_keycredlink.c gen_ndr/ndr_keycredlink.c', + public_deps='ndr' + ) # # Cmocka tests # diff --git a/python/samba/tests/key_credential_link.py b/python/samba/tests/key_credential_link.py new file mode 100755 index 00000000000..b73c6a19e24 --- /dev/null +++ b/python/samba/tests/key_credential_link.py @@ -0,0 +1,555 @@ +#!/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 . +# + +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() diff --git a/selftest/tests.py b/selftest/tests.py index 53461229644..c5336c7130a 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -350,6 +350,7 @@ planpythontestsuite("none", "samba.tests.glue") planpythontestsuite("none", "samba.tests.tdb_util") planpythontestsuite("none", "samba.tests.samdb") planpythontestsuite("none", "samba.tests.samdb_api") +planpythontestsuite("none", "samba.tests.key_credential_link") planpythontestsuite("none", "samba.tests.ndr.gkdi") planpythontestsuite("none", "samba.tests.ndr.gmsa") planpythontestsuite("none", "samba.tests.ndr.sd") diff --git a/source4/librpc/wscript_build b/source4/librpc/wscript_build index d8c414f820b..2dd196066d3 100644 --- a/source4/librpc/wscript_build +++ b/source4/librpc/wscript_build @@ -268,6 +268,14 @@ bld.SAMBA_PYTHON('python_claims', cflags_end=gen_cflags ) +bld.SAMBA_PYTHON('python_keycredlink', + source=('../../librpc/gen_ndr/py_keycredlink.c ' + '../../librpc/gen_ndr/ndr_keycredlink.c'), + deps='NDR_KEYCREDLINK %s %s' % (pytalloc_util, pyrpc_util), + realname='samba/dcerpc/keycredlink.so', + cflags_end=gen_cflags + ) + bld.SAMBA_PYTHON('python_gkdi', source='../../librpc/gen_ndr/py_gkdi.c', deps='RPC_NDR_GKDI %s %s' % (pytalloc_util, pyrpc_util),