From: Gary Lockyer Date: Mon, 30 Jun 2025 21:43:07 +0000 (+1200) Subject: librpc/idl: Add idl for tpm20_rsakey_blob X-Git-Tag: tdb-1.4.14~74 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=93eecdddfe5f192fa4c4463d3c9a25ab9160bd6a;p=thirdparty%2Fsamba.git librpc/idl: Add idl for tpm20_rsakey_blob Idl and tests for TPM20_RSAKEY_BLOB, one of the possible encoding of msDSKeyCredentialLink KeyMaterial Derived from: https://dox.ipxe.org/Tpm20_8h_source.html#l00164 https://stackoverflow.com/questions/78958315/cannot-parse-tpm2-0-public-key Note: this is a greatly simplified implementation that only handles TPM version 2, RSA public keys. Signed-off-by: Gary Lockyer Reviewed-by: Douglas Bagnall --- diff --git a/librpc/idl/keycredlink.idl b/librpc/idl/keycredlink.idl index 1ae197c2272..8934ff681bd 100644 --- a/librpc/idl/keycredlink.idl +++ b/librpc/idl/keycredlink.idl @@ -34,8 +34,9 @@ interface keycredlink KEY_USAGE_NGC = 0x01, /* * KeyMaterial is a 2048 bit RSA (RFC8017) public key - * encoded as a BCRYPT_RSAKEY_BLOB, - * see bcrypt_rsakey_blob.idl + * encoded as a + * BCRYPT_RSAKEY_BLOB, see bcrypt_rsakey_blob.idl + * TPM20_RSAKEY_BLOB, see tpm20_rsakey_blob.idl */ KEY_USAGE_FIDO = 0x02, KEY_USAGE_FEK = 0x03 diff --git a/librpc/idl/tpm20_rsakey_blob.idl b/librpc/idl/tpm20_rsakey_blob.idl new file mode 100644 index 00000000000..5e0a1f081e1 --- /dev/null +++ b/librpc/idl/tpm20_rsakey_blob.idl @@ -0,0 +1,126 @@ +/* + Definitions for packing and unpacking TPM 2.0 Public key + structures, derived from: + + https://dox.ipxe.org/Tpm20_8h_source.html#l00164 + https://stackoverflow.com/questions/78958315/cannot-parse-tpm2-0-public-key + + Note: This is a greatly simplified implementation, that: + - only handles TPM version 2.0 blobs + - only extracts the RSA public key + - ignores other fields + - and in the case of unions assumes that only the RSA option will + be present. +*/ + +#include "idl_types.h" + +[ + pointer_default(unique) +] +interface tpm20_rsakey_blob +{ + const uint32 TPM20_MAGIC = 0x4D504350; /* PCPM */ + + const uint32 PCP_TYPE_TPM_12 = 0x00000001; + const uint32 PCP_TYPE_TPM_20 = 0x00000002; + + const uint16 TPM_ALG_RSA = 0x0001; + const uint16 TPM_ALG_NULL = 0x0010; + const uint16 TPM_ALG_SHA256 = 0x000B; + const uint16 TPM_ALG_RSASSA = 0x0014; + + + const uint16 TPM_RSA_EXPONENT_SIZE = sizeof(uint32_t); + + /* Public structures. */ + typedef struct { + /* Only supporting Public keys with SHA256 hashes */ + [flag(NDR_BIG_ENDIAN), value(0x0014), range(0x0014, 0x0014)] + uint16 scheme; + /* + * Strictly speaking this is a union but we're + * restricting the scheme to RSASSA, and the + * TPMS_SCHEME_RSA contains just the hash algorithm + */ + [flag(NDR_BIG_ENDIAN), value(0x000B), range(0x000B, 0x000B)] + uint16 hash_algorithm; + } TPMT_RSA_SCHEME; + + typedef [public] struct { + /* Only catering for TPM_ALG_NONE */ + [flag(NDR_BIG_ENDIAN), value(0x0010), range(0x0010, 0x0010)] + uint16 symmetric_algorithm; + TPMT_RSA_SCHEME scheme; + [flag(NDR_BIG_ENDIAN)] uint16 keyBits; + /* + * Defined in the spec as a big endian uint32, but defined + * here as a byte array for convenience + */ + uint8 exponent[TPM_RSA_EXPONENT_SIZE]; + } TPMS_RSA_PARMS; + + + typedef [public] struct { + [flag(NDR_BIG_ENDIAN)] uint16 size; + uint8 buffer[size]; + } TPM2B_PUBLIC_KEY_RSA; + + typedef [public] struct { + [flag(NDR_BIG_ENDIAN)] uint16 size; + uint8 buffer[size]; + } TPM2B_DIGEST; + + typedef [public] struct { + [flag(NDR_BIG_ENDIAN)] uint16 size; + /* definitions folded in from TPMT_PUBLIC */ + + /* Only supporting RSA Public keys with SHA256 hashes */ + [flag(NDR_BIG_ENDIAN), value(0x0001), range(0x0001, 0x0001)] + uint16 type; + [flag(NDR_BIG_ENDIAN), value(0x000B), range(0x000B, 0x000B)] + uint16 hash_algorithm; + uint32 attributes; + TPM2B_DIGEST auth_policy; + /* + * strictly speaking the next two elements are unions + * but we're only processing RSA entries, by restricting + * the values of type. + */ + TPMS_RSA_PARMS rsa_detail; + TPM2B_PUBLIC_KEY_RSA rsa; + } TPM2B_PUBLIC; + + /* + * As far as I can tell the TPM20_KEY_BLOB is little endian, + * BUT the TPM2B_PUBLIC is big endian + */ + typedef [public] struct { + [value(0x4D504350), range(0x4D504350, 0x4D504350)] + uint32 magic; /* PCPM */ + [value(46)] uint32 header_length; + /* Only supporting version 2.0 blobs */ + [value(0x00000002), range(0x00000002, 0x00000002)] + uint32 type; + uint32 flags; + uint32 public_length; + /* + * Ignore the rest of the lengths and the pcra_alg_id + * only getting the public key, for key trust authentication + */ + uint32 private_length; + uint32 migration_public_length; + uint32 migration_private_length; + uint32 policy_digest_list_length; + uint32 pcr_binding_length; + uint32 pcr_digest_length; + uint32 encrypted_secret_length; + uint32 tpm12_hostage_blob_length; + uint16 pcr_alg_id; + + /* Lets get the public key */ + [flag(NDR_NOALIGN)] TPM2B_PUBLIC public_key; + /* just collect all the remaining bytes after the public key */ + [flag(NDR_REMAINING)] DATA_BLOB remaining; + } TPM20_RSAKEY_BLOB; +} diff --git a/librpc/idl/wscript_build b/librpc/idl/wscript_build index 2fc8956eb6a..188fa45d651 100644 --- a/librpc/idl/wscript_build +++ b/librpc/idl/wscript_build @@ -145,6 +145,7 @@ bld.SAMBA_PIDL_LIST('PIDL', security.idl server_id.idl smb_acl.idl + tpm20_rsakey_blob.idl xattr.idl smb3posix.idl ''', diff --git a/librpc/wscript_build b/librpc/wscript_build index f907989c595..98f2777a528 100644 --- a/librpc/wscript_build +++ b/librpc/wscript_build @@ -648,7 +648,7 @@ bld.SAMBA_LIBRARY('ndr-samba', 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_KEYCREDLINK NDR_BCRYPT_RSAKEY_BLOB''', + NDR_KEYCREDLINK NDR_BCRYPT_RSAKEY_BLOB NDR_TPM20_RSAKEY_BLOB''', private_library=True, grouping_library=True ) @@ -766,6 +766,11 @@ bld.SAMBA_SUBSYSTEM('NDR_BCRYPT_RSAKEY_BLOB', source='gen_ndr/ndr_bcrypt_rsakey_blob.c', public_deps='ndr' ) + +bld.SAMBA_SUBSYSTEM('NDR_TPM20_RSAKEY_BLOB', + source='gen_ndr/ndr_tpm20_rsakey_blob.c', + public_deps='ndr' + ) # # Cmocka tests # diff --git a/python/samba/tests/tpm20_rsakey_blob.py b/python/samba/tests/tpm20_rsakey_blob.py new file mode 100755 index 00000000000..143adb01c33 --- /dev/null +++ b/python/samba/tests/tpm20_rsakey_blob.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# Tests for NDR packing and unpacking of TPM 2.0 public keys +# +# 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 tpm20_rsakey_blob +from samba.ndr import ndr_pack, ndr_unpack +from samba.tests import TestCase + + +class Tpm20RsaKeyBlobTests(TestCase): + def test_unpack_empty_key_blob(self): + """ + ensure that a minimal header only TPM_KEY_BLOB + can be unpacked, then packed into an identical bytes + """ + key_blob = bytes.fromhex( + "50 43 50 4D" # Magic value PCPM + "2E 00 00 00" # header length + "02 00 00 00" # type TPM 2.0 + "00 00 00 00" # flags + "00 00 00 00" # public_length + "00 00 00 00" # private length + "00 00 00 00" # migration public length + "00 00 00 00" # migration private length + "00 00 00 00" # policy digest list length + "00 00 00 00" # PCR binding length + "00 00 00 00" # PCR digest length + "00 00 00 00" # Encrypted secret length + "00 00 00 00" # TPM 1.2 hostage blob length + "00 00" # PCRA Algorithm Id + "00 00" # size + "00 01" # type + "00 0B" # hash algorithm + "00 00 00 00" # attributes + "00 00" # auth_policy empty + "00 10" # algorithm + "00 14" # scheme + "00 0B" # hash algorithm + "00 00" # key bits + "00 00 00 00" # exponent + "00 00" # public key length + ) + blob = ndr_unpack(tpm20_rsakey_blob.TPM20_RSAKEY_BLOB, key_blob) + + self.assertEqual(blob.type, 2) + self.assertEqual(blob.public_key.type, 1) + packed = ndr_pack(blob) + self.assertEqual(key_blob, packed) + + def test_unpack_sample_key_blob(self): + """ + ensure that a sample TPM_KEY_BLOB + can be unpacked, then packed into an identical bytes + """ + key_blob = bytes.fromhex( + "50 43 50 4D" # Magic value PCPM + "2E 00 00 00" # header length + "02 00 00 00" # type TPM 2.0 + "00 00 00 00" # flags + "00 00 00 00" # public_length + "00 00 00 00" # private length + "00 00 00 00" # migration public length + "00 00 00 00" # migration private length + "00 00 00 00" # policy digest list length + "00 00 00 00" # PCR binding length + "00 00 00 00" # PCR digest length + "00 00 00 00" # Encrypted secret length + "00 00 00 00" # TPM 1.2 hostage blob length + "00 00" # PCRA Algorithm Id + "18 01" # size 280 bytes + "00 01" # type + "00 0B" # hash algorithm + "00 05 24 72" # attributes + "00 00" # auth policy" + "00 10" # algorithm + "00 14" # scheme + "00 0B" # hash algorithm + "08 00" # key bits + "00 00 00 00" # exponent + "01 00" # size 256 bytes + "9A 9E F6 5D E2 92 D6 D0 E5 B3 C4 35 B1 5B 36 F3" + "9E 83 7B A9 34 AB D9 67 E1 1C 75 43 E5 B6 48 9B" + "6E CD 8D FC 30 5F 4C B6 8E A0 69 A4 07 21 E7 D7" + "A1 74 4A 29 BC C9 5D 78 70 C4 3B E4 20 54 BC D0" + "AA FF 21 44 54 FC 09 08 2A CC DE 44 68 ED 9F B2" + "3E F7 ED 82 D7 2D 28 74 42 2A 2F 55 A2 E0 DA 45" + "F1 08 C0 83 8C 95 81 6D 92 CC A8 5D A4 B8 06 8C" + "76 F5 68 94 E7 60 E6 F4 EE 40 50 28 6C 82 47 89" + "07 E7 BC 0D 56 5D DA 86 57 E2 CE D3 19 A1 A2 7F" + "56 F8 99 8B 4A 71 32 6A 57 3B F9 E5 2D 39 35 6E" + "13 3E 84 DC 5C 96 E1 75 38 C3 AA 23 5B 68 BE 41" + "52 49 72 7A F6 2A 8F C5 C5 E0 6C DB 99 D1 A8 84" + "5F 70 21 87 2E A0 D2 68 D3 76 5C 9E D4 9C B5 E1" + "72 9D 17 8B DC 11 55 09 90 8D 96 F3 68 34 DD 50" + "63 AC 4A 74 A7 AF 0D DC 15 06 07 D7 5A B3 86 1A" + "54 96 E0 FA 66 25 31 F5 B4 C7 97 C7 7C 70 94 E3" + ) + + blob = ndr_unpack(tpm20_rsakey_blob.TPM20_RSAKEY_BLOB, key_blob) + + self.assertEqual(blob.type, 2) + self.assertEqual(blob.public_key.type, 1) + packed = ndr_pack(blob) + self.assertEqual(key_blob, packed) + +if __name__ == "__main__": + import unittest + + unittest.main() diff --git a/selftest/tests.py b/selftest/tests.py index b679a602f89..1f4d8e5cfd6 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -352,6 +352,7 @@ planpythontestsuite("none", "samba.tests.samdb") planpythontestsuite("none", "samba.tests.samdb_api") planpythontestsuite("none", "samba.tests.key_credential_link") planpythontestsuite("none", "samba.tests.bcrypt_rsakey_blob") +planpythontestsuite("none", "samba.tests.tpm20_rsakey_blob") 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 9b7d3a0791e..216b61ef5e1 100644 --- a/source4/librpc/wscript_build +++ b/source4/librpc/wscript_build @@ -284,6 +284,14 @@ bld.SAMBA_PYTHON('python_bycrypt_rsakey_blob', cflags_end=gen_cflags ) +bld.SAMBA_PYTHON('python_tpm20_rsakey_blob', + source=('../../librpc/gen_ndr/py_tpm20_rsakey_blob.c ' + '../../librpc/gen_ndr/ndr_tpm20_rsakey_blob.c'), + deps='NDR_TPM20_RSAKEY_BLOB %s %s' % (pytalloc_util, pyrpc_util), + realname='samba/dcerpc/tpm20_rsakey_blob.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),