From 3ea1c559213d02cff7fae5cdf2694178cc88a817 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Mon, 3 Jul 2023 14:43:10 +1200 Subject: [PATCH] tests/krb5: Add PK-INIT testing framework MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit To run these tests standalone, you will need the certificate and private key of the Certificate Authority. These can be specified together in the same file with the environment variable CA_CERT, or the private key may be specified in its own file with CA_PRIVATE_KEY. If either of these files are encrypted, you can specify the password in the environment variable CA_PASS. These tests create a new certificate for the user account, signed with the private key of the Certificate Authority. We negotiate the reply key with either of the public-key and Diffie-Hellman PK-INIT variants, and use the reply key to decrypt the enc-part in the response. We also check that the KDC’s signatures are valid. Most of the failures with the Heimdal KDC are due to the wrong nonce being returned in the reply compared to Windows, which issue is simple enough to correct. An example command line for manual testing against Windows: SMB_CONF_PATH=ad_dc.conf KRB5_CONFIG=krb5.conf SERVICE_USERNAME=win2k19-dc.example.com ADMIN_USERNAME=Administrator ADMIN_PASSWORD=locDCpass ADMIN_KVNO=1 FOR_USER=Administrator USERNAME=Administrator PASSWORD=locDCpass DC_SERVER=win2k19-dc.example.com SERVER=win2k19-dc.example.com DOMAIN=example REALM=example.com PYTHONPATH=bin/python STRICT_CHECKING=1 FAST_SUPPORT=1 CLAIMS_SUPPORT=1 COMPOUND_ID_SUPPORT=1 TKT_SIG_SUPPORT=1 FULL_SIG_SUPPORT=1 GNUTLS_PBKDF2_SUPPORT=1 EXPECT_PAC=1 EXPECT_EXTRA_PAC_BUFFERS=1 CHECK_CNAME=1 CHECK_PADATA=1 KADMIN_IS_TGS=0 FORCED_RC4=1 DEFAULT_ETYPES=36 CA_CERT=./win2k19-ca.pfx CA_PASS=1234 python3 python/samba/tests/krb5/pkinit_tests.py To set up windows for this I first installed an Certificate Authority with an Enterprise CA. Then I exported the private key and certificate of the CA: 1. go into the Certification Authority snap-in for the relevant computer, 2. right-clicking the CA 3. clicking ‘All Tasks’ → ‘Back up CA...’ 4. and exporting the private key and CA certificate. (I downloaded the resulting file via smbclient). After setting up an Enterprise CA, I also needed to edit the domain controller GPO to enable auto-enrollment, otherwise Windows would refuse to accept as legitimate any certificates provided by the client. That can be done by first enabling the policy: ‘Computer Configuration/Policies/Windows Settings/Security Settings/Public Key Policies/Certificate Services Client — Auto-Enrollment’, and then ticking both ‘Renew expired certificates…’ and ‘Update certificates…’) Signed-off-by: Joseph Sutton Reviewed-by: Andrew Bartlett --- python/samba/tests/krb5/pkinit_tests.py | 684 +++++++++++++++++++ python/samba/tests/krb5/raw_testcase.py | 398 ++++++++++- python/samba/tests/krb5/rfc4120_constants.py | 5 + selftest/knownfail_heimdal_kdc | 23 + selftest/knownfail_mit_kdc_1_20 | 23 + source4/selftest/tests.py | 23 + 6 files changed, 1143 insertions(+), 13 deletions(-) create mode 100755 python/samba/tests/krb5/pkinit_tests.py diff --git a/python/samba/tests/krb5/pkinit_tests.py b/python/samba/tests/krb5/pkinit_tests.py new file mode 100755 index 00000000000..2be2cf4aa44 --- /dev/null +++ b/python/samba/tests/krb5/pkinit_tests.py @@ -0,0 +1,684 @@ +#!/usr/bin/env python3 +# Unix SMB/CIFS implementation. +# Copyright (C) Stefan Metzmacher 2020 +# Copyright (C) Catalyst.Net Ltd 2023 +# +# 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 datetime import datetime, timedelta + +from pyasn1.type import univ + +from cryptography import x509 +from cryptography.hazmat.primitives.serialization import pkcs12 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import dh, padding +from cryptography.x509.oid import NameOID + +import samba.tests +from samba.tests.krb5 import kcrypto +from samba.tests.krb5.kdc_base_test import KDCBaseTest +from samba.tests.krb5.raw_testcase import PkInit +from samba.tests.krb5.rfc4120_constants import ( + DES_EDE3_CBC, + KDC_ERR_ETYPE_NOSUPP, + NT_PRINCIPAL, + PADATA_PK_AS_REQ, +) +import samba.tests.krb5.rfc4120_pyasn1 as krb5_asn1 + +global_asn1_print = False +global_hexdump = False + + +class PkInitTests(KDCBaseTest): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def setUp(self): + super().setUp() + self.do_asn1_print = global_asn1_print + self.do_hexdump = global_hexdump + + def _get_creds(self, account_type=KDCBaseTest.AccountType.USER): + """Return credentials with an account having a UPN for performing + PK-INIT.""" + samdb = self.get_samdb() + realm = samdb.domain_dns_name().upper() + + return self.get_cached_creds( + account_type=account_type, + opts={'upn': f'{{account}}.{realm}@{realm}'}) + + def test_pkinit(self): + """Test public-key PK-INIT.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds) + + def test_pkinit_dh(self): + """Test Diffie-Hellman PK-INIT.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN) + + def test_pkinit_no_des3(self): + """Test public-key PK-INIT without specifying the DES3 encryption + type. It should fail.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + etypes=(kcrypto.Enctype.AES256, kcrypto.Enctype.RC4), + expect_error=KDC_ERR_ETYPE_NOSUPP) + + def test_pkinit_no_des3_dh(self): + """Test Diffie-Hellman PK-INIT without specifying the DES3 encryption + type. This time, it should succeed.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN, + etypes=(kcrypto.Enctype.AES256, kcrypto.Enctype.RC4)) + + def test_pkinit_aes128(self): + """Test public-key PK-INIT, specifying the AES128 encryption type + first.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + etypes=( + kcrypto.Enctype.AES128, + kcrypto.Enctype.AES256, + DES_EDE3_CBC, + )) + + def test_pkinit_rc4(self): + """Test public-key PK-INIT, specifying the RC4 encryption type first. + """ + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + etypes=( + kcrypto.Enctype.RC4, + kcrypto.Enctype.AES256, + DES_EDE3_CBC, + )) + + def test_pkinit_zero_nonce(self): + """Test public-key PK-INIT with a nonce of zero. The nonce in the + request body should take precedence.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, pk_nonce=0) + + def test_pkinit_zero_nonce_dh(self): + """Test Diffie-Hellman PK-INIT with a nonce of zero. The nonce in the + request body should take precedence. + """ + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN, + pk_nonce=0) + + def test_pkinit_computer(self): + """Test public-key PK-INIT with a computer account.""" + client_creds = self._get_creds(self.AccountType.COMPUTER) + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds) + + def test_pkinit_computer_dh(self): + """Test Diffie-Hellman PK-INIT with a computer account.""" + client_creds = self._get_creds(self.AccountType.COMPUTER) + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN) + + def test_pkinit_service(self): + """Test public-key PK-INIT with a service account.""" + client_creds = self._get_creds(self.AccountType.MANAGED_SERVICE) + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds) + + def test_pkinit_service_dh(self): + """Test Diffie-Hellman PK-INIT with a service account.""" + client_creds = self._get_creds(self.AccountType.MANAGED_SERVICE) + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN) + + def test_pkinit_no_supported_cms_types(self): + """Test public-key PK-INIT, excluding the supportedCmsTypes field. This + causes Windows to reply with differently-encoded ASN.1.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + supported_cms_types=False) + + def test_pkinit_no_supported_cms_types_dh(self): + """Test Diffie-Hellman PK-INIT, excluding the supportedCmsTypes field. + """ + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN, + supported_cms_types=False) + + def test_pkinit_empty_supported_cms_types(self): + """Test public-key PK-INIT with an empty supportedCmsTypes field.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + supported_cms_types=[]) + + def test_pkinit_empty_supported_cms_types_dh(self): + """Test Diffie-Hellman PK-INIT with an empty supportedCmsTypes field. + """ + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req(client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN, + supported_cms_types=[]) + + def test_pkinit_sha256_signature(self): + """Test public-key PK-INIT with a SHA256 signature.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req( + client_creds, target_creds, + signature_algorithm=krb5_asn1.id_pkcs1_sha256WithRSAEncryption) + + def test_pkinit_sha256_signature_dh(self): + """Test Diffie-Hellman PK-INIT with a SHA256 signature.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req( + client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN, + signature_algorithm=krb5_asn1.id_pkcs1_sha256WithRSAEncryption) + + def test_pkinit_sha256_certificate_signature(self): + """Test public-key PK-INIT with a SHA256 certificate signature.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req( + client_creds, target_creds, + certificate_signature=hashes.SHA256) + + def test_pkinit_sha256_certificate_signature_dh(self): + """Test Diffie-Hellman PK-INIT with a SHA256 certificate signature.""" + client_creds = self._get_creds() + target_creds = self.get_service_creds() + + self._pkinit_req( + client_creds, target_creds, + using_pkinit=PkInit.DIFFIE_HELLMAN, + certificate_signature=hashes.SHA256) + + def _pkinit_req(self, + creds, + target_creds, + *, + expect_error=0, + using_pkinit=PkInit.PUBLIC_KEY, + etypes=None, + pk_nonce=None, + supported_cms_types=None, + signature_algorithm=None, + certificate_signature=None, + ): + self.assertIsNot(using_pkinit, PkInit.NOT_USED) + + if signature_algorithm is None: + # This algorithm must be one of ‘sig_algs’ for it to be supported + # by Heimdal. + signature_algorithm = krb5_asn1.sha1WithRSAEncryption + + signature_algorithm_id = self.AlgorithmIdentifier_create( + signature_algorithm) + + if certificate_signature is None: + certificate_signature = hashes.SHA1 + + user_name = creds.get_username() + + # The password with which to try to encrypt the certificate or private + # key specified on the command line. + ca_pass = samba.tests.env_get_var_value('CA_PASS', allow_missing=True) + if ca_pass is not None: + ca_pass = ca_pass.encode('utf-8') + + # The root certificate of the CA, with which we can issue new + # certificates. + ca_cert_path = samba.tests.env_get_var_value('CA_CERT') + with open(ca_cert_path, mode='rb') as f: + ca_cert_data = f.read() + + try: + # If the certificate file is in the PKCS#12 format (such as is + # found in a .pfx file) try to get the private key and the + # certificate all in one go. + ca_private_key, ca_cert, _additional_ca_certs = ( + pkcs12.load_key_and_certificates( + ca_cert_data, ca_pass, default_backend())) + except ValueError: + # Fall back to loading a PEM-encoded certificate. + ca_private_key = None + ca_cert = x509.load_pem_x509_certificate( + ca_cert_data, default_backend()) + + # If we didn’t get the private key, do that now. + if ca_private_key is None: + ca_private_key_path = samba.tests.env_get_var_value( + 'CA_PRIVATE_KEY') + with open(ca_private_key_path, mode='rb') as f: + ca_private_key = serialization.load_pem_private_key( + f.read(), password=ca_pass, backend=default_backend()) + + builder = x509.CertificateBuilder() + + # Add the subject name. + cert_name = f'{user_name}@{creds.get_realm().lower()}' + builder = builder.subject_name(x509.Name([ + # This name can be anything; it isn’t needed to authorize the + # user. The SubjectAlternativeName is used for that instead. + x509.NameAttribute(NameOID.COUNTRY_NAME, 'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, 'SambaState'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'SambaSelfTesting'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, 'Users'), + x509.NameAttribute(NameOID.COMMON_NAME, + f'{cert_name}/emailAddress={cert_name}'), + ])) + + # The new certificate must be issued by the root CA. + builder = builder.issuer_name(ca_cert.issuer) + + one_day = timedelta(1, 0, 0) + + # Put the certificate start time in the past to avoid issues where the + # KDC considers the certificate to be invalid due to clock skew. Note + # that if the certificate predates the existence of the account in AD, + # Windows will refuse authentication unless a strong mapping is + # present (in the certificate, or in AD). + # See https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16#ID0EFR + builder = builder.not_valid_before(datetime.today() - one_day) + + builder = builder.not_valid_after(datetime.today() + (one_day * 30)) + + builder = builder.serial_number(x509.random_serial_number()) + + public_key = creds.get_public_key() + builder = builder.public_key(public_key) + + # Add the SubjectAlternativeName. Windows uses this to map the account + # to the certificate. + id_pkinit_ms_san = x509.ObjectIdentifier( + str(krb5_asn1.id_pkinit_ms_san)) + encoded_upn = self.der_encode(creds.get_upn(), + asn1Spec=krb5_asn1.MS_UPN_SAN()) + ms_upn_san = x509.OtherName(id_pkinit_ms_san, encoded_upn) + builder = builder.add_extension( + x509.SubjectAlternativeName([ms_upn_san]), + critical=False, + ) + + builder = builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), critical=True, + ) + + # The key identifier is used to identify the certificate. + subject_key_id = x509.SubjectKeyIdentifier.from_public_key(public_key) + builder = builder.add_extension( + subject_key_id, critical=True, + ) + + # Add the key usages for which this certificate is valid. Windows + # doesn’t actually require this extension to be present. + builder = builder.add_extension( + # Heimdal requires that the certificate be valid for digital + # signatures. + x509.KeyUsage(digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False), + critical=True, + ) + + # Windows doesn’t require this extension to be present either; but if + # it is, Windows will not accept the certificate unless either client + # authentication or smartcard logon is specified, returning + # KDC_ERR_INCONSISTENT_KEY_PURPOSE otherwise. + builder = builder.add_extension( + x509.ExtendedKeyUsage([ + x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH, + ]), + 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. + + # 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') + + # 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 + + # …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) + + # 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. + certificate = builder.sign( + private_key=ca_private_key, algorithm=certificate_signature(), + backend=default_backend() + ) + + private_key = creds.get_private_key() + + if using_pkinit is PkInit.DIFFIE_HELLMAN: + # This is the 2048-bit MODP Group from RFC 3526. Heimdal refers to + # it as “rfc3526-MODP-group14”. + p, g = 32317006071311007300338913926423828248817941241140239112842009751400741706634354222619689417363569347117901737909704191754605873209195028853758986185622153212175412514901774520270235796078236248884246189477587641105928646099411723245426622522193230540919037680524235519125679715870117001058055877651038861847280257976054903569732561526167081339361799541336476559160368317896729073178384589680639671900977202194168647225871031411336429319536193471636533209717077448227988588565369208645296636077250268955505928362751121174096972998068410554359584866583291642136218231078990999448652468262416972035911852507045361090559, 2 + + numbers = dh.DHParameterNumbers(p, g) + dh_params = numbers.parameters(default_backend()) + + dh_private_key = dh_params.generate_private_key() + + preauth_key = dh_private_key + else: + preauth_key = private_key + + if pk_nonce is None: + pk_nonce = self.get_Nonce() + + def generate_pk_padata(_kdc_exchange_dict, + _callback_dict, + req_body): + checksum_blob = self.der_encode( + req_body, + asn1Spec=krb5_asn1.KDC_REQ_BODY()) + + # Calculate the SHA1 checksum over the KDC-REQ-BODY. This checksum + # is required to be present in the authenticator, and must be SHA1. + digest = hashes.Hash(hashes.SHA1(), default_backend()) + digest.update(checksum_blob) + digest = digest.finalize() + + ctime, cusec = self.get_KerberosTimeWithUsec() + + # Create the authenticator, which shows that we had possession of + # the private key at some point. + authenticator_obj = self.PKAuthenticator_create(cusec, + ctime, + pk_nonce, + pa_checksum=digest) + + if using_pkinit is PkInit.DIFFIE_HELLMAN: + dh_public_key = dh_private_key.public_key() + + encoded_dh_public_key = dh_public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo) + decoded_dh_public_key = self.der_decode( + encoded_dh_public_key, + asn1Spec=krb5_asn1.SubjectPublicKeyInfo()) + dh_public_key_bitstring = decoded_dh_public_key[ + 'subjectPublicKey'] + + # Encode the Diffie-Hellman parameters. + params = dh_params.parameter_bytes( + serialization.Encoding.DER, + serialization.ParameterFormat.PKCS3) + + pk_algorithm = self.AlgorithmIdentifier_create( + krb5_asn1.dhpublicnumber, + parameters=params) + + # Create the structure containing information about the public + # key of the certificate that we shall present. + client_public_value = self.SubjectPublicKeyInfo_create( + pk_algorithm, + dh_public_key_bitstring) + else: + client_public_value = None + + # An optional set of algorithms supported by the client in + # decreasing order of preference. For whatever reason, if this + # field is missing or empty, Windows will respond with a slightly + # differently encoded ReplyKeyPack, wrapping it first in a + # ContentInfo structure. + nonlocal supported_cms_types + if supported_cms_types is False: + # Exclude this field. + supported_cms_types = None + elif supported_cms_types is None: + supported_cms_types = [ + self.AlgorithmIdentifier_create( + krb5_asn1.id_pkcs1_sha256WithRSAEncryption), + ] + + # The client may include this field if it wishes to reuse DH keys + # or allow the KDC to do so. + client_dh_nonce = None + + auth_pack_obj = self.AuthPack_create( + authenticator_obj, + client_public_value=client_public_value, + supported_cms_types=supported_cms_types, + client_dh_nonce=client_dh_nonce) + + auth_pack = self.der_encode(auth_pack_obj, + asn1Spec=krb5_asn1.AuthPack()) + + signature_hash = self.hash_from_algorithm(signature_algorithm) + + pad = padding.PKCS1v15() + signed = private_key.sign(auth_pack, + padding=pad, + algorithm=signature_hash()) + + encap_content_info_obj = self.EncapsulatedContentInfo_create( + krb5_asn1.id_pkinit_authData, auth_pack) + + signer_identifier = self.SignerIdentifier_create( + subject_key_id=subject_key_id.digest) + + signer_info = self.SignerInfo_create( + signer_identifier, + signature_algorithm_id, + signature_algorithm_id, + signed, + signed_attrs=[ + # Note: these attributes are optional. + krb5_asn1.id_pkinit_authData, + krb5_asn1.id_messageDigest, + ]) + + encoded_cert = certificate.public_bytes(serialization.Encoding.DER) + decoded_cert = self.der_decode( + encoded_cert, asn1Spec=krb5_asn1.CertificateChoices()) + + signed_auth_pack = self.SignedData_create( + [signature_algorithm_id], + encap_content_info_obj, + signer_infos=[signer_info], + certificates=[decoded_cert], + crls=None) + + signed_auth_pack = self.der_encode(signed_auth_pack, + asn1Spec=krb5_asn1.SignedData()) + + pk_as_req = self.PK_AS_REQ_create(signed_auth_pack, + # This contains a list of CAs, + # trusted by the client, that can + # be used to certify the KDC. + trusted_certifiers=None, + kdc_pk_id=None) + + padata = [self.PA_DATA_create(PADATA_PK_AS_REQ, pk_as_req)] + + return padata, req_body + + cname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=user_name.split('/')) + + target_name = target_creds.get_username() + target_realm = target_creds.get_realm() + + sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=['host', target_name[:-1]]) + + if expect_error: + check_error_fn = self.generic_check_kdc_error + check_rep_fn = None + + expected_sname = sname + else: + check_error_fn = None + check_rep_fn = self.generic_check_kdc_rep + + expected_sname = self.PrincipalName_create(name_type=NT_PRINCIPAL, + names=[target_name]) + + kdc_options = ('forwardable,' + 'renewable,' + 'canonicalize,' + 'renewable-ok') + kdc_options = krb5_asn1.KDCOptions(kdc_options) + + ticket_decryption_key = self.TicketDecryptionKey_from_creds( + target_creds) + + kdc_exchange_dict = self.as_exchange_dict( + creds=creds, + client_cert=certificate, + expected_crealm=creds.get_realm(), + expected_cname=cname, + expected_srealm=target_realm, + expected_sname=expected_sname, + expected_supported_etypes=target_creds.tgs_supported_enctypes, + ticket_decryption_key=ticket_decryption_key, + generate_padata_fn=generate_pk_padata, + check_error_fn=check_error_fn, + check_rep_fn=check_rep_fn, + check_kdc_private_fn=self.generic_check_kdc_private, + expected_error_mode=expect_error, + expected_salt=creds.get_salt(), + preauth_key=preauth_key, + kdc_options=str(kdc_options), + using_pkinit=using_pkinit, + pk_nonce=pk_nonce, + expect_edata=False) + + till = self.get_KerberosTime(offset=36000) + + if etypes is None: + etypes = kcrypto.Enctype.AES256, kcrypto.Enctype.RC4, + + if using_pkinit is PkInit.PUBLIC_KEY: + # DES-EDE3-CBC is required for public-key PK-INIT to work on + # Windows. + etypes += DES_EDE3_CBC, + + rep = self._generic_kdc_exchange(kdc_exchange_dict, + cname=cname, + realm=target_realm, + sname=sname, + till_time=till, + etypes=etypes) + if expect_error: + self.check_error_rep(rep, expect_error) + return None + + self.check_as_reply(rep) + return kdc_exchange_dict['rep_ticket_creds'] + + +if __name__ == '__main__': + global_asn1_print = False + global_hexdump = False + import unittest + unittest.main() diff --git a/python/samba/tests/krb5/raw_testcase.py b/python/samba/tests/krb5/raw_testcase.py index 3fe24424ea6..f50fcd0a167 100644 --- a/python/samba/tests/krb5/raw_testcase.py +++ b/python/samba/tests/krb5/raw_testcase.py @@ -30,8 +30,9 @@ import math from enum import Enum from pprint import pprint +from cryptography import x509 from cryptography.hazmat.primitives import asymmetric, hashes -from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from pyasn1.codec.der.decoder import decode as pyasn1_der_decode @@ -88,6 +89,8 @@ from samba.tests.krb5.rfc4120_constants import ( KU_FAST_REQ_CHKSUM, KU_KRB_PRIV, KU_NON_KERB_CKSUM_SALT, + KU_NON_KERB_SALT, + KU_PKINIT_AS_REQ, KU_TGS_REP_ENC_PART_SESSION, KU_TGS_REP_ENC_PART_SUB_KEY, KU_TGS_REQ_AUTH, @@ -111,6 +114,7 @@ from samba.tests.krb5.rfc4120_constants import ( PADATA_PAC_OPTIONS, PADATA_PAC_REQUEST, PADATA_PKINIT_KX, + PADATA_PK_AS_REP, PADATA_PK_AS_REQ, PADATA_PK_AS_REP_19, PADATA_SUPPORTED_ETYPES, @@ -592,6 +596,12 @@ class KerberosTicketCreds: self.sname = sname +class PkInit(Enum): + NOT_USED = object() + PUBLIC_KEY = object() + DIFFIE_HELLMAN = object() + + class RawKerberosTest(TestCase): """A raw Kerberos Test case.""" @@ -2909,6 +2919,7 @@ class RawKerberosTest(TestCase): def as_exchange_dict(self, creds=None, + client_cert=None, expected_crealm=None, expected_cname=None, expected_anon=False, @@ -2955,6 +2966,8 @@ class RawKerberosTest(TestCase): ap_options=None, fast_ap_options=None, strict_edata_checking=True, + using_pkinit=PkInit.NOT_USED, + pk_nonce=None, expect_edata=None, expect_pac=True, expect_client_claims=None, @@ -2984,6 +2997,7 @@ class RawKerberosTest(TestCase): 'rep_asn1Spec': krb5_asn1.AS_REP, 'rep_encpart_asn1Spec': krb5_asn1.EncASRepPart, 'creds': creds, + 'client_cert': client_cert, 'expected_crealm': expected_crealm, 'expected_cname': expected_cname, 'expected_anon': expected_anon, @@ -3030,6 +3044,8 @@ class RawKerberosTest(TestCase): 'ap_options': ap_options, 'fast_ap_options': fast_ap_options, 'strict_edata_checking': strict_edata_checking, + 'using_pkinit': using_pkinit, + 'pk_nonce': pk_nonce, 'expect_edata': expect_edata, 'expect_pac': expect_pac, 'expect_client_claims': expect_client_claims, @@ -3283,9 +3299,355 @@ class RawKerberosTest(TestCase): encpart_decryption_key, encpart_decryption_usage = ( self.get_preauth_key(kdc_exchange_dict)) - if armor_key is not None: - pa_dict = self.get_pa_dict(padata) + pa_dict = self.get_pa_dict(padata) + + if PADATA_PK_AS_REP in pa_dict: + pk_as_rep = pa_dict[PADATA_PK_AS_REP] + pk_as_rep = self.der_decode(pk_as_rep, + asn1Spec=krb5_asn1.PA_PK_AS_REP()) + + using_pkinit = kdc_exchange_dict['using_pkinit'] + if using_pkinit is PkInit.PUBLIC_KEY: + content_info = self.der_decode( + pk_as_rep['encKeyPack'], + asn1Spec=krb5_asn1.ContentInfo()) + self.assertEqual(str(krb5_asn1.id_envelopedData), + content_info['contentType']) + + content = self.der_decode(content_info['content'], + asn1Spec=krb5_asn1.EnvelopedData()) + + self.assertEqual(0, content['version']) + originator_info = content['originatorInfo'] + self.assertFalse(originator_info.get('certs')) + self.assertFalse(originator_info.get('crls')) + self.assertFalse(content.get('unprotectedAttrs')) + + encrypted_content_info = content['encryptedContentInfo'] + recipient_infos = content['recipientInfos'] + + self.assertEqual(1, len(recipient_infos)) + ktri = recipient_infos[0]['ktri'] + + if self.strict_checking: + self.assertEqual(0, ktri['version']) + + private_key = encpart_decryption_key + self.assertIsInstance(private_key, + asymmetric.rsa.RSAPrivateKey) + + client_subject_key_id = ( + x509.SubjectKeyIdentifier.from_public_key( + private_key.public_key())) + + # Check that the client certificate is named as the recipient. + ktri_rid = ktri['rid'] + try: + issuer_and_serial_number = ktri_rid[ + 'issuerAndSerialNumber'] + except KeyError: + subject_key_id = ktri_rid['subjectKeyIdentifier'] + self.assertEqual(subject_key_id, + client_subject_key_id.digest) + else: + client_certificate = kdc_exchange_dict['client_cert'] + + self.assertIsNotNone(issuer_and_serial_number['issuer']) + self.assertEqual(issuer_and_serial_number['serialNumber'], + client_certificate.serial_number) + + key_encryption_algorithm = ktri['keyEncryptionAlgorithm'] + self.assertEqual(str(krb5_asn1.rsaEncryption), + key_encryption_algorithm['algorithm']) + if self.strict_checking: + self.assertEqual( + b'\x05\x00', + key_encryption_algorithm.get('parameters')) + + encrypted_key = ktri['encryptedKey'] + + # Decrypt the key. + pad_len = 256 - len(encrypted_key) + if pad_len: + encrypted_key = bytes(pad_len) + encrypted_key + decrypted_key = private_key.decrypt( + encrypted_key, + padding=asymmetric.padding.PKCS1v15()) + + self.assertEqual(str(krb5_asn1.id_signedData), + encrypted_content_info['contentType']) + + encrypted_content = encrypted_content_info['encryptedContent'] + encryption_algorithm = encrypted_content_info[ + 'contentEncryptionAlgorithm'] + + cipher_algorithm = self.cipher_from_algorithm(encryption_algorithm['algorithm']) + + # This will serve as the IV. + parameters = self.der_decode( + encryption_algorithm['parameters'], + asn1Spec=krb5_asn1.CMSCBCParameter()) + + # Decrypt the content. + cipher = Cipher(cipher_algorithm(decrypted_key), + modes.CBC(parameters), + default_backend()) + decryptor = cipher.decryptor() + decrypted_content = decryptor.update(encrypted_content) + decrypted_content += decryptor.finalize() + + # The padding doesn’t fully comply to PKCS7 with a specified + # blocksize, so we must unpad the data ourselves. + decrypted_content = self.unpad(decrypted_content) + + signed_data = None + signed_data_rfc2315 = None + + first_tag = decrypted_content[0] + if first_tag == 0x30: # ASN.1 SEQUENCE tag + signed_data = decrypted_content + else: + # Windows encodes the ASN.1 incorrectly, neglecting to add + # the SEQUENCE tag. We’ll have to prepend it ourselves in + # order for the decoding to work. + encoded_len = self.asn1_length(decrypted_content) + decrypted_content = bytes([0x30]) + encoded_len + ( + decrypted_content) + + if first_tag == 0x02: # ASN.1 INTEGER tag + + # The INTEGER tag indicates that the data is encoded + # with the earlier variant of the SignedData ASN.1 + # schema specified in RFC2315, as per [MS-PKCA] 2.2.4 + # (PA-PK-AS-REP). + signed_data_rfc2315 = decrypted_content + + elif first_tag == 0x06: # ASN.1 OBJECT IDENTIFIER tag + + # The OBJECT IDENTIFIER tag indicates that the data is + # encoded as SignedData and wrapped in a ContentInfo + # structure, which we shall have to decode first. This + # seems to be the case when the supportedCMSTypes field + # in the client’s AuthPack is missing or empty. + + content_info = self.der_decode( + decrypted_content, + asn1Spec=krb5_asn1.ContentInfo()) + self.assertEqual(str(krb5_asn1.id_signedData), + content_info['contentType']) + signed_data = content_info['content'] + else: + self.fail(f'got reply with unknown initial tag ' + f'({first_tag})') + + if signed_data is not None: + signed_data = self.der_decode( + signed_data, asn1Spec=krb5_asn1.SignedData()) + + encap_content_info = signed_data['encapContentInfo'] + + content_type = encap_content_info['eContentType'] + content = encap_content_info['eContent'] + elif signed_data_rfc2315 is not None: + signed_data = self.der_decode( + signed_data_rfc2315, + asn1Spec=krb5_asn1.SignedData_RFC2315()) + + encap_content_info = signed_data['contentInfo'] + + content_type = encap_content_info['contentType'] + content = self.der_decode( + encap_content_info['content'], + asn1Spec=pyasn1.type.univ.OctetString()) + else: + self.fail('we must have got SignedData') + + self.assertEqual(str(krb5_asn1.id_pkinit_rkeyData), + content_type) + reply_key_pack = self.der_decode( + content, asn1Spec=krb5_asn1.ReplyKeyPack()) + + as_checksum = reply_key_pack['asChecksum'] + + req_obj = kdc_exchange_dict['req_obj'] + req_asn1Spec = kdc_exchange_dict['req_asn1Spec'] + req_obj = self.der_encode(req_obj, + asn1Spec=req_asn1Spec()) + + reply_key = reply_key_pack['replyKey'] + + # Reply the encpart decryption key with the decrypted key from + # the reply. + encpart_decryption_key = self.SessionKey_create( + etype=reply_key['keytype'], + contents=reply_key['keyvalue'], + kvno=None) + + # Verify the checksum over the AS request body. + kcrypto.verify_checksum(as_checksum['cksumtype'], + encpart_decryption_key.key, + KU_PKINIT_AS_REQ, + req_obj, + as_checksum['checksum']) + elif using_pkinit is PkInit.DIFFIE_HELLMAN: + content_info = self.der_decode( + pk_as_rep['dhInfo']['dhSignedData'], + asn1Spec=krb5_asn1.ContentInfo()) + self.assertEqual(str(krb5_asn1.id_signedData), + content_info['contentType']) + + signed_data = self.der_decode(content_info['content'], + asn1Spec=krb5_asn1.SignedData()) + encap_content_info = signed_data['encapContentInfo'] + content = encap_content_info['eContent'] + + self.assertEqual(str(krb5_asn1.id_pkinit_DHKeyData), + encap_content_info['eContentType']) + + dh_key_info = self.der_decode( + content, asn1Spec=krb5_asn1.KDCDHKeyInfo()) + + self.assertNotIn('dhKeyExpiration', dh_key_info) + + dh_private_key = encpart_decryption_key + self.assertIsInstance(dh_private_key, + asymmetric.dh.DHPrivateKey) + + self.assertElementEqual(dh_key_info, 'nonce', + kdc_exchange_dict['pk_nonce']) + + dh_public_key_data = self.bytes_from_bit_string( + dh_key_info['subjectPublicKey']) + dh_public_key_decoded = self.der_decode( + dh_public_key_data, asn1Spec=krb5_asn1.DHPublicKey()) + + dh_numbers = dh_private_key.parameters().parameter_numbers() + + public_numbers = asymmetric.dh.DHPublicNumbers( + dh_public_key_decoded, dh_numbers) + dh_public_key = public_numbers.public_key(default_backend()) + + # Perform the Diffie-Hellman key exchange. + shared_secret = dh_private_key.exchange(dh_public_key) + + # Pad the shared secret out to the length of ‘p’. + p_len = self.length_in_bytes(dh_numbers.p) + padding_len = p_len - len(shared_secret) + self.assertGreaterEqual(padding_len, 0) + padded_shared_secret = bytes(padding_len) + shared_secret + + reply_key_enc_type = self.expected_etype(kdc_exchange_dict) + + # At the moment, we don’t specify a nonce in the request, so we + # can assume these are empty. + client_nonce = b'' + server_nonce = b'' + + ciphertext = padded_shared_secret + client_nonce + server_nonce + + # Replace the encpart decryption key with the key derived from + # the Diffie-Hellman key exchange. + encpart_decryption_key = self.octetstring2key( + ciphertext, reply_key_enc_type) + else: + self.fail(f'invalid value for using_pkinit: {using_pkinit}') + + self.assertEqual(3, signed_data['version']) + + digest_algorithms = signed_data['digestAlgorithms'] + self.assertEqual(1, len(digest_algorithms)) + digest_algorithm = digest_algorithms[0] + # Ensure the hash algorithm is valid. + _ = self.hash_from_algorithm_id(digest_algorithm) + + self.assertFalse(signed_data.get('crls')) + + signer_infos = signed_data['signerInfos'] + self.assertEqual(1, len(signer_infos)) + signer_info = signer_infos[0] + + self.assertEqual(1, signer_info['version']) + + # Get the certificate presented by the KDC. + kdc_certificates = signed_data['certificates'] + self.assertEqual(1, len(kdc_certificates)) + kdc_certificate = self.der_encode( + kdc_certificates[0], asn1Spec=krb5_asn1.CertificateChoices()) + kdc_certificate = x509.load_der_x509_certificate(kdc_certificate, + default_backend()) + + # Verify that the KDC’s certificate is named as the signer. + sid = signer_info['sid'] + try: + issuer_and_serial_number = sid['issuerAndSerialNumber'] + except KeyError: + extension = kdc_certificate.extensions.get_extension_for_oid( + x509.oid.ExtensionOID.SUBJECT_KEY_IDENTIFIER) + cert_subject_key_id = extension.value.digest + self.assertEqual(sid['subjectKeyIdentifier'], cert_subject_key_id) + else: + self.assertIsNotNone(issuer_and_serial_number['issuer']) + self.assertEqual(issuer_and_serial_number['serialNumber'], + kdc_certificate.serial_number) + + digest_algorithm = signer_info['digestAlgorithm'] + digest_hash_fn = self.hash_from_algorithm_id(digest_algorithm) + + signed_attrs = signer_info['signedAttrs'] + self.assertEqual(2, len(signed_attrs)) + + signed_attr0 = signed_attrs[0] + self.assertEqual(str(krb5_asn1.id_contentType), + signed_attr0['attrType']) + signed_attr0_values = signed_attr0['attrValues'] + self.assertEqual(1, len(signed_attr0_values)) + signed_attr0_value = self.der_decode( + signed_attr0_values[0], + asn1Spec=krb5_asn1.ContentType()) + if using_pkinit is PkInit.DIFFIE_HELLMAN: + self.assertEqual(str(krb5_asn1.id_pkinit_DHKeyData), + signed_attr0_value) + else: + self.assertEqual(str(krb5_asn1.id_pkinit_rkeyData), + signed_attr0_value) + + signed_attr1 = signed_attrs[1] + self.assertEqual(str(krb5_asn1.id_messageDigest), + signed_attr1['attrType']) + signed_attr1_values = signed_attr1['attrValues'] + self.assertEqual(1, len(signed_attr1_values)) + message_digest = self.der_decode(signed_attr1_values[0], + krb5_asn1.MessageDigest()) + + signature_algorithm = signer_info['signatureAlgorithm'] + hash_fn = self.hash_from_algorithm_id(signature_algorithm) + + # Compute the hash of the content to be signed. With the + # Diffie-Hellman key exchange, this signature is over the type + # KDCDHKeyInfo; otherwise, it is over the type ReplyKeyPack. + digest = hashes.Hash(digest_hash_fn(), default_backend()) + digest.update(content) + digest = digest.finalize() + + # Verify the hash. Note: this is a non–constant time comparison. + self.assertEqual(digest, message_digest) + + # Re-encode the attributes ready for verifying the signature. + cms_attrs = self.der_encode(signed_attrs, + asn1Spec=krb5_asn1.CMSAttributes()) + + # Verify the signature. + kdc_public_key = kdc_certificate.public_key() + kdc_public_key.verify( + signer_info['signature'], + cms_attrs, + asymmetric.padding.PKCS1v15(), + hash_fn()) + + self.assertFalse(signer_info.get('unsignedAttrs')) + + if armor_key is not None: if PADATA_FX_FAST in pa_dict: fx_fast_data = pa_dict[PADATA_FX_FAST] fast_response = self.check_fx_fast_data(kdc_exchange_dict, @@ -3495,8 +3857,11 @@ class RawKerberosTest(TestCase): self.assertElementPresent(encpart_key, 'keyvalue') encpart_session_key = self.EncryptionKey_import(encpart_key) self.assertElementPresent(encpart_private, 'last-req') + expected_nonce = kdc_exchange_dict.get('pk_nonce') + if not expected_nonce: + expected_nonce = kdc_exchange_dict['nonce'] self.assertElementEqual(encpart_private, 'nonce', - kdc_exchange_dict['nonce']) + expected_nonce) if rep_msg_type == KRB_AS_REP: if self.strict_checking: self.assertElementPresent(encpart_private, @@ -4361,21 +4726,26 @@ class RawKerberosTest(TestCase): expected_patypes = () sent_fast = self.sent_fast(kdc_exchange_dict) + using_pkinit = kdc_exchange_dict.get('using_pkinit', PkInit.NOT_USED) rep_msg_type = kdc_exchange_dict['rep_msg_type'] if sent_fast: expected_patypes += (PADATA_FX_FAST,) elif rep_msg_type == KRB_AS_REP: - chosen_etype = self.getElementValue(encpart, 'etype') - self.assertIsNotNone(chosen_etype) + if using_pkinit is PkInit.NOT_USED: + chosen_etype = self.getElementValue(encpart, 'etype') + self.assertIsNotNone(chosen_etype) - if chosen_etype in {kcrypto.Enctype.AES256, - kcrypto.Enctype.AES128}: - expected_patypes += (PADATA_ETYPE_INFO2,) + if chosen_etype in {kcrypto.Enctype.AES256, + kcrypto.Enctype.AES128}: + expected_patypes += (PADATA_ETYPE_INFO2,) - preauth_key = kdc_exchange_dict['preauth_key'] - if preauth_key.etype == kcrypto.Enctype.RC4 and rep_padata is None: - rep_padata = () + preauth_key = kdc_exchange_dict['preauth_key'] + self.assertIsInstance(preauth_key, Krb5EncryptionKey) + if preauth_key.etype == kcrypto.Enctype.RC4 and rep_padata is None: + rep_padata = () + else: + expected_patypes += PADATA_PK_AS_REP, elif rep_msg_type == KRB_TGS_REP: if expected_patypes == () and rep_padata is None: rep_padata = () @@ -4385,7 +4755,9 @@ class RawKerberosTest(TestCase): self.assertIsNotNone(rep_padata) got_patypes = tuple(pa['padata-type'] for pa in rep_padata) - self.assertSequenceElementsEqual(expected_patypes, got_patypes) + self.assertSequenceElementsEqual(expected_patypes, got_patypes, + # Windows does not add this. + unchecked={PADATA_PKINIT_KX}) if len(expected_patypes) == 0: return None diff --git a/python/samba/tests/krb5/rfc4120_constants.py b/python/samba/tests/krb5/rfc4120_constants.py index 990cc36e318..d9d8c30a4b3 100644 --- a/python/samba/tests/krb5/rfc4120_constants.py +++ b/python/samba/tests/krb5/rfc4120_constants.py @@ -33,6 +33,8 @@ DES3_CBC_MD5 = int( DES3_CBC_SHA1 = int( krb5_asn1.EncryptionTypeValues('kRB5-ENCTYPE-DES3-CBC-SHA1')) +DES_EDE3_CBC = 15 # des-ede3-cbc-EnvOID — required for Windows PK-INIT. + # Message types KRB_ERROR = int(krb5_asn1.MessageTypeValues('krb-error')) KRB_AP_REP = int(krb5_asn1.MessageTypeValues('krb-ap-rep')) @@ -84,6 +86,8 @@ PADATA_GSS = int( krb5_asn1.PADataTypeValues('kRB5-PADATA-GSS')) PADATA_REQ_ENC_PA_REP = int( krb5_asn1.PADataTypeValues('kRB5-PADATA-REQ-ENC-PA-REP')) +PADATA_AS_FRESHNESS = int( + krb5_asn1.PADataTypeValues('kRB5-PADATA-AS-FRESHNESS')) # Error codes KDC_ERR_C_PRINCIPAL_UNKNOWN = 6 @@ -185,6 +189,7 @@ KU_TGS_REQ_AUTH_DAT_SUBKEY = 5 KU_TGS_REQ_AUTH_CKSUM = 6 ''' TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator cksum, keyed with the tgs session key (section 5.5.1) ''' +KU_PKINIT_AS_REQ = 6 KU_TGS_REQ_AUTH = 7 ''' TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes tgs authenticator subkey), encrypted with the tgs session key diff --git a/selftest/knownfail_heimdal_kdc b/selftest/knownfail_heimdal_kdc index eea5fb2dfb8..88f9c51e3de 100644 --- a/selftest/knownfail_heimdal_kdc +++ b/selftest/knownfail_heimdal_kdc @@ -63,3 +63,26 @@ # ^samba.tests.krb5.authn_policy_tests.samba.tests.krb5.authn_policy_tests.AuthnPolicyTests.test_authn_policy_allowed_from_empty.ad_dc ^samba.tests.krb5.authn_policy_tests.samba.tests.krb5.authn_policy_tests.AuthnPolicyTests.test_authn_policy_allowed_to_empty.ad_dc +# +# PK-INIT tests +# +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_aes128.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_computer.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_computer_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_empty_supported_cms_types.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_empty_supported_cms_types_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_des3.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_des3_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_supported_cms_types.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_supported_cms_types_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_rc4.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_service.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_service_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_certificate_signature.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_certificate_signature_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_signature.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_signature_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_zero_nonce.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_zero_nonce_dh.ad_dc diff --git a/selftest/knownfail_mit_kdc_1_20 b/selftest/knownfail_mit_kdc_1_20 index d0ff07100d7..4968e21c37d 100644 --- a/selftest/knownfail_mit_kdc_1_20 +++ b/selftest/knownfail_mit_kdc_1_20 @@ -72,3 +72,26 @@ # ^samba.tests.krb5.authn_policy_tests.samba.tests.krb5.authn_policy_tests.AuthnPolicyTests.test_authn_policy_allowed_from_no_fast_negative_lifetime.ad_dc ^samba.tests.krb5.authn_policy_tests.samba.tests.krb5.authn_policy_tests.AuthnPolicyTests.test_authn_policy_allowed_to_user_deny_s4u2self_constrained_delegation.ad_dc +# +# PK-INIT tests +# +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_aes128.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_computer.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_computer_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_empty_supported_cms_types.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_empty_supported_cms_types_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_des3.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_des3_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_supported_cms_types.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_no_supported_cms_types_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_rc4.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_service.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_service_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_certificate_signature.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_certificate_signature_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_signature.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_sha256_signature_dh.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_zero_nonce.ad_dc +^samba.tests.krb5.pkinit_tests.samba.tests.krb5.pkinit_tests.PkInitTests.test_pkinit_zero_nonce_dh.ad_dc diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index 13b3b1ce6ab..cc3703373a9 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -1212,6 +1212,22 @@ check_padata = int('SAMBA4_USES_HEIMDAL' in config_hash) expect_nt_status = int('SAMBA4_USES_HEIMDAL' in config_hash) as_req_logging_support = int('SAMBA4_USES_HEIMDAL' in config_hash) tgs_req_logging_support = int('SAMBA4_USES_HEIMDAL' in config_hash) + +ca_dir = os.path.join('selftest', 'manage-ca', 'CA-samba.example.com') + +# This certificate is currently used just to get the name of the certificate +# issuer. +ca_cert_path = os.path.join(ca_dir, + 'DCs', + 'addc.addom.samba.example.com', + 'DC-addc.addom.samba.example.com-cert.pem') + +# The private key is used to issue new certificates. +ca_private_key_path = os.path.join(ca_dir, + 'Private', + 'CA-samba.example.com-private-key.pem') +ca_pass = '1234' + krb5_environ = { 'SERVICE_USERNAME': '$SERVER', 'ADMIN_USERNAME': '$DC_USERNAME', @@ -1232,6 +1248,9 @@ krb5_environ = { 'EXPECT_NT_STATUS': expect_nt_status, 'AS_REQ_LOGGING_SUPPORT': as_req_logging_support, 'TGS_REQ_LOGGING_SUPPORT': tgs_req_logging_support, + 'CA_CERT': ca_cert_path, + 'CA_PRIVATE_KEY': ca_private_key_path, + 'CA_PASS': ca_pass, } planoldpythontestsuite("none", "samba.tests.krb5.kcrypto") planoldpythontestsuite("none", "samba.tests.krb5.claims_in_pac") @@ -1994,6 +2013,10 @@ planoldpythontestsuite( 'ad_dc', 'samba.tests.krb5.authn_policy_tests', environ=krb5_environ) +planoldpythontestsuite( + 'ad_dc', + 'samba.tests.krb5.pkinit_tests', + environ=krb5_environ) for env in [ 'vampire_dc', -- 2.34.1