]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
librpc/idl: Add idl for tpm20_rsakey_blob
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 30 Jun 2025 21:43:07 +0000 (09:43 +1200)
committerDouglas Bagnall <dbagnall@samba.org>
Tue, 29 Jul 2025 04:30:34 +0000 (04:30 +0000)
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 <gary@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
librpc/idl/keycredlink.idl
librpc/idl/tpm20_rsakey_blob.idl [new file with mode: 0644]
librpc/idl/wscript_build
librpc/wscript_build
python/samba/tests/tpm20_rsakey_blob.py [new file with mode: 0755]
selftest/tests.py
source4/librpc/wscript_build

index 1ae197c2272740019029c1ed725487546102f8eb..8934ff681bd338319f2e2ef38d55708b67b7289e 100644 (file)
@@ -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 (file)
index 0000000..5e0a1f0
--- /dev/null
@@ -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;
+}
index 2fc8956eb6a256b54c209271e953e2d6a2efca9d..188fa45d65177c6d7613b062c6305d38c2a04363 100644 (file)
@@ -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
                     ''',
index f907989c595d0571d8bb30e70a75d516b8be8911..98f2777a528ae52dff43c038e4db1e0cc8b43f30 100644 (file)
@@ -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 (executable)
index 0000000..143adb0
--- /dev/null
@@ -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 <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()
index b679a602f89f62a0b4cb69051e682ebf12147840..1f4d8e5cfd6a09f80b2d76f7b01a7f78ad9d8490 100644 (file)
@@ -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")
index 9b7d3a0791e8d312b423b8207cd819d3cd6e8370..216b61ef5e18b96174a2dfb6214baafb261f3a98 100644 (file)
@@ -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),