1
0
mirror of https://github.com/samba-team/samba.git synced 2025-12-16 00:23:52 +03:00

librpc/idl: Add idl for BCRYPT_RSAKEY_BLOB

Idl and tests for BCRYPT_RSAKEY_BLOB
See https://learn.microsoft.com/en-us/windows/win32/api/
            bcrypt/ns-bcrypt-bcrypt_rsakey_blob

This is one of the encodings of msDSKeyCredentialLink KeyMaterial when
KeyUsage is KEY_USAGE_NGC. As there appears to be no official
documentation on the contents of KeyMaterial have based this on.

    271dd969e0/
            dsinternals/common/data/hello/KeyCredential.py#L75-L92

Note: only RSA public keys are handled

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
This commit is contained in:
Gary Lockyer
2025-06-23 15:01:37 +12:00
committed by Douglas Bagnall
parent 7c7455926d
commit acd0fccfdd
7 changed files with 285 additions and 1 deletions

View File

@@ -0,0 +1,221 @@
#!/usr/bin/env python3
# Tests for NDR packing and unpacking of BCRYPT_RSAPUBLIC_BLOB structures
#
# See https://learn.microsoft.com/en-us/windows/win32/api/
# bcrypt/ns-bcrypt-bcrypt_rsakey_blob
#
# 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 bcrypt_rsakey_blob
from samba.ndr import ndr_pack, ndr_unpack
from samba.tests import TestCase
class BcryptRsaKeyBlobTests(TestCase):
def test_unpack_empty_key_blob(self):
"""
Ensure that a minimal header only BCRYPT_RSAPUBLIC_BLOB
can be unpacked, then packed into identical bytes
"""
empty_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"00 00 00 00" # public exponent length
"00 00 00 00" # modulus length"
"00 00 00 00" # prime one length"
"00 00 00 00" # prime two length"
)
blob = ndr_unpack(
bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB, empty_key_blob)
self.assertEqual(blob.magic, 0x31415352)
self.assertEqual(blob.bit_length, 0)
self.assertEqual(blob.public_exponent_len, 0)
self.assertEqual(blob.modulus_len, 0)
self.assertEqual(blob.prime1_len_unused, 0)
self.assertEqual(blob.prime2_len_unused, 0)
self.assertEqual(len(blob.public_exponent), 0)
self.assertEqual(len(blob.modulus), 0)
packed = ndr_pack(blob)
self.assertEqual(empty_key_blob, packed)
def test_unpack_invalid_magic(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with an invalid magic value is
rejected
"""
invalid_magic_key_blob = bytes.fromhex(
"52 53 41 30" # Magic value RSA0
"00 00 00 00" # bit length
"00 00 00 00" # public exponent length
"00 00 00 00" # modulus length
"00 00 00 00" # prime one length
"00 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_magic_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_extra_data(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with extra data is
rejected
"""
extra_data_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"00 00 00 00" # public exponent length
"00 00 00 00" # modulus length
"00 00 00 00" # prime one length
"00 00 00 00" # prime two length
"01" # a trailing byte of data
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
extra_data_key_blob)
self.assertEqual(e.exception.args[0], 18)
self.assertEqual(e.exception.args[1], "Unread Bytes")
def test_unpack_missing_data(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with missing data is
rejected
"""
short_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"08 00 00 00" # bit length, 2048
"01 00 00 00" # public exponent length, one byte
"02 00 00 00" # modulus length, two bytes
"00 00 00 00" # prime one length must be zero
"00 00 00 00" # prime two length must be zero
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB, short_key_blob)
self.assertEqual(e.exception.args[0], 11)
self.assertEqual(e.exception.args[1], "Buffer Size Error")
def test_unpack_invalid_exponent_length(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with an invalid exponent length is
rejected
"""
invalid_magic_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"09 00 00 00" # public exponent length, 9 bytes
"00 00 00 00" # modulus length
"00 00 00 00" # prime one length
"00 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_magic_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_non_zero_prime1(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with a non zero prime 1 length is
rejected
"""
invalid_prime1_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"00 00 00 00" # public exponent length, 9 bytes
"00 00 00 00" # modulus length
"01 00 00 00" # prime one length
"00 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_prime1_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack_non_zero_prime2(self):
"""
Ensure that a BCRYPT_RSAPUBLIC_BLOB with a non zero prime 2 length is
rejected
"""
invalid_prime2_key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 00 00 00" # bit length
"00 00 00 00" # public exponent length, 9 bytes
"00 00 00 00" # modulus length
"00 00 00 00" # prime one length
"01 00 00 00" # prime two length"
)
with self.assertRaises(RuntimeError) as e:
ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB,
invalid_prime2_key_blob)
self.assertEqual(e.exception.args[0], 13)
self.assertEqual(e.exception.args[1], "Range Error")
def test_unpack(self):
"""
Ensure that a fully populated BCRYPT_RSAPUBLIC_BLOB
can be unpacked, then packed into identical bytes
"""
key_blob = bytes.fromhex(
"52 53 41 31" # Magic value RSA1
"00 08 00 00" # bit length, 2048
"01 00 00 00" # public exponent length
"02 00 00 00" # modulus length"
"00 00 00 00" # prime one length"
"00 00 00 00" # prime two length"
"01" # public exponent
"02 03" # modulus
)
blob = ndr_unpack(bcrypt_rsakey_blob.BCRYPT_RSAPUBLIC_BLOB, key_blob)
self.assertEqual(blob.magic, 0x31415352)
self.assertEqual(blob.bit_length, 2048)
self.assertEqual(blob.public_exponent_len, 1)
self.assertEqual(len(blob.public_exponent), 1)
self.assertEqual(bytes(blob.public_exponent), bytes.fromhex("01"))
self.assertEqual(blob.modulus_len, 2)
self.assertEqual(len(blob.modulus), 2)
self.assertEqual(bytes(blob.modulus), bytes.fromhex("02 03"))
self.assertEqual(blob.prime1_len_unused, 0)
self.assertEqual(blob.prime2_len_unused, 0)
packed = ndr_pack(blob)
self.assertEqual(key_blob, packed)
if __name__ == "__main__":
import unittest
unittest.main()