From d16fb8af463a660ce8a42f6d1e7711331812e027 Mon Sep 17 00:00:00 2001 From: Jennifer Sutton Date: Fri, 3 Oct 2025 14:31:30 +1300 Subject: [PATCH] tests/krb5: Add tests for the Object SID certificate security extension MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit View with ‘git show -b’. Signed-off-by: Jennifer Sutton Reviewed-by: Gary Lockyer --- .../krb5/pkinit_certificate_mapping_tests.py | 136 +++++++++++++----- .../knownfail_heimdal_kdc.d/sid-extension | 2 + 2 files changed, 102 insertions(+), 36 deletions(-) create mode 100644 selftest/knownfail_heimdal_kdc.d/sid-extension diff --git a/python/samba/tests/krb5/pkinit_certificate_mapping_tests.py b/python/samba/tests/krb5/pkinit_certificate_mapping_tests.py index 7c19afe433d..99cc23b66ec 100755 --- a/python/samba/tests/krb5/pkinit_certificate_mapping_tests.py +++ b/python/samba/tests/krb5/pkinit_certificate_mapping_tests.py @@ -451,6 +451,62 @@ class PkInitCertificateMappingTests(KDCBaseTest): expect_error=self.STRONG_EXPECTED_RESULT, ) + def test_object_sid(self): + """ + Test PKINIT logon with a user account and a strong object SID mapping + """ + + client_creds = self._get_creds() + target_creds = self.get_service_creds() + ca_cert, ca_private_key = self.get_ca_cert_and_private_key() + + # Create a certificate for the client signed by the CA which includes + # the object SID. + certificate = self.create_certificate( + client_creds, + ca_cert, + ca_private_key, + None, + [], + object_sid=client_creds.get_sid(), + ) + + self._pkinit_req( + client_creds, + target_creds, + certificate=certificate, + expect_error=self.STRONG_EXPECTED_RESULT, + ) + + def test_mismatched_object_sid(self): + """ + Test PKINIT logon with a user account and a mismatched object SID + mapping + """ + + client_creds = self._get_creds() + target_creds = self.get_service_creds() + ca_cert, ca_private_key = self.get_ca_cert_and_private_key() + + # Create a certificate for the client signed by the CA which includes + # the object SID. + certificate = self.create_certificate( + client_creds, + ca_cert, + ca_private_key, + None, + [], + object_sid=target_creds.get_sid(), + ) + + self._pkinit_req( + client_creds, + target_creds, + certificate=certificate, + # We choose to treat the mapping as if it does not exist. + expect_error=self.NONE_EXPECTED_RESULT, + ) + def _rfc4514_string(self, name): """ Convert an X509 name to it's RFC 4514 form, however we need @@ -540,6 +596,7 @@ class PkInitCertificateMappingTests(KDCBaseTest): certificate_signature=None, san=[], notBefore=None, + object_sid=None, ): if certificate_signature is None: certificate_signature = hashes.SHA256 @@ -637,49 +694,56 @@ class PkInitCertificateMappingTests(KDCBaseTest): critical=False, ) - # If the certificate predates (as ours does) the existence of the - # account that presents it Windows will refuse to accept it unless - # there exists a strong mapping from one to the other. This strong - # mapping will in this case take the form of a certificate extension - # described in [MS-WCCE] 2.2.2.7.7.4 (szOID_NTDS_CA_SECURITY_EXT) and - # containing the account’s SID. + if object_sid is not None: + # If the certificate predates (as ours does) the existence of the + # account that presents it Windows will refuse to accept it unless + # there exists a strong mapping from one to the other. This strong + # mapping will in this case take the form of a certificate extension + # described in [MS-WCCE] 2.2.2.7.7.4 (szOID_NTDS_CA_SECURITY_EXT) and + # containing the account’s SID. - # Encode this structure manually until we are able to produce the same - # ASN.1 encoding that Windows does. + # Encode this structure manually until we are able to produce the same + # ASN.1 encoding that Windows does. - encoded_sid = creds.get_sid().encode("utf-8") + encoded_sid = object_sid.encode("utf-8") - # The OCTET STRING tag, followed by length and encoded SID… - security_ext = bytes([0x04]) + self.asn1_length(encoded_sid) + (encoded_sid) + # The OCTET STRING tag, followed by length and encoded SID… + security_ext = bytes([0x04]) + self.asn1_length(encoded_sid) + (encoded_sid) - # …enclosed in a construct tagged with the application-specific value - # 0… - security_ext = bytes([0xA0]) + self.asn1_length(security_ext) + (security_ext) - - # …preceded by the extension OID… - encoded_oid = self.der_encode( - krb5_asn1.szOID_NTDS_OBJECTSID, univ.ObjectIdentifier() - ) - security_ext = encoded_oid + security_ext + # …enclosed in a construct tagged with the application-specific value + # 0… + security_ext = ( + bytes([0xA0]) + self.asn1_length(security_ext) + (security_ext) + ) - # …and another application-specific tag 0… - # (This is the part about which I’m unsure. This length is not just of - # the OID, but of the entire structure so far, as if there’s some - # nesting going on. So far I haven’t been able to replicate this with - # pyasn1.) - security_ext = bytes([0xA0]) + self.asn1_length(security_ext) + (security_ext) + # …preceded by the extension OID… + encoded_oid = self.der_encode( + krb5_asn1.szOID_NTDS_OBJECTSID, univ.ObjectIdentifier() + ) + security_ext = encoded_oid + security_ext + + # …and another application-specific tag 0… + # (This is the part about which I’m unsure. This length is not just of + # the OID, but of the entire structure so far, as if there’s some + # nesting going on. So far I haven’t been able to replicate this with + # pyasn1.) + security_ext = ( + bytes([0xA0]) + self.asn1_length(security_ext) + (security_ext) + ) - # …all enclosed in a structure with a SEQUENCE tag. - security_ext = bytes([0x30]) + self.asn1_length(security_ext) + (security_ext) + # …all enclosed in a structure with a SEQUENCE tag. + security_ext = ( + bytes([0x30]) + self.asn1_length(security_ext) + (security_ext) + ) - # Add the security extension to the certificate. - builder = builder.add_extension( - x509.UnrecognizedExtension( - x509.ObjectIdentifier(str(krb5_asn1.szOID_NTDS_CA_SECURITY_EXT)), - security_ext, - ), - critical=False, - ) + # Add the security extension to the certificate. + builder = builder.add_extension( + x509.UnrecognizedExtension( + x509.ObjectIdentifier(str(krb5_asn1.szOID_NTDS_CA_SECURITY_EXT)), + security_ext, + ), + critical=False, + ) # Sign the certificate with the CA’s private key. Windows accepts both # SHA1 and SHA256 hashes. diff --git a/selftest/knownfail_heimdal_kdc.d/sid-extension b/selftest/knownfail_heimdal_kdc.d/sid-extension new file mode 100644 index 00000000000..007e53703b7 --- /dev/null +++ b/selftest/knownfail_heimdal_kdc.d/sid-extension @@ -0,0 +1,2 @@ +^samba\.tests\.krb5\.pkinit_certificate_mapping_tests\.samba\.tests\.krb5\.pkinit_certificate_mapping_tests\.PkInitCertificateMappingTests\.test_object_sid\(ad_dc_ntvfs\) +^samba\.tests\.krb5\.pkinit_certificate_mapping_tests\.samba\.tests\.krb5\.pkinit_certificate_mapping_tests\.PkInitCertificateMappingTests\.test_object_sid\(ad_dc_smb1\) -- 2.47.3