# Copyright (c) 2024-2025 Broadcom. All Rights Reserved. # The term "Broadcom" refers to Broadcom Inc. # and/or its subsidiaries. import logging import re import OpenSSL import glob from enum import Enum from lib import vcdb from lib import vecs from lib import vmdir from lib.certificate_utils import ( get_x509_certificate, get_certificate_expiry_in_days, get_certificate_extensions, get_certificate_fingerprint, build_certification_path, split_certificates_from_pem, get_subject_keyid, get_certificate_from_host, build_pem_certificate, is_ca_certificate, get_subject_hash, get_authority_keyid, get_subject_and_issuer_dn, get_certificate_name ) from lib.console import ( print_task, print_task_status, print_text_error, print_task_status_error, print_task_status_warning, print_header, ColorKey, print_text ) from lib.constants import (VMCA_CERT_FILE_PATH, RBD_CERT_FILE_PATH, READ, WRITE, VMWARE_DEPOT_HOST, VMWARE_DEPOT_CERT_ISSUER) from lib.environment import Environment from lib.exceptions import MenuExitException from lib.host_utils import ( is_file_exists, get_file_contents, get_vc_version, VcVersion, get_ip_address ) from lib.menu import Menu from lib.services import is_service_running from operation.common import get_vcenter_extensions, get_vcenter_extension_expected_thumbprints logger = logging.getLogger(__name__) class CertificateStatus(Enum): CERT_STATUS_EXPIRES_SOON = 'One or more certificates are expiring within 30 days' CERT_STATUS_MISSING_PNID = 'One or more certificates are missing the PNID >>DETAILS<< from the SAN entry' CERT_STATUS_MISSING_SAN = 'One or more certificates do not have any Subject Alternative Name values' CERT_STATUS_KEY_USAGE = 'One or more certificates do not have the recommended\nKey Usage values' CERT_STATUS_EXPIRED = 'One or more certificates are expired' CERT_STATUS_NON_CA = 'One or more certificates are not CA certificates' CERT_STATUS_BAD_ALIAS = \ 'One or more entries in the TRUSTED_ROOTS store have an alias that is not the SHA1 thumbprint' CERT_STATUS_SHA1_SIGNING = 'One or more certificates are signed using the SHA-1 algorithm' CERT_STATUS_MISSING = 'One or more certificates are missing' CERT_STATUS_MISSING_VMDIR = \ 'One or more CA certificates are missing from VMware Directory' CERT_STATUS_MISMATCH_SERVICE_PRINCIPAL = \ 'One or more Solution User certificates does not match\nthe Service Principal certificate in VMware Directory' CERT_STATUS_TOO_MANY_CRLS = 'The number of CRLs in VECS may be preventing some services from starting' CERT_STATUS_MISSING_CA = \ 'One or more certificates do not have all of the CA\n' \ 'certificates in its signing chain in VMware Directory' CERT_STATUS_EXPIRED_EMBEDDED_CA = \ 'One or more certificates has a CA certificate embedded\n' \ 'in its chain that is expired' CERT_STATUS_STORE_MISSING = 'One or more VECS stores are missing' CERT_STATUS_STORE_PERMISSIONS = 'One or more VECS stores are missing permissions' CERT_STATUS_SERVICE_PRINCIPAL_MISSING = \ 'One or more Service Principal entries are missing\nfrom VMware Directory' CERT_STATUS_VMCA_EMPTY_CONFIG = \ 'There are one or more vpxd.certmgmt.certs.cn.* settings with empty values\n' \ 'This can cause issues pushing VMCA-signed certificates to ESXi hosts' CERT_STATUS_VMCA_MODE = \ "The certificate management mode is set to 'thumbprint'\n" \ "This is not recommended, and should be set to 'vmca' or 'custom'" CERT_STATUS_CLIENT_CA_LIST_FILE_MISSING = \ 'The Smart Card issuing CA filter file does not exist at the following location:\n>>DETAILS<<' CERT_STATUS_CLIENT_CA_LIST_FILE_EMPTY = \ 'The Smart Card issuing CA filter file at the following location is empty:\n>>DETAILS<<' CERT_STATUS_STS_VECS_CONFIG = \ 'The STS server is configured to use a VECS store other than\nthe MACHINE_SSL_CERT store' CERT_STATUS_STS_CONNECTION_STRINGS_NUMBER = \ 'There are multiple STS ConnectionStrings values found in VMware Directory' CERT_STATUS_STS_CONNECTION_STRINGS_HOSTNAME = \ 'The STS ConnectionStrings value is not set properly for an SSO\ndomain with multiple Domain Controllers' CERT_STATUS_UNSUPPORTED_SIGNATURE_ALGORITHM = \ 'One or more certificates is using an unsupported\nsignature algorithm' CERT_STATUS_CA_MISSING_SKID = 'One or more CA certificates is missing the Subject Key ID extension' CERT_STATUS_ROGUE_CA = \ 'One or more certificates are invalid because it or a signing CA\n' \ 'extends beyond the pathlen restrictions of a parent CA' CERT_STATUS_DUPLICATE_CA = \ 'Two or more CA certificates in VMWare Directory or VECS have the\n' \ 'same Subject string, which can cause issues with certificate\n' \ 'validation' TRUST_ANCHORS_MISMATCH = "One or more vCenter/PSC nodes have mismatched SSL trust anchors" TRUST_ANCHORS_UNKNOWN = \ 'The Machine SSL certificate could not be obtained from\n' \ 'the following nodes to check SSL trust anchors:' TRUST_ANCHORS_CHECK_URI_MISMATCH = \ 'One or more vCenter/PSC nodes have mismatched SSL trust anchors and\n' \ 'have Lookup Service registrations using the IP address instead\n' \ 'of the PNID in the endpoint URIs. These can be fixed with the\n' \ 'lsdoctor utility: https://knowledge.broadcom.com/external/article/320837/using-the-lsdoctor-tool.html' TRUST_ANCHORS_CHECK_URI_IP = \ 'One or more vCenter/PSC nodes have Lookup Service registrations\n' \ 'using the IP address instead of the PNID in the endpoint URIs.\n' \ 'These can be fixed with the lsdoctor utility:\n' \ 'https://knowledge.broadcom.com/external/article/320837/using-the-lsdoctor-tool.html' TRUST_ANCHORS_CHECK_URI_OTHER = \ 'One or more vCenters have no Lookup Service registration endpoints\n' \ 'using the current hostname or IP address. There could be registrations\n' \ 'for these vCenters using a different hostname or IP address.\n' \ 'These can be fixed with the lsdoctor utility: \n' \ 'https://knowledge.broadcom.com/external/article/320837/using-the-lsdoctor-tool.html' TRUST_ANCHORS_CHECK_PENDING = \ 'The current Machine SSL certificate is not being served on port\n' \ '443 due to a pending service restart\n' def check_certificate_dummy(**_): print_text_error('=== Unsupported check certificate operation! ===') def get_check_options(store, is_solution_user): """ Get check options based on VECS store name or solution user :param store: VECS store name :param is_solution_user: whether the certificate is solution user certificate :return: list of check option """ if store == 'MACHINE_SSL_CERT': options = ['CHECK_PNID', 'CHECK_KU', 'CHECK_SAN', 'CHECK_CA_CHAIN', 'CHECK_EMBEDDED_CHAIN', 'CHECK_ROGUE_CA'] elif store == 'SMS': options = [] else: options = ['CHECK_KU', 'CHECK_SAN', 'CHECK_CA_CHAIN', 'CHECK_EMBEDDED_CHAIN', 'CHECK_ROGUE_CA'] if is_solution_user: options.append('CHECK_SERVICE_PRINCIPAL') if store in ['wcp', 'wcpsvc']: options.remove('CHECK_SAN') return options def check_vecs_certificate(store, alias, is_solution_user=False): """ Check specific certificate in VECS :param store: VECS store name :param alias: certificate alias :param is_solution_user: whether the certificate is solution user certificate """ logger.info("Checking VECS certificate: store {}, alias {}".format(store, alias)) options = get_check_options(store, is_solution_user) logger.info("Check options: {}".format(options)) aliases = vecs.get_certificate_aliases(store) if alias not in aliases: add_certificate_status(CertificateStatus.CERT_STATUS_MISSING) print_task_status_error('NOT FOUND') logger.error("Certificate for alias {} was not found in store {}".format(alias, store)) return pem_cert = vecs.get_certificate(store, alias) if not pem_cert: print_task_status_error('PROBLEM') logger.error("Failed to obtain certificate for alias {} in store {}".format(alias, store)) return cert = get_x509_certificate(pem_cert) days_left = get_certificate_expiry_in_days(cert) cert_desc = "Certificate for alias {} in store {}".format(alias, store) if days_left < 0: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRED) print_task_status_warning('EXPIRED') logger.warning("{} is expired".format(cert_desc)) return elif days_left < 30: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRES_SOON) print_task_status_warning("{} DAYS".format(days_left)) logger.info("{} will expire in {} days".format(cert_desc, days_left)) return extensions = get_certificate_extensions(cert) env = Environment.get_environment() if 'CHECK_PNID' in options: pnid = env.get_value('PNID') san = extensions.get('subjectAltName') if san is None or pnid not in san: add_certificate_status(CertificateStatus.CERT_STATUS_MISSING_PNID, pnid) print_task_status_warning('NO PNID') logger.warning("{} does not have the PNID {} in the Subject Alternative Name field".format(cert_desc, pnid)) return if 'CHECK_KU' in options: if not check_key_usage(cert, "{}:{}".format(store, alias)): add_certificate_status(CertificateStatus.CERT_STATUS_KEY_USAGE) print_task_status_warning('KEY USAGE') logger.warning("{} does not have the expected Key Usage values".format(cert_desc)) return if 'CHECK_SAN' in options: san = extensions.get('subjectAltName') if not san: add_certificate_status(CertificateStatus.CERT_STATUS_MISSING_SAN) print_task_status_warning('NO SAN') logger.warning("{} has no values in Subject Alternative Name field".format(cert_desc)) return if 'CHECK_SERVICE_PRINCIPAL' in options: fingerprint1 = get_certificate_fingerprint(cert, 'sha1') solution_user_cert = vmdir.get_solution_user_certificate(store) fingerprint2 = get_certificate_fingerprint(get_x509_certificate(solution_user_cert), 'sha1') if fingerprint1 != fingerprint2: add_certificate_status(CertificateStatus.CERT_STATUS_MISMATCH_SERVICE_PRINCIPAL) print_task_status_warning('MISMATCH') logger.warning("{} does not match the certificate for the corresponding Service Principal " "in VMware Directory".format(cert_desc)) return if 'CHECK_CA_CHAIN' in options: if check_missing_ca(pem_cert): add_certificate_status(CertificateStatus.CERT_STATUS_MISSING_CA) print_task_status_warning('MISSING CA') logger.warning("{} is missing one of the CA certificates in the certificate chain".format(cert_desc)) return if 'CHECK_EMBEDDED_CHAIN' in options: pem_certs = split_certificates_from_pem(pem_cert) pem_certs = pem_certs[1:] for pem in pem_certs: x509_cert = get_x509_certificate(pem) sha1_fingerprint = get_certificate_fingerprint(x509_cert) logger.info("Checking embedded CA certificate with SHA1 fingerprint {}".format(sha1_fingerprint)) days_left = get_certificate_expiry_in_days(x509_cert) if days_left < 0: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRED_EMBEDDED_CA) print_task_status_warning('EMBEDDED CA') logger.warning("The embedded CA certificate is expired:\n{}".format(pem)) return if 'CHECK_ROGUE_CA' in options: is_rogue_cert, _ = check_rogue_ca(pem_cert) if is_rogue_cert: add_certificate_status(CertificateStatus.CERT_STATUS_ROGUE_CA) print_task_status_warning('ROGUE') logger.warning("The certificate is invalid because of a CA that extends beyond a parent CA path length restriction:\n") return if not check_signature_algorithm(cert): add_certificate_status(CertificateStatus.CERT_STATUS_UNSUPPORTED_SIGNATURE_ALGORITHM) print_task_status_warning('ALGORITHM') logger.warning("{} is signed with unsupported signature algorithm".format(cert_desc)) else: print_task_status('VALID') def check_signature_algorithm(x509_cert): """ Check if the signature algorithm is supported :param x509_cert: X509Certificate object :return: True if the signature algorithm is supported """ unsupported_signature_algs = ['md2WithRSAEncryption', 'md5WithRSAEncryption', 'RSASSA-PSS', 'dsaWithSHA1', 'ecdsa_with_SHA1', 'sha1WithRSAEncryption'] sign_alg = x509_cert.get_signature_algorithm().decode('utf-8') logger.info("Checking certificate signature algorithm {} against unsupported signature algorithms {}" .format(sign_alg, unsupported_signature_algs)) return False if sign_alg in unsupported_signature_algs else True def check_key_usage(x509_cert, cert_desc): """ Check certificate keyUsage extension and validate that all keyUsage are in the supported list :param x509_cert: X509Certificate object :param cert_desc: Certificate description (for logging) :return: True if all keyUsages are supported """ supported_kus = ['Digital Signature', 'Key Encipherment', 'Key Agreement', 'Data Encipherment', 'Non Repudiation'] logger.info("Checking Key Usage for cert {} among supported values of: {}".format(cert_desc, supported_kus)) extensions = get_certificate_extensions(x509_cert) kus = extensions.get('keyUsage', '').split(', ') for ku in kus: if ku not in supported_kus: logger.warning("Found unsupported Key Usage value: {}".format(ku)) return False return True def check_certificate_basic(cert): """ Some basic checks on certificate :param cert: X509Certificate """ x509_cert = get_x509_certificate(cert) days_left = get_certificate_expiry_in_days(x509_cert) if days_left < 0: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRED) print_task_status_warning('EXPIRED') logger.warning("Certificate is expired") return elif days_left < 30: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRES_SOON) print_task_status_warning("{} DAYS".format(days_left)) logger.warning("Certificate expires in {} days".format(days_left)) return if not check_signature_algorithm(x509_cert): add_certificate_status(CertificateStatus.CERT_STATUS_UNSUPPORTED_SIGNATURE_ALGORITHM) print_task_status_warning('ALGORITHM') logger.warning("Certificate is signed with unsupported signature algorithm") return if is_ca_certificate(x509_cert): skid = get_subject_keyid(x509_cert) if not skid: add_certificate_status(CertificateStatus.CERT_STATUS_CA_MISSING_SKID) print_task_status_warning('NO SKID') return print_task_status('VALID') def check_file_system_certificate(file): """ Check certificate file stored on file system :param file: Path of the certificate file :return: """ if not is_file_exists(file): add_certificate_status(CertificateStatus.CERT_STATUS_MISSING) print_task_status_warning('NOT FOUND') logger.error("Certificate at {} could not be found".format(file)) return logger.info("Checking certificate at {}".format(file)) pem_cert = get_file_contents(file) check_certificate_basic(pem_cert) def check_certificate_status(): """ Entry point for check certificate status operation """ env = Environment.get_environment() print_task('Checking Machine SSL certificate') check_vecs_certificate('MACHINE_SSL_CERT', '__MACHINE_CERT') aliases = vecs.get_certificate_aliases('MACHINE_SSL_CERT') if '__MACHINE_CSR' in aliases: print_task('Checking Machine SSL CSR') check_vecs_certificate('MACHINE_SSL_CERT', '__MACHINE_CSR') print('Checking Solution User certificates:') solution_users = env.get_value('SOLUTION_USERS') for sol_user in solution_users: print_task(" {}".format(sol_user)) check_vecs_certificate(sol_user, sol_user, is_solution_user=True) vc_version = get_vc_version() print_task('Checking SMS self-signed certificate') check_vecs_certificate('SMS', 'sms_self_signed') if vc_version >= VcVersion.V8: print_task('Checking SMS VMCA-signed certificate') check_vecs_certificate('SMS', 'sps-extension') print_task('Checking data-encipherment certificate') check_vecs_certificate('data-encipherment', 'data-encipherment') print_task('Checking Authentication Proxy certificate') check_file_system_certificate(VMCA_CERT_FILE_PATH) print_task('Checking Auto Deploy CA certificate') check_file_system_certificate(RBD_CERT_FILE_PATH) cert_file = '/usr/lib/vmware-vmdir/share/config/vmdircert.pem' if is_file_exists(cert_file): print_task('Checking VMDir certificate') check_file_system_certificate(cert_file) store_list = vecs.get_store_list() if 'BACKUP_STORE' in store_list: print('Checking BACKUP_STORE entries:') for alias in vecs.get_certificate_aliases('BACKUP_STORE'): print_task(" {}".format(alias)) check_vecs_certificate('BACKUP_STORE', alias) if 'BACKUP_STORE_H5C' in store_list: print_task('Checking BACKUP_STORE_H5C entries:') for alias in vecs.get_certificate_aliases('BACKUP_STORE_H5C'): print_task(" {}".format(alias)) check_vecs_certificate('BACKUP_STORE_H5C', alias) if 'STS_INTERNAL_SSL_CERT' in store_list: print_task('Checking legacy Lookup Service certificate') check_vecs_certificate('STS_INTERNAL_SSL_CERT', '__MACHINE_CERT') print_task('Checking VMCA certificate') check_file_system_certificate(VMCA_CERT_FILE_PATH) def check_sts_tenant_certificates(): """ Entry point for STS Tenant certificates check """ certs_map = vmdir.get_sts_tenant_certificates() for tenant in certs_map.keys(): print("Checking {}:".format(tenant)) for pem_cert in certs_map[tenant]: x509_cert = get_x509_certificate(pem_cert) check_sts_tenant_certificate(x509_cert, tenant) def check_sts_tenant_certificate(x509_cert, tenant): """ Check specific STS tenant certificate :param x509_cert: X509Certificate object :param tenant: Tenant name (for output message) """ is_ca = is_ca_certificate(x509_cert) print_task(" {} {} certificate".format(tenant, 'CA' if is_ca else 'signing')) cert_desc = "STS tenant certificate {}".format(tenant) days_left = get_certificate_expiry_in_days(x509_cert) if days_left < 0: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRED) print_task_status_warning('EXPIRED') logger.warning("{} is expired".format(cert_desc)) return if is_ca: skid = get_subject_keyid(x509_cert, remove_colons=True) if not vmdir.get_ca_certificate(skid): add_certificate_status(CertificateStatus.CERT_STATUS_MISSING_VMDIR) print_task_status_warning('MISSING') logger.warning("{} is missing from VMDir".format(cert_desc)) return elif not check_key_usage(x509_cert, 'STS Tenant'): add_certificate_status(CertificateStatus.CERT_STATUS_KEY_USAGE) print_task_status_warning('KEY USAGE') return if not check_signature_algorithm(x509_cert): add_certificate_status(CertificateStatus.CERT_STATUS_UNSUPPORTED_SIGNATURE_ALGORITHM) print_task_status_warning('ALGORITHM') logger.warning("{}is signed with unsupported signature algorithm".format(cert_desc)) return if days_left < 30: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRES_SOON) print_task_status_warning("{} DAYS".format(days_left)) logger.warning("{} expires in {} days".format(cert_desc, days_left)) else: print_task_status('VALID') def check_ca_certificates_in_vmdir(): """ Entry point for CA certificates check in VMDir """ logger.info('Checking CA certificates in VMDir') subject_keyids = vmdir.get_all_ca_subject_keyids() for subject_keyid in subject_keyids: logger.info("Checking certificate with CN(id) {}".format(subject_keyid)) pem_cert = vmdir.get_ca_certificate(subject_keyid) print_task(subject_keyid) cert = get_x509_certificate(pem_cert) extensions = get_certificate_extensions(cert) days_left = get_certificate_expiry_in_days(cert) cert_subject_keyid = get_subject_keyid(cert) basic_constraints = extensions.get('basicConstraints') cert_desc = "Certificate with CN(id) {}".format(subject_keyid) is_rogue_ca, _ = check_rogue_ca(pem_cert) if days_left < 0: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRED) print_task_status_warning('EXPIRED') logger.warning("{} is expired".format(cert_desc)) elif basic_constraints is None or 'CA:TRUE' not in basic_constraints: add_certificate_status(CertificateStatus.CERT_STATUS_NON_CA) print_task_status_warning('NON-CA') logger.warning("{} is not a CA certificate".format(cert_desc)) elif is_rogue_ca: add_certificate_status(CertificateStatus.CERT_STATUS_ROGUE_CA) print_task_status_warning('ROGUE') logger.warning("{} violates the path length restriction of a parent CA certificate".format(cert_desc)) elif check_duplicate_ca(pem_cert): add_certificate_status(CertificateStatus.CERT_STATUS_DUPLICATE_CA) print_task_status_warning('DUPLICATE') logger.warning("{} has a duplicate Subject string with one or more CA certificates".format(cert_desc)) elif not cert_subject_keyid: add_certificate_status(CertificateStatus.CERT_STATUS_CA_MISSING_SKID) print_task_status_warning('NO SKID') logger.warning("{} does not have Subject Key Id".format(cert_desc)) elif days_left < 30: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRES_SOON) print_task_status_warning("{} DAYS".format(days_left)) logger.warning("{} expires in {} days".format(cert_desc, days_left)) else: print_task_status('VALID') def check_ca_certificates_in_vecs(): """ Entry point for CA certificates check in VECS """ logger.info('Checking CA certificates in VECS') pem_certs, aliases = vecs.get_all_ca_certificates() for pem_cert, alias in zip(pem_certs, aliases): logger.info("Checking certificate with alias {}".format(alias)) print_task(alias) cert = get_x509_certificate(pem_cert) basic_constraints = get_certificate_extensions(cert).get('basicConstraints') days_left = get_certificate_expiry_in_days(cert) cert_desc = "Certificate with alias {}".format(alias) is_rogue_ca, _ = check_rogue_ca(pem_cert) if days_left < 0: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRED) print_task_status_warning('EXPIRED') logger.warning("{} is expired".format(cert_desc)) elif basic_constraints is None or 'CA:TRUE' not in basic_constraints: add_certificate_status(CertificateStatus.CERT_STATUS_NON_CA) print_task_status_warning('NON-CA') logger.warning("{} is not a CA certificate".format(cert_desc)) elif is_rogue_ca: add_certificate_status(CertificateStatus.CERT_STATUS_ROGUE_CA) print_task_status_warning('ROGUE') logger.warning("{} violates the path length restriction of a parent CA certificate".format(cert_desc)) elif check_duplicate_ca(pem_cert): add_certificate_status(CertificateStatus.CERT_STATUS_DUPLICATE_CA) print_task_status_warning('DUPLICATE') logger.warning("{} has a duplicate Subject string with one or more CA certificates".format(cert_desc)) elif alias != get_certificate_fingerprint(cert, remove_colons=True).lower(): add_certificate_status(CertificateStatus.CERT_STATUS_BAD_ALIAS) print_task_status_warning('BAD ALIAS') logger.warning("{} is registered using a bad alias".format(cert_desc)) elif days_left < 30: add_certificate_status(CertificateStatus.CERT_STATUS_EXPIRES_SOON) print_task_status_warning("{} DAYS".format(days_left)) logger.warning("{} expires in {} days".format(cert_desc, days_left)) else: print_task_status('VALID') def check_service_principals(): """ Entry point for service principals check """ logger.info('Checking service principals in VMware Directory') service_principals = vmdir.get_service_principals() if not service_principals: print_task('Listing SSO Service Principals') print_task_status_warning('FAILED') logger.error('Could not get list of Service Principal entries from VMware Directory') return env = Environment.get_environment() machine_id = env.get_value('MACHINE_ID') solution_users = env.get_value('SOLUTION_USERS') print("Node {}:".format(machine_id)) for solution_user in solution_users: print_task(" {}".format(solution_user)) if "{}-{}".format(solution_user, machine_id) in service_principals: print_task_status('PRESENT') else: print_task_status_warning('MISSING') add_certificate_status(CertificateStatus.CERT_STATUS_SERVICE_PRINCIPAL_MISSING) logger.warning("Missing service principal {} in VMware Directory".format(solution_user)) def check_crls(): """ Check Certificate Revocation List in VECS """ logger.info("Checking the number of CRLS in VECS") num_entries = len(vecs.get_certificate_aliases('TRUSTED_ROOT_CRLS')) print_task('Number of CRLs in VECS') if num_entries < 30: print_task_status(num_entries) elif num_entries < 100: print_task_status_warning(num_entries) else: print_task_status_error(num_entries) add_certificate_status(CertificateStatus.CERT_STATUS_TOO_MANY_CRLS) logger.info("Number of CRLs in VECS: {}".format(num_entries)) def get_ids_domain_and_certificates(identity_sources, source_type): result = [] for source in identity_sources: if source['type'] == source_type: result.append((source['domain_name'], source['certificates'])) return result def get_ids_domain_and_certificates_by_domain(identity_sources, source_type, source_domain): result = [] for source in identity_sources: if source['type'] == source_type and source['domain_name'] == source_domain: result.append((source['domain_name'], source['certificates'])) return result def check_identity_source_certificates(): """ Entry point for checking identity source certificates """ logger.info('Checking identity source certificates') env = Environment.get_environment() is_cac_configured = env.get_value('CAC_CONFIGURED') if is_cac_configured: check_smart_card_filter_file_certs() check_smart_card_vmdir_certs() identity_sources = vmdir.get_identity_sources() # OpenLDAP domain_and_certs = get_ids_domain_and_certificates(identity_sources, 'OpenLDAP') if domain_and_certs: print_header('Checking OpenLDAP LDAPS certificates') for domain_name, certificates in domain_and_certs: print("Domain: {}".format(domain_name)) for index, cert in enumerate(certificates, 1): print_task(" Certificate {}".format(index)) check_certificate_basic(cert) # AD over LDAP domain_and_certs = get_ids_domain_and_certificates(identity_sources, 'AD over LDAP') if domain_and_certs: print_header('Checking AD over LDAPS certificates') for domain_name, certificates in domain_and_certs: print("Domain: {}".format(domain_name)) for index, cert in enumerate(certificates, 1): print_task(" Certificate {}".format(index)) check_certificate_basic(cert) # ADFS domain_and_certs = get_ids_domain_and_certificates(identity_sources, 'ADFS') if domain_and_certs: print_header('Checking ADFS certificates') for _, certificates in domain_and_certs: for index, cert in enumerate(certificates, 1): print_task("Certificate {}".format(index)) check_certificate_basic(cert) def check_smart_card_filter_file_certs(): print_header('Check Smart Card Issuing CA Filter File') print_task('Check CA Filter File') env = Environment.get_environment() cac_filter_file = env.get_value('SMART_CARD_FILTER_FILE') if not is_file_exists(cac_filter_file): print_task_status_warning('MISSING') add_certificate_status(CertificateStatus.CERT_STATUS_CLIENT_CA_LIST_FILE_MISSING, cac_filter_file) else: pem = get_file_contents(cac_filter_file) filter_certs = split_certificates_from_pem(pem) if not filter_certs: print_task_status_warning('EMPTY') add_certificate_status(CertificateStatus.CERT_STATUS_CLIENT_CA_LIST_FILE_EMPTY, cac_filter_file) return print_task_status('OK') for index, cert in enumerate(filter_certs, 1): print_task("Certificate {}".format(index)) check_certificate_basic(cert) def check_smart_card_vmdir_certs(): print_header('Check VMDir Smart Card Issuing CA Certificates') cac_ca_certs = vmdir.get_smart_card_issuing_ca_certs() for index, cert in enumerate(cac_ca_certs, 1): print_task("Certificate {}".format(index)) check_certificate_basic(cert) def check_ssl_trust_anchors(): """ Check if trust anchor certificates are match to server SSL certificate by calculating the certificate thumbprints Note: This check seems not correct. The check should be a certificate chain validation using the trust anchor, not just leaf certificate thumbprint comparison. """ logger.info("Checking SSL trust anchors") env = Environment.get_environment() hostname = env.get_value('HOSTNAME') sso_domain_nodes = vmdir.get_sso_domain_nodes() is_mismatch = False is_using_ip_address = False is_pending = False for node in sso_domain_nodes: print_task(node) try: pem_cert = get_certificate_from_host(node, 443) x509_cert = get_x509_certificate(pem_cert) node_thumbprint = get_certificate_fingerprint(x509_cert) except OpenSSL.crypto.Error: add_certificate_status(CertificateStatus.TRUST_ANCHORS_UNKNOWN) unknown_nodes = env.get_value('TRUST_ANCHORS_UNKNOWN_NODES') if unknown_nodes is None: unknown_nodes = [] env.set_value('TRUST_ANCHORS_UNKNOWN_NODES', unknown_nodes) unknown_nodes.append(node) print_task_status_warning('UNKNOWN') continue ip_address = get_ip_address(node) trust_anchors = vmdir.get_node_trust_anchors(node) if not trust_anchors: is_using_ip_address = True trust_anchors = vmdir.get_node_trust_anchors(ip_address) if not trust_anchors: add_certificate_status(CertificateStatus.TRUST_ANCHORS_CHECK_URI_OTHER) print_task_status_warning('MISSING') continue for cert_entry in trust_anchors: pem_cert = build_pem_certificate(cert_entry) fingerprint = get_certificate_fingerprint(get_x509_certificate(pem_cert)) logger.info("Checking node thumbprint {} against trust anchor thumbprint {}" .format(node_thumbprint, fingerprint)) if fingerprint != node_thumbprint: if node == hostname or node == ip_address: machine_ssl_cert = vecs.get_certificate('MACHINE_SSL_CERT', '__MACHINE_CERT') x509_machine_ssl_cert = get_x509_certificate(machine_ssl_cert) machine_ssl_thumbprint = get_certificate_fingerprint(x509_machine_ssl_cert) if fingerprint == machine_ssl_thumbprint: is_pending = True else: is_mismatch = True logger.info("Searching for ghost trust anchors") vcs = vmdir.get_registered_vcenters() for deployment_id, dn in vcs: uris = '\n'.join(vmdir.get_endpoint_registrations(dn)) pattern = ".*https://{0}/.*|.*https://{0}:.*|.*https://{1}/.*|.*https://{1}:.*".format(node, ip_address) if not re.match(pattern, uris): continue logger.debug("Found vCenter registration for {}: {}".format(node, deployment_id)) ghost_vmonapi_dn = get_ghost_vmonapi_dn(deployment_id) if not ghost_vmonapi_dn: continue logger.debug("cis.vmonapi registration DN: {}".format(ghost_vmonapi_dn)) trust_anchors = get_ghost_vmonapi_trust_anchors(ghost_vmonapi_dn) for cert in trust_anchors: pem_cert = build_pem_certificate(cert) x509_cert = get_x509_certificate(pem_cert) fingerprint = get_certificate_fingerprint(x509_cert) logger.info("Checking node thumbprint {} against trust anchor thumbprint {}" .format(node_thumbprint, fingerprint)) if node_thumbprint != fingerprint: if node == hostname or node == ip_address: machine_ssl_cert = vecs.get_certificate('MACHINE_SSL_CERT', '__MACHINE_CERT') x509_machine_ssl_cert = get_x509_certificate(machine_ssl_cert) machine_ssl_thumbprint = get_certificate_fingerprint(x509_machine_ssl_cert) if fingerprint == machine_ssl_thumbprint: is_pending = True else: is_mismatch = True if is_mismatch: add_certificate_status(CertificateStatus.TRUST_ANCHORS_MISMATCH) if is_using_ip_address: add_certificate_status(CertificateStatus.TRUST_ANCHORS_CHECK_URI_MISMATCH) print_task_status_warning('MISMATCH*') else: print_task_status_warning('MISMATCH') elif is_pending: print_task_status_warning('PENDING') add_certificate_status(CertificateStatus.TRUST_ANCHORS_CHECK_PENDING) else: if is_using_ip_address: add_certificate_status(CertificateStatus.TRUST_ANCHORS_CHECK_URI_OTHER) print_task_status_warning('CHECK URI') else: print_task_status('VALID') def get_ghost_vmonapi_dn(deployment_id): domain_dn = Environment.get_environment().get_value("SSO_DOMAIN_DN") search_base = "cn=Sites,cn=Configuration,{}".format(domain_dn) search_filter = "(&(vmwLKUPType=cis.vmonapi)(vmwLKUPDeploymentNodeId={}))".format(deployment_id) search_attributes = ['dn'] results = vmdir.perform_ldap_search(search_base, search_filter, search_attributes) return results[0]['dn'] if results else '' def get_ghost_vmonapi_trust_anchors(vmonapi_dn): search_filter = '(vmwLKUPURI=http://localhost*)' search_attributes = ['vmwLKUPEndpointSslTrust'] results = vmdir.perform_ldap_search(vmonapi_dn, search_filter, search_attributes) trust_anchors = [] for entry in results: trust_anchors.extend(entry['vmwLKUPEndpointSslTrust']) return trust_anchors def check_vcenter_extension_thumbprints(): """ Entry point for certificate check on vcenter extension thumbprints """ if not is_service_running('vmware-vpostgres'): print_text_error('The vPostgres service is stopped!\n''' 'Please ensure this service is running before updating vCenter extension thumbprints.\n' 'Hint: Check the number of CRL entries in VECS') return None vcenter_extensions = get_vcenter_extensions() extension_thumbprints = vcdb.get_extension_thumbprints(vcenter_extensions) expected_thumbprints = get_vcenter_extension_expected_thumbprints(vcenter_extensions) check_result = True for extension, (thumbprint, db_cert_pem) in extension_thumbprints.items(): expected_thumbprint, expected_cert_type, expected_cert_pem = expected_thumbprints[extension] print_task("{} ({})".format(extension, expected_cert_type)) logger.info("Comparing {} thumbprint of {} to {}".format(extension, thumbprint, expected_thumbprint)) if get_vc_version() < VcVersion.V9: if thumbprint == expected_thumbprint: print_task_status('MATCHES') else: print_task_status_warning('MISMATCH') check_result = False else: if thumbprint == expected_thumbprint and db_cert_pem == expected_cert_pem: print_task_status('MATCHES') else: print_task_status_warning('MISMATCH') check_result = False return check_result def add_certificate_status(cert_status, detail=None): env = Environment.get_environment() results = env.get_value('CERTIFICATE_CHECK_RESULT') if results is None: results = [] env.set_value('CERTIFICATE_CHECK_RESULT', results) if cert_status not in [r[0] for r in results]: results.append([cert_status, detail]) def reset_certificate_status(): env = Environment.get_environment() env.set_value('CERTIFICATE_CHECK_RESULT', None) env.set_value('TRUST_ANCHORS_UNKNOWN_NODES', None) def show_check_result_summary(): env = Environment.get_environment() results = env.get_value('CERTIFICATE_CHECK_RESULT') if not results: return print_text_error('\n------------------------!!! Attention !!!------------------------') for status, detail in results: lines = status.value.format_map(env.get_map()).splitlines() for idx, line in enumerate(lines): print_text_error(' - ' if idx == 0 else ' ', end='') if status == CertificateStatus.TRUST_ANCHORS_UNKNOWN: for unknown_node in env.get_value('TRUST_ANCHORS_UNKNOWN_NODES'): print_text_error(" {}".format(unknown_node)) print_text_error(line if not detail else line.replace('>>DETAILS<<', detail)) def check_rogue_ca(pem_cert): """ Check if a certificate is invalid due to a CA extending beyond the path length restriction of a parent CA :param pem_cert: Base64 certificate hash :return cert_is_rogue: True if certificate is invalid due to a rogue CA, False otherwise, rogue_ca: name of CA in the signing chain that violates the path length restriction of a parent CA """ logger.info('Entering the function to check for Rogue CA') cert_is_rogue = False rogue_ca = '' subject_keyids = vmdir.get_all_ca_subject_keyids() cert_path = build_certification_path(pem_cert, subject_keyids, vmdir.get_ca_certificate) ca_certs_in_path = len(cert_path) if cert_path[-1]['is_ca'] is True else len(cert_path)-1 for index, cert in enumerate(cert_path): logger.info("Checking for path length restrictions on certificate {}".format(cert_path[index]['cert_name'])) if cert['pathlen'] is not False: logger.info("Path length restriction found on certificate {}: {}".format(cert_path[index]['cert_name'], cert_path[index]['pathlen'])) max_allowed_ca = index + int(cert['pathlen']) + 1 logger.info("Maximum allowed CAs in the signing chain is: {}".format(max_allowed_ca)) if ca_certs_in_path > max_allowed_ca: cert_is_rogue = True rogue_ca = cert_path[max_allowed_ca]['cert_name'] logger.info("Path length violation found on certificate {}".format(cert_path[max_allowed_ca]['cert_name'])) break return cert_is_rogue, rogue_ca def check_duplicate_ca(pem_cert): """ Check if there are multiple CA certificates with the same Subject string (but different private key). This can cause certificate validation issues. :param pem_cert: Base64 certificate hash :return True if there are found to be duplicate certificates with the same Subject string, False otherwise """ subject_hash = get_subject_hash(pem_cert) return True if len(glob.glob("/etc/ssl/certs/{}.[0-9]".format(subject_hash))) > 1 else False def check_missing_ca(pem_cert, provided_ca_chain=False): """ Check to see if any CA certificates in the signing chain of a cert are missing from VMware Directory :param pem_cert: Base64 certificate hash :return: True if there is a CA certificate in the signing chain missing from VMware Directory, False otherwise """ ca_is_missing = False x509_cert = get_x509_certificate(pem_cert) if not provided_ca_chain: subject_keyids = vmdir.get_all_ca_subject_keyids() else: subject_keyids = [] ca_certs = split_certificates_from_pem(get_file_contents(provided_ca_chain)) for cert in ca_certs: ca_x509 = get_x509_certificate(cert) subject_keyids.append(get_authority_keyid(ca_x509, True)) current_keyid = get_authority_keyid(x509_cert, True) while True: logger.info("Looking for AKID {} in {}".format(current_keyid, subject_keyids)) if current_keyid not in subject_keyids: logger.info("AKID {} was NOT found in {}".format(current_keyid, subject_keyids)) ca_is_missing = True break else: ca_x509 = get_x509_certificate(vmdir.get_ca_certificate(current_keyid)) subject_dn, issuer_dn = get_subject_and_issuer_dn(ca_x509) # if we've reached the Root CA cert, we're done if subject_dn == issuer_dn: break current_keyid = get_authority_keyid(ca_x509, True) return ca_is_missing def check_ssl_interception(): """ Checks to see if SSL interception is occurring between vCenter and one of the online VMware repositories """ print_task("Checking {}".format(VMWARE_DEPOT_HOST)) depot_certs = split_certificates_from_pem(get_certificate_from_host('hostupdate.vmware.com')) if len(depot_certs) == 0: print_task_status_warning('FAILED') print_text("{}Unable to obtain a certificate for {}".format(ColorKey.YELLOW, VMWARE_DEPOT_HOST)) print_text("Ensure the remote repository is accessible.{}".format(ColorKey.NORMAL)) return else: depot_issuer_cert_name = get_certificate_name(depot_certs[0], 'issuer') logger.info("Certificate for {} is issued by {}".format(VMWARE_DEPOT_HOST, depot_issuer_cert_name)) if depot_issuer_cert_name == VMWARE_DEPOT_CERT_ISSUER: print_task_status('OK') print() print_text("Issuing CA for {} is {}{}{}".format(VMWARE_DEPOT_HOST, ColorKey.GREEN, depot_issuer_cert_name, ColorKey.NORMAL)) return else: print_task_status_warning('WARNING') print() print_text("Issuing CA for {} is {}{}{}".format(VMWARE_DEPOT_HOST, ColorKey.YELLOW, depot_issuer_cert_name, ColorKey.NORMAL)) print_text("Expected issuer is {}{}{}".format(ColorKey.GREEN, VMWARE_DEPOT_CERT_ISSUER, ColorKey.NORMAL)) print() print_text("{}SSL interception is likely taking place!{}".format(ColorKey.YELLOW, ColorKey.NORMAL)) env = Environment.get_environment() menu = Menu.load_menu_from_config('config/check_config/ssl_interception/menu_manage_ssl_interception.yaml') try: menu.run() except MenuExitException: pass