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
--- /dev/null
+/*
+ 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;
+}
security.idl
server_id.idl
smb_acl.idl
+ tpm20_rsakey_blob.idl
xattr.idl
smb3posix.idl
''',
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
)
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
#
--- /dev/null
+#!/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 <http://www.gnu.org/licenses/>.
+#
+
+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()
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")
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),